Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ ALETHEIA_BACKEND_HOST=0.0.0.0
FRONTEND_URL=http://localhost:3000
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001

# Legacy Aletheia is retained only for explicit compatibility and migration
# work. The active Vera runtime keeps both surfaces off by default; only the
# exact lowercase value "true" enables a gate. Enabling routes can initialize
# Legacy route-owned database/runtime objects, so do not enable either flag in a
# normal Vera deployment.
VERA_ENABLE_LEGACY_ROUTES=false
VERA_ENABLE_LEGACY_RUNTIME=false

# Local Aletheia auth. single_user is easiest for a local workstation.
ALETHEIA_AUTH_MODE=single_user
ALETHEIA_LOCAL_USER_ID=local-user
Expand Down
37 changes: 37 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Third-Party Notices

Vera is distributed under `AGPL-3.0-only`; see `LICENSE`.

## Open Legal Products Mike

Vera contains controlled source adaptations from Open Legal Products Mike:

```text
Repository: https://github.com/Open-Legal-Products/mike
Commit: e32daad5a4c64a5561e04c53ee12411e3c5e7238
License: AGPL-3.0-only
```

Source provenance, the approved adaptation rules, and affected areas are listed
in `docs/mike_port_manifest.md` and `docs/license_attribution.md`. Original
copyright and provenance comments must be retained.

## SQLCipher Node binding

Encrypted Workspace database mode uses `@signalapp/sqlcipher` 3.3.9, licensed
`AGPL-3.0-only` according to its installed package metadata. Its package license
is included with the installed dependency.

## Other dependencies and optional runtimes

JavaScript dependency versions are fixed by the repository lockfiles and carry
their own license metadata and distributed license files. The current Legacy
voice adapter can use an operator-provided faster-whisper installation and model;
neither that toolkit nor model weights are bundled or approved for redistribution
by this notice. The Legacy Word proof-of-concept references Microsoft's hosted
Office.js runtime and will undergo a separate terms and security review before
the target Word integration is released.

The maintained convergence inventory is
`docs/provenance/open-source-inventory.md`. Candidate projects listed there are
not incorporated merely because they were evaluated or mentioned.
5 changes: 5 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
"test:aletheia:malware-scanner": "tsx src/scripts/aletheiaMalwareScannerAudit.ts",
"test:aletheia:encryption": "tsx src/scripts/aletheiaLocalEncryptionAudit.ts",
"test:aletheia:sqlcipher": "tsx src/scripts/aletheiaSqlcipherIntegrationAudit.ts",
"test:vera:matter:migration": "tsx src/scripts/veraMatterProfileMigrationAudit.ts",
"test:vera:matter:module": "tsx src/scripts/veraMatterProfileModuleAudit.ts",
"test:vera:matter:inference": "tsx src/scripts/veraMatterInferencePolicyAudit.ts",
"test:vera:matter": "npm run test:vera:matter:migration && npm run test:vera:matter:module && npm run test:vera:matter:inference",
"test:vera:convergence": "npm run test:vera:matter && npm run test:workspace:p1-convergence",
"test:workspace:migrations": "tsx src/scripts/veraWorkspaceMigrationAudit.ts",
"test:workspace:source-foundation": "tsx src/scripts/veraWorkspaceSourceFoundationAudit.ts",
"test:workspace:source-retention": "tsx src/scripts/veraWorkspaceSourceRetentionAudit.ts",
Expand Down
8 changes: 7 additions & 1 deletion backend/src/lib/workspace/jobs/pump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {
WorkspaceJobRuntime,
type WorkspaceJobHandlers,
} from "../services/jobs";
import { WORKSPACE_JOB_TYPES, type WorkspaceJobType } from "../jobs/types";
import {
WORKSPACE_JOB_TYPES,
type ProjectInferenceScopeResolver,
type WorkspaceJobType,
} from "../jobs/types";
import type { WorkspaceJobStoredRecord } from "../repositories/jobs";

export interface WorkspaceJobPumpTimerHandle {
Expand Down Expand Up @@ -36,6 +40,7 @@ export interface WorkspaceJobPumpOptions {
drainTimeoutMs?: number;
leaseOwner?: string;
leaseDurationMs?: number;
inferenceScopeResolver?: ProjectInferenceScopeResolver;
}

export interface WorkspaceJobPumpStartResult {
Expand Down Expand Up @@ -191,6 +196,7 @@ export class WorkspaceJobPump {
leaseDurationMs: options.leaseDurationMs,
recoveryMode: "fenced",
allowedJobTypes,
inferenceScopeResolver: options.inferenceScopeResolver,
manageProcessSignals: false,
timer: this.timer,
},
Expand Down
31 changes: 31 additions & 0 deletions backend/src/lib/workspace/jobs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,34 @@ export {
type WorkspaceJobType,
type WorkspaceJobValueProjection,
} from "../jobContractV7";

import type { WorkspaceJobStoredRecord } from "../jobPersistenceV7";

export const PROJECT_INFERENCE_JOB_TYPES = [
"assistant_generate",
"workflow_run",
"tabular_cell",
] as const;

export type ProjectInferenceJobType =
(typeof PROJECT_INFERENCE_JOB_TYPES)[number];

/** Immutable scope captured before an inference handler can lose its owner. */
export type WorkspaceInferenceActivityScope = Readonly<
| {
jobId: string;
type: ProjectInferenceJobType;
scope: "project";
projectId: string;
}
| {
jobId: string;
type: ProjectInferenceJobType;
scope: "global" | "unresolved";
projectId: null;
}
>;

export type ProjectInferenceScopeResolver = (
job: WorkspaceJobStoredRecord,
) => WorkspaceInferenceActivityScope | null;
6 changes: 6 additions & 0 deletions backend/src/lib/workspace/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { PROJECT_SOURCE_FOUNDATION_V11_MIGRATION } from "./v11ProjectSourceFound
import { DOCUMENT_STUDIO_V12_MIGRATION } from "./v12DocumentStudio";
import { SOURCE_RETENTION_LIFECYCLE_V13_MIGRATION } from "./v13SourceRetentionLifecycle";
import { DOCUMENT_STUDIO_SUGGESTIONS_V14_MIGRATION } from "./v14DocumentStudioSuggestions";
import { MATTER_PROFILES_V15_MIGRATION } from "./v15MatterProfiles";
import { MATTER_CLASSIFICATION_V16_MIGRATION } from "./v16MatterClassification";

export {
detectWorkspaceDatabaseCapabilities,
Expand Down Expand Up @@ -41,6 +43,8 @@ export { PROJECT_SOURCE_FOUNDATION_V11_MIGRATION } from "./v11ProjectSourceFound
export { DOCUMENT_STUDIO_V12_MIGRATION } from "./v12DocumentStudio";
export { SOURCE_RETENTION_LIFECYCLE_V13_MIGRATION } from "./v13SourceRetentionLifecycle";
export { DOCUMENT_STUDIO_SUGGESTIONS_V14_MIGRATION } from "./v14DocumentStudioSuggestions";
export { MATTER_PROFILES_V15_MIGRATION } from "./v15MatterProfiles";
export { MATTER_CLASSIFICATION_V16_MIGRATION } from "./v16MatterClassification";

export const WORKSPACE_MIGRATIONS = [
INITIAL_WORKSPACE_MIGRATION,
Expand All @@ -57,4 +61,6 @@ export const WORKSPACE_MIGRATIONS = [
DOCUMENT_STUDIO_V12_MIGRATION,
SOURCE_RETENTION_LIFECYCLE_V13_MIGRATION,
DOCUMENT_STUDIO_SUGGESTIONS_V14_MIGRATION,
MATTER_PROFILES_V15_MIGRATION,
MATTER_CLASSIFICATION_V16_MIGRATION,
] as const;
217 changes: 217 additions & 0 deletions backend/src/lib/workspace/migrations/v15MatterProfiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import type {
WorkspaceDatabaseAdapter,
WorkspaceDatabaseCapabilities,
WorkspaceMigration,
} from "./types";

const CREATED_AT = "(strftime('%Y-%m-%dT%H:%M:%fZ','now'))";

const strictUtcTimestamp = (column: string) => `
typeof(${column}) = 'text'
AND length(${column}) = 24
AND ${column} GLOB
'[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9].[0-9][0-9][0-9]Z'
AND strftime('%Y-%m-%dT%H:%M:%fZ', ${column}) = ${column}
`;

const boundedNullableText = (column: string, maximum: number) => `
${column} IS NULL OR (
typeof(${column}) = 'text'
AND length(trim(${column})) BETWEEN 1 AND ${maximum}
AND instr(${column}, char(0)) = 0
)
`;

/*
* A Matter Profile is the optional legal-semantic extension of the existing
* Project ownership boundary. It intentionally contains intake metadata only:
* sourced facts, AI findings, review decisions, and other durable legal state
* belong in the later Artifact Graph rather than this table.
*/
const MATTER_PROFILES_V15_SQL = `
CREATE TABLE matter_profiles (
project_id TEXT PRIMARY KEY
CHECK (
typeof(project_id) = 'text'
AND length(trim(project_id)) BETWEEN 1 AND 120
AND instr(project_id, char(0)) = 0
)
REFERENCES projects(id) ON DELETE CASCADE,
matter_type TEXT NOT NULL
CHECK (
typeof(matter_type) = 'text'
AND matter_type IN (
'civil_litigation',
'commercial_dispute',
'contract_review',
'legal_research',
'general'
)
),
client_name TEXT CHECK (${boundedNullableText("client_name", 500)}),
represented_role TEXT CHECK (
${boundedNullableText("represented_role", 240)}
),
counterparty TEXT CHECK (${boundedNullableText("counterparty", 1000)}),
court TEXT CHECK (${boundedNullableText("court", 500)}),
case_number TEXT CHECK (${boundedNullableText("case_number", 240)}),
stage TEXT CHECK (${boundedNullableText("stage", 240)}),
objective TEXT CHECK (${boundedNullableText("objective", 16384)}),
risk_level TEXT CHECK (
risk_level IS NULL OR (
typeof(risk_level) = 'text'
AND risk_level IN ('low', 'medium', 'high')
)
),
opened_at TEXT CHECK (
opened_at IS NULL OR (${strictUtcTimestamp("opened_at")})
),
closed_at TEXT CHECK (
closed_at IS NULL OR (${strictUtcTimestamp("closed_at")})
),
created_at TEXT NOT NULL DEFAULT ${CREATED_AT}
CHECK (${strictUtcTimestamp("created_at")}),
updated_at TEXT NOT NULL DEFAULT ${CREATED_AT}
CHECK (${strictUtcTimestamp("updated_at")}),
CHECK (closed_at IS NULL OR opened_at IS NULL OR closed_at >= opened_at),
CHECK (updated_at >= created_at)
) WITHOUT ROWID;

CREATE INDEX idx_matter_profiles_type_updated
ON matter_profiles(matter_type, updated_at DESC, project_id);

CREATE INDEX idx_matter_profiles_risk_updated
ON matter_profiles(risk_level, updated_at DESC, project_id)
WHERE risk_level IS NOT NULL;

CREATE INDEX idx_matter_profiles_case_number
ON matter_profiles(case_number, project_id)
WHERE case_number IS NOT NULL;

CREATE TRIGGER matter_profiles_v15_update_guard
BEFORE UPDATE ON matter_profiles BEGIN
SELECT CASE WHEN new.project_id IS NOT old.project_id
THEN RAISE(ABORT, 'Matter Profile Project ownership is immutable')
END;
SELECT CASE WHEN new.created_at IS NOT old.created_at
THEN RAISE(ABORT, 'Matter Profile creation time is immutable')
END;
SELECT CASE WHEN new.updated_at < old.updated_at
THEN RAISE(ABORT, 'Matter Profile update time cannot move backwards')
END;
END;

CREATE TABLE matter_policies (
project_id TEXT PRIMARY KEY
CHECK (
typeof(project_id) = 'text'
AND length(trim(project_id)) BETWEEN 1 AND 120
AND instr(project_id, char(0)) = 0
)
REFERENCES matter_profiles(project_id) ON DELETE CASCADE,
external_egress_mode TEXT NOT NULL DEFAULT 'disabled'
CHECK (
typeof(external_egress_mode) = 'text'
AND external_egress_mode IN (
'disabled',
'approval',
'allowed_by_policy'
)
),
audio_retention_days INTEGER
CHECK (
audio_retention_days IS NULL OR (
typeof(audio_retention_days) = 'integer'
AND audio_retention_days BETWEEN 0 AND 36500
)
),
allow_external_legal_sources INTEGER NOT NULL DEFAULT 0
CHECK (
typeof(allow_external_legal_sources) = 'integer'
AND allow_external_legal_sources IN (0, 1)
),
allow_word_bridge INTEGER NOT NULL DEFAULT 0
CHECK (
typeof(allow_word_bridge) = 'integer'
AND allow_word_bridge IN (0, 1)
),
created_at TEXT NOT NULL DEFAULT ${CREATED_AT}
CHECK (${strictUtcTimestamp("created_at")}),
updated_at TEXT NOT NULL DEFAULT ${CREATED_AT}
CHECK (${strictUtcTimestamp("updated_at")}),
CHECK (updated_at >= created_at)
) WITHOUT ROWID;

CREATE INDEX idx_matter_policies_egress_updated
ON matter_policies(external_egress_mode, updated_at DESC, project_id);

CREATE TRIGGER matter_policies_v15_update_guard
BEFORE UPDATE ON matter_policies BEGIN
SELECT CASE WHEN new.project_id IS NOT old.project_id
THEN RAISE(ABORT, 'Matter Policy Project ownership is immutable')
END;
SELECT CASE WHEN new.created_at IS NOT old.created_at
THEN RAISE(ABORT, 'Matter Policy creation time is immutable')
END;
SELECT CASE WHEN new.updated_at < old.updated_at
THEN RAISE(ABORT, 'Matter Policy update time cannot move backwards')
END;
END;

/*
* Zero rows is the canonical deny-all execution-location set. A missing
* matter_policies row is also interpreted fail-closed by policy evaluation;
* neither state silently enables local or remote inference.
*/
CREATE TABLE matter_policy_execution_locations (
project_id TEXT NOT NULL
REFERENCES matter_policies(project_id) ON DELETE CASCADE,
execution_location TEXT NOT NULL
CHECK (
typeof(execution_location) = 'text'
AND execution_location IN (
'local',
'firm_private',
'confidential_remote',
'standard_remote'
)
),
created_at TEXT NOT NULL DEFAULT ${CREATED_AT}
CHECK (${strictUtcTimestamp("created_at")}),
PRIMARY KEY (project_id, execution_location)
) WITHOUT ROWID;

CREATE INDEX idx_matter_policy_execution_locations_location
ON matter_policy_execution_locations(execution_location, project_id);

CREATE TRIGGER matter_policy_execution_locations_v15_immutable
BEFORE UPDATE ON matter_policy_execution_locations BEGIN
SELECT RAISE(
ABORT,
'Matter Policy execution-location membership is immutable; replace it'
);
END;
`;

function applyMatterProfilesV15(
database: WorkspaceDatabaseAdapter,
_capabilities: WorkspaceDatabaseCapabilities,
) {
database.exec(MATTER_PROFILES_V15_SQL);
}

export const MATTER_PROFILES_V15_MIGRATION: WorkspaceMigration = {
version: 15,
name: "project_matter_foundation",
checksumMaterial: [
"workspace-migration-v15",
"additive-optional-project-one-to-one-legal-semantic-profile",
"intake-metadata-only-no-sources-ai-findings-or-formal-matter-state",
"optional-profile-owned-policy-with-normalized-execution-locations",
"missing-policy-and-empty-execution-set-are-deny-all",
"default-egress-disabled-audio-retention-unconfigured-external-and-word-off",
"strict-bounded-text-enums-canonical-utc-times-and-cascade-ownership",
MATTER_PROFILES_V15_SQL,
].join("\n-- checksum boundary --\n"),
apply: applyMatterProfilesV15,
};
Loading
Loading