@alma-harness/postgres 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/index.d.ts +62 -6
- package/dist/index.js +142 -20
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-store.ts","../src/schema.ts","../src/scoped.ts","../src/memory-stores.ts","../src/memory-schema.ts","../src/spend-schema.ts","../src/spend-store.ts","../src/audit-schema.ts","../src/audit-store.ts","../src/turn-store.ts","../src/turn-schema.ts","../src/routine-run-store.ts","../src/routine-schema.ts"],"sourcesContent":["import type {\n LoadOpts,\n Msg,\n Scope,\n SessionStore,\n ToolTrafficExpiry,\n} from \"@alma-harness/core\";\nimport { assertWellFormed, scopePath } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { assertIso8601 } from \"./schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresSessionStoreOptions = ScopedStoreOptions;\n\n/**\n * Reference `SessionStore` adapter (§6.6, §6.8) — spec 001.\n *\n * Transactional seq: `append` claims a seq range with an atomic counter\n * upsert, so concurrent appenders serialize on the session row. Every\n * operation runs inside a transaction scoped by transaction-local settings\n * (`alma.org` / `alma.uid`) that the RLS policies compare against.\n */\nexport class PostgresSessionStore implements SessionStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresSessionStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n // Was accepted and silently ignored when this helper was extracted for the\n // memory stores (spec 013). An option that does nothing is worse than one\n // that does not exist: it reads as configured protection.\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async append(scope: Scope, sessionId: string, entries: Msg[]): Promise<void> {\n scopePath(scope); // validate before any early return, like every adapter must\n if (entries.length === 0) return;\n // `jsonb` would refuse this anyway — the guard is here to make the FAILURE\n // the same one the in-memory reference gives, naming the row and the\n // remedy instead of surfacing a driver error about a Unicode escape, and\n // to fail before a connection is taken (spec 040).\n for (const [i, entry] of entries.entries()) assertWellFormed(entry, `entries[${i}]`);\n await this.#inScope(scope, async (client) => {\n const { rows } = await client.query<{ last_seq: string }>(\n `insert into alma_sessions (org, uid, session_id, last_seq)\n values ($1, $2, $3, $4)\n on conflict (org, uid, session_id)\n do update set last_seq = alma_sessions.last_seq + $4, updated_at = now()\n returning last_seq`,\n [scope.org, scope.uid, sessionId, entries.length],\n );\n const lastSeq = Number(rows[0]?.last_seq);\n const firstSeq = lastSeq - entries.length + 1;\n\n const params: unknown[] = [scope.org, scope.uid, sessionId];\n const tuples = entries.map((msg, i) => {\n params.push(firstSeq + i, JSON.stringify(msg));\n return `($1, $2, $3, $${params.length - 1}, $${params.length}::jsonb)`;\n });\n await client.query(\n `insert into alma_session_entries (org, uid, session_id, seq, msg)\n values ${tuples.join(\", \")}`,\n params,\n );\n });\n }\n\n async load(scope: Scope, sessionId: string, opts?: LoadOpts): Promise<Msg[]> {\n scopePath(scope); // validate before any early return, like every adapter must\n const limit = opts?.limit;\n if (limit !== undefined && limit <= 0) return [];\n return this.#inScope(scope, async (client) => {\n // With a limit we want the most recent N, still chronological: take\n // them seq-descending and reverse (spec 001 / LoadOpts decision).\n const { rows } = await client.query<{ msg: Msg }>(\n limit === undefined\n ? `select msg from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3\n order by seq asc`\n : `select msg from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3\n order by seq desc limit $4`,\n limit === undefined\n ? [scope.org, scope.uid, sessionId]\n : [scope.org, scope.uid, sessionId, limit],\n );\n const msgs = rows.map((r) => r.msg);\n return limit === undefined ? msgs : msgs.reverse();\n });\n }\n\n async expireToolTraffic(\n scope: Scope,\n sessionId: string,\n opts: { inactiveSince: string },\n ): Promise<ToolTrafficExpiry> {\n scopePath(scope); // validate before any connection, like every adapter must\n const cutoff = instant(opts.inactiveSince);\n return this.#inScope(scope, async (client) => {\n // Serialise against `append` by taking the counter row it upserts — spec\n // 039 review. Without this the SELECT below reads a session that a\n // concurrent append makes ACTIVE before the transaction commits, and the\n // history is rewritten on stale information. The in-memory reference\n // cannot show this (its whole operation is synchronous, so it is atomic\n // with respect to the event loop), which is exactly the kind of\n // divergence a Postgres-only race hides behind a green contract suite.\n await client.query(\n `select last_seq from alma_sessions\n where org = $1 and uid = $2 and session_id = $3\n for update`,\n [scope.org, scope.uid, sessionId],\n );\n const { rows } = await client.query<{ seq: string; msg: Msg }>(\n `select seq, msg from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3\n order by seq asc`,\n [scope.org, scope.uid, sessionId],\n );\n // Refused, not thrown (spec 039). A message with no `meta.at` counts as\n // ACTIVE — an entry whose age cannot be established must not be assumed\n // old, which is the safe direction when being wrong costs the +27%\n // prefix rewrite §6.6 measured.\n const active = rows.some((r) => {\n const at = r.msg.meta?.at;\n // A MALFORMED stamp counts as active too, and the first version missed\n // it: Date.parse gives NaN, and NaN > cutoff is false, so an entry whose\n // age could not be established was treated as OLD — the opposite of\n // what the rule says (spec 039 review).\n if (at === undefined) return true;\n const ms = Date.parse(at);\n return Number.isNaN(ms) || ms > cutoff;\n });\n if (active) return { blocks: 0, messages: 0, expired: false };\n\n let blocks = 0;\n const rewrites: { seq: string; msg: Msg }[] = [];\n const removals: string[] = [];\n for (const row of rows) {\n // ALL the tool blocks or none: a partial expiry leaves a `tool_call`\n // without its `tool_result`, which a provider answers with a 400\n // (spec 026). `media` survives — it is a pointer, and user-facing.\n // Reasoning goes with the tool traffic (spec: reasoning-blocks).\n const survivors = row.msg.blocks.filter(\n (b) => b.type !== \"tool_call\" && b.type !== \"tool_result\" && b.type !== \"reasoning\",\n );\n if (survivors.length === row.msg.blocks.length) continue;\n blocks += row.msg.blocks.length - survivors.length;\n if (survivors.length === 0) removals.push(row.seq);\n else rewrites.push({ seq: row.seq, msg: { ...row.msg, blocks: survivors } });\n }\n\n for (const r of rewrites) {\n await client.query(\n `update alma_session_entries set msg = $5::jsonb\n where org = $1 and uid = $2 and session_id = $3 and seq = $4`,\n [scope.org, scope.uid, sessionId, r.seq, JSON.stringify(r.msg)],\n );\n }\n if (removals.length > 0) {\n // The seq numbers of surviving entries are NOT renumbered: `load`\n // orders by seq and never assumes it is contiguous, and rewriting them\n // would race the `last_seq` counter that `append` claims ranges from.\n await client.query(\n `delete from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3 and seq = any($4::bigint[])`,\n [scope.org, scope.uid, sessionId, removals],\n );\n }\n return { blocks, messages: removals.length, expired: true };\n });\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n await this.#inScope(scope, async (client) => {\n // Entries follow via ON DELETE CASCADE.\n if (sessionId === undefined) {\n await client.query(`delete from alma_sessions where org = $1 and uid = $2`, [\n scope.org,\n scope.uid,\n ]);\n } else {\n await client.query(\n `delete from alma_sessions where org = $1 and uid = $2 and session_id = $3`,\n [scope.org, scope.uid, sessionId],\n );\n }\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/** Rejects an unparseable cutoff rather than silently treating it as the epoch. */\nconst instant = (at: string): number => Date.parse(assertIso8601(at));\n","import type { Pool } from \"pg\";\n\n/**\n * Schema and migration for the session store — spec 001 — plus the RLS\n * building blocks every Alma table shares (spec 011).\n *\n * Scope-stamped tables under row-level security. Migrations are idempotent\n * (safe to run on every startup) and creating/granting the RLS role is part of\n * them: defense-in-depth must not depend on a manual step.\n */\n\nexport const DEFAULT_RLS_ROLE = \"alma_app\";\n\n/**\n * Role the retention sweeps assume — spec 039 review. It exists because the\n * app role deliberately cannot delete an audit row, and a sweep therefore\n * cannot run as it; the claims' sweep uses it for the same separation of\n * actors (spec: erasure-reaches-the-claims).\n */\nexport const DEFAULT_RETENTION_ROLE = \"alma_retention\";\n\nconst IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\n\n/**\n * Role and table names are interpolated into DDL (Postgres cannot parameterize\n * identifiers), so they are validated strictly — never raw.\n */\nexport function assertRoleIdentifier(role: string): void {\n if (!IDENTIFIER.test(role)) {\n throw new Error(`Invalid Postgres role identifier: ${JSON.stringify(role)}`);\n }\n}\n\n/**\n * The scope-isolation policy, identical on every Alma table — spec 001, §6.8.\n * One generator, because a table that received a hand-copied policy with a\n * dropped `with check` would still pass its functional tests while accepting\n * cross-tenant writes.\n *\n * `keys` exists for the one org-keyed table (the spend store's tenant-day\n * counter, which has no uid column — spec: spend-store); every scope-stamped\n * table takes the default. Parameterized rather than forked, so RLS\n * hardening lands on every table at once (review finding).\n */\nexport function rlsPolicySql(table: string, keys: readonly (\"org\" | \"uid\")[] = [\"org\", \"uid\"]): string {\n assertRoleIdentifier(table);\n const name = keys.includes(\"uid\") ? \"alma_scope_isolation\" : \"alma_org_isolation\";\n const predicate = keys\n .map((key) => `${key} = current_setting('alma.${key}', true)`)\n .join(\"\\n and \");\n return `\nalter table ${table} enable row level security;\nalter table ${table} force row level security;\n\ndrop policy if exists ${name} on ${table};\ncreate policy ${name} on ${table}\n using (\n ${predicate}\n )\n with check (\n ${predicate}\n );\n`;\n}\n\n/**\n * The retention actor's policies on one table — spec 039, shared since the\n * claims got them too (spec: erasure-reaches-the-claims). TWO policies: a\n * DELETE with a WHERE must SCAN the rows, and the scope-keyed policy is FOR\n * ALL, which governs SELECT too. `name` keeps each table's existing policy\n * names, so a migration on a populated database drops and recreates its own.\n */\nexport function retentionPolicySql(table: string, retentionRole: string, name: string): string {\n assertRoleIdentifier(table);\n assertRoleIdentifier(retentionRole);\n assertRoleIdentifier(name);\n return `\ndrop policy if exists ${name}_read on ${table};\ncreate policy ${name}_read on ${table}\n for select\n to ${retentionRole}\n using (true);\n\ndrop policy if exists ${name} on ${table};\ncreate policy ${name} on ${table}\n for delete\n to ${retentionRole}\n using (true);`;\n}\n\n/** Rejects an unparseable ISO 8601 timestamp with the value in the message, before it reaches SQL. */\nexport function assertIso8601(at: string, label = \"timestamp\"): string {\n if (Number.isNaN(Date.parse(at))) {\n throw new Error(`invalid ISO 8601 ${label}: ${JSON.stringify(at)}`);\n }\n return at;\n}\n\n/** Creates the RLS role if it does not exist, tolerating a concurrent race. */\nexport function roleBootstrapSql(role: string): string {\n assertRoleIdentifier(role);\n return `\ndo $$\nbegin\n if not exists (select from pg_roles where rolname = '${role}') then\n begin\n create role ${role} nologin;\n exception when duplicate_object or unique_violation then\n -- Another instance won the race between the existence check and the\n -- create (pg_roles is cluster-wide and CREATE ROLE has no IF NOT\n -- EXISTS); losing it must not roll back the rest of the migration.\n -- Two creates that overlap surface as unique_violation on\n -- pg_authid, not duplicate_object (review of 056–058).\n null;\n end;\n end if;\nend $$;\n`;\n}\n\n/**\n * Membership for the connection user — spec: erasure-reaches-the-claims.\n * `SET LOCAL ROLE` admits only roles the SESSION user is a member of, and no\n * migration can know which login the product connects as. The suites never\n * noticed: they connect as a superuser, which may assume any role, so the\n * first least-privilege deployment failed on every call. Re-granting is a\n * no-op, so this is safe to run at every startup.\n */\nexport function grantRoleSql(role: string, to: string): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(to);\n return `grant ${role} to ${to};`;\n}\n\n/**\n * Run by a role that may grant (typically the migrations' superuser), once\n * per login. Deliberately NOT part of any migration: the retention role must\n * not reach the app's login by accident — the sweep is a different actor.\n */\nexport async function grantRole(pool: Pool, opts: { role?: string; to: string }): Promise<void> {\n await pool.query(grantRoleSql(opts.role ?? DEFAULT_RLS_ROLE, opts.to));\n}\n\n/**\n * DECISION (spec 001): SQL lives as a TS constant, not a .sql file — packages\n * ship TypeScript source in Phase 0/1, and a migration runner would be\n * premature for one migration.\n */\nexport function sessionStoreMigrationSql(role: string = DEFAULT_RLS_ROLE): string {\n assertRoleIdentifier(role);\n return `\ncreate table if not exists alma_sessions (\n org text not null,\n uid text not null,\n session_id text not null,\n last_seq bigint not null default 0,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n primary key (org, uid, session_id)\n);\n\ncreate table if not exists alma_session_entries (\n org text not null,\n uid text not null,\n session_id text not null,\n seq bigint not null,\n msg jsonb not null,\n created_at timestamptz not null default now(),\n primary key (org, uid, session_id, seq),\n foreign key (org, uid, session_id)\n references alma_sessions (org, uid, session_id) on delete cascade\n);\n${rlsPolicySql(\"alma_sessions\")}${rlsPolicySql(\"alma_session_entries\")}\n${roleBootstrapSql(role)}\ngrant select, insert, update, delete\n on alma_sessions, alma_session_entries\n to ${role};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateSessionStore(\n pool: Pool,\n opts: { role?: string } = {},\n): Promise<void> {\n await pool.query(sessionStoreMigrationSql(opts.role));\n}\n","import { scopePath, type Scope } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { assertRoleIdentifier, DEFAULT_RLS_ROLE } from \"./schema\";\n\n/**\n * The RLS binding, shared by every Postgres store — spec 001, spec 011.\n *\n * One implementation, because \"the scoped transaction\" is exactly the place a\n * copy-paste divergence would silently weaken tenancy: a store that forgot the\n * `SET LOCAL ROLE`, or set the scope after the first query, would still pass\n * its functional tests while running with isolation off.\n */\n\nexport interface ScopedStoreOptions {\n /**\n * Per-transaction `statement_timeout` in milliseconds (default 30s; `null`\n * disables). A store whose queries are shaped by model-supplied input needs\n * a ceiling that does not depend on the caller remembering one — the\n * adversarial review drove a single search query to 25 seconds while it held\n * a pooled connection. Set transaction-locally, so it resets on commit.\n */\n statementTimeoutMs?: number | null;\n /**\n * Role assumed per transaction via SET LOCAL ROLE, so RLS binds even when\n * the connection user is privileged (superusers bypass RLS; dev and CI both\n * connect as superusers — spec 001). `null` opts out, which weakens\n * defense-in-depth to application-level isolation only.\n */\n role?: string | null;\n}\n\nexport const DEFAULT_STATEMENT_TIMEOUT_MS = 30_000;\n\n/** Resolves and validates the configured role once, at construction. */\nexport function resolveRlsRole(opts: ScopedStoreOptions): string | null {\n const role = opts.role === undefined ? DEFAULT_RLS_ROLE : opts.role;\n if (role !== null) assertRoleIdentifier(role);\n return role;\n}\n\nexport function resolveStatementTimeout(opts: ScopedStoreOptions): number | null {\n const ms = opts.statementTimeoutMs === undefined ? DEFAULT_STATEMENT_TIMEOUT_MS : opts.statementTimeoutMs;\n if (ms !== null && (!Number.isInteger(ms) || ms <= 0)) {\n throw new Error(`statementTimeoutMs must be a positive integer or null, got ${ms}`);\n }\n return ms;\n}\n\n/**\n * One transaction as `role`, on a pooled connection: begin, `set local role`,\n * `fn`, commit. Shared by the scoped stores and the retention sweeps (review\n * of 056–058): the sweeps had copied the shape and dropped the one line that\n * matters on failure. A failed ROLLBACK means the connection may still be\n * inside an aborted transaction; handing it back to the pool would give the\n * next borrower cascading errors, so it is destroyed instead of reused.\n */\nexport async function inTransaction<T>(\n pool: Pool,\n role: string | null,\n fn: (client: PoolClient) => Promise<T>,\n): Promise<T> {\n const client = await pool.connect();\n let rollbackFailed: unknown;\n try {\n await client.query(\"begin\");\n if (role !== null) await client.query(`set local role ${role}`);\n const result = await fn(client);\n await client.query(\"commit\");\n return result;\n } catch (err) {\n await client.query(\"rollback\").catch((rollbackErr: unknown) => {\n rollbackFailed = rollbackErr;\n });\n throw err;\n } finally {\n client.release(rollbackFailed === undefined ? undefined : (rollbackFailed as Error));\n }\n}\n\n/**\n * A retention sweep in BATCHES — spec: close-review-part-two. One unbounded\n * `delete … where stamp < cutoff` on a backlog is one long transaction and a\n * WAL burst, and under a `statement_timeout` fails with nothing freed. Each\n * batch of `ctid`s is its own transaction as `role`; the count is summed.\n * Table and column are module constants, validated as identifiers anyway.\n */\nexport async function sweepBefore(\n pool: Pool,\n opts: { role: string; table: string; column: string; before: string; batch?: number },\n): Promise<number> {\n assertRoleIdentifier(opts.table);\n assertRoleIdentifier(opts.column);\n const batch = opts.batch ?? 5_000;\n if (!Number.isInteger(batch) || batch < 1) throw new Error(`batch must be a positive integer, got ${batch}`);\n let total = 0;\n for (;;) {\n const removed = await inTransaction(pool, opts.role, async (client) => {\n const { rowCount } = await client.query(\n `delete from ${opts.table}\n where ctid = any(array(select ctid from ${opts.table} where ${opts.column} < $1::timestamptz limit $2))`,\n [opts.before, batch],\n );\n return rowCount ?? 0;\n });\n total += removed;\n if (removed < batch) return total;\n }\n}\n\n/**\n * One transaction: assume the RLS role, bind the scope as transaction-local\n * settings, run `fn`. Everything resets at commit/rollback, so pooled\n * connections never leak role or scope.\n */\nexport async function inScope<T>(\n pool: Pool,\n role: string | null,\n scope: Scope,\n fn: (client: PoolClient) => Promise<T>,\n statementTimeoutMs: number | null = DEFAULT_STATEMENT_TIMEOUT_MS,\n): Promise<T> {\n scopePath(scope); // central segment validation (§6.8), before any connection\n return inTransaction(pool, role, async (client) => {\n // One round trip for scope binding AND the timeout — all transaction-local.\n await client.query(\n `select set_config('alma.org', $1, true),\n set_config('alma.uid', $2, true),\n set_config('statement_timeout', $3, true)`,\n [scope.org, scope.uid, statementTimeoutMs === null ? \"0\" : String(statementTimeoutMs)],\n );\n return fn(client);\n });\n}\n","import {\n scopePath,\n type CopySurface,\n type Episode,\n type EpisodeInput,\n type EpisodeQuery,\n type EpisodeQueryResult,\n type EpisodeStore,\n type ErasureSelector,\n type ErasureWatermarkStore,\n type FactObservation,\n type InvalidateResult,\n type ObserveResult,\n type Profile,\n type ProfileFact,\n type ProfileReadOpts,\n type ProfileStore,\n type Scope,\n type TombstoneResult,\n} from \"@alma-harness/core\";\nimport {\n applyMemoryBudget,\n assertBatchSize,\n assertEpisodeInput,\n assertEpisodeQuery,\n assertErasureSelector,\n assertFactObservation,\n compareFactsForRecall,\n decideObservation,\n DEFAULT_CONFIDENCE,\n DEFAULT_IMPORTANCE,\n deriveEpisodeId,\n deriveFactId,\n isProtectedProfileKey,\n mergeRefresh,\n MEMORY_LIMITS,\n rankEpisodes,\n StaleWriteError,\n toIsoInstant,\n tokenizeMemoryText,\n} from \"@alma-harness/memory\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { EPISODES_TABLE, FACTS_TABLE, SCOPE_STATE_TABLE } from \"./memory-schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\n/**\n * Reference memory adapters (§6.7, §6.8) — spec 011. Same RLS discipline as\n * the session store: every operation runs inside a transaction that assumes\n * the app role and binds `alma.org`/`alma.uid`, which the policies compare\n * against.\n *\n * Ranking is NOT done in SQL. The adapter filters CANDIDATES — a superset of\n * what the tokenizer matches — and hands them to core's `rankEpisodes`, so\n * every backend orders results identically (spec 011).\n */\n\nexport type PostgresMemoryStoreOptions = ScopedStoreOptions;\n\n/**\n * DECISION (spec 011): how many rows a text query fetches before ranking.\n * Consequence, documented rather than hidden: a term appearing only in\n * episodes older than the most recent `CANDIDATE_WINDOW` can be missed.\n * `tsvector`/pgvector is the upgrade when a consumer hits it.\n */\nconst CANDIDATE_WINDOW = 200;\n\nconst EPISODE_COLUMNS = `id, at, kind, summary, importance, state,\n source_session_id, source_turn_id, erased_at`;\n\nconst FACT_COLUMNS = `id, key, value, confidence, source_episode_ids,\n observed_at, last_seen_at, superseded_at, superseded_by, invalidated_at, ttl_days`;\n\ninterface EpisodeRow {\n id: string;\n at: Date;\n kind: string;\n summary: string;\n importance: number;\n state: Episode[\"state\"];\n source_session_id: string | null;\n source_turn_id: string | null;\n erased_at: Date | null;\n}\n\nfunction toEpisode(row: EpisodeRow): Episode {\n const episode: Episode = {\n id: row.id,\n at: row.at.toISOString(),\n kind: row.kind,\n summary: row.summary,\n importance: row.importance,\n state: row.state,\n };\n if (row.source_session_id !== null || row.source_turn_id !== null) {\n const source: NonNullable<Episode[\"source\"]> = {};\n if (row.source_session_id !== null) source.sessionId = row.source_session_id;\n if (row.source_turn_id !== null) source.turnId = row.source_turn_id;\n episode.source = source;\n }\n if (row.erased_at !== null) episode.erasedAt = row.erased_at.toISOString();\n return episode;\n}\n\ninterface FactRow {\n id: string;\n key: string;\n value: string;\n confidence: number;\n source_episode_ids: string[];\n observed_at: Date;\n last_seen_at: Date;\n superseded_at: Date | null;\n superseded_by: string | null;\n invalidated_at: Date | null;\n ttl_days: number | null;\n}\n\n/**\n * A `FactRow` LEFT JOINed onto the profile's stamp: every fact column is null\n * on the row a scope with nothing readable still returns — see `get`.\n */\ntype FactJoinRow = { [K in keyof FactRow]: FactRow[K] | null } & { updated_at: Date | null };\n\nfunction toFact(row: FactRow): ProfileFact {\n const fact: ProfileFact = {\n id: row.id,\n key: row.key,\n value: row.value,\n confidence: row.confidence,\n sourceEpisodeIds: row.source_episode_ids,\n observedAt: row.observed_at.toISOString(),\n lastSeenAt: row.last_seen_at.toISOString(),\n };\n if (row.superseded_at !== null) fact.supersededAt = row.superseded_at.toISOString();\n if (row.superseded_by !== null) fact.supersededBy = row.superseded_by;\n if (row.invalidated_at !== null) fact.invalidatedAt = row.invalidated_at.toISOString();\n if (row.ttl_days !== null) fact.ttlDays = row.ttl_days;\n return fact;\n}\n\nexport class PostgresEpisodeStore implements EpisodeStore {\n readonly copySurfaces: readonly CopySurface[] = [{ name: EPISODES_TABLE, kind: \"primary\" }];\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresMemoryStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async append(scope: Scope, input: EpisodeInput): Promise<Episode> {\n assertEpisodeInput(input);\n const id = deriveEpisodeId(scope, input);\n const at = toIsoInstant(input.at ?? new Date().toISOString());\n return this.#inScope(scope, async (client) => {\n // The episode tier joins the erasure barrier too (spec 013 review). It\n // was outside it: an extraction in flight during an erase({kind:\"all\"})\n // inserted a NEW derived id after the tombstone pass — no conflict, so\n // the `state <> 'tombstoned'` guard never applied — and the user's\n // verbatim summary survived an erasure that reported itself complete.\n await client.query(scopeLockSql(\"shared\"), [scopeLockKey(scope)]);\n const { rows: mark } = await client.query<{ erasure_watermark: Date | null }>(\n `select erasure_watermark from ${SCOPE_STATE_TABLE} where org = $1 and uid = $2`,\n [scope.org, scope.uid],\n );\n const watermark = mark[0]?.erasure_watermark?.toISOString() ?? null;\n if (watermark !== null && at < watermark) {\n throw new StaleWriteError(\n `episode stamped ${at} precedes this scope's erasure at ${watermark}`,\n );\n }\n // The guard is part of the statement, not a read-then-write: terminal\n // states are create-only, and a re-append racing an erasure must never\n // resurrect content (spec 010). `state` is deliberately absent from the\n // SET list, so an archived episode stays archived.\n await client.query(\n `insert into ${EPISODES_TABLE}\n (org, uid, id, at, kind, summary, summary_fold, importance, source_session_id, source_turn_id)\n values ($1, $2, $3, $4::timestamptz, $5, $6, $7, $8, $9, $10)\n on conflict (org, uid, id) do update\n set at = excluded.at,\n kind = excluded.kind,\n summary = excluded.summary,\n summary_fold = excluded.summary_fold,\n importance = excluded.importance,\n source_session_id = excluded.source_session_id,\n source_turn_id = excluded.source_turn_id,\n updated_at = now()\n where ${EPISODES_TABLE}.state <> 'tombstoned'`,\n [\n scope.org,\n scope.uid,\n id,\n at,\n input.kind,\n input.summary,\n // Folded HERE, in JS, with the same toLowerCase the tokenizer uses —\n // never in SQL, where the fold depends on the database's ctype (see\n // the schema comment on summary_fold).\n input.summary.toLowerCase(),\n input.importance ?? DEFAULT_IMPORTANCE,\n input.source?.sessionId ?? null,\n input.source?.turnId ?? null,\n ],\n );\n const { rows } = await client.query<EpisodeRow>(\n `select ${EPISODE_COLUMNS} from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and id = $3`,\n [scope.org, scope.uid, id],\n );\n const row = rows[0];\n if (!row) throw new Error(`episode ${id} vanished between write and read`);\n return toEpisode(row);\n });\n }\n\n async query(scope: Scope, q: EpisodeQuery): Promise<EpisodeQueryResult> {\n // Validate the scope even on no-op paths, like every adapter must.\n scopePath(scope);\n assertEpisodeQuery(q);\n if (q.limit !== undefined && q.limit <= 0) return { episodes: [], truncated: false };\n const states = q.includeArchived === true ? [\"active\", \"archived\"] : [\"active\"];\n // A candidate filter, not a ranking: substring matching is a SUPERSET of\n // the tokenizer's whole-token match, and core's ranker drops the extras.\n // Tokens are alphanumeric by construction, so no LIKE metacharacter can\n // reach the pattern. Matched against summary_fold — folded in JS at write\n // time, like the pattern is at query time — because `ilike` folds in the\n // DATABASE's ctype: under lc_ctype of C it folds ASCII only, so accented\n // matches were silently dropped while the in-memory reference matched\n // them, breaking the superset the ranker depends on (the 011–013 review).\n const terms = q.text === undefined ? new Set<string>() : tokenizeMemoryText(q.text);\n // Text that carries no usable term matches NOTHING — the ranker says the\n // same, and a null pattern list here would have meant \"no filter\".\n if (q.text !== undefined && terms.size === 0) return { episodes: [], truncated: false };\n const patterns = terms.size === 0 ? null : [...terms].map((t) => `%${t}%`);\n const window = Math.max(CANDIDATE_WINDOW, 4 * (q.limit ?? 0));\n\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<EpisodeRow>(\n `select ${EPISODE_COLUMNS} from ${EPISODES_TABLE}\n where org = $1 and uid = $2\n and state = any($3::text[])\n and ($4::text[] is null or kind = any($4::text[]))\n and ($5::timestamptz is null or at >= $5::timestamptz)\n and ($6::timestamptz is null or at <= $6::timestamptz)\n and ($7::text[] is null or summary_fold like any($7::text[]))\n order by at desc\n limit $8`,\n [\n scope.org,\n scope.uid,\n states,\n q.kinds === undefined ? null : [...q.kinds],\n // Normalized like every other timestamp in the tier: raw bounds are\n // read in the SERVER's timezone while the ranker reads them in the\n // process's, so an offsetless bound filtered differently on each.\n q.since === undefined ? null : toIsoInstant(q.since),\n q.until === undefined ? null : toIsoInstant(q.until),\n patterns,\n window + 1, // one extra row is how we learn the window was saturated\n ],\n );\n // Saturating the candidate window means matches were dropped. Saying\n // `truncated: false` there would let a caller read a clipped answer as\n // a complete one.\n const clipped = rows.length > window;\n const ranked = rankEpisodes(\n rows.slice(0, window).map(toEpisode),\n q,\n new Date().toISOString(),\n );\n const limited = q.limit === undefined ? ranked : ranked.slice(0, q.limit);\n const { kept, truncated } = applyMemoryBudget(limited, q.budget, (ep) => ep.summary.length);\n return {\n episodes: kept,\n truncated: truncated || limited.length < ranked.length || clipped,\n };\n });\n }\n\n async get(scope: Scope, episodeIds: readonly string[]): Promise<Episode[]> {\n scopePath(scope);\n assertBatchSize(episodeIds.length, \"episodeIds\", MEMORY_LIMITS.idsPerCall);\n if (episodeIds.length === 0) return [];\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<EpisodeRow>(\n `select ${EPISODE_COLUMNS} from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and id = any($3::text[])`,\n [scope.org, scope.uid, [...episodeIds]],\n );\n const byId = new Map(rows.map((r) => [r.id, toEpisode(r)]));\n // Requested order, so callers can zip against their own id list.\n return episodeIds.flatMap((id) => {\n const ep = byId.get(id);\n return ep ? [ep] : [];\n });\n });\n }\n\n async tombstone(\n scope: Scope,\n selector: ErasureSelector,\n rawAt: string,\n ): Promise<TombstoneResult> {\n const at = toIsoInstant(rawAt);\n assertErasureSelector(selector);\n const [predicate, param] = selectorPredicate(selector);\n return this.#inScope(scope, async (client) => {\n // Exclusive, like the profile tier's invalidation: waits out every\n // append that read the watermark before it was written.\n await client.query(scopeLockSql(\"exclusive\"), [scopeLockKey(scope)]);\n // One statement reports BOTH: the full match set (the provenance chain a\n // retry must still walk) and which of them this call actually blanked.\n const { rows } = await client.query<{ id: string; written: boolean }>(\n `with matched as (\n select id from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and ${predicate}\n ), blanked as (\n update ${EPISODES_TABLE}\n set kind = '', summary = '', summary_fold = '', state = 'tombstoned',\n erased_at = $3::timestamptz, updated_at = now()\n where org = $1 and uid = $2 and state <> 'tombstoned'\n and id in (select id from matched)\n returning id\n )\n select m.id, (b.id is not null) as written\n from matched m left join blanked b on b.id = m.id`,\n param === null ? [scope.org, scope.uid, at] : [scope.org, scope.uid, at, param],\n );\n return {\n episodeIds: rows.map((r) => r.id),\n written: rows.filter((r) => r.written).length,\n surfaces: [EPISODES_TABLE],\n };\n });\n }\n\n async archive(scope: Scope, episodeIds: readonly string[]): Promise<number> {\n scopePath(scope);\n assertBatchSize(episodeIds.length, \"episodeIds\", MEMORY_LIMITS.idsPerCall);\n if (episodeIds.length === 0) return 0;\n return this.#inScope(scope, async (client) => {\n const { rowCount } = await client.query(\n `update ${EPISODES_TABLE} set state = 'archived', updated_at = now()\n where org = $1 and uid = $2 and state = 'active' and id = any($3::text[])`,\n [scope.org, scope.uid, [...episodeIds]],\n );\n return rowCount ?? 0;\n });\n }\n\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/** The erasure selector as a SQL predicate plus its single bound parameter. */\nfunction selectorPredicate(selector: ErasureSelector): [string, string[] | null] {\n switch (selector.kind) {\n case \"all\":\n return [\"true\", null];\n case \"episodes\":\n return [\"id = any($4::text[])\", [...selector.episodeIds]];\n case \"sessions\":\n return [\"source_session_id = any($4::text[])\", [...selector.sessionIds]];\n }\n}\n\nconst EPOCH = \"1970-01-01T00:00:00.000Z\";\n\n/**\n * The scope-level barrier both sides of an erasure hold — spec 013.\n *\n * Writers take it SHARED (alongside their per-key exclusive locks); the\n * invalidation pass takes it EXCLUSIVE. That makes erasure part of the same\n * protocol writers already follow, which is what the per-key locks alone could\n * not do: an in-flight writer now finishes before invalidation runs, so its\n * fact is caught, and a writer starting afterwards reads a watermark that is\n * already written and is refused if its observation predates the erasure.\n */\nfunction scopeLockSql(mode: \"shared\" | \"exclusive\"): string {\n const fn = mode === \"shared\" ? \"pg_advisory_xact_lock_shared\" : \"pg_advisory_xact_lock\";\n return `select ${fn}(hashtextextended($1::text, 0::bigint))`;\n}\n\nconst scopeLockKey = (scope: Scope): string => `${scope.org}/${scope.uid}`;\n\nexport class PostgresProfileStore implements ProfileStore {\n readonly copySurfaces: readonly CopySurface[] = [{ name: FACTS_TABLE, kind: \"primary\" }];\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresMemoryStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async get(scope: Scope, opts: ProfileReadOpts = {}): Promise<Profile> {\n return this.#inScope(scope, async (client) => {\n // ONE round trip. The stamp is a single-row aggregate that the facts\n // LEFT JOIN onto, so it still comes back for a scope with nothing\n // readable (an aggregate over an empty set is one null row); it used to\n // be a second query against the same table in the same transaction.\n //\n // Invalidated versions are excluded from the stamp: an emptied profile\n // that still reports when the erased data was last touched leaks the\n // timing of what it just erased. Superseded versions are NOT excluded —\n // those were replaced, not erased.\n const { rows } = await client.query<FactJoinRow>(\n `with stamp as (\n select max(last_seen_at) as updated_at from ${FACTS_TABLE}\n where org = $1 and uid = $2 and invalidated_at is null\n )\n select updated_at, ${FACT_COLUMNS}\n from stamp\n left join ${FACTS_TABLE}\n on ${FACTS_TABLE}.org = $1 and ${FACTS_TABLE}.uid = $2\n and ($3::boolean or (${FACTS_TABLE}.superseded_at is null\n and ${FACTS_TABLE}.invalidated_at is null))`,\n [scope.org, scope.uid, opts.includeHistory === true],\n );\n // Ordering and budgeting happen HERE, not in SQL, so every backend\n // returns the same facts in the same order under the same budget.\n const facts = rows\n // The stamp row survives a scope with no readable facts; every one of\n // its fact columns is null, `id` included.\n .filter((row) => row.id !== null)\n .map((row) => toFact(row as FactRow))\n .sort(compareFactsForRecall);\n const { kept, truncated } = applyMemoryBudget(\n facts,\n opts.budget,\n (f) => f.key.length + f.value.length,\n );\n return {\n facts: kept,\n updatedAt: rows[0]?.updated_at?.toISOString() ?? EPOCH,\n truncated,\n };\n });\n }\n\n async observe(scope: Scope, obs: readonly FactObservation[]): Promise<readonly ObserveResult[]> {\n return this.#write(scope, obs, false);\n }\n\n async setProtected(\n scope: Scope,\n facts: readonly FactObservation[],\n ): Promise<readonly ObserveResult[]> {\n return this.#write(scope, facts, true);\n }\n\n async invalidateBySource(\n scope: Scope,\n episodeIds: readonly string[] | \"all\",\n rawAt: string,\n ): Promise<InvalidateResult> {\n const at = toIsoInstant(rawAt);\n return this.#inScope(scope, async (client) => {\n // Exclusive: waits out every writer that read the watermark before it\n // was written, so their facts are inside this pass rather than behind it.\n await client.query(scopeLockSql(\"exclusive\"), [scopeLockKey(scope)]);\n // Key AND value blanked, not merely flagged: both are model-chosen and\n // both carry content, and the export surface reads closed versions.\n const { rowCount } = await client.query(\n `update ${FACTS_TABLE} set invalidated_at = $3::timestamptz, key = '', value = ''\n where org = $1 and uid = $2 and invalidated_at is null\n and ($4::text[] is null or source_episode_ids && $4::text[])`,\n [scope.org, scope.uid, at, episodeIds === \"all\" ? null : [...episodeIds]],\n );\n return { invalidated: rowCount ?? 0, surfaces: [FACTS_TABLE] };\n });\n }\n\n async #write(\n scope: Scope,\n obs: readonly FactObservation[],\n trusted: boolean,\n ): Promise<readonly ObserveResult[]> {\n scopePath(scope);\n assertBatchSize(obs.length, \"observations\", MEMORY_LIMITS.batchItems);\n // Validate the WHOLE batch before opening a transaction: a mid-batch\n // rejection must not depend on rollback to stay atomic.\n for (const o of obs) assertFactObservation(o);\n if (obs.length === 0) return [];\n // Every key this call will touch, locked UP FRONT in one canonical order.\n // Locking lazily per observation deadlocks: two concurrent calls naming\n // the same keys in different orders each hold what the other waits for,\n // and Postgres resolves it by killing one of the turns.\n const lockKeys = [\n ...new Set(\n obs.filter((o) => trusted || !isProtectedProfileKey(o.key)).map((o) => o.key),\n ),\n ].sort();\n // ONE stamp for every observation that did not bring its own, like the\n // erasure walk uses one timestamp for every surface. Per-observation\n // `new Date()` made a batch straddling a millisecond boundary derive\n // different ids for the same observation depending on timing, which the\n // in-memory reference (one injected clock read) never did.\n const batchAt = new Date().toISOString();\n const stamps = obs.map((o) => toIsoInstant(o.at ?? batchAt));\n // Ids are derived from (key, value, instant), so every id this batch\n // could write is known before the transaction opens — which is what lets\n // the replay check below be one read instead of one per observation.\n const candidateIds = obs.map((o, i) =>\n deriveFactId(scope, { key: o.key, value: o.value, observedAt: stamps[i]! }),\n );\n\n return this.#inScope(scope, async (client) => {\n // Shared: many writers proceed together, but an erasure's exclusive\n // claim waits for all of them (spec 013).\n await client.query(scopeLockSql(\"shared\"), [scopeLockKey(scope)]);\n if (lockKeys.length > 0) {\n // ONE statement for every key lock, still in the canonical order the\n // sort above fixed: `unnest` yields rows in array order, and\n // `pg_advisory_xact_lock` is parallel-unsafe, so nothing reorders\n // them. A round trip per key was the batch's worst offender — it held\n // the shared scope lock, and every key lock already taken, for the\n // whole walk (the 011-013 review).\n await client.query(\n `select pg_advisory_xact_lock(hashtextextended(k, 0::bigint))\n from unnest($1::text[]) as k`,\n [lockKeys.map((key) => `${scope.org}/${scope.uid}/${key}`)],\n );\n }\n const state = await this.#loadWriteState(client, scope, obs, lockKeys, candidateIds);\n const { results, writes } = planObservations(obs, trusted, stamps, candidateIds, state);\n // Only the writes the plan actually calls for, batched per round.\n const lost = await applyWrites(client, scope, writes);\n for (const index of lost) {\n results[index] = {\n key: obs[index]!.key,\n outcome: \"stale\",\n detail: \"the version this observation refreshed was closed concurrently\",\n };\n }\n return results;\n });\n }\n\n /**\n * Everything the batch needs to decide, read in a FIXED number of queries.\n *\n * This is the half of the N+1 that was pure reads: the watermark once, then\n * a provenance check, a current-version read, and a replay check PER\n * OBSERVATION — up to three hundred round trips for a full batch, every one\n * of them holding the scope's shared lock and all its key locks (the\n * 011-013 review).\n */\n async #loadWriteState(\n client: PoolClient,\n scope: Scope,\n obs: readonly FactObservation[],\n keys: readonly string[],\n candidateIds: readonly string[],\n ): Promise<WriteState> {\n // Read in the SAME transaction as the writes, so an erasure committing\n // mid-batch cannot slip past the guard.\n const { rows: mark } = await client.query<{ erasure_watermark: Date | null }>(\n `select erasure_watermark from ${SCOPE_STATE_TABLE} where org = $1 and uid = $2`,\n [scope.org, scope.uid],\n );\n const watermark = mark[0]?.erasure_watermark?.toISOString() ?? null;\n\n // The timestamp guard is opt-in on the CALLER: `at` defaults to write\n // time, so an in-flight job that omits it — the documented default — is\n // never stale, and a fact citing an erased episode sailed through a\n // completed erasure (spec 013 review). Provenance is not opt-in: an\n // observation that names a tombstoned episode is refused whatever its\n // clock says.\n const cited = [...new Set(obs.flatMap((o) => o.sourceEpisodeIds ?? []))];\n const tombstoned = new Set<string>();\n if (cited.length > 0) {\n const { rows } = await client.query<{ id: string }>(\n `select id from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and id = any($3::text[]) and state = 'tombstoned'`,\n [scope.org, scope.uid, cited],\n );\n for (const row of rows) tombstoned.add(row.id);\n }\n\n // The advisory lock for each of these keys was taken above, before any\n // observation was decided.\n const currentByKey = new Map<string, ProfileFact>();\n if (keys.length > 0) {\n const { rows } = await client.query<FactRow>(\n `select ${FACT_COLUMNS} from ${FACTS_TABLE}\n where org = $1 and uid = $2 and key = any($3::text[])\n and superseded_at is null and invalidated_at is null`,\n [scope.org, scope.uid, [...keys]],\n );\n for (const row of rows) {\n const fact = toFact(row);\n currentByKey.set(fact.key, fact);\n }\n }\n\n // A stored version with the same id is a REPLAY — the same observation\n // (key, value, instant) arriving twice. Deterministic ids exist so\n // re-extraction overwrites and never duplicates (spec 010), and a closed\n // version must never be resurrected by one.\n const existingIds = new Set<string>();\n const { rows: replays } = await client.query<{ id: string }>(\n `select id from ${FACTS_TABLE} where org = $1 and uid = $2 and id = any($3::text[])`,\n [scope.org, scope.uid, [...new Set(candidateIds)]],\n );\n for (const row of replays) existingIds.add(row.id);\n\n return { watermark, tombstoned, currentByKey, existingIds };\n }\n\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/**\n * The batch writer — spec 011, made batch-shaped by the 011-013 review.\n *\n * `observe` accepts up to `MEMORY_LIMITS.batchItems` observations, and the\n * first implementation ran the whole decision procedure once per observation:\n * three reads and up to two writes each, sequentially, inside one transaction\n * holding the scope's shared lock and every key's advisory lock. A full batch\n * was ~500 round trips of lock-hold time, and erasure — which wants the\n * exclusive scope lock — waited behind all of it.\n *\n * The rewrite splits it into read / decide / write. Reads are a fixed four\n * queries (see `#loadWriteState`); the decision procedure is pure and runs\n * against those results; the writes are batched statements.\n */\n\n/** What one batch reads before deciding anything. */\ninterface WriteState {\n watermark: string | null;\n tombstoned: ReadonlySet<string>;\n /** Mutated as the plan is built, so a key observed twice chains correctly. */\n currentByKey: Map<string, ProfileFact>;\n /** Likewise: an id this batch inserts is a replay for a later observation. */\n existingIds: Set<string>;\n}\n\n/**\n * A write the plan calls for, tagged with its ROUND.\n *\n * Two observations of the same key chain — the second may close the version\n * the first inserted — so round N of every key is executed before round N+1\n * of any key. Keys never interact, so one round's writes are safe to batch\n * across keys, and the ordinary batch (distinct keys) is a single round.\n */\ntype PlannedWrite =\n | {\n kind: \"refresh\";\n round: number;\n index: number;\n id: string;\n merged: ReturnType<typeof mergeRefresh>;\n }\n | {\n kind: \"insert\";\n round: number;\n index: number;\n version: ProfileFact;\n /** Id of the current version this one supersedes, when it does. */\n closes?: string;\n };\n\n/**\n * Decides the whole batch against the state read for it. PURE — the same\n * three-case confidence rule, provenance guard and watermark comparison the\n * per-observation version applied, in the same order, one observation at a\n * time.\n */\nfunction planObservations(\n obs: readonly FactObservation[],\n trusted: boolean,\n stamps: readonly string[],\n candidateIds: readonly string[],\n state: WriteState,\n): { results: ObserveResult[]; writes: PlannedWrite[] } {\n const { currentByKey, existingIds } = state;\n const results: ObserveResult[] = [];\n const writes: PlannedWrite[] = [];\n /** Writes already planned per key — the round the next one belongs to. */\n const rounds = new Map<string, number>();\n\n for (let index = 0; index < obs.length; index++) {\n const o = obs[index]!;\n const at = stamps[index]!;\n\n if (!trusted && isProtectedProfileKey(o.key)) {\n // Identity is NEVER extracted (spec 010 normative). Nothing written.\n results.push({\n key: o.key,\n outcome: \"refused\",\n detail: `${JSON.stringify(o.key)} is in the protected profile namespace; only the product may write it`,\n });\n continue;\n }\n\n const dead = (o.sourceEpisodeIds ?? []).filter((id) => state.tombstoned.has(id));\n if (dead.length > 0) {\n results.push({\n key: o.key,\n outcome: \"stale\",\n detail: `cites erased episode(s): ${dead.join(\", \")}`,\n });\n continue;\n }\n\n if (state.watermark !== null && at < state.watermark) {\n // Submitted before this scope was erased — discard. `append` has always\n // had its terminal-state guard; this is the profile tier's.\n results.push({\n key: o.key,\n outcome: \"stale\",\n detail: `observation stamped ${at} precedes this scope's erasure at ${state.watermark}`,\n });\n continue;\n }\n\n const confidence = o.confidence ?? DEFAULT_CONFIDENCE;\n const current = currentByKey.get(o.key);\n const decision = decideObservation(current, { value: o.value, confidence }, { trusted });\n const round = rounds.get(o.key) ?? 0;\n\n if (decision.outcome === \"refreshed\" && current !== undefined) {\n const refresh: Parameters<typeof mergeRefresh>[1] = { confidence, at };\n if (o.sourceEpisodeIds !== undefined) refresh.sourceEpisodeIds = o.sourceEpisodeIds;\n if (o.ttlDays !== undefined) refresh.ttlDays = o.ttlDays;\n const merged = mergeRefresh(current, refresh);\n currentByKey.set(o.key, { ...current, ...merged });\n rounds.set(o.key, round + 1);\n writes.push({ kind: \"refresh\", round, index, id: current.id, merged });\n results.push({ key: o.key, outcome: \"refreshed\", factId: current.id });\n continue;\n }\n\n const id = candidateIds[index]!;\n if (existingIds.has(id)) {\n // Reported as `replayed`, not `refreshed`: the version it names may\n // well be closed.\n results.push({\n key: o.key,\n outcome: \"replayed\",\n factId: id,\n detail: \"this exact observation is already a stored version; nothing was written\",\n });\n continue;\n }\n\n const version: ProfileFact = {\n id,\n key: o.key,\n value: o.value,\n confidence,\n sourceEpisodeIds: [...(o.sourceEpisodeIds ?? [])],\n observedAt: at,\n lastSeenAt: at,\n };\n if (o.ttlDays !== undefined) version.ttlDays = o.ttlDays;\n existingIds.add(id);\n rounds.set(o.key, round + 1);\n\n if (decision.outcome === \"conflict\" && current !== undefined) {\n // Stored ALREADY CLOSED, pointing at the version that beat it: full\n // history, contradiction handling, and audit in one write. The current\n // fact stands, so it stays the key's current version here too.\n version.supersededAt = at;\n version.supersededBy = current.id;\n writes.push({ kind: \"insert\", round, index, version });\n const conflict: ObserveResult = { key: o.key, outcome: \"conflict\", factId: id };\n if (decision.detail !== undefined) conflict.detail = decision.detail;\n results.push(conflict);\n continue;\n }\n\n currentByKey.set(o.key, version);\n writes.push(\n decision.outcome === \"superseded\" && current !== undefined\n ? { kind: \"insert\", round, index, version, closes: current.id }\n : { kind: \"insert\", round, index, version },\n );\n const result: ObserveResult = { key: o.key, outcome: decision.outcome, factId: id };\n if (decision.detail !== undefined) result.detail = decision.detail;\n results.push(result);\n }\n\n return { results, writes };\n}\n\n/**\n * Runs the plan, at most three statements per round.\n *\n * Returns the indices whose refresh found no row to update — a version closed\n * between the read and the write, which the caller reports as `stale`.\n */\nasync function applyWrites(\n client: PoolClient,\n scope: Scope,\n writes: readonly PlannedWrite[],\n): Promise<ReadonlySet<number>> {\n const lost = new Set<number>();\n const lastRound = writes.reduce((max, w) => Math.max(max, w.round), -1);\n\n for (let round = 0; round <= lastRound; round++) {\n const refreshes = writes.filter(\n (w): w is Extract<PlannedWrite, { kind: \"refresh\" }> =>\n w.round === round && w.kind === \"refresh\",\n );\n const inserts = writes.filter(\n (w): w is Extract<PlannedWrite, { kind: \"insert\" }> =>\n w.round === round && w.kind === \"insert\",\n );\n\n if (refreshes.length > 0) {\n const updated = await refreshVersions(client, scope, refreshes);\n for (const w of refreshes) if (!updated.has(w.id)) lost.add(w.index);\n }\n // Close the old versions BEFORE inserting the new ones: the partial\n // unique index would otherwise see two current versions for the key.\n const closes = inserts.flatMap((w) =>\n w.closes === undefined\n ? []\n : [{ id: w.closes, at: w.version.observedAt, by: w.version.id }],\n );\n if (closes.length > 0) await closeVersions(client, scope, closes);\n if (inserts.length > 0) await insertVersions(client, scope, inserts.map((w) => w.version));\n }\n\n return lost;\n}\n\n/**\n * Appends one row's values to `params` and returns their `$N::type` list.\n * Only parameter INDICES and the caller's own literal type names are\n * interpolated — never data, which stays bound.\n */\nfunction placeholderList(params: unknown[], cells: readonly (readonly [unknown, string])[]): string {\n return cells\n .map(([value, type]) => {\n params.push(value);\n return `$${params.length}::${type}`;\n })\n .join(\", \");\n}\n\n/** One statement for every `refreshed` outcome in a round. */\nasync function refreshVersions(\n client: PoolClient,\n scope: Scope,\n writes: readonly Extract<PlannedWrite, { kind: \"refresh\" }>[],\n): Promise<ReadonlySet<string>> {\n const params: unknown[] = [scope.org, scope.uid];\n const rows = writes.map(\n (w) =>\n `(${placeholderList(params, [\n [w.id, \"text\"],\n [w.merged.confidence, \"double precision\"],\n [w.merged.lastSeenAt, \"timestamptz\"],\n [[...w.merged.sourceEpisodeIds], \"text[]\"],\n [w.merged.ttlDays ?? null, \"integer\"],\n ])})`,\n );\n // The guard matters: a concurrent `invalidateBySource` between the read and\n // this write would otherwise leave the key with NO current version while\n // the caller was told its value was refreshed. `returning` is how the lost\n // row is still identified now that the round is one statement.\n const { rows: updated } = await client.query<{ id: string }>(\n `update ${FACTS_TABLE}\n set confidence = v.confidence, last_seen_at = v.last_seen_at,\n source_episode_ids = v.source_episode_ids, ttl_days = v.ttl_days\n from (values ${rows.join(\", \")}) as v(id, confidence, last_seen_at, source_episode_ids, ttl_days)\n where ${FACTS_TABLE}.org = $1 and ${FACTS_TABLE}.uid = $2\n and ${FACTS_TABLE}.id = v.id\n and ${FACTS_TABLE}.superseded_at is null and ${FACTS_TABLE}.invalidated_at is null\n returning ${FACTS_TABLE}.id`,\n params,\n );\n return new Set(updated.map((row) => row.id));\n}\n\n/** One statement for every version a round supersedes. */\nasync function closeVersions(\n client: PoolClient,\n scope: Scope,\n closes: readonly { id: string; at: string; by: string }[],\n): Promise<void> {\n const params: unknown[] = [scope.org, scope.uid];\n const rows = closes.map(\n (c) =>\n `(${placeholderList(params, [\n [c.id, \"text\"],\n [c.at, \"timestamptz\"],\n [c.by, \"text\"],\n ])})`,\n );\n await client.query(\n `update ${FACTS_TABLE} set superseded_at = v.at, superseded_by = v.by\n from (values ${rows.join(\", \")}) as v(id, at, by)\n where ${FACTS_TABLE}.org = $1 and ${FACTS_TABLE}.uid = $2 and ${FACTS_TABLE}.id = v.id`,\n params,\n );\n}\n\n/** One statement for every version a round inserts. */\nasync function insertVersions(\n client: PoolClient,\n scope: Scope,\n versions: readonly ProfileFact[],\n): Promise<void> {\n const params: unknown[] = [scope.org, scope.uid];\n const rows = versions.map(\n (f) =>\n `($1, $2, ${placeholderList(params, [\n [f.id, \"text\"],\n [f.key, \"text\"],\n [f.value, \"text\"],\n [f.confidence, \"double precision\"],\n [[...f.sourceEpisodeIds], \"text[]\"],\n [f.observedAt, \"timestamptz\"],\n [f.lastSeenAt, \"timestamptz\"],\n [f.supersededAt ?? null, \"timestamptz\"],\n [f.supersededBy ?? null, \"text\"],\n [f.ttlDays ?? null, \"integer\"],\n ])})`,\n );\n await client.query(\n `insert into ${FACTS_TABLE}\n (org, uid, id, key, value, confidence, source_episode_ids,\n observed_at, last_seen_at, superseded_at, superseded_by, ttl_days)\n values ${rows.join(\", \")}`,\n params,\n );\n}\n\n/**\n * The scope's last erasure timestamp — the in-flight guard slice 013 compares\n * against, so a job that started before an erasure cannot submit afterwards\n * and re-materialize erased content.\n */\nexport class PostgresErasureWatermarks implements ErasureWatermarkStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresMemoryStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async get(scope: Scope): Promise<string | null> {\n // #timeoutMs was resolved and then never passed — the configured\n // statement timeout silently did not apply to watermark reads/writes.\n return inScope(\n this.#pool,\n this.#role,\n scope,\n async (client) => {\n const { rows } = await client.query<{ erasure_watermark: Date | null }>(\n `select erasure_watermark from ${SCOPE_STATE_TABLE} where org = $1 and uid = $2`,\n [scope.org, scope.uid],\n );\n return rows[0]?.erasure_watermark?.toISOString() ?? null;\n },\n this.#timeoutMs,\n );\n }\n\n async set(scope: Scope, rawAt: string): Promise<void> {\n const at = toIsoInstant(rawAt);\n await inScope(\n this.#pool,\n this.#role,\n scope,\n async (client) => {\n // MONOTONIC: `greatest` (which ignores a null existing value) keeps\n // the later of the two stamps. Unconditional assignment let a second\n // erasure stamped by a lagging server clock move the watermark\n // BACKWARDS, un-blocking in-flight writes the first erasure's guard\n // had already refused as stale (the 011–013 review).\n await client.query(\n `insert into ${SCOPE_STATE_TABLE} (org, uid, erasure_watermark)\n values ($1, $2, $3::timestamptz)\n on conflict (org, uid) do update\n set erasure_watermark =\n greatest(${SCOPE_STATE_TABLE}.erasure_watermark, excluded.erasure_watermark)`,\n [scope.org, scope.uid, at],\n );\n },\n this.#timeoutMs,\n );\n }\n}\n","import type { Pool } from \"pg\";\n\nimport { assertRoleIdentifier, DEFAULT_RLS_ROLE, rlsPolicySql, roleBootstrapSql } from \"./schema\";\n\n/**\n * Schema and migration for the memory stores — spec 011.\n *\n * Three scope-stamped tables under the same row-level-security discipline as\n * the session store (spec 001): policies keyed on the transaction-local\n * `alma.org`/`alma.uid` settings, `FORCE ROW LEVEL SECURITY`, and a dedicated\n * non-superuser role assumed per transaction. The migration is idempotent.\n */\n\nexport const EPISODES_TABLE = \"alma_memory_episodes\";\nexport const FACTS_TABLE = \"alma_memory_facts\";\nexport const SCOPE_STATE_TABLE = \"alma_memory_scope_state\";\n\n/**\n * DECISION (spec 011): `importance` and `confidence` are `double precision`,\n * not `real`. float4 round-trips through a text protocol with just enough\n * digits to survive, and a 0.7 that comes back 0.699999988 would make the\n * shared contract suite disagree with every other backend for no reason.\n *\n * DECISION: no pgvector column yet. Lexical ranking first (spec 010); the\n * embedding column and its index arrive with the consumer that needs them,\n * and will be declared as an additional COPY SURFACE so erasure reaches it.\n */\nexport function memoryStoreMigrationSql(role: string = DEFAULT_RLS_ROLE): string {\n assertRoleIdentifier(role);\n return `\ncreate table if not exists ${EPISODES_TABLE} (\n org text not null,\n uid text not null,\n id text not null,\n at timestamptz not null,\n kind text not null,\n summary text not null,\n -- Case-folded copy of summary, folded in the ADAPTER (JS toLowerCase), so\n -- the candidate filter never depends on the database's ctype: under lc_ctype\n -- of C, ilike folds ASCII only, and 'CAFÉ' silently stopped matching 'café'\n -- while the in-memory reference matched it (the 011–013 review). Blanked by the\n -- tombstone exactly as summary is — it is the same content.\n summary_fold text not null default '',\n importance double precision not null,\n state text not null default 'active',\n source_session_id text,\n source_turn_id text,\n erased_at timestamptz,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n primary key (org, uid, id),\n constraint alma_memory_episodes_state_check\n check (state in ('active', 'archived', 'tombstoned'))\n);\n\n-- Migration for tables created before summary_fold existed. The SQL lower()\n-- backfill is the best the database can do (exact under a folding ctype,\n-- ASCII-only under C); every adapter write from then on stores the JS fold.\nalter table ${EPISODES_TABLE} add column if not exists summary_fold text not null default '';\nupdate ${EPISODES_TABLE} set summary_fold = lower(summary)\n where summary_fold = '' and summary <> '';\n\ncreate index if not exists alma_memory_episodes_recent\n on ${EPISODES_TABLE} (org, uid, state, at desc);\n\ncreate index if not exists alma_memory_episodes_session\n on ${EPISODES_TABLE} (org, uid, source_session_id);\n\ncreate table if not exists ${FACTS_TABLE} (\n org text not null,\n uid text not null,\n id text not null,\n key text not null,\n value text not null,\n confidence double precision not null,\n source_episode_ids text[] not null default '{}',\n observed_at timestamptz not null,\n last_seen_at timestamptz not null,\n superseded_at timestamptz,\n superseded_by text,\n invalidated_at timestamptz,\n ttl_days integer,\n primary key (org, uid, id)\n);\n\n-- \"At most one CURRENT version per key\" is a database invariant, not an\n-- application hope: the confidence-gated supersession rule reads the current\n-- version and writes a new one, and a lost race must fail loudly rather than\n-- leave a profile with two contradictory current facts.\ncreate unique index if not exists alma_memory_facts_current\n on ${FACTS_TABLE} (org, uid, key)\n where superseded_at is null and invalidated_at is null;\n\n-- Derived invalidation walks provenance on every erasure.\ncreate index if not exists alma_memory_facts_sources\n on ${FACTS_TABLE} using gin (source_episode_ids);\n\ncreate table if not exists ${SCOPE_STATE_TABLE} (\n org text not null,\n uid text not null,\n erasure_watermark timestamptz,\n primary key (org, uid)\n);\n${rlsPolicySql(EPISODES_TABLE)}${rlsPolicySql(FACTS_TABLE)}${rlsPolicySql(SCOPE_STATE_TABLE)}\n${roleBootstrapSql(role)}\ngrant select, insert, update, delete\n on ${EPISODES_TABLE}, ${FACTS_TABLE}, ${SCOPE_STATE_TABLE}\n to ${role};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateMemoryStores(\n pool: Pool,\n opts: { role?: string } = {},\n): Promise<void> {\n await pool.query(memoryStoreMigrationSql(opts.role));\n}\n","import type { Pool } from \"pg\";\n\nimport { assertRoleIdentifier, DEFAULT_RLS_ROLE, rlsPolicySql, roleBootstrapSql } from \"./schema\";\n\n/**\n * Schema and migration for the spend store — spec: spend-store.\n *\n * Two counter tables. The session counter is scope-stamped and carries the\n * standard `{org, uid}` policy. The tenant-day counter is deliberately\n * ORG-KEYED — it aggregates across every uid and session of the org, which is\n * what an operator caps or watches — so its policy compares org alone, via\n * the SAME shared generator (`rlsPolicySql(table, [\"org\"])`): forking the\n * generator would let future RLS hardening skip this one table (review\n * finding).\n */\n\nexport const SPEND_SESSIONS_TABLE = \"alma_spend_sessions\";\nexport const SPEND_TENANT_DAYS_TABLE = \"alma_spend_tenant_days\";\n\n/**\n * DECISION (spec: spend-store): `usd` is `double precision`, like every\n * fractional number in this package — the whole pricing pipeline computes in\n * JS floats, and a NUMERIC column would round-trip as a string the driver\n * does not sum. A single step costs fractions of a cent; the contract suite\n * pins that nothing rounds it away.\n *\n * The grant carries NO delete: spend counters are retained through scoped\n * purge (a financial record, not personal content — spec: spend-store), and\n * the app role simply cannot remove one.\n */\nexport function spendStoreMigrationSql(role: string = DEFAULT_RLS_ROLE): string {\n assertRoleIdentifier(role);\n return `\ncreate table if not exists ${SPEND_SESSIONS_TABLE} (\n org text not null,\n uid text not null,\n session_id text not null,\n usd double precision not null default 0,\n updated_at timestamptz not null default now(),\n primary key (org, uid, session_id)\n);\n\ncreate table if not exists ${SPEND_TENANT_DAYS_TABLE} (\n org text not null,\n day date not null,\n usd double precision not null default 0,\n updated_at timestamptz not null default now(),\n primary key (org, day)\n);\n${rlsPolicySql(SPEND_SESSIONS_TABLE)}${rlsPolicySql(SPEND_TENANT_DAYS_TABLE, [\"org\"])}\n${roleBootstrapSql(role)}\ngrant select, insert, update\n on ${SPEND_SESSIONS_TABLE}, ${SPEND_TENANT_DAYS_TABLE}\n to ${role};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateSpendStore(pool: Pool, opts: { role?: string } = {}): Promise<void> {\n await pool.query(spendStoreMigrationSql(opts.role));\n}\n","import type { Scope, SpendKey, SpendStore, SpendTotals } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { SPEND_SESSIONS_TABLE, SPEND_TENANT_DAYS_TABLE } from \"./spend-schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresSpendStoreOptions = ScopedStoreOptions;\n\n/**\n * Reference `SpendStore` adapter — spec: spend-store. Same RLS discipline as\n * every other store; the tenant-day counter's policy is org-only, because the\n * counter is (see `spend-schema.ts`).\n *\n * `add` is one transaction over two atomic upserts, each\n * increment-and-RETURNING, so a concurrent add serializes on the row lock and\n * every caller observes its own distinct running total — the property the\n * budget guard's enforcement stands on. The session row is always touched\n * before the day row, so two adds can never order the same pair of locks\n * differently and deadlock.\n *\n * The UTC day bucket is derived in TS — the same `Date.parse` reading the\n * in-memory reference and the memory tier's `toIsoInstant` use — and reaches\n * SQL as a finished `date` literal. Deriving it server-side read the\n * timestamp in the SERVER's TimeZone while the reference read it in the\n * process's, so an offset-less stamp could credit different day counters in\n * the two stores (review finding — the same bug the episode tier fixed once\n * already).\n */\nexport class PostgresSpendStore implements SpendStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresSpendStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async add(entry: SpendKey & { usd: number }): Promise<SpendTotals> {\n if (!Number.isFinite(entry.usd) || entry.usd < 0) {\n throw new Error(`spend must be a non-negative finite number, got ${entry.usd}`);\n }\n const day = utcDayBucket(entry.at);\n return this.#inScope(entry.scope, async (client) => {\n const { rows: session } = await client.query<{ usd: number }>(\n `insert into ${SPEND_SESSIONS_TABLE} (org, uid, session_id, usd)\n values ($1, $2, $3, $4)\n on conflict (org, uid, session_id)\n do update set usd = ${SPEND_SESSIONS_TABLE}.usd + excluded.usd, updated_at = now()\n returning usd`,\n [entry.scope.org, entry.scope.uid, entry.sessionId, entry.usd],\n );\n const { rows: dayRows } = await client.query<{ usd: number }>(\n `insert into ${SPEND_TENANT_DAYS_TABLE} (org, day, usd)\n values ($1, $2::date, $3)\n on conflict (org, day)\n do update set usd = ${SPEND_TENANT_DAYS_TABLE}.usd + excluded.usd, updated_at = now()\n returning usd`,\n [entry.scope.org, day, entry.usd],\n );\n return { sessionUsd: session[0]!.usd, tenantDayUsd: dayRows[0]!.usd };\n });\n }\n\n async peek(key: SpendKey): Promise<SpendTotals> {\n const day = utcDayBucket(key.at);\n return this.#inScope(key.scope, async (client) => {\n const { rows } = await client.query<{ session_usd: number; tenant_day_usd: number }>(\n `select\n coalesce((select usd from ${SPEND_SESSIONS_TABLE}\n where org = $1 and uid = $2 and session_id = $3), 0) as session_usd,\n coalesce((select usd from ${SPEND_TENANT_DAYS_TABLE}\n where org = $1 and day = $4::date), 0) as tenant_day_usd`,\n [key.scope.org, key.scope.uid, key.sessionId, day],\n );\n return { sessionUsd: rows[0]!.session_usd, tenantDayUsd: rows[0]!.tenant_day_usd };\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/**\n * ISO 8601 → `YYYY-MM-DD`, read by `Date.parse` exactly as the in-memory\n * reference reads it (an offset-less stamp is process-local, per the JS\n * spec), so the two stores can never bucket the same input differently. The\n * contract suite pins the agreement.\n */\nfunction utcDayBucket(at: string): string {\n const ms = Date.parse(at);\n if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);\n return new Date(ms).toISOString().slice(0, 10);\n}\n","import type { Pool } from \"pg\";\n\nimport {\n assertIso8601,\n assertRoleIdentifier,\n DEFAULT_RETENTION_ROLE,\n DEFAULT_RLS_ROLE,\n retentionPolicySql,\n rlsPolicySql,\n roleBootstrapSql,\n} from \"./schema\";\nimport { sweepBefore } from \"./scoped\";\n\n/**\n * Schema and migration for the audit trails — spec 038.\n *\n * FIVE tables, one per event family, rather than one with a `payload jsonb`.\n * The shapes are genuinely different, but the deciding argument is that this\n * makes the metadata-only guarantee STRUCTURAL: `audit.ts` promises trails\n * carry \"METADATA ONLY, never content — by construction, not by reviewer\n * vigilance\", and `AccessEvent.resource` is documented as an identifier and\n * never its content. As a `text` column named `resource` that is a constraint\n * a reviewer can look at and a DBA can audit; as a key inside a bag, anything\n * fits and nothing notices.\n *\n * It also leaves room for what comes next: cost feeds billing and must be\n * kept, while recall and context are diagnostic and can expire early. Separate\n * tables make that a policy per table rather than a `where family = …` smeared\n * across every statement.\n */\n\nexport const AUDIT_ACCESS_TABLE = \"alma_audit_access\";\nexport const AUDIT_ROUTING_TABLE = \"alma_audit_routing\";\nexport const AUDIT_COST_TABLE = \"alma_audit_cost\";\nexport const AUDIT_RECALL_TABLE = \"alma_audit_recall\";\nexport const AUDIT_CONTEXT_TABLE = \"alma_audit_context\";\n\nexport const AUDIT_TABLES = [\n AUDIT_ACCESS_TABLE,\n AUDIT_ROUTING_TABLE,\n AUDIT_COST_TABLE,\n AUDIT_RECALL_TABLE,\n AUDIT_CONTEXT_TABLE,\n] as const;\n\n/**\n * DECISION (spec 038): the primary key is a `uuid` defaulted by\n * `gen_random_uuid()`, not a `bigserial`. A trail is append-only with no\n * natural key, and a sequence would need its own `usage` grant for the\n * non-superuser app role — one more thing to forget in a migration whose whole\n * point is that defense-in-depth must not depend on a manual step (spec 001).\n * `gen_random_uuid()` is core Postgres since 13; no extension.\n *\n * DECISION (spec 038): the grant carries `select, insert` — no `delete`, and\n * no `update`. The spend counters' posture, for the same reason: these are\n * content-free records kept as evidence, and an erasure that removed the proof\n * an erasure happened is not an improvement (§10). `update` is excluded too,\n * because an audit row that can be edited is not an audit row.\n *\n * DECISION (spec 038): optional array fields (`capsCrossed`, `degradedTiers`,\n * `refused`) are `text[] not null default '{}'`. Absent and empty mean the\n * same thing for all three — no caps crossed, no tiers degraded, nothing\n * refused — so normalising removes a null check rather than losing a\n * distinction.\n */\nexport function auditLogMigrationSql(\n role: string = DEFAULT_RLS_ROLE,\n retentionRole: string = DEFAULT_RETENTION_ROLE,\n): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(retentionRole);\n return `\ncreate table if not exists ${AUDIT_ACCESS_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n tool text not null,\n action text not null,\n -- Identifier of the touched resource (id/path) — NEVER its content.\n resource text,\n session_id text,\n turn_id text,\n primary key (id),\n constraint alma_audit_access_action_check\n check (action in ('read', 'write', 'delete', 'export'))\n);\n\ncreate index if not exists alma_audit_access_turn\n on ${AUDIT_ACCESS_TABLE} (org, uid, turn_id);\n\ncreate index if not exists alma_audit_access_recent\n on ${AUDIT_ACCESS_TABLE} (org, uid, at desc);\n\ncreate table if not exists ${AUDIT_ROUTING_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n tier text not null,\n sensitivity text not null,\n model_provider text not null,\n model_id text not null,\n -- Carried verbatim from ModelChoice.rationale: a policy must explain itself.\n rationale text not null,\n session_id text,\n turn_id text,\n primary key (id)\n);\n\ncreate index if not exists alma_audit_routing_turn\n on ${AUDIT_ROUTING_TABLE} (org, uid, turn_id);\n\ncreate table if not exists ${AUDIT_COST_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n model_provider text not null,\n model_id text not null,\n input_tokens bigint not null,\n output_tokens bigint not null,\n cache_read_input_tokens bigint,\n cache_write_input_tokens bigint,\n -- double precision, like every fractional number in this package: the whole\n -- pricing pipeline computes in JS floats, and NUMERIC round-trips as a\n -- string the driver does not sum (spec: spend-store).\n cost_usd double precision not null,\n caps_crossed text[] not null default '{}',\n session_id text,\n turn_id text,\n primary key (id)\n);\n\ncreate index if not exists alma_audit_cost_turn\n on ${AUDIT_COST_TABLE} (org, uid, turn_id);\n\ncreate index if not exists alma_audit_cost_recent\n on ${AUDIT_COST_TABLE} (org, uid, at desc);\n\ncreate table if not exists ${AUDIT_RECALL_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n session_id text not null,\n turn_id text not null,\n -- Provenance, never the recalled text: a verbatim copy would be a surface\n -- erasure cannot reach (spec 012).\n fact_ids text[] not null default '{}',\n episode_ids text[] not null default '{}',\n budget_tokens bigint not null,\n estimated_tokens bigint not null,\n dropped_tokens bigint,\n truncated boolean not null,\n degraded_tiers text[] not null default '{}',\n primary key (id)\n);\n\ncreate index if not exists alma_audit_recall_turn\n on ${AUDIT_RECALL_TABLE} (org, uid, turn_id);\n\ncreate table if not exists ${AUDIT_CONTEXT_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n session_id text not null,\n turn_id text not null,\n step integer not null,\n delegate boolean not null default false,\n changed text[] not null,\n refused text[] not null default '{}',\n -- The two shapes, flattened. Six numbers should be six numbers (spec 038).\n before_system_blocks bigint not null,\n before_system_chars bigint not null,\n before_messages bigint not null,\n before_message_blocks bigint not null,\n before_message_chars bigint not null,\n before_max_tokens bigint not null,\n after_system_blocks bigint not null,\n after_system_chars bigint not null,\n after_messages bigint not null,\n after_message_blocks bigint not null,\n after_message_chars bigint not null,\n after_max_tokens bigint not null,\n primary key (id)\n);\n\ncreate index if not exists alma_audit_context_turn\n on ${AUDIT_CONTEXT_TABLE} (org, uid, turn_id);\n${AUDIT_TABLES.map((t) => rlsPolicySql(t)).join(\"\")}\n${roleBootstrapSql(role)}\n${roleBootstrapSql(retentionRole)}\ngrant select, insert\n on ${AUDIT_TABLES.join(\", \")}\n to ${role};\n\n-- Retention is a DIFFERENT ACTOR from erasure (spec 039). The app role above\n-- cannot delete, so a scoped purge can never remove the proof an erasure\n-- happened; this role can delete any row, and only that — the grant below\n-- withholds insert and update from it.\n--\n-- These policies are not optional decoration. Every audit table carries FORCE\n-- ROW LEVEL SECURITY, which subjects even the table OWNER to the predicate, so\n-- a sweep running as any non-superuser matched the scope-keyed policy, found\n-- nothing, and deleted zero rows while reporting success. That is what the spec\n-- 039 review caught: a retention mechanism that silently retains forever, in\n-- exactly the deployments careful enough not to connect as a superuser.\n--\n-- TWO policies, not one (see retentionPolicySql). A FOR DELETE policy alone\n-- still deleted nothing, because a DELETE with a WHERE clause must SCAN the\n-- rows to filter them, and the scope-keyed policy is FOR ALL -- which governs\n-- SELECT too. That second step surfaced only by running the statement.\n${AUDIT_TABLES.map((t) => retentionPolicySql(t, retentionRole, \"alma_audit_retention\")).join(\"\")}\n\ngrant select, delete\n on ${AUDIT_TABLES.join(\", \")}\n to ${retentionRole};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateAuditLog(\n pool: Pool,\n opts: { role?: string; retentionRole?: string } = {},\n): Promise<void> {\n await pool.query(auditLogMigrationSql(opts.role, opts.retentionRole));\n}\n\n/** The audit table names, as a type — one window per family (spec 039). */\nexport type AuditTable = (typeof AUDIT_TABLES)[number];\n\n/**\n * Deletes audit rows older than each family's cutoff — spec 039.\n *\n * This resolves a consequence spec 038 created without naming: the app role\n * was granted `select, insert` and NOT `delete`, so a scoped erasure cannot\n * remove the proof an erasure happened — which also means the app role cannot\n * expire audit rows.\n *\n * DECISION (spec 039): erasure may not delete audit rows; time-based retention\n * may, and they are DIFFERENT ACTORS. A scoped purge runs as the app role and\n * is blocked at the grant. Retention is maintenance: it runs as the pool's own\n * role, crosses scopes by nature, and deliberately does NOT go through the\n * RLS-bound `inScope` path.\n *\n * A plain function taking the pool rather than a method on a store, and the\n * shape is the point — a method on a seam would suggest it participates in the\n * scope-bound discipline, and this does not. Passing the pool makes the\n * privilege visible at the call site.\n *\n * PER FAMILY, because that is what five tables bought: cost feeds billing and\n * is kept for years while recall and context are diagnostic and can go in\n * weeks. A single window would have made the split pointless. A table absent\n * from `windows` is not touched.\n *\n * It ASSUMES {@link DEFAULT_RETENTION_ROLE} for the duration of one\n * transaction, and that is the correction the spec 039 review forced. The first\n * version ran the deletes straight on the pool, on the theory that it therefore\n * \"bypassed RLS\". It did not: every audit table carries FORCE ROW LEVEL\n * SECURITY, which subjects even the table owner to the predicate, so the sweep\n * matched the scope-keyed policy with no scope bound, deleted ZERO rows, and\n * reported success. It appeared to work only because dev and CI connect as\n * superusers — the same elevation `scoped.ts` calls out as the reason every\n * other store must actively assume a role rather than assume privilege.\n *\n * A retention mechanism that silently retains forever, in exactly the\n * deployments careful enough not to connect as a superuser, is the failure this\n * whole slice exists to prevent one layer up.\n */\nexport async function purgeAuditBefore(\n pool: Pool,\n windows: Partial<Record<AuditTable, string>>,\n opts: { retentionRole?: string; batch?: number } = {},\n): Promise<Partial<Record<AuditTable, number>>> {\n const retentionRole = opts.retentionRole ?? DEFAULT_RETENTION_ROLE;\n assertRoleIdentifier(retentionRole);\n // Validated BEFORE a connection is taken, so a typo'd window fails without\n // having deleted from an earlier table in the loop.\n for (const table of AUDIT_TABLES) {\n const before = windows[table];\n if (before !== undefined) assertIso8601(before, `timestamp for ${table}`);\n }\n\n const purged: Partial<Record<AuditTable, number>> = {};\n for (const table of AUDIT_TABLES) {\n const before = windows[table];\n if (before === undefined) continue;\n // The table name is a module constant, never caller input — `windows` is\n // keyed by a closed union and the loop drives from `AUDIT_TABLES`. In\n // batches, each its own transaction (spec: close-review-part-two).\n purged[table] = await sweepBefore(pool, { role: retentionRole, table, column: \"at\", before, ...(opts.batch !== undefined ? { batch: opts.batch } : {}) });\n }\n return purged;\n}\n","import type {\n AccessEvent,\n AuditLog,\n ContextEvent,\n CostEvent,\n RecallEvent,\n RoutingEvent,\n Scope,\n} from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport {\n AUDIT_ACCESS_TABLE,\n AUDIT_CONTEXT_TABLE,\n AUDIT_COST_TABLE,\n AUDIT_RECALL_TABLE,\n AUDIT_ROUTING_TABLE,\n} from \"./audit-schema\";\nimport { assertIso8601 } from \"./schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresAuditLogOptions = ScopedStoreOptions;\n\n/**\n * Reference `AuditLog` sink — §6.8, spec 038. Same RLS discipline as every\n * other store in this package: each write runs inside a transaction that\n * assumes the app role and binds `alma.org`/`alma.uid` for the policies to\n * compare against.\n *\n * WRITES SYNCHRONOUSLY, and ships no buffering wrapper. The contract permits\n * either — \"buffer internally and return synchronously to stay off the critical\n * path, or return a promise and be awaited\" — and the cost of this choice is\n * real: a three-step turn with two tool calls emits one routing, three cost and\n * two access events, plus recall and context, so roughly eight round trips join\n * the turn's critical path.\n *\n * The alternative is worse in the way that matters. A buffered sink that loses\n * its buffer on a crash stops writing SILENTLY, which is exactly the failure\n * `AuditSinkError` was typed to catch (spec 027) and exactly what \"where trails\n * are written is swappable; THAT they are written is not\" forbids. A product\n * that wants the trade should own a visible wrapper, not inherit it from the\n * reference adapter.\n *\n * There is no read surface here, deliberately (spec 038). §7.3's thesis is that\n * \"what exactly did the model see about this user?\" is a query — but which\n * queries matter is not yet known, and a contract shaped before its consumers\n * exist is public API from its first release. The product queries its own\n * tables; the rule of two decides when one has earned promotion.\n */\nexport class PostgresAuditLog implements AuditLog {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresAuditLogOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async access(e: AccessEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_ACCESS_TABLE}\n (org, uid, at, tool, action, resource, session_id, turn_id)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.tool,\n e.action,\n e.resource ?? null,\n e.sessionId ?? null,\n e.turnId ?? null,\n ],\n ),\n );\n }\n\n async routing(e: RoutingEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_ROUTING_TABLE}\n (org, uid, at, tier, sensitivity, model_provider, model_id, rationale,\n session_id, turn_id)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9, $10)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.tier,\n e.sensitivity,\n e.model.provider,\n e.model.id,\n e.rationale,\n e.sessionId ?? null,\n e.turnId ?? null,\n ],\n ),\n );\n }\n\n async cost(e: CostEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_COST_TABLE}\n (org, uid, at, model_provider, model_id, input_tokens, output_tokens,\n cache_read_input_tokens, cache_write_input_tokens, cost_usd,\n caps_crossed, session_id, turn_id)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.model.provider,\n e.model.id,\n e.usage.inputTokens,\n e.usage.outputTokens,\n // Absent stays absent rather than becoming a reported zero — the\n // distinction `addUsage` is careful about, preserved at the boundary.\n e.usage.cacheReadInputTokens ?? null,\n e.usage.cacheWriteInputTokens ?? null,\n e.costUsd,\n [...(e.capsCrossed ?? [])],\n e.sessionId ?? null,\n e.turnId ?? null,\n ],\n ),\n );\n }\n\n async recall(e: RecallEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_RECALL_TABLE}\n (org, uid, at, session_id, turn_id, fact_ids, episode_ids,\n budget_tokens, estimated_tokens, dropped_tokens, truncated,\n degraded_tiers)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.sessionId,\n e.turnId,\n [...e.factIds],\n [...e.episodeIds],\n e.budgetTokens,\n e.estimatedTokens,\n e.droppedTokens ?? null,\n e.truncated,\n [...(e.degradedTiers ?? [])],\n ],\n ),\n );\n }\n\n async context(e: ContextEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_CONTEXT_TABLE}\n (org, uid, at, session_id, turn_id, step, delegate, changed, refused,\n before_system_blocks, before_system_chars, before_messages,\n before_message_blocks, before_message_chars, before_max_tokens,\n after_system_blocks, after_system_chars, after_messages,\n after_message_blocks, after_message_chars, after_max_tokens)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9,\n $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.sessionId,\n e.turnId,\n e.step,\n e.delegate ?? false,\n [...e.changed],\n [...(e.refused ?? [])],\n e.before.systemBlocks,\n e.before.systemChars,\n e.before.messages,\n e.before.messageBlocks,\n e.before.messageChars,\n e.before.maxTokens,\n e.after.systemBlocks,\n e.after.systemChars,\n e.after.messages,\n e.after.messageBlocks,\n e.after.messageChars,\n e.after.maxTokens,\n ],\n ),\n );\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #write(scope: Scope, run: (client: PoolClient) => Promise<unknown>): Promise<void> {\n await inScope(this.#pool, this.#role, scope, async (client) => {\n await run(client);\n }, this.#timeoutMs);\n }\n}\n\n/**\n * Validates the event's ISO timestamp before it reaches SQL. Rejecting here\n * rather than letting Postgres parse it is the same choice the spend store\n * made for its day bucket: a server with a different `TimeZone` must not be\n * able to read a timestamp differently from the process that wrote it.\n */\nconst instant = (at: string): string => assertIso8601(at);\n","import {\n assertWellFormed,\n scopePath,\n type CompletedTurn,\n type LeaseOpts,\n type Scope,\n type TurnClaim,\n type TurnKey,\n type TurnLease,\n type TurnStore,\n} from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\nimport { TURN_CLAIMS_TABLE, TURN_LEASES_TABLE } from \"./turn-schema\";\n\nexport type PostgresTurnStoreOptions = ScopedStoreOptions;\n\n/**\n * Reference `TurnStore` adapter — spec 030. Same RLS discipline as every other\n * store in this package.\n *\n * The lease is ONE conditional upsert: the row is taken only when there is no\n * row or the existing one has expired, and Postgres serializes contenders on\n * the primary key, so exactly one concurrent caller sees a returned row. That\n * single statement is the whole mutual-exclusion argument — a read-then-write\n * would let two callers both observe a free session.\n *\n * Waiting is POLLING with backoff, not `LISTEN`/`NOTIFY` or an advisory lock.\n * An advisory lock is bound to the connection that took it, and a turn holds\n * its lease across many queries from a POOL — the lease has to outlive any one\n * connection, which is what makes it a row.\n */\nexport class PostgresTurnStore implements TurnStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresTurnStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async acquire(scope: Scope, sessionId: string, opts: LeaseOpts): Promise<TurnLease | null> {\n assertLeaseOpts(opts);\n scopePath(scope);\n const deadline = Date.now() + opts.waitMs;\n // Backoff rather than a tight loop: a contended session with a 30s wait\n // would otherwise be 600 round trips against a row nobody is releasing.\n let backoffMs = 25;\n for (;;) {\n const lease = await this.#tryAcquire(scope, sessionId, opts.ttlMs);\n if (lease !== null) return lease;\n const remaining = deadline - Date.now();\n if (remaining <= 0) return null;\n await new Promise((resolve) => setTimeout(resolve, Math.min(backoffMs, remaining)));\n backoffMs = Math.min(backoffMs * 2, 250);\n }\n }\n\n async #tryAcquire(scope: Scope, sessionId: string, ttlMs: number): Promise<TurnLease | null> {\n const token = crypto.randomUUID();\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<{ token: string; expires_at: Date }>(\n // The `where` on the conflict target is what makes this atomic: a live\n // lease makes the update match nothing, so no row comes back and the\n // caller lost. Expiry is compared against the SERVER's clock, so two\n // app instances with drifting clocks cannot disagree about whether a\n // lease is live — which is how the same session reaches two holders.\n `insert into ${TURN_LEASES_TABLE} (org, uid, session_id, token, expires_at)\n values ($1, $2, $3, $4, now() + make_interval(secs => $5::double precision))\n on conflict (org, uid, session_id) do update\n set token = excluded.token, expires_at = excluded.expires_at\n where ${TURN_LEASES_TABLE}.expires_at <= now()\n returning token, expires_at`,\n [scope.org, scope.uid, sessionId, token, ttlMs / 1000],\n );\n const row = rows[0];\n return row === undefined\n ? null\n : { token: row.token, expiresAt: row.expires_at.toISOString() };\n });\n }\n\n async release(scope: Scope, sessionId: string, lease: TurnLease): Promise<void> {\n await this.#inScope(scope, async (client) => {\n // Token-matched: a STALE holder deletes nothing rather than freeing the\n // session the next turn is holding (spec 030).\n await client.query(\n `delete from ${TURN_LEASES_TABLE}\n where org = $1 and uid = $2 and session_id = $3 and token = $4`,\n [scope.org, scope.uid, sessionId, lease.token],\n );\n });\n }\n\n async claim(key: TurnKey): Promise<TurnClaim> {\n return this.#inScope(key.scope, async (client) => {\n // Insert-if-absent, then read. Two statements in one transaction rather\n // than a CTE: the row may already exist in either of two states, and\n // being obviously correct is worth one extra round trip on a path that\n // runs once per turn.\n await client.query(\n `insert into ${TURN_CLAIMS_TABLE} (org, uid, session_id, idempotency_key)\n values ($1, $2, $3, $4)\n on conflict (org, uid, session_id, idempotency_key) do nothing`,\n [key.scope.org, key.scope.uid, key.sessionId, key.idempotencyKey],\n );\n const { rows } = await client.query<{ completed: CompletedTurn | null }>(\n `select completed from ${TURN_CLAIMS_TABLE}\n where org = $1 and uid = $2 and session_id = $3 and idempotency_key = $4`,\n [key.scope.org, key.scope.uid, key.sessionId, key.idempotencyKey],\n );\n const completed = rows[0]?.completed ?? null;\n // NULL is IN FLIGHT — reachable only after a crash, since the lease makes\n // concurrency impossible — and re-running is the correct answer there.\n return completed === null ? { status: \"fresh\" } : { status: \"replay\", completed };\n });\n }\n\n /**\n * PRECONDITION: every string in `completed`, keys included, is well-formed\n * UTF-16 — `jsonb` refuses a lone surrogate and the write fails (spec:\n * well-formed-text).\n *\n * The loop repairs it on the way in: `reply` is drawn from messages\n * `record()` passed through `toWellFormedDeep`. But that repair is\n * BEST-EFFORT by design — its `catch` keeps the unrepaired message, because\n * failing to repair must never cost more than not having tried — so a\n * pathologically nested payload can still arrive malformed.\n *\n * Now ENFORCED here and in the in-memory reference alike, by the same guard\n * `SessionStore.append` uses (spec 040). The adapters used to differ — that\n * store kept the lone surrogate, this one refused the write — which is the\n * gap spec 025's review recorded and two contracts then documented instead\n * of closing. The check runs BEFORE the UPDATE and regardless of whether a\n * row matches, because `$5::jsonb` is parsed either way; the in-memory\n * reference orders it the same for that reason. The first version of this\n * comment claimed the loop simply guaranteed it (spec 033).\n */\n async complete(key: TurnKey, completed: CompletedTurn): Promise<void> {\n // `jsonb` would refuse this anyway — the guard makes the failure the same\n // one the in-memory reference now gives, and raises it before a connection\n // is taken (spec 040).\n assertWellFormed(completed, \"completed\");\n await this.#inScope(key.scope, async (client) => {\n // UPDATE, never upsert: a session erased while its turn was still\n // running must not have the reply resurrected by that turn finishing.\n await client.query(\n `update ${TURN_CLAIMS_TABLE} set completed = $5::jsonb\n where org = $1 and uid = $2 and session_id = $3 and idempotency_key = $4`,\n [\n key.scope.org,\n key.scope.uid,\n key.sessionId,\n key.idempotencyKey,\n JSON.stringify(completed),\n ],\n );\n });\n }\n\n async abandon(key: TurnKey): Promise<void> {\n await this.#inScope(key.scope, async (client) => {\n await client.query(\n `delete from ${TURN_CLAIMS_TABLE}\n where org = $1 and uid = $2 and session_id = $3 and idempotency_key = $4`,\n [key.scope.org, key.scope.uid, key.sessionId, key.idempotencyKey],\n );\n });\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n await this.#inScope(scope, async (client) => {\n const params =\n sessionId === undefined ? [scope.org, scope.uid] : [scope.org, scope.uid, sessionId];\n const bySession = sessionId === undefined ? \"\" : \" and session_id = $3\";\n // Claims only — the lease guards a turn that may be in flight (spec: close-review-part-two).\n await client.query(\n `delete from ${TURN_CLAIMS_TABLE} where org = $1 and uid = $2${bySession}`,\n params,\n );\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\nfunction assertLeaseOpts(opts: LeaseOpts): void {\n for (const [name, value] of [\n [\"ttlMs\", opts.ttlMs],\n [\"waitMs\", opts.waitMs],\n ] as const) {\n // Mirrors the in-memory reference: a NaN interval reaches SQL as `NaN\n // seconds`, and every later expiry comparison against it is false — the\n // lease reads as live forever and the session is blocked until someone\n // deletes the row by hand.\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`${name} must be a non-negative finite number, got ${value}`);\n }\n }\n}\n","import type { Pool } from \"pg\";\n\nimport {\n assertIso8601,\n assertRoleIdentifier,\n DEFAULT_RETENTION_ROLE,\n DEFAULT_RLS_ROLE,\n retentionPolicySql,\n rlsPolicySql,\n roleBootstrapSql,\n} from \"./schema\";\nimport { sweepBefore } from \"./scoped\";\n\n/**\n * Schema and migration for the turn store — spec 030.\n *\n * Two scope-stamped tables under the standard `{org, uid}` policy, through the\n * SAME shared generator every other Alma table uses: forking it would let\n * future RLS hardening skip these two (the finding spec: spend-store recorded).\n *\n * The lease is one row per session, replaced in place; the claim is one row\n * per idempotency key, holding the replayable turn as `jsonb`.\n */\n\nexport const TURN_LEASES_TABLE = \"alma_turn_leases\";\nexport const TURN_CLAIMS_TABLE = \"alma_turn_claims\";\n\n/**\n * DECISION (spec 030): `expires_at` is a `timestamptz` computed from the\n * SERVER's `now()`, not from the process clock. Every contender for one lease\n * must compare against one clock, and two app instances with a few seconds of\n * drift would otherwise disagree about whether a lease is live — which is\n * exactly the disagreement that hands the same session to two holders. This is\n * the opposite of the spend store's day bucket, which is derived in TS\n * precisely because it must match the reference implementation's reading of a\n * caller-supplied timestamp; here there is no caller timestamp to match.\n *\n * DECISION (spec 030): `completed` is nullable, and NULL means in flight. The\n * claim row is inserted before the turn runs and updated when it finishes, so\n * the row's existence is the claim and its content is the result.\n *\n * The grant carries `delete`: unlike spend counters, these rows hold content\n * (the reply verbatim) and §10 erasure must be able to remove them.\n *\n * DECISION (spec: erasure-reaches-the-claims): time-based retention of the\n * claims runs as the retention role, never as the app role — the two actors\n * spec 039 separated for the audit tables. The app role's delete is scoped\n * by RLS and serves erasure; the sweep crosses scopes and needs its own\n * policies, `for select` AND `for delete`, since a DELETE with a WHERE scans.\n */\nexport function turnStoreMigrationSql(\n role: string = DEFAULT_RLS_ROLE,\n retentionRole: string = DEFAULT_RETENTION_ROLE,\n): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(retentionRole);\n return `\ncreate table if not exists ${TURN_LEASES_TABLE} (\n org text not null,\n uid text not null,\n session_id text not null,\n token text not null,\n expires_at timestamptz not null,\n primary key (org, uid, session_id)\n);\n\ncreate table if not exists ${TURN_CLAIMS_TABLE} (\n org text not null,\n uid text not null,\n session_id text not null,\n idempotency_key text not null,\n completed jsonb,\n created_at timestamptz not null default now(),\n primary key (org, uid, session_id, idempotency_key)\n);\n\ncreate index if not exists alma_turn_claims_created\n on ${TURN_CLAIMS_TABLE} (created_at);\n${rlsPolicySql(TURN_LEASES_TABLE)}${rlsPolicySql(TURN_CLAIMS_TABLE)}\n${roleBootstrapSql(role)}\n${roleBootstrapSql(retentionRole)}\ngrant select, insert, update, delete\n on ${TURN_LEASES_TABLE}, ${TURN_CLAIMS_TABLE}\n to ${role};\n${retentionPolicySql(TURN_CLAIMS_TABLE, retentionRole, \"alma_turn_claims_retention\")}\n\ngrant select, delete\n on ${TURN_CLAIMS_TABLE}\n to ${retentionRole};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateTurnStore(\n pool: Pool,\n opts: { role?: string; retentionRole?: string } = {},\n): Promise<void> {\n await pool.query(turnStoreMigrationSql(opts.role, opts.retentionRole));\n}\n\n/**\n * Deletes claims created before the cutoff — spec: erasure-reaches-the-claims.\n * The largest copy of content in this schema had no retention at all: one\n * reply per delivered message, forever.\n *\n * Same shape as `purgeAuditBefore`, for the same reason: a plain function\n * taking the pool, assuming the retention role for one transaction, crossing\n * scopes by nature and never through the RLS-bound `inScope` path. A claim\n * still in flight at that age belongs to a crashed turn, which spec 030\n * already makes re-runnable, so it goes too. Leases are untouched: they\n * expire on their own clock.\n */\nexport async function purgeTurnClaimsBefore(\n pool: Pool,\n before: string,\n opts: { retentionRole?: string; batch?: number } = {},\n): Promise<number> {\n const role = opts.retentionRole ?? DEFAULT_RETENTION_ROLE;\n assertRoleIdentifier(role);\n assertIso8601(before);\n return sweepBefore(pool, { role, table: TURN_CLAIMS_TABLE, column: \"created_at\", before, ...(opts.batch !== undefined ? { batch: opts.batch } : {}) });\n}\n","import { scopePath, type JobHandle, type RoutineRun, type RoutineRunOutcome, type RoutineRunStore, type Scope } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { ROUTINE_RUNS_TABLE } from \"./routine-schema\";\nimport { assertIso8601 } from \"./schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresRoutineRunStoreOptions = ScopedStoreOptions;\n\ninterface RunRow {\n routine_id: string;\n run_id: string;\n started_at: Date;\n finished_at: Date | null;\n outcome: RoutineRunOutcome;\n reason: string | null;\n cost_usd: number;\n session_id: string | null;\n turn_id: string | null;\n handle: JobHandle | null;\n delivery_hash: string | null;\n}\n\nconst COLUMNS = \"routine_id, run_id, started_at, finished_at, outcome, reason, cost_usd, session_id, turn_id, handle, delivery_hash\";\n\n/**\n * Reference `RoutineRunStore` adapter — spec: postgres-routine-runs. Same RLS\n * discipline as every other store. The three reads the runner makes — the\n * same fire again, today's count, the last run of an outcome — are one key\n * lookup and one indexed range scan; `since` is compared as `timestamptz`,\n * so a bound with an offset filters the same instants the reference does.\n */\nexport class PostgresRoutineRunStore implements RoutineRunStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresRoutineRunStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async record(run: RoutineRun): Promise<void> {\n scopePath(run.scope);\n assertIso8601(run.startedAt, \"startedAt\");\n if (run.finishedAt !== undefined) assertIso8601(run.finishedAt, \"finishedAt\");\n await this.#inScope(run.scope, async (client) => {\n await client.query(\n `insert into ${ROUTINE_RUNS_TABLE}\n (org, uid, routine_id, run_id, started_at, finished_at, outcome, reason, cost_usd,\n session_id, turn_id, handle, delivery_hash)\n values ($1, $2, $3, $4, $5::timestamptz, $6::timestamptz, $7, $8, $9, $10, $11, $12::jsonb, $13)\n on conflict (org, uid, routine_id, run_id) do update set\n started_at = excluded.started_at, finished_at = excluded.finished_at,\n outcome = excluded.outcome, reason = excluded.reason, cost_usd = excluded.cost_usd,\n session_id = excluded.session_id, turn_id = excluded.turn_id,\n handle = excluded.handle, delivery_hash = excluded.delivery_hash`,\n [\n run.scope.org,\n run.scope.uid,\n run.routineId,\n run.id,\n run.startedAt,\n run.finishedAt ?? null,\n run.outcome,\n run.reason ?? null,\n run.costUsd,\n run.sessionId ?? null,\n run.turnId ?? null,\n run.handle === undefined ? null : JSON.stringify(run.handle),\n run.deliveryHash ?? null,\n ],\n );\n });\n }\n\n async get(scope: Scope, routineId: string, runId: string): Promise<RoutineRun | null> {\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<RunRow>(\n `select ${COLUMNS} from ${ROUTINE_RUNS_TABLE}\n where org = $1 and uid = $2 and routine_id = $3 and run_id = $4`,\n [scope.org, scope.uid, routineId, runId],\n );\n return rows[0] === undefined ? null : toRun(scope, rows[0]);\n });\n }\n\n async list(\n scope: Scope,\n routineId: string,\n opts: { since?: string; outcome?: RoutineRunOutcome; limit?: number } = {},\n ): Promise<RoutineRun[]> {\n scopePath(scope);\n if (opts.since !== undefined) assertIso8601(opts.since, \"since\");\n if (opts.limit !== undefined && opts.limit <= 0) return [];\n return this.#inScope(scope, async (client) => {\n const params: unknown[] = [scope.org, scope.uid, routineId];\n const where = [\"org = $1\", \"uid = $2\", \"routine_id = $3\"];\n if (opts.since !== undefined) {\n params.push(opts.since);\n where.push(`started_at >= $${params.length}::timestamptz`);\n }\n if (opts.outcome !== undefined) {\n params.push(opts.outcome);\n where.push(`outcome = $${params.length}`);\n }\n let limit = \"\";\n if (opts.limit !== undefined) {\n params.push(opts.limit);\n limit = ` limit $${params.length}`;\n }\n const { rows } = await client.query<RunRow>(\n `select ${COLUMNS} from ${ROUTINE_RUNS_TABLE}\n where ${where.join(\" and \")}\n order by started_at desc, run_id asc${limit}`,\n params,\n );\n return rows.map((row) => toRun(scope, row));\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/** Absent stays ABSENT, never `null`: the two references must agree byte for byte. */\nfunction toRun(scope: Scope, row: RunRow): RoutineRun {\n const run: RoutineRun = {\n id: row.run_id,\n routineId: row.routine_id,\n scope: { org: scope.org, uid: scope.uid },\n startedAt: row.started_at.toISOString(),\n outcome: row.outcome,\n costUsd: row.cost_usd,\n };\n if (row.finished_at !== null) run.finishedAt = row.finished_at.toISOString();\n if (row.reason !== null) run.reason = row.reason;\n if (row.session_id !== null) run.sessionId = row.session_id;\n if (row.turn_id !== null) run.turnId = row.turn_id;\n if (row.handle !== null) run.handle = row.handle;\n if (row.delivery_hash !== null) run.deliveryHash = row.delivery_hash;\n return run;\n}\n","import type { Pool } from \"pg\";\n\nimport {\n assertIso8601,\n assertRoleIdentifier,\n DEFAULT_RETENTION_ROLE,\n DEFAULT_RLS_ROLE,\n retentionPolicySql,\n rlsPolicySql,\n roleBootstrapSql,\n} from \"./schema\";\nimport { sweepBefore } from \"./scoped\";\n\n/**\n * Schema and migration for routine runs — spec: postgres-routine-runs. One\n * scope-stamped table under the shared policy generator, METADATA only:\n * never the text delivered (the hash is what a store may keep, spec:\n * routine-runner), so the app role gets no delete — a run record is retained\n * like a trail, and swept by age as the retention actor.\n */\n\nexport const ROUTINE_RUNS_TABLE = \"alma_routine_runs\";\n\nexport function routineRunStoreMigrationSql(\n role: string = DEFAULT_RLS_ROLE,\n retentionRole: string = DEFAULT_RETENTION_ROLE,\n): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(retentionRole);\n return `\ncreate table if not exists ${ROUTINE_RUNS_TABLE} (\n org text not null,\n uid text not null,\n routine_id text not null,\n run_id text not null,\n started_at timestamptz not null,\n finished_at timestamptz,\n outcome text not null,\n reason text,\n cost_usd double precision not null,\n session_id text,\n turn_id text,\n handle jsonb,\n delivery_hash text,\n primary key (org, uid, routine_id, run_id),\n constraint alma_routine_runs_outcome_check\n check (outcome in ('delivered', 'duplicate', 'submitted', 'waiting', 'refused', 'failed'))\n);\n\ncreate index if not exists alma_routine_runs_recent\n on ${ROUTINE_RUNS_TABLE} (org, uid, routine_id, started_at desc);\n${rlsPolicySql(ROUTINE_RUNS_TABLE)}\n${roleBootstrapSql(role)}\n${roleBootstrapSql(retentionRole)}\ngrant select, insert, update\n on ${ROUTINE_RUNS_TABLE}\n to ${role};\n${retentionPolicySql(ROUTINE_RUNS_TABLE, retentionRole, \"alma_routine_runs_retention\")}\n\ngrant select, delete\n on ${ROUTINE_RUNS_TABLE}\n to ${retentionRole};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateRoutineRunStore(\n pool: Pool,\n opts: { role?: string; retentionRole?: string } = {},\n): Promise<void> {\n await pool.query(routineRunStoreMigrationSql(opts.role, opts.retentionRole));\n}\n\n/** Deletes run records started before the cutoff, as the retention role — the shape of every sweep here. */\nexport async function purgeRoutineRunsBefore(\n pool: Pool,\n before: string,\n opts: { retentionRole?: string; batch?: number } = {},\n): Promise<number> {\n const role = opts.retentionRole ?? DEFAULT_RETENTION_ROLE;\n assertRoleIdentifier(role);\n assertIso8601(before);\n return sweepBefore(pool, { role, table: ROUTINE_RUNS_TABLE, column: \"started_at\", before, ...(opts.batch !== undefined ? { batch: opts.batch } : {}) });\n}\n"],"mappings":"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","names":["scopePath","scopePath","scopePath","scopePath","id","instant","assertWellFormed","scopePath","scopePath","assertWellFormed","scopePath","scopePath"]}
|
|
1
|
+
{"version":3,"sources":["../src/session-store.ts","../src/schema.ts","../src/scoped.ts","../src/memory-stores.ts","../src/memory-schema.ts","../src/spend-schema.ts","../src/spend-store.ts","../src/audit-schema.ts","../src/audit-store.ts","../src/turn-store.ts","../src/turn-schema.ts","../src/routine-store-schema.ts","../src/routine-store.ts","../src/routine-run-store.ts","../src/routine-schema.ts"],"sourcesContent":["import type {\n LoadOpts,\n Msg,\n Scope,\n SessionStore,\n ToolTrafficExpiry,\n} from \"@alma-harness/core\";\nimport { assertWellFormed, scopePath } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { assertIso8601 } from \"./schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresSessionStoreOptions = ScopedStoreOptions;\n\n/**\n * Reference `SessionStore` adapter (§6.6, §6.8) — spec 001.\n *\n * Transactional seq: `append` claims a seq range with an atomic counter\n * upsert, so concurrent appenders serialize on the session row. Every\n * operation runs inside a transaction scoped by transaction-local settings\n * (`alma.org` / `alma.uid`) that the RLS policies compare against.\n */\nexport class PostgresSessionStore implements SessionStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresSessionStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n // Was accepted and silently ignored when this helper was extracted for the\n // memory stores (spec 013). An option that does nothing is worse than one\n // that does not exist: it reads as configured protection.\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async append(scope: Scope, sessionId: string, entries: Msg[]): Promise<void> {\n scopePath(scope); // validate before any early return, like every adapter must\n if (entries.length === 0) return;\n // `jsonb` would refuse this anyway — the guard is here to make the FAILURE\n // the same one the in-memory reference gives, naming the row and the\n // remedy instead of surfacing a driver error about a Unicode escape, and\n // to fail before a connection is taken (spec 040).\n for (const [i, entry] of entries.entries()) assertWellFormed(entry, `entries[${i}]`);\n await this.#inScope(scope, async (client) => {\n const { rows } = await client.query<{ last_seq: string }>(\n `insert into alma_sessions (org, uid, session_id, last_seq)\n values ($1, $2, $3, $4)\n on conflict (org, uid, session_id)\n do update set last_seq = alma_sessions.last_seq + $4, updated_at = now()\n returning last_seq`,\n [scope.org, scope.uid, sessionId, entries.length],\n );\n const lastSeq = Number(rows[0]?.last_seq);\n const firstSeq = lastSeq - entries.length + 1;\n\n const params: unknown[] = [scope.org, scope.uid, sessionId];\n const tuples = entries.map((msg, i) => {\n params.push(firstSeq + i, JSON.stringify(msg));\n return `($1, $2, $3, $${params.length - 1}, $${params.length}::jsonb)`;\n });\n await client.query(\n `insert into alma_session_entries (org, uid, session_id, seq, msg)\n values ${tuples.join(\", \")}`,\n params,\n );\n });\n }\n\n async load(scope: Scope, sessionId: string, opts?: LoadOpts): Promise<Msg[]> {\n scopePath(scope); // validate before any early return, like every adapter must\n const limit = opts?.limit;\n if (limit !== undefined && limit <= 0) return [];\n return this.#inScope(scope, async (client) => {\n // With a limit we want the most recent N, still chronological: take\n // them seq-descending and reverse (spec 001 / LoadOpts decision).\n const { rows } = await client.query<{ msg: Msg }>(\n limit === undefined\n ? `select msg from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3\n order by seq asc`\n : `select msg from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3\n order by seq desc limit $4`,\n limit === undefined\n ? [scope.org, scope.uid, sessionId]\n : [scope.org, scope.uid, sessionId, limit],\n );\n const msgs = rows.map((r) => r.msg);\n return limit === undefined ? msgs : msgs.reverse();\n });\n }\n\n async expireToolTraffic(\n scope: Scope,\n sessionId: string,\n opts: { inactiveSince: string },\n ): Promise<ToolTrafficExpiry> {\n scopePath(scope); // validate before any connection, like every adapter must\n const cutoff = instant(opts.inactiveSince);\n return this.#inScope(scope, async (client) => {\n // Serialise against `append` by taking the counter row it upserts — spec\n // 039 review. Without this the SELECT below reads a session that a\n // concurrent append makes ACTIVE before the transaction commits, and the\n // history is rewritten on stale information. The in-memory reference\n // cannot show this (its whole operation is synchronous, so it is atomic\n // with respect to the event loop), which is exactly the kind of\n // divergence a Postgres-only race hides behind a green contract suite.\n await client.query(\n `select last_seq from alma_sessions\n where org = $1 and uid = $2 and session_id = $3\n for update`,\n [scope.org, scope.uid, sessionId],\n );\n const { rows } = await client.query<{ seq: string; msg: Msg }>(\n `select seq, msg from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3\n order by seq asc`,\n [scope.org, scope.uid, sessionId],\n );\n // Refused, not thrown (spec 039). A message with no `meta.at` counts as\n // ACTIVE — an entry whose age cannot be established must not be assumed\n // old, which is the safe direction when being wrong costs the +27%\n // prefix rewrite §6.6 measured.\n const active = rows.some((r) => {\n const at = r.msg.meta?.at;\n // A MALFORMED stamp counts as active too, and the first version missed\n // it: Date.parse gives NaN, and NaN > cutoff is false, so an entry whose\n // age could not be established was treated as OLD — the opposite of\n // what the rule says (spec 039 review).\n if (at === undefined) return true;\n const ms = Date.parse(at);\n return Number.isNaN(ms) || ms > cutoff;\n });\n if (active) return { blocks: 0, messages: 0, expired: false };\n\n let blocks = 0;\n const rewrites: { seq: string; msg: Msg }[] = [];\n const removals: string[] = [];\n for (const row of rows) {\n // ALL the tool blocks or none: a partial expiry leaves a `tool_call`\n // without its `tool_result`, which a provider answers with a 400\n // (spec 026). `media` survives — it is a pointer, and user-facing.\n // Reasoning goes with the tool traffic (spec: reasoning-blocks).\n const survivors = row.msg.blocks.filter(\n (b) => b.type !== \"tool_call\" && b.type !== \"tool_result\" && b.type !== \"reasoning\",\n );\n if (survivors.length === row.msg.blocks.length) continue;\n blocks += row.msg.blocks.length - survivors.length;\n if (survivors.length === 0) removals.push(row.seq);\n else rewrites.push({ seq: row.seq, msg: { ...row.msg, blocks: survivors } });\n }\n\n for (const r of rewrites) {\n await client.query(\n `update alma_session_entries set msg = $5::jsonb\n where org = $1 and uid = $2 and session_id = $3 and seq = $4`,\n [scope.org, scope.uid, sessionId, r.seq, JSON.stringify(r.msg)],\n );\n }\n if (removals.length > 0) {\n // The seq numbers of surviving entries are NOT renumbered: `load`\n // orders by seq and never assumes it is contiguous, and rewriting them\n // would race the `last_seq` counter that `append` claims ranges from.\n await client.query(\n `delete from alma_session_entries\n where org = $1 and uid = $2 and session_id = $3 and seq = any($4::bigint[])`,\n [scope.org, scope.uid, sessionId, removals],\n );\n }\n return { blocks, messages: removals.length, expired: true };\n });\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n await this.#inScope(scope, async (client) => {\n // Entries follow via ON DELETE CASCADE.\n if (sessionId === undefined) {\n await client.query(`delete from alma_sessions where org = $1 and uid = $2`, [\n scope.org,\n scope.uid,\n ]);\n } else {\n await client.query(\n `delete from alma_sessions where org = $1 and uid = $2 and session_id = $3`,\n [scope.org, scope.uid, sessionId],\n );\n }\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/** Rejects an unparseable cutoff rather than silently treating it as the epoch. */\nconst instant = (at: string): number => Date.parse(assertIso8601(at));\n","import { assertIso8601 } from \"@alma-harness/core\";\nimport type { Pool } from \"pg\";\n\n// The ONE ISO-8601 guard lives in core since spec: close-060-064-findings; re-exported for the callers here.\nexport { assertIso8601 };\n\n/**\n * Schema and migration for the session store — spec 001 — plus the RLS\n * building blocks every Alma table shares (spec 011).\n *\n * Scope-stamped tables under row-level security. Migrations are idempotent\n * (safe to run on every startup) and creating/granting the RLS role is part of\n * them: defense-in-depth must not depend on a manual step.\n */\n\nexport const DEFAULT_RLS_ROLE = \"alma_app\";\n\n/**\n * Role the retention sweeps assume — spec 039 review. It exists because the\n * app role deliberately cannot delete an audit row, and a sweep therefore\n * cannot run as it; the claims' sweep uses it for the same separation of\n * actors (spec: erasure-reaches-the-claims).\n */\nexport const DEFAULT_RETENTION_ROLE = \"alma_retention\";\n\nconst IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\n\n/**\n * Role and table names are interpolated into DDL (Postgres cannot parameterize\n * identifiers), so they are validated strictly — never raw.\n */\nexport function assertRoleIdentifier(role: string): void {\n if (!IDENTIFIER.test(role)) {\n throw new Error(`Invalid Postgres role identifier: ${JSON.stringify(role)}`);\n }\n}\n\n/**\n * The scope-isolation policy, identical on every Alma table — spec 001, §6.8.\n * One generator, because a table that received a hand-copied policy with a\n * dropped `with check` would still pass its functional tests while accepting\n * cross-tenant writes.\n *\n * `keys` exists for the one org-keyed table (the spend store's tenant-day\n * counter, which has no uid column — spec: spend-store); every scope-stamped\n * table takes the default. Parameterized rather than forked, so RLS\n * hardening lands on every table at once (review finding).\n */\nexport function rlsPolicySql(table: string, keys: readonly (\"org\" | \"uid\")[] = [\"org\", \"uid\"]): string {\n assertRoleIdentifier(table);\n const name = keys.includes(\"uid\") ? \"alma_scope_isolation\" : \"alma_org_isolation\";\n const predicate = keys\n .map((key) => `${key} = current_setting('alma.${key}', true)`)\n .join(\"\\n and \");\n return `\nalter table ${table} enable row level security;\nalter table ${table} force row level security;\n\ndrop policy if exists ${name} on ${table};\ncreate policy ${name} on ${table}\n using (\n ${predicate}\n )\n with check (\n ${predicate}\n );\n`;\n}\n\n/**\n * The retention actor's policies on one table — spec 039, shared since the\n * claims got them too (spec: erasure-reaches-the-claims). TWO policies: a\n * DELETE with a WHERE must SCAN the rows, and the scope-keyed policy is FOR\n * ALL, which governs SELECT too. `name` keeps each table's existing policy\n * names, so a migration on a populated database drops and recreates its own.\n */\nexport function retentionPolicySql(table: string, retentionRole: string, name: string): string {\n assertRoleIdentifier(table);\n assertRoleIdentifier(retentionRole);\n assertRoleIdentifier(name);\n return `\ndrop policy if exists ${name}_read on ${table};\ncreate policy ${name}_read on ${table}\n for select\n to ${retentionRole}\n using (true);\n\ndrop policy if exists ${name} on ${table};\ncreate policy ${name} on ${table}\n for delete\n to ${retentionRole}\n using (true);`;\n}\n\n/** Creates the RLS role if it does not exist, tolerating a concurrent race. */\nexport function roleBootstrapSql(role: string): string {\n assertRoleIdentifier(role);\n return `\ndo $$\nbegin\n if not exists (select from pg_roles where rolname = '${role}') then\n begin\n create role ${role} nologin;\n exception when duplicate_object or unique_violation then\n -- Another instance won the race between the existence check and the\n -- create (pg_roles is cluster-wide and CREATE ROLE has no IF NOT\n -- EXISTS); losing it must not roll back the rest of the migration.\n -- Two creates that overlap surface as unique_violation on\n -- pg_authid, not duplicate_object (review of 056–058).\n null;\n end;\n end if;\nend $$;\n`;\n}\n\n/**\n * Membership for the connection user — spec: erasure-reaches-the-claims.\n * `SET LOCAL ROLE` admits only roles the SESSION user is a member of, and no\n * migration can know which login the product connects as. The suites never\n * noticed: they connect as a superuser, which may assume any role, so the\n * first least-privilege deployment failed on every call. Re-granting is a\n * no-op, so this is safe to run at every startup.\n */\nexport function grantRoleSql(role: string, to: string): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(to);\n return `grant ${role} to ${to};`;\n}\n\n/**\n * Run by a role that may grant (typically the migrations' superuser), once\n * per login. Deliberately NOT part of any migration: the retention role must\n * not reach the app's login by accident — the sweep is a different actor.\n */\nexport async function grantRole(pool: Pool, opts: { role?: string; to: string }): Promise<void> {\n await pool.query(grantRoleSql(opts.role ?? DEFAULT_RLS_ROLE, opts.to));\n}\n\n/**\n * DECISION (spec 001): SQL lives as a TS constant, not a .sql file — packages\n * ship TypeScript source in Phase 0/1, and a migration runner would be\n * premature for one migration.\n */\nexport function sessionStoreMigrationSql(role: string = DEFAULT_RLS_ROLE): string {\n assertRoleIdentifier(role);\n return `\ncreate table if not exists alma_sessions (\n org text not null,\n uid text not null,\n session_id text not null,\n last_seq bigint not null default 0,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n primary key (org, uid, session_id)\n);\n\ncreate table if not exists alma_session_entries (\n org text not null,\n uid text not null,\n session_id text not null,\n seq bigint not null,\n msg jsonb not null,\n created_at timestamptz not null default now(),\n primary key (org, uid, session_id, seq),\n foreign key (org, uid, session_id)\n references alma_sessions (org, uid, session_id) on delete cascade\n);\n${rlsPolicySql(\"alma_sessions\")}${rlsPolicySql(\"alma_session_entries\")}\n${roleBootstrapSql(role)}\ngrant select, insert, update, delete\n on alma_sessions, alma_session_entries\n to ${role};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateSessionStore(\n pool: Pool,\n opts: { role?: string } = {},\n): Promise<void> {\n await pool.query(sessionStoreMigrationSql(opts.role));\n}\n","import { scopePath, type Scope } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { assertIso8601, assertRoleIdentifier, DEFAULT_RETENTION_ROLE, DEFAULT_RLS_ROLE } from \"./schema\";\n\n/**\n * The RLS binding, shared by every Postgres store — spec 001, spec 011.\n *\n * One implementation, because \"the scoped transaction\" is exactly the place a\n * copy-paste divergence would silently weaken tenancy: a store that forgot the\n * `SET LOCAL ROLE`, or set the scope after the first query, would still pass\n * its functional tests while running with isolation off.\n */\n\nexport interface ScopedStoreOptions {\n /**\n * Per-transaction `statement_timeout` in milliseconds (default 30s; `null`\n * disables). A store whose queries are shaped by model-supplied input needs\n * a ceiling that does not depend on the caller remembering one — the\n * adversarial review drove a single search query to 25 seconds while it held\n * a pooled connection. Set transaction-locally, so it resets on commit.\n */\n statementTimeoutMs?: number | null;\n /**\n * Role assumed per transaction via SET LOCAL ROLE, so RLS binds even when\n * the connection user is privileged (superusers bypass RLS; dev and CI both\n * connect as superusers — spec 001). `null` opts out, which weakens\n * defense-in-depth to application-level isolation only.\n */\n role?: string | null;\n}\n\nexport const DEFAULT_STATEMENT_TIMEOUT_MS = 30_000;\n\n/** Resolves and validates the configured role once, at construction. */\nexport function resolveRlsRole(opts: ScopedStoreOptions): string | null {\n const role = opts.role === undefined ? DEFAULT_RLS_ROLE : opts.role;\n if (role !== null) assertRoleIdentifier(role);\n return role;\n}\n\nexport function resolveStatementTimeout(opts: ScopedStoreOptions): number | null {\n const ms = opts.statementTimeoutMs === undefined ? DEFAULT_STATEMENT_TIMEOUT_MS : opts.statementTimeoutMs;\n if (ms !== null && (!Number.isInteger(ms) || ms <= 0)) {\n throw new Error(`statementTimeoutMs must be a positive integer or null, got ${ms}`);\n }\n return ms;\n}\n\n/**\n * One transaction as `role`, on a pooled connection: begin, `set local role`,\n * `fn`, commit. Shared by the scoped stores and the retention sweeps (review\n * of 056–058): the sweeps had copied the shape and dropped the one line that\n * matters on failure. A failed ROLLBACK means the connection may still be\n * inside an aborted transaction; handing it back to the pool would give the\n * next borrower cascading errors, so it is destroyed instead of reused.\n */\nexport async function inTransaction<T>(\n pool: Pool,\n role: string | null,\n fn: (client: PoolClient) => Promise<T>,\n /** A per-transaction `statement_timeout`; the scoped path always sets one, the deployment reads may (spec: close-060-064-findings). */\n statementTimeoutMs?: number | null,\n): Promise<T> {\n const client = await pool.connect();\n let rollbackFailed: unknown;\n try {\n await client.query(\"begin\");\n if (role !== null) await client.query(`set local role ${role}`);\n if (statementTimeoutMs !== undefined) {\n await client.query(`select set_config('statement_timeout', $1, true)`, [statementTimeoutMs === null ? \"0\" : String(statementTimeoutMs)]);\n }\n const result = await fn(client);\n await client.query(\"commit\");\n return result;\n } catch (err) {\n await client.query(\"rollback\").catch((rollbackErr: unknown) => {\n rollbackFailed = rollbackErr;\n });\n throw err;\n } finally {\n client.release(rollbackFailed === undefined ? undefined : (rollbackFailed as Error));\n }\n}\n\n/**\n * A retention sweep in BATCHES — spec: close-review-part-two. One unbounded\n * `delete … where stamp < cutoff` on a backlog is one long transaction and a\n * WAL burst, and under a `statement_timeout` fails with nothing freed. Each\n * batch of `ctid`s is its own transaction as `role`; the count is summed.\n * Table and column are module constants, validated as identifiers anyway.\n */\nexport async function sweepBefore(\n pool: Pool,\n opts: { role: string; table: string; column: string; before: string; batch?: number },\n): Promise<number> {\n assertRoleIdentifier(opts.table);\n assertRoleIdentifier(opts.column);\n const batch = opts.batch ?? 5_000;\n if (!Number.isInteger(batch) || batch < 1) throw new Error(`batch must be a positive integer, got ${batch}`);\n let total = 0;\n for (;;) {\n const removed = await inTransaction(pool, opts.role, async (client) => {\n // Ordered by the column, so the index the migrations carry is what the planner walks.\n const { rowCount } = await client.query(\n `delete from ${opts.table}\n where ctid = any(array(select ctid from ${opts.table} where ${opts.column} < $1::timestamptz order by ${opts.column} limit $2))`,\n [opts.before, batch],\n );\n return rowCount ?? 0;\n });\n total += removed;\n if (removed < batch) return total;\n }\n}\n\n/** The three sweeps' one preamble (spec: close-060-064-findings): the role, the cutoff, the batch. */\nexport async function purgeBefore(\n pool: Pool,\n opts: { table: string; column: string; before: string; retentionRole?: string; batch?: number },\n): Promise<number> {\n const role = opts.retentionRole ?? DEFAULT_RETENTION_ROLE;\n assertRoleIdentifier(role);\n assertIso8601(opts.before);\n return sweepBefore(pool, { role, table: opts.table, column: opts.column, before: opts.before, ...(opts.batch !== undefined ? { batch: opts.batch } : {}) });\n}\n\n/**\n * One transaction: assume the RLS role, bind the scope as transaction-local\n * settings, run `fn`. Everything resets at commit/rollback, so pooled\n * connections never leak role or scope.\n */\nexport async function inScope<T>(\n pool: Pool,\n role: string | null,\n scope: Scope,\n fn: (client: PoolClient) => Promise<T>,\n statementTimeoutMs: number | null = DEFAULT_STATEMENT_TIMEOUT_MS,\n): Promise<T> {\n scopePath(scope); // central segment validation (§6.8), before any connection\n return inTransaction(pool, role, async (client) => {\n // One round trip for scope binding AND the timeout — all transaction-local.\n await client.query(\n `select set_config('alma.org', $1, true),\n set_config('alma.uid', $2, true),\n set_config('statement_timeout', $3, true)`,\n [scope.org, scope.uid, statementTimeoutMs === null ? \"0\" : String(statementTimeoutMs)],\n );\n return fn(client);\n });\n}\n","import {\n scopePath,\n type CopySurface,\n type Episode,\n type EpisodeInput,\n type EpisodeQuery,\n type EpisodeQueryResult,\n type EpisodeStore,\n type ErasureSelector,\n type ErasureWatermarkStore,\n type FactObservation,\n type InvalidateResult,\n type ObserveResult,\n type Profile,\n type ProfileFact,\n type ProfileReadOpts,\n type ProfileStore,\n type Scope,\n type TombstoneResult,\n} from \"@alma-harness/core\";\nimport {\n applyMemoryBudget,\n assertBatchSize,\n assertEpisodeInput,\n assertEpisodeQuery,\n assertErasureSelector,\n assertFactObservation,\n compareFactsForRecall,\n decideObservation,\n DEFAULT_CONFIDENCE,\n DEFAULT_IMPORTANCE,\n deriveEpisodeId,\n deriveFactId,\n isProtectedProfileKey,\n mergeRefresh,\n MEMORY_LIMITS,\n rankEpisodes,\n StaleWriteError,\n toIsoInstant,\n tokenizeMemoryText,\n} from \"@alma-harness/memory\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { EPISODES_TABLE, FACTS_TABLE, SCOPE_STATE_TABLE } from \"./memory-schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\n/**\n * Reference memory adapters (§6.7, §6.8) — spec 011. Same RLS discipline as\n * the session store: every operation runs inside a transaction that assumes\n * the app role and binds `alma.org`/`alma.uid`, which the policies compare\n * against.\n *\n * Ranking is NOT done in SQL. The adapter filters CANDIDATES — a superset of\n * what the tokenizer matches — and hands them to core's `rankEpisodes`, so\n * every backend orders results identically (spec 011).\n */\n\nexport type PostgresMemoryStoreOptions = ScopedStoreOptions;\n\n/**\n * DECISION (spec 011): how many rows a text query fetches before ranking.\n * Consequence, documented rather than hidden: a term appearing only in\n * episodes older than the most recent `CANDIDATE_WINDOW` can be missed.\n * `tsvector`/pgvector is the upgrade when a consumer hits it.\n */\nconst CANDIDATE_WINDOW = 200;\n\nconst EPISODE_COLUMNS = `id, at, kind, summary, importance, state,\n source_session_id, source_turn_id, erased_at`;\n\nconst FACT_COLUMNS = `id, key, value, confidence, source_episode_ids,\n observed_at, last_seen_at, superseded_at, superseded_by, invalidated_at, ttl_days`;\n\ninterface EpisodeRow {\n id: string;\n at: Date;\n kind: string;\n summary: string;\n importance: number;\n state: Episode[\"state\"];\n source_session_id: string | null;\n source_turn_id: string | null;\n erased_at: Date | null;\n}\n\nfunction toEpisode(row: EpisodeRow): Episode {\n const episode: Episode = {\n id: row.id,\n at: row.at.toISOString(),\n kind: row.kind,\n summary: row.summary,\n importance: row.importance,\n state: row.state,\n };\n if (row.source_session_id !== null || row.source_turn_id !== null) {\n const source: NonNullable<Episode[\"source\"]> = {};\n if (row.source_session_id !== null) source.sessionId = row.source_session_id;\n if (row.source_turn_id !== null) source.turnId = row.source_turn_id;\n episode.source = source;\n }\n if (row.erased_at !== null) episode.erasedAt = row.erased_at.toISOString();\n return episode;\n}\n\ninterface FactRow {\n id: string;\n key: string;\n value: string;\n confidence: number;\n source_episode_ids: string[];\n observed_at: Date;\n last_seen_at: Date;\n superseded_at: Date | null;\n superseded_by: string | null;\n invalidated_at: Date | null;\n ttl_days: number | null;\n}\n\n/**\n * A `FactRow` LEFT JOINed onto the profile's stamp: every fact column is null\n * on the row a scope with nothing readable still returns — see `get`.\n */\ntype FactJoinRow = { [K in keyof FactRow]: FactRow[K] | null } & { updated_at: Date | null };\n\nfunction toFact(row: FactRow): ProfileFact {\n const fact: ProfileFact = {\n id: row.id,\n key: row.key,\n value: row.value,\n confidence: row.confidence,\n sourceEpisodeIds: row.source_episode_ids,\n observedAt: row.observed_at.toISOString(),\n lastSeenAt: row.last_seen_at.toISOString(),\n };\n if (row.superseded_at !== null) fact.supersededAt = row.superseded_at.toISOString();\n if (row.superseded_by !== null) fact.supersededBy = row.superseded_by;\n if (row.invalidated_at !== null) fact.invalidatedAt = row.invalidated_at.toISOString();\n if (row.ttl_days !== null) fact.ttlDays = row.ttl_days;\n return fact;\n}\n\nexport class PostgresEpisodeStore implements EpisodeStore {\n readonly copySurfaces: readonly CopySurface[] = [{ name: EPISODES_TABLE, kind: \"primary\" }];\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresMemoryStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async append(scope: Scope, input: EpisodeInput): Promise<Episode> {\n assertEpisodeInput(input);\n const id = deriveEpisodeId(scope, input);\n const at = toIsoInstant(input.at ?? new Date().toISOString());\n return this.#inScope(scope, async (client) => {\n // The episode tier joins the erasure barrier too (spec 013 review). It\n // was outside it: an extraction in flight during an erase({kind:\"all\"})\n // inserted a NEW derived id after the tombstone pass — no conflict, so\n // the `state <> 'tombstoned'` guard never applied — and the user's\n // verbatim summary survived an erasure that reported itself complete.\n await client.query(scopeLockSql(\"shared\"), [scopeLockKey(scope)]);\n const { rows: mark } = await client.query<{ erasure_watermark: Date | null }>(\n `select erasure_watermark from ${SCOPE_STATE_TABLE} where org = $1 and uid = $2`,\n [scope.org, scope.uid],\n );\n const watermark = mark[0]?.erasure_watermark?.toISOString() ?? null;\n if (watermark !== null && at < watermark) {\n throw new StaleWriteError(\n `episode stamped ${at} precedes this scope's erasure at ${watermark}`,\n );\n }\n // The guard is part of the statement, not a read-then-write: terminal\n // states are create-only, and a re-append racing an erasure must never\n // resurrect content (spec 010). `state` is deliberately absent from the\n // SET list, so an archived episode stays archived.\n await client.query(\n `insert into ${EPISODES_TABLE}\n (org, uid, id, at, kind, summary, summary_fold, importance, source_session_id, source_turn_id)\n values ($1, $2, $3, $4::timestamptz, $5, $6, $7, $8, $9, $10)\n on conflict (org, uid, id) do update\n set at = excluded.at,\n kind = excluded.kind,\n summary = excluded.summary,\n summary_fold = excluded.summary_fold,\n importance = excluded.importance,\n source_session_id = excluded.source_session_id,\n source_turn_id = excluded.source_turn_id,\n updated_at = now()\n where ${EPISODES_TABLE}.state <> 'tombstoned'`,\n [\n scope.org,\n scope.uid,\n id,\n at,\n input.kind,\n input.summary,\n // Folded HERE, in JS, with the same toLowerCase the tokenizer uses —\n // never in SQL, where the fold depends on the database's ctype (see\n // the schema comment on summary_fold).\n input.summary.toLowerCase(),\n input.importance ?? DEFAULT_IMPORTANCE,\n input.source?.sessionId ?? null,\n input.source?.turnId ?? null,\n ],\n );\n const { rows } = await client.query<EpisodeRow>(\n `select ${EPISODE_COLUMNS} from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and id = $3`,\n [scope.org, scope.uid, id],\n );\n const row = rows[0];\n if (!row) throw new Error(`episode ${id} vanished between write and read`);\n return toEpisode(row);\n });\n }\n\n async query(scope: Scope, q: EpisodeQuery): Promise<EpisodeQueryResult> {\n // Validate the scope even on no-op paths, like every adapter must.\n scopePath(scope);\n assertEpisodeQuery(q);\n if (q.limit !== undefined && q.limit <= 0) return { episodes: [], truncated: false };\n const states = q.includeArchived === true ? [\"active\", \"archived\"] : [\"active\"];\n // A candidate filter, not a ranking: substring matching is a SUPERSET of\n // the tokenizer's whole-token match, and core's ranker drops the extras.\n // Tokens are alphanumeric by construction, so no LIKE metacharacter can\n // reach the pattern. Matched against summary_fold — folded in JS at write\n // time, like the pattern is at query time — because `ilike` folds in the\n // DATABASE's ctype: under lc_ctype of C it folds ASCII only, so accented\n // matches were silently dropped while the in-memory reference matched\n // them, breaking the superset the ranker depends on (the 011–013 review).\n const terms = q.text === undefined ? new Set<string>() : tokenizeMemoryText(q.text);\n // Text that carries no usable term matches NOTHING — the ranker says the\n // same, and a null pattern list here would have meant \"no filter\".\n if (q.text !== undefined && terms.size === 0) return { episodes: [], truncated: false };\n const patterns = terms.size === 0 ? null : [...terms].map((t) => `%${t}%`);\n const window = Math.max(CANDIDATE_WINDOW, 4 * (q.limit ?? 0));\n\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<EpisodeRow>(\n `select ${EPISODE_COLUMNS} from ${EPISODES_TABLE}\n where org = $1 and uid = $2\n and state = any($3::text[])\n and ($4::text[] is null or kind = any($4::text[]))\n and ($5::timestamptz is null or at >= $5::timestamptz)\n and ($6::timestamptz is null or at <= $6::timestamptz)\n and ($7::text[] is null or summary_fold like any($7::text[]))\n order by at desc\n limit $8`,\n [\n scope.org,\n scope.uid,\n states,\n q.kinds === undefined ? null : [...q.kinds],\n // Normalized like every other timestamp in the tier: raw bounds are\n // read in the SERVER's timezone while the ranker reads them in the\n // process's, so an offsetless bound filtered differently on each.\n q.since === undefined ? null : toIsoInstant(q.since),\n q.until === undefined ? null : toIsoInstant(q.until),\n patterns,\n window + 1, // one extra row is how we learn the window was saturated\n ],\n );\n // Saturating the candidate window means matches were dropped. Saying\n // `truncated: false` there would let a caller read a clipped answer as\n // a complete one.\n const clipped = rows.length > window;\n const ranked = rankEpisodes(\n rows.slice(0, window).map(toEpisode),\n q,\n new Date().toISOString(),\n );\n const limited = q.limit === undefined ? ranked : ranked.slice(0, q.limit);\n const { kept, truncated } = applyMemoryBudget(limited, q.budget, (ep) => ep.summary.length);\n return {\n episodes: kept,\n truncated: truncated || limited.length < ranked.length || clipped,\n };\n });\n }\n\n async get(scope: Scope, episodeIds: readonly string[]): Promise<Episode[]> {\n scopePath(scope);\n assertBatchSize(episodeIds.length, \"episodeIds\", MEMORY_LIMITS.idsPerCall);\n if (episodeIds.length === 0) return [];\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<EpisodeRow>(\n `select ${EPISODE_COLUMNS} from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and id = any($3::text[])`,\n [scope.org, scope.uid, [...episodeIds]],\n );\n const byId = new Map(rows.map((r) => [r.id, toEpisode(r)]));\n // Requested order, so callers can zip against their own id list.\n return episodeIds.flatMap((id) => {\n const ep = byId.get(id);\n return ep ? [ep] : [];\n });\n });\n }\n\n async tombstone(\n scope: Scope,\n selector: ErasureSelector,\n rawAt: string,\n ): Promise<TombstoneResult> {\n const at = toIsoInstant(rawAt);\n assertErasureSelector(selector);\n const [predicate, param] = selectorPredicate(selector);\n return this.#inScope(scope, async (client) => {\n // Exclusive, like the profile tier's invalidation: waits out every\n // append that read the watermark before it was written.\n await client.query(scopeLockSql(\"exclusive\"), [scopeLockKey(scope)]);\n // One statement reports BOTH: the full match set (the provenance chain a\n // retry must still walk) and which of them this call actually blanked.\n const { rows } = await client.query<{ id: string; written: boolean }>(\n `with matched as (\n select id from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and ${predicate}\n ), blanked as (\n update ${EPISODES_TABLE}\n set kind = '', summary = '', summary_fold = '', state = 'tombstoned',\n erased_at = $3::timestamptz, updated_at = now()\n where org = $1 and uid = $2 and state <> 'tombstoned'\n and id in (select id from matched)\n returning id\n )\n select m.id, (b.id is not null) as written\n from matched m left join blanked b on b.id = m.id`,\n param === null ? [scope.org, scope.uid, at] : [scope.org, scope.uid, at, param],\n );\n return {\n episodeIds: rows.map((r) => r.id),\n written: rows.filter((r) => r.written).length,\n surfaces: [EPISODES_TABLE],\n };\n });\n }\n\n async archive(scope: Scope, episodeIds: readonly string[]): Promise<number> {\n scopePath(scope);\n assertBatchSize(episodeIds.length, \"episodeIds\", MEMORY_LIMITS.idsPerCall);\n if (episodeIds.length === 0) return 0;\n return this.#inScope(scope, async (client) => {\n const { rowCount } = await client.query(\n `update ${EPISODES_TABLE} set state = 'archived', updated_at = now()\n where org = $1 and uid = $2 and state = 'active' and id = any($3::text[])`,\n [scope.org, scope.uid, [...episodeIds]],\n );\n return rowCount ?? 0;\n });\n }\n\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/** The erasure selector as a SQL predicate plus its single bound parameter. */\nfunction selectorPredicate(selector: ErasureSelector): [string, string[] | null] {\n switch (selector.kind) {\n case \"all\":\n return [\"true\", null];\n case \"episodes\":\n return [\"id = any($4::text[])\", [...selector.episodeIds]];\n case \"sessions\":\n return [\"source_session_id = any($4::text[])\", [...selector.sessionIds]];\n }\n}\n\nconst EPOCH = \"1970-01-01T00:00:00.000Z\";\n\n/**\n * The scope-level barrier both sides of an erasure hold — spec 013.\n *\n * Writers take it SHARED (alongside their per-key exclusive locks); the\n * invalidation pass takes it EXCLUSIVE. That makes erasure part of the same\n * protocol writers already follow, which is what the per-key locks alone could\n * not do: an in-flight writer now finishes before invalidation runs, so its\n * fact is caught, and a writer starting afterwards reads a watermark that is\n * already written and is refused if its observation predates the erasure.\n */\nfunction scopeLockSql(mode: \"shared\" | \"exclusive\"): string {\n const fn = mode === \"shared\" ? \"pg_advisory_xact_lock_shared\" : \"pg_advisory_xact_lock\";\n return `select ${fn}(hashtextextended($1::text, 0::bigint))`;\n}\n\nconst scopeLockKey = (scope: Scope): string => `${scope.org}/${scope.uid}`;\n\nexport class PostgresProfileStore implements ProfileStore {\n readonly copySurfaces: readonly CopySurface[] = [{ name: FACTS_TABLE, kind: \"primary\" }];\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresMemoryStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async get(scope: Scope, opts: ProfileReadOpts = {}): Promise<Profile> {\n return this.#inScope(scope, async (client) => {\n // ONE round trip. The stamp is a single-row aggregate that the facts\n // LEFT JOIN onto, so it still comes back for a scope with nothing\n // readable (an aggregate over an empty set is one null row); it used to\n // be a second query against the same table in the same transaction.\n //\n // Invalidated versions are excluded from the stamp: an emptied profile\n // that still reports when the erased data was last touched leaks the\n // timing of what it just erased. Superseded versions are NOT excluded —\n // those were replaced, not erased.\n const { rows } = await client.query<FactJoinRow>(\n `with stamp as (\n select max(last_seen_at) as updated_at from ${FACTS_TABLE}\n where org = $1 and uid = $2 and invalidated_at is null\n )\n select updated_at, ${FACT_COLUMNS}\n from stamp\n left join ${FACTS_TABLE}\n on ${FACTS_TABLE}.org = $1 and ${FACTS_TABLE}.uid = $2\n and ($3::boolean or (${FACTS_TABLE}.superseded_at is null\n and ${FACTS_TABLE}.invalidated_at is null))`,\n [scope.org, scope.uid, opts.includeHistory === true],\n );\n // Ordering and budgeting happen HERE, not in SQL, so every backend\n // returns the same facts in the same order under the same budget.\n const facts = rows\n // The stamp row survives a scope with no readable facts; every one of\n // its fact columns is null, `id` included.\n .filter((row) => row.id !== null)\n .map((row) => toFact(row as FactRow))\n .sort(compareFactsForRecall);\n const { kept, truncated } = applyMemoryBudget(\n facts,\n opts.budget,\n (f) => f.key.length + f.value.length,\n );\n return {\n facts: kept,\n updatedAt: rows[0]?.updated_at?.toISOString() ?? EPOCH,\n truncated,\n };\n });\n }\n\n async observe(scope: Scope, obs: readonly FactObservation[]): Promise<readonly ObserveResult[]> {\n return this.#write(scope, obs, false);\n }\n\n async setProtected(\n scope: Scope,\n facts: readonly FactObservation[],\n ): Promise<readonly ObserveResult[]> {\n return this.#write(scope, facts, true);\n }\n\n async invalidateBySource(\n scope: Scope,\n episodeIds: readonly string[] | \"all\",\n rawAt: string,\n ): Promise<InvalidateResult> {\n const at = toIsoInstant(rawAt);\n return this.#inScope(scope, async (client) => {\n // Exclusive: waits out every writer that read the watermark before it\n // was written, so their facts are inside this pass rather than behind it.\n await client.query(scopeLockSql(\"exclusive\"), [scopeLockKey(scope)]);\n // Key AND value blanked, not merely flagged: both are model-chosen and\n // both carry content, and the export surface reads closed versions.\n const { rowCount } = await client.query(\n `update ${FACTS_TABLE} set invalidated_at = $3::timestamptz, key = '', value = ''\n where org = $1 and uid = $2 and invalidated_at is null\n and ($4::text[] is null or source_episode_ids && $4::text[])`,\n [scope.org, scope.uid, at, episodeIds === \"all\" ? null : [...episodeIds]],\n );\n return { invalidated: rowCount ?? 0, surfaces: [FACTS_TABLE] };\n });\n }\n\n async #write(\n scope: Scope,\n obs: readonly FactObservation[],\n trusted: boolean,\n ): Promise<readonly ObserveResult[]> {\n scopePath(scope);\n assertBatchSize(obs.length, \"observations\", MEMORY_LIMITS.batchItems);\n // Validate the WHOLE batch before opening a transaction: a mid-batch\n // rejection must not depend on rollback to stay atomic.\n for (const o of obs) assertFactObservation(o);\n if (obs.length === 0) return [];\n // Every key this call will touch, locked UP FRONT in one canonical order.\n // Locking lazily per observation deadlocks: two concurrent calls naming\n // the same keys in different orders each hold what the other waits for,\n // and Postgres resolves it by killing one of the turns.\n const lockKeys = [\n ...new Set(\n obs.filter((o) => trusted || !isProtectedProfileKey(o.key)).map((o) => o.key),\n ),\n ].sort();\n // ONE stamp for every observation that did not bring its own, like the\n // erasure walk uses one timestamp for every surface. Per-observation\n // `new Date()` made a batch straddling a millisecond boundary derive\n // different ids for the same observation depending on timing, which the\n // in-memory reference (one injected clock read) never did.\n const batchAt = new Date().toISOString();\n const stamps = obs.map((o) => toIsoInstant(o.at ?? batchAt));\n // Ids are derived from (key, value, instant), so every id this batch\n // could write is known before the transaction opens — which is what lets\n // the replay check below be one read instead of one per observation.\n const candidateIds = obs.map((o, i) =>\n deriveFactId(scope, { key: o.key, value: o.value, observedAt: stamps[i]! }),\n );\n\n return this.#inScope(scope, async (client) => {\n // Shared: many writers proceed together, but an erasure's exclusive\n // claim waits for all of them (spec 013).\n await client.query(scopeLockSql(\"shared\"), [scopeLockKey(scope)]);\n if (lockKeys.length > 0) {\n // ONE statement for every key lock, still in the canonical order the\n // sort above fixed: `unnest` yields rows in array order, and\n // `pg_advisory_xact_lock` is parallel-unsafe, so nothing reorders\n // them. A round trip per key was the batch's worst offender — it held\n // the shared scope lock, and every key lock already taken, for the\n // whole walk (the 011-013 review).\n await client.query(\n `select pg_advisory_xact_lock(hashtextextended(k, 0::bigint))\n from unnest($1::text[]) as k`,\n [lockKeys.map((key) => `${scope.org}/${scope.uid}/${key}`)],\n );\n }\n const state = await this.#loadWriteState(client, scope, obs, lockKeys, candidateIds);\n const { results, writes } = planObservations(obs, trusted, stamps, candidateIds, state);\n // Only the writes the plan actually calls for, batched per round.\n const lost = await applyWrites(client, scope, writes);\n for (const index of lost) {\n results[index] = {\n key: obs[index]!.key,\n outcome: \"stale\",\n detail: \"the version this observation refreshed was closed concurrently\",\n };\n }\n return results;\n });\n }\n\n /**\n * Everything the batch needs to decide, read in a FIXED number of queries.\n *\n * This is the half of the N+1 that was pure reads: the watermark once, then\n * a provenance check, a current-version read, and a replay check PER\n * OBSERVATION — up to three hundred round trips for a full batch, every one\n * of them holding the scope's shared lock and all its key locks (the\n * 011-013 review).\n */\n async #loadWriteState(\n client: PoolClient,\n scope: Scope,\n obs: readonly FactObservation[],\n keys: readonly string[],\n candidateIds: readonly string[],\n ): Promise<WriteState> {\n // Read in the SAME transaction as the writes, so an erasure committing\n // mid-batch cannot slip past the guard.\n const { rows: mark } = await client.query<{ erasure_watermark: Date | null }>(\n `select erasure_watermark from ${SCOPE_STATE_TABLE} where org = $1 and uid = $2`,\n [scope.org, scope.uid],\n );\n const watermark = mark[0]?.erasure_watermark?.toISOString() ?? null;\n\n // The timestamp guard is opt-in on the CALLER: `at` defaults to write\n // time, so an in-flight job that omits it — the documented default — is\n // never stale, and a fact citing an erased episode sailed through a\n // completed erasure (spec 013 review). Provenance is not opt-in: an\n // observation that names a tombstoned episode is refused whatever its\n // clock says.\n const cited = [...new Set(obs.flatMap((o) => o.sourceEpisodeIds ?? []))];\n const tombstoned = new Set<string>();\n if (cited.length > 0) {\n const { rows } = await client.query<{ id: string }>(\n `select id from ${EPISODES_TABLE}\n where org = $1 and uid = $2 and id = any($3::text[]) and state = 'tombstoned'`,\n [scope.org, scope.uid, cited],\n );\n for (const row of rows) tombstoned.add(row.id);\n }\n\n // The advisory lock for each of these keys was taken above, before any\n // observation was decided.\n const currentByKey = new Map<string, ProfileFact>();\n if (keys.length > 0) {\n const { rows } = await client.query<FactRow>(\n `select ${FACT_COLUMNS} from ${FACTS_TABLE}\n where org = $1 and uid = $2 and key = any($3::text[])\n and superseded_at is null and invalidated_at is null`,\n [scope.org, scope.uid, [...keys]],\n );\n for (const row of rows) {\n const fact = toFact(row);\n currentByKey.set(fact.key, fact);\n }\n }\n\n // A stored version with the same id is a REPLAY — the same observation\n // (key, value, instant) arriving twice. Deterministic ids exist so\n // re-extraction overwrites and never duplicates (spec 010), and a closed\n // version must never be resurrected by one.\n const existingIds = new Set<string>();\n const { rows: replays } = await client.query<{ id: string }>(\n `select id from ${FACTS_TABLE} where org = $1 and uid = $2 and id = any($3::text[])`,\n [scope.org, scope.uid, [...new Set(candidateIds)]],\n );\n for (const row of replays) existingIds.add(row.id);\n\n return { watermark, tombstoned, currentByKey, existingIds };\n }\n\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/**\n * The batch writer — spec 011, made batch-shaped by the 011-013 review.\n *\n * `observe` accepts up to `MEMORY_LIMITS.batchItems` observations, and the\n * first implementation ran the whole decision procedure once per observation:\n * three reads and up to two writes each, sequentially, inside one transaction\n * holding the scope's shared lock and every key's advisory lock. A full batch\n * was ~500 round trips of lock-hold time, and erasure — which wants the\n * exclusive scope lock — waited behind all of it.\n *\n * The rewrite splits it into read / decide / write. Reads are a fixed four\n * queries (see `#loadWriteState`); the decision procedure is pure and runs\n * against those results; the writes are batched statements.\n */\n\n/** What one batch reads before deciding anything. */\ninterface WriteState {\n watermark: string | null;\n tombstoned: ReadonlySet<string>;\n /** Mutated as the plan is built, so a key observed twice chains correctly. */\n currentByKey: Map<string, ProfileFact>;\n /** Likewise: an id this batch inserts is a replay for a later observation. */\n existingIds: Set<string>;\n}\n\n/**\n * A write the plan calls for, tagged with its ROUND.\n *\n * Two observations of the same key chain — the second may close the version\n * the first inserted — so round N of every key is executed before round N+1\n * of any key. Keys never interact, so one round's writes are safe to batch\n * across keys, and the ordinary batch (distinct keys) is a single round.\n */\ntype PlannedWrite =\n | {\n kind: \"refresh\";\n round: number;\n index: number;\n id: string;\n merged: ReturnType<typeof mergeRefresh>;\n }\n | {\n kind: \"insert\";\n round: number;\n index: number;\n version: ProfileFact;\n /** Id of the current version this one supersedes, when it does. */\n closes?: string;\n };\n\n/**\n * Decides the whole batch against the state read for it. PURE — the same\n * three-case confidence rule, provenance guard and watermark comparison the\n * per-observation version applied, in the same order, one observation at a\n * time.\n */\nfunction planObservations(\n obs: readonly FactObservation[],\n trusted: boolean,\n stamps: readonly string[],\n candidateIds: readonly string[],\n state: WriteState,\n): { results: ObserveResult[]; writes: PlannedWrite[] } {\n const { currentByKey, existingIds } = state;\n const results: ObserveResult[] = [];\n const writes: PlannedWrite[] = [];\n /** Writes already planned per key — the round the next one belongs to. */\n const rounds = new Map<string, number>();\n\n for (let index = 0; index < obs.length; index++) {\n const o = obs[index]!;\n const at = stamps[index]!;\n\n if (!trusted && isProtectedProfileKey(o.key)) {\n // Identity is NEVER extracted (spec 010 normative). Nothing written.\n results.push({\n key: o.key,\n outcome: \"refused\",\n detail: `${JSON.stringify(o.key)} is in the protected profile namespace; only the product may write it`,\n });\n continue;\n }\n\n const dead = (o.sourceEpisodeIds ?? []).filter((id) => state.tombstoned.has(id));\n if (dead.length > 0) {\n results.push({\n key: o.key,\n outcome: \"stale\",\n detail: `cites erased episode(s): ${dead.join(\", \")}`,\n });\n continue;\n }\n\n if (state.watermark !== null && at < state.watermark) {\n // Submitted before this scope was erased — discard. `append` has always\n // had its terminal-state guard; this is the profile tier's.\n results.push({\n key: o.key,\n outcome: \"stale\",\n detail: `observation stamped ${at} precedes this scope's erasure at ${state.watermark}`,\n });\n continue;\n }\n\n const confidence = o.confidence ?? DEFAULT_CONFIDENCE;\n const current = currentByKey.get(o.key);\n const decision = decideObservation(current, { value: o.value, confidence }, { trusted });\n const round = rounds.get(o.key) ?? 0;\n\n if (decision.outcome === \"refreshed\" && current !== undefined) {\n const refresh: Parameters<typeof mergeRefresh>[1] = { confidence, at };\n if (o.sourceEpisodeIds !== undefined) refresh.sourceEpisodeIds = o.sourceEpisodeIds;\n if (o.ttlDays !== undefined) refresh.ttlDays = o.ttlDays;\n const merged = mergeRefresh(current, refresh);\n currentByKey.set(o.key, { ...current, ...merged });\n rounds.set(o.key, round + 1);\n writes.push({ kind: \"refresh\", round, index, id: current.id, merged });\n results.push({ key: o.key, outcome: \"refreshed\", factId: current.id });\n continue;\n }\n\n const id = candidateIds[index]!;\n if (existingIds.has(id)) {\n // Reported as `replayed`, not `refreshed`: the version it names may\n // well be closed.\n results.push({\n key: o.key,\n outcome: \"replayed\",\n factId: id,\n detail: \"this exact observation is already a stored version; nothing was written\",\n });\n continue;\n }\n\n const version: ProfileFact = {\n id,\n key: o.key,\n value: o.value,\n confidence,\n sourceEpisodeIds: [...(o.sourceEpisodeIds ?? [])],\n observedAt: at,\n lastSeenAt: at,\n };\n if (o.ttlDays !== undefined) version.ttlDays = o.ttlDays;\n existingIds.add(id);\n rounds.set(o.key, round + 1);\n\n if (decision.outcome === \"conflict\" && current !== undefined) {\n // Stored ALREADY CLOSED, pointing at the version that beat it: full\n // history, contradiction handling, and audit in one write. The current\n // fact stands, so it stays the key's current version here too.\n version.supersededAt = at;\n version.supersededBy = current.id;\n writes.push({ kind: \"insert\", round, index, version });\n const conflict: ObserveResult = { key: o.key, outcome: \"conflict\", factId: id };\n if (decision.detail !== undefined) conflict.detail = decision.detail;\n results.push(conflict);\n continue;\n }\n\n currentByKey.set(o.key, version);\n writes.push(\n decision.outcome === \"superseded\" && current !== undefined\n ? { kind: \"insert\", round, index, version, closes: current.id }\n : { kind: \"insert\", round, index, version },\n );\n const result: ObserveResult = { key: o.key, outcome: decision.outcome, factId: id };\n if (decision.detail !== undefined) result.detail = decision.detail;\n results.push(result);\n }\n\n return { results, writes };\n}\n\n/**\n * Runs the plan, at most three statements per round.\n *\n * Returns the indices whose refresh found no row to update — a version closed\n * between the read and the write, which the caller reports as `stale`.\n */\nasync function applyWrites(\n client: PoolClient,\n scope: Scope,\n writes: readonly PlannedWrite[],\n): Promise<ReadonlySet<number>> {\n const lost = new Set<number>();\n const lastRound = writes.reduce((max, w) => Math.max(max, w.round), -1);\n\n for (let round = 0; round <= lastRound; round++) {\n const refreshes = writes.filter(\n (w): w is Extract<PlannedWrite, { kind: \"refresh\" }> =>\n w.round === round && w.kind === \"refresh\",\n );\n const inserts = writes.filter(\n (w): w is Extract<PlannedWrite, { kind: \"insert\" }> =>\n w.round === round && w.kind === \"insert\",\n );\n\n if (refreshes.length > 0) {\n const updated = await refreshVersions(client, scope, refreshes);\n for (const w of refreshes) if (!updated.has(w.id)) lost.add(w.index);\n }\n // Close the old versions BEFORE inserting the new ones: the partial\n // unique index would otherwise see two current versions for the key.\n const closes = inserts.flatMap((w) =>\n w.closes === undefined\n ? []\n : [{ id: w.closes, at: w.version.observedAt, by: w.version.id }],\n );\n if (closes.length > 0) await closeVersions(client, scope, closes);\n if (inserts.length > 0) await insertVersions(client, scope, inserts.map((w) => w.version));\n }\n\n return lost;\n}\n\n/**\n * Appends one row's values to `params` and returns their `$N::type` list.\n * Only parameter INDICES and the caller's own literal type names are\n * interpolated — never data, which stays bound.\n */\nfunction placeholderList(params: unknown[], cells: readonly (readonly [unknown, string])[]): string {\n return cells\n .map(([value, type]) => {\n params.push(value);\n return `$${params.length}::${type}`;\n })\n .join(\", \");\n}\n\n/** One statement for every `refreshed` outcome in a round. */\nasync function refreshVersions(\n client: PoolClient,\n scope: Scope,\n writes: readonly Extract<PlannedWrite, { kind: \"refresh\" }>[],\n): Promise<ReadonlySet<string>> {\n const params: unknown[] = [scope.org, scope.uid];\n const rows = writes.map(\n (w) =>\n `(${placeholderList(params, [\n [w.id, \"text\"],\n [w.merged.confidence, \"double precision\"],\n [w.merged.lastSeenAt, \"timestamptz\"],\n [[...w.merged.sourceEpisodeIds], \"text[]\"],\n [w.merged.ttlDays ?? null, \"integer\"],\n ])})`,\n );\n // The guard matters: a concurrent `invalidateBySource` between the read and\n // this write would otherwise leave the key with NO current version while\n // the caller was told its value was refreshed. `returning` is how the lost\n // row is still identified now that the round is one statement.\n const { rows: updated } = await client.query<{ id: string }>(\n `update ${FACTS_TABLE}\n set confidence = v.confidence, last_seen_at = v.last_seen_at,\n source_episode_ids = v.source_episode_ids, ttl_days = v.ttl_days\n from (values ${rows.join(\", \")}) as v(id, confidence, last_seen_at, source_episode_ids, ttl_days)\n where ${FACTS_TABLE}.org = $1 and ${FACTS_TABLE}.uid = $2\n and ${FACTS_TABLE}.id = v.id\n and ${FACTS_TABLE}.superseded_at is null and ${FACTS_TABLE}.invalidated_at is null\n returning ${FACTS_TABLE}.id`,\n params,\n );\n return new Set(updated.map((row) => row.id));\n}\n\n/** One statement for every version a round supersedes. */\nasync function closeVersions(\n client: PoolClient,\n scope: Scope,\n closes: readonly { id: string; at: string; by: string }[],\n): Promise<void> {\n const params: unknown[] = [scope.org, scope.uid];\n const rows = closes.map(\n (c) =>\n `(${placeholderList(params, [\n [c.id, \"text\"],\n [c.at, \"timestamptz\"],\n [c.by, \"text\"],\n ])})`,\n );\n await client.query(\n `update ${FACTS_TABLE} set superseded_at = v.at, superseded_by = v.by\n from (values ${rows.join(\", \")}) as v(id, at, by)\n where ${FACTS_TABLE}.org = $1 and ${FACTS_TABLE}.uid = $2 and ${FACTS_TABLE}.id = v.id`,\n params,\n );\n}\n\n/** One statement for every version a round inserts. */\nasync function insertVersions(\n client: PoolClient,\n scope: Scope,\n versions: readonly ProfileFact[],\n): Promise<void> {\n const params: unknown[] = [scope.org, scope.uid];\n const rows = versions.map(\n (f) =>\n `($1, $2, ${placeholderList(params, [\n [f.id, \"text\"],\n [f.key, \"text\"],\n [f.value, \"text\"],\n [f.confidence, \"double precision\"],\n [[...f.sourceEpisodeIds], \"text[]\"],\n [f.observedAt, \"timestamptz\"],\n [f.lastSeenAt, \"timestamptz\"],\n [f.supersededAt ?? null, \"timestamptz\"],\n [f.supersededBy ?? null, \"text\"],\n [f.ttlDays ?? null, \"integer\"],\n ])})`,\n );\n await client.query(\n `insert into ${FACTS_TABLE}\n (org, uid, id, key, value, confidence, source_episode_ids,\n observed_at, last_seen_at, superseded_at, superseded_by, ttl_days)\n values ${rows.join(\", \")}`,\n params,\n );\n}\n\n/**\n * The scope's last erasure timestamp — the in-flight guard slice 013 compares\n * against, so a job that started before an erasure cannot submit afterwards\n * and re-materialize erased content.\n */\nexport class PostgresErasureWatermarks implements ErasureWatermarkStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresMemoryStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async get(scope: Scope): Promise<string | null> {\n // #timeoutMs was resolved and then never passed — the configured\n // statement timeout silently did not apply to watermark reads/writes.\n return inScope(\n this.#pool,\n this.#role,\n scope,\n async (client) => {\n const { rows } = await client.query<{ erasure_watermark: Date | null }>(\n `select erasure_watermark from ${SCOPE_STATE_TABLE} where org = $1 and uid = $2`,\n [scope.org, scope.uid],\n );\n return rows[0]?.erasure_watermark?.toISOString() ?? null;\n },\n this.#timeoutMs,\n );\n }\n\n async set(scope: Scope, rawAt: string): Promise<void> {\n const at = toIsoInstant(rawAt);\n await inScope(\n this.#pool,\n this.#role,\n scope,\n async (client) => {\n // MONOTONIC: `greatest` (which ignores a null existing value) keeps\n // the later of the two stamps. Unconditional assignment let a second\n // erasure stamped by a lagging server clock move the watermark\n // BACKWARDS, un-blocking in-flight writes the first erasure's guard\n // had already refused as stale (the 011–013 review).\n await client.query(\n `insert into ${SCOPE_STATE_TABLE} (org, uid, erasure_watermark)\n values ($1, $2, $3::timestamptz)\n on conflict (org, uid) do update\n set erasure_watermark =\n greatest(${SCOPE_STATE_TABLE}.erasure_watermark, excluded.erasure_watermark)`,\n [scope.org, scope.uid, at],\n );\n },\n this.#timeoutMs,\n );\n }\n}\n","import type { Pool } from \"pg\";\n\nimport { assertRoleIdentifier, DEFAULT_RLS_ROLE, rlsPolicySql, roleBootstrapSql } from \"./schema\";\n\n/**\n * Schema and migration for the memory stores — spec 011.\n *\n * Three scope-stamped tables under the same row-level-security discipline as\n * the session store (spec 001): policies keyed on the transaction-local\n * `alma.org`/`alma.uid` settings, `FORCE ROW LEVEL SECURITY`, and a dedicated\n * non-superuser role assumed per transaction. The migration is idempotent.\n */\n\nexport const EPISODES_TABLE = \"alma_memory_episodes\";\nexport const FACTS_TABLE = \"alma_memory_facts\";\nexport const SCOPE_STATE_TABLE = \"alma_memory_scope_state\";\n\n/**\n * DECISION (spec 011): `importance` and `confidence` are `double precision`,\n * not `real`. float4 round-trips through a text protocol with just enough\n * digits to survive, and a 0.7 that comes back 0.699999988 would make the\n * shared contract suite disagree with every other backend for no reason.\n *\n * DECISION: no pgvector column yet. Lexical ranking first (spec 010); the\n * embedding column and its index arrive with the consumer that needs them,\n * and will be declared as an additional COPY SURFACE so erasure reaches it.\n */\nexport function memoryStoreMigrationSql(role: string = DEFAULT_RLS_ROLE): string {\n assertRoleIdentifier(role);\n return `\ncreate table if not exists ${EPISODES_TABLE} (\n org text not null,\n uid text not null,\n id text not null,\n at timestamptz not null,\n kind text not null,\n summary text not null,\n -- Case-folded copy of summary, folded in the ADAPTER (JS toLowerCase), so\n -- the candidate filter never depends on the database's ctype: under lc_ctype\n -- of C, ilike folds ASCII only, and 'CAFÉ' silently stopped matching 'café'\n -- while the in-memory reference matched it (the 011–013 review). Blanked by the\n -- tombstone exactly as summary is — it is the same content.\n summary_fold text not null default '',\n importance double precision not null,\n state text not null default 'active',\n source_session_id text,\n source_turn_id text,\n erased_at timestamptz,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n primary key (org, uid, id),\n constraint alma_memory_episodes_state_check\n check (state in ('active', 'archived', 'tombstoned'))\n);\n\n-- Migration for tables created before summary_fold existed. The SQL lower()\n-- backfill is the best the database can do (exact under a folding ctype,\n-- ASCII-only under C); every adapter write from then on stores the JS fold.\nalter table ${EPISODES_TABLE} add column if not exists summary_fold text not null default '';\nupdate ${EPISODES_TABLE} set summary_fold = lower(summary)\n where summary_fold = '' and summary <> '';\n\ncreate index if not exists alma_memory_episodes_recent\n on ${EPISODES_TABLE} (org, uid, state, at desc);\n\ncreate index if not exists alma_memory_episodes_session\n on ${EPISODES_TABLE} (org, uid, source_session_id);\n\ncreate table if not exists ${FACTS_TABLE} (\n org text not null,\n uid text not null,\n id text not null,\n key text not null,\n value text not null,\n confidence double precision not null,\n source_episode_ids text[] not null default '{}',\n observed_at timestamptz not null,\n last_seen_at timestamptz not null,\n superseded_at timestamptz,\n superseded_by text,\n invalidated_at timestamptz,\n ttl_days integer,\n primary key (org, uid, id)\n);\n\n-- \"At most one CURRENT version per key\" is a database invariant, not an\n-- application hope: the confidence-gated supersession rule reads the current\n-- version and writes a new one, and a lost race must fail loudly rather than\n-- leave a profile with two contradictory current facts.\ncreate unique index if not exists alma_memory_facts_current\n on ${FACTS_TABLE} (org, uid, key)\n where superseded_at is null and invalidated_at is null;\n\n-- Derived invalidation walks provenance on every erasure.\ncreate index if not exists alma_memory_facts_sources\n on ${FACTS_TABLE} using gin (source_episode_ids);\n\ncreate table if not exists ${SCOPE_STATE_TABLE} (\n org text not null,\n uid text not null,\n erasure_watermark timestamptz,\n primary key (org, uid)\n);\n${rlsPolicySql(EPISODES_TABLE)}${rlsPolicySql(FACTS_TABLE)}${rlsPolicySql(SCOPE_STATE_TABLE)}\n${roleBootstrapSql(role)}\ngrant select, insert, update, delete\n on ${EPISODES_TABLE}, ${FACTS_TABLE}, ${SCOPE_STATE_TABLE}\n to ${role};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateMemoryStores(\n pool: Pool,\n opts: { role?: string } = {},\n): Promise<void> {\n await pool.query(memoryStoreMigrationSql(opts.role));\n}\n","import type { Pool } from \"pg\";\n\nimport { assertRoleIdentifier, DEFAULT_RLS_ROLE, rlsPolicySql, roleBootstrapSql } from \"./schema\";\n\n/**\n * Schema and migration for the spend store — spec: spend-store.\n *\n * Two counter tables. The session counter is scope-stamped and carries the\n * standard `{org, uid}` policy. The tenant-day counter is deliberately\n * ORG-KEYED — it aggregates across every uid and session of the org, which is\n * what an operator caps or watches — so its policy compares org alone, via\n * the SAME shared generator (`rlsPolicySql(table, [\"org\"])`): forking the\n * generator would let future RLS hardening skip this one table (review\n * finding).\n */\n\nexport const SPEND_SESSIONS_TABLE = \"alma_spend_sessions\";\nexport const SPEND_TENANT_DAYS_TABLE = \"alma_spend_tenant_days\";\n\n/**\n * DECISION (spec: spend-store): `usd` is `double precision`, like every\n * fractional number in this package — the whole pricing pipeline computes in\n * JS floats, and a NUMERIC column would round-trip as a string the driver\n * does not sum. A single step costs fractions of a cent; the contract suite\n * pins that nothing rounds it away.\n *\n * The grant carries NO delete: spend counters are retained through scoped\n * purge (a financial record, not personal content — spec: spend-store), and\n * the app role simply cannot remove one.\n */\nexport function spendStoreMigrationSql(role: string = DEFAULT_RLS_ROLE): string {\n assertRoleIdentifier(role);\n return `\ncreate table if not exists ${SPEND_SESSIONS_TABLE} (\n org text not null,\n uid text not null,\n session_id text not null,\n usd double precision not null default 0,\n updated_at timestamptz not null default now(),\n primary key (org, uid, session_id)\n);\n\ncreate table if not exists ${SPEND_TENANT_DAYS_TABLE} (\n org text not null,\n day date not null,\n usd double precision not null default 0,\n updated_at timestamptz not null default now(),\n primary key (org, day)\n);\n${rlsPolicySql(SPEND_SESSIONS_TABLE)}${rlsPolicySql(SPEND_TENANT_DAYS_TABLE, [\"org\"])}\n${roleBootstrapSql(role)}\ngrant select, insert, update\n on ${SPEND_SESSIONS_TABLE}, ${SPEND_TENANT_DAYS_TABLE}\n to ${role};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateSpendStore(pool: Pool, opts: { role?: string } = {}): Promise<void> {\n await pool.query(spendStoreMigrationSql(opts.role));\n}\n","import type { Scope, SpendKey, SpendStore, SpendTotals } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { SPEND_SESSIONS_TABLE, SPEND_TENANT_DAYS_TABLE } from \"./spend-schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresSpendStoreOptions = ScopedStoreOptions;\n\n/**\n * Reference `SpendStore` adapter — spec: spend-store. Same RLS discipline as\n * every other store; the tenant-day counter's policy is org-only, because the\n * counter is (see `spend-schema.ts`).\n *\n * `add` is one transaction over two atomic upserts, each\n * increment-and-RETURNING, so a concurrent add serializes on the row lock and\n * every caller observes its own distinct running total — the property the\n * budget guard's enforcement stands on. The session row is always touched\n * before the day row, so two adds can never order the same pair of locks\n * differently and deadlock.\n *\n * The UTC day bucket is derived in TS — the same `Date.parse` reading the\n * in-memory reference and the memory tier's `toIsoInstant` use — and reaches\n * SQL as a finished `date` literal. Deriving it server-side read the\n * timestamp in the SERVER's TimeZone while the reference read it in the\n * process's, so an offset-less stamp could credit different day counters in\n * the two stores (review finding — the same bug the episode tier fixed once\n * already).\n */\nexport class PostgresSpendStore implements SpendStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresSpendStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async add(entry: SpendKey & { usd: number }): Promise<SpendTotals> {\n if (!Number.isFinite(entry.usd) || entry.usd < 0) {\n throw new Error(`spend must be a non-negative finite number, got ${entry.usd}`);\n }\n const day = utcDayBucket(entry.at);\n return this.#inScope(entry.scope, async (client) => {\n const { rows: session } = await client.query<{ usd: number }>(\n `insert into ${SPEND_SESSIONS_TABLE} (org, uid, session_id, usd)\n values ($1, $2, $3, $4)\n on conflict (org, uid, session_id)\n do update set usd = ${SPEND_SESSIONS_TABLE}.usd + excluded.usd, updated_at = now()\n returning usd`,\n [entry.scope.org, entry.scope.uid, entry.sessionId, entry.usd],\n );\n const { rows: dayRows } = await client.query<{ usd: number }>(\n `insert into ${SPEND_TENANT_DAYS_TABLE} (org, day, usd)\n values ($1, $2::date, $3)\n on conflict (org, day)\n do update set usd = ${SPEND_TENANT_DAYS_TABLE}.usd + excluded.usd, updated_at = now()\n returning usd`,\n [entry.scope.org, day, entry.usd],\n );\n return { sessionUsd: session[0]!.usd, tenantDayUsd: dayRows[0]!.usd };\n });\n }\n\n async peek(key: SpendKey): Promise<SpendTotals> {\n const day = utcDayBucket(key.at);\n return this.#inScope(key.scope, async (client) => {\n const { rows } = await client.query<{ session_usd: number; tenant_day_usd: number }>(\n `select\n coalesce((select usd from ${SPEND_SESSIONS_TABLE}\n where org = $1 and uid = $2 and session_id = $3), 0) as session_usd,\n coalesce((select usd from ${SPEND_TENANT_DAYS_TABLE}\n where org = $1 and day = $4::date), 0) as tenant_day_usd`,\n [key.scope.org, key.scope.uid, key.sessionId, day],\n );\n return { sessionUsd: rows[0]!.session_usd, tenantDayUsd: rows[0]!.tenant_day_usd };\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/**\n * ISO 8601 → `YYYY-MM-DD`, read by `Date.parse` exactly as the in-memory\n * reference reads it (an offset-less stamp is process-local, per the JS\n * spec), so the two stores can never bucket the same input differently. The\n * contract suite pins the agreement.\n */\nfunction utcDayBucket(at: string): string {\n const ms = Date.parse(at);\n if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);\n return new Date(ms).toISOString().slice(0, 10);\n}\n","import type { Pool } from \"pg\";\n\nimport {\n assertIso8601,\n assertRoleIdentifier,\n DEFAULT_RETENTION_ROLE,\n DEFAULT_RLS_ROLE,\n retentionPolicySql,\n rlsPolicySql,\n roleBootstrapSql,\n} from \"./schema\";\nimport { purgeBefore } from \"./scoped\";\n\n/**\n * Schema and migration for the audit trails — spec 038.\n *\n * FIVE tables, one per event family, rather than one with a `payload jsonb`.\n * The shapes are genuinely different, but the deciding argument is that this\n * makes the metadata-only guarantee STRUCTURAL: `audit.ts` promises trails\n * carry \"METADATA ONLY, never content — by construction, not by reviewer\n * vigilance\", and `AccessEvent.resource` is documented as an identifier and\n * never its content. As a `text` column named `resource` that is a constraint\n * a reviewer can look at and a DBA can audit; as a key inside a bag, anything\n * fits and nothing notices.\n *\n * It also leaves room for what comes next: cost feeds billing and must be\n * kept, while recall and context are diagnostic and can expire early. Separate\n * tables make that a policy per table rather than a `where family = …` smeared\n * across every statement.\n */\n\nexport const AUDIT_ACCESS_TABLE = \"alma_audit_access\";\nexport const AUDIT_ROUTING_TABLE = \"alma_audit_routing\";\nexport const AUDIT_COST_TABLE = \"alma_audit_cost\";\nexport const AUDIT_RECALL_TABLE = \"alma_audit_recall\";\nexport const AUDIT_CONTEXT_TABLE = \"alma_audit_context\";\n\nexport const AUDIT_TABLES = [\n AUDIT_ACCESS_TABLE,\n AUDIT_ROUTING_TABLE,\n AUDIT_COST_TABLE,\n AUDIT_RECALL_TABLE,\n AUDIT_CONTEXT_TABLE,\n] as const;\n\n/**\n * DECISION (spec 038): the primary key is a `uuid` defaulted by\n * `gen_random_uuid()`, not a `bigserial`. A trail is append-only with no\n * natural key, and a sequence would need its own `usage` grant for the\n * non-superuser app role — one more thing to forget in a migration whose whole\n * point is that defense-in-depth must not depend on a manual step (spec 001).\n * `gen_random_uuid()` is core Postgres since 13; no extension.\n *\n * DECISION (spec 038): the grant carries `select, insert` — no `delete`, and\n * no `update`. The spend counters' posture, for the same reason: these are\n * content-free records kept as evidence, and an erasure that removed the proof\n * an erasure happened is not an improvement (§10). `update` is excluded too,\n * because an audit row that can be edited is not an audit row.\n *\n * DECISION (spec 038): optional array fields (`capsCrossed`, `degradedTiers`,\n * `refused`) are `text[] not null default '{}'`. Absent and empty mean the\n * same thing for all three — no caps crossed, no tiers degraded, nothing\n * refused — so normalising removes a null check rather than losing a\n * distinction.\n */\nexport function auditLogMigrationSql(\n role: string = DEFAULT_RLS_ROLE,\n retentionRole: string = DEFAULT_RETENTION_ROLE,\n): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(retentionRole);\n return `\ncreate table if not exists ${AUDIT_ACCESS_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n tool text not null,\n action text not null,\n -- Identifier of the touched resource (id/path) — NEVER its content.\n resource text,\n session_id text,\n turn_id text,\n primary key (id),\n constraint alma_audit_access_action_check\n check (action in ('read', 'write', 'delete', 'export'))\n);\n\ncreate index if not exists alma_audit_access_turn\n on ${AUDIT_ACCESS_TABLE} (org, uid, turn_id);\n\ncreate index if not exists alma_audit_access_recent\n on ${AUDIT_ACCESS_TABLE} (org, uid, at desc);\n\ncreate index if not exists alma_audit_access_at\n on ${AUDIT_ACCESS_TABLE} (at);\n\ncreate table if not exists ${AUDIT_ROUTING_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n tier text not null,\n sensitivity text not null,\n model_provider text not null,\n model_id text not null,\n -- Carried verbatim from ModelChoice.rationale: a policy must explain itself.\n rationale text not null,\n session_id text,\n turn_id text,\n primary key (id)\n);\n\ncreate index if not exists alma_audit_routing_turn\n on ${AUDIT_ROUTING_TABLE} (org, uid, turn_id);\n\ncreate index if not exists alma_audit_routing_at\n on ${AUDIT_ROUTING_TABLE} (at);\n\ncreate table if not exists ${AUDIT_COST_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n model_provider text not null,\n model_id text not null,\n input_tokens bigint not null,\n output_tokens bigint not null,\n cache_read_input_tokens bigint,\n cache_write_input_tokens bigint,\n -- double precision, like every fractional number in this package: the whole\n -- pricing pipeline computes in JS floats, and NUMERIC round-trips as a\n -- string the driver does not sum (spec: spend-store).\n cost_usd double precision not null,\n caps_crossed text[] not null default '{}',\n session_id text,\n turn_id text,\n primary key (id)\n);\n\ncreate index if not exists alma_audit_cost_turn\n on ${AUDIT_COST_TABLE} (org, uid, turn_id);\n\ncreate index if not exists alma_audit_cost_recent\n on ${AUDIT_COST_TABLE} (org, uid, at desc);\n\ncreate index if not exists alma_audit_cost_at\n on ${AUDIT_COST_TABLE} (at);\n\ncreate table if not exists ${AUDIT_RECALL_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n session_id text not null,\n turn_id text not null,\n -- Provenance, never the recalled text: a verbatim copy would be a surface\n -- erasure cannot reach (spec 012).\n fact_ids text[] not null default '{}',\n episode_ids text[] not null default '{}',\n budget_tokens bigint not null,\n estimated_tokens bigint not null,\n dropped_tokens bigint,\n truncated boolean not null,\n degraded_tiers text[] not null default '{}',\n primary key (id)\n);\n\ncreate index if not exists alma_audit_recall_turn\n on ${AUDIT_RECALL_TABLE} (org, uid, turn_id);\n\ncreate index if not exists alma_audit_recall_at\n on ${AUDIT_RECALL_TABLE} (at);\n\ncreate table if not exists ${AUDIT_CONTEXT_TABLE} (\n id uuid not null default gen_random_uuid(),\n org text not null,\n uid text not null,\n at timestamptz not null,\n session_id text not null,\n turn_id text not null,\n step integer not null,\n delegate boolean not null default false,\n changed text[] not null,\n refused text[] not null default '{}',\n -- The two shapes, flattened. Six numbers should be six numbers (spec 038).\n before_system_blocks bigint not null,\n before_system_chars bigint not null,\n before_messages bigint not null,\n before_message_blocks bigint not null,\n before_message_chars bigint not null,\n before_max_tokens bigint not null,\n after_system_blocks bigint not null,\n after_system_chars bigint not null,\n after_messages bigint not null,\n after_message_blocks bigint not null,\n after_message_chars bigint not null,\n after_max_tokens bigint not null,\n primary key (id)\n);\n\ncreate index if not exists alma_audit_context_turn\n on ${AUDIT_CONTEXT_TABLE} (org, uid, turn_id);\n\ncreate index if not exists alma_audit_context_at\n on ${AUDIT_CONTEXT_TABLE} (at);\n${AUDIT_TABLES.map((t) => rlsPolicySql(t)).join(\"\")}\n${roleBootstrapSql(role)}\n${roleBootstrapSql(retentionRole)}\ngrant select, insert\n on ${AUDIT_TABLES.join(\", \")}\n to ${role};\n\n-- Retention is a DIFFERENT ACTOR from erasure (spec 039). The app role above\n-- cannot delete, so a scoped purge can never remove the proof an erasure\n-- happened; this role can delete any row, and only that — the grant below\n-- withholds insert and update from it.\n--\n-- These policies are not optional decoration. Every audit table carries FORCE\n-- ROW LEVEL SECURITY, which subjects even the table OWNER to the predicate, so\n-- a sweep running as any non-superuser matched the scope-keyed policy, found\n-- nothing, and deleted zero rows while reporting success. That is what the spec\n-- 039 review caught: a retention mechanism that silently retains forever, in\n-- exactly the deployments careful enough not to connect as a superuser.\n--\n-- TWO policies, not one (see retentionPolicySql). A FOR DELETE policy alone\n-- still deleted nothing, because a DELETE with a WHERE clause must SCAN the\n-- rows to filter them, and the scope-keyed policy is FOR ALL -- which governs\n-- SELECT too. That second step surfaced only by running the statement.\n${AUDIT_TABLES.map((t) => retentionPolicySql(t, retentionRole, \"alma_audit_retention\")).join(\"\")}\n\ngrant select, delete\n on ${AUDIT_TABLES.join(\", \")}\n to ${retentionRole};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateAuditLog(\n pool: Pool,\n opts: { role?: string; retentionRole?: string } = {},\n): Promise<void> {\n await pool.query(auditLogMigrationSql(opts.role, opts.retentionRole));\n}\n\n/** The audit table names, as a type — one window per family (spec 039). */\nexport type AuditTable = (typeof AUDIT_TABLES)[number];\n\n/**\n * Deletes audit rows older than each family's cutoff — spec 039.\n *\n * This resolves a consequence spec 038 created without naming: the app role\n * was granted `select, insert` and NOT `delete`, so a scoped erasure cannot\n * remove the proof an erasure happened — which also means the app role cannot\n * expire audit rows.\n *\n * DECISION (spec 039): erasure may not delete audit rows; time-based retention\n * may, and they are DIFFERENT ACTORS. A scoped purge runs as the app role and\n * is blocked at the grant. Retention is maintenance: it runs as the pool's own\n * role, crosses scopes by nature, and deliberately does NOT go through the\n * RLS-bound `inScope` path.\n *\n * A plain function taking the pool rather than a method on a store, and the\n * shape is the point — a method on a seam would suggest it participates in the\n * scope-bound discipline, and this does not. Passing the pool makes the\n * privilege visible at the call site.\n *\n * PER FAMILY, because that is what five tables bought: cost feeds billing and\n * is kept for years while recall and context are diagnostic and can go in\n * weeks. A single window would have made the split pointless. A table absent\n * from `windows` is not touched.\n *\n * It ASSUMES {@link DEFAULT_RETENTION_ROLE} for the duration of one\n * transaction, and that is the correction the spec 039 review forced. The first\n * version ran the deletes straight on the pool, on the theory that it therefore\n * \"bypassed RLS\". It did not: every audit table carries FORCE ROW LEVEL\n * SECURITY, which subjects even the table owner to the predicate, so the sweep\n * matched the scope-keyed policy with no scope bound, deleted ZERO rows, and\n * reported success. It appeared to work only because dev and CI connect as\n * superusers — the same elevation `scoped.ts` calls out as the reason every\n * other store must actively assume a role rather than assume privilege.\n *\n * A retention mechanism that silently retains forever, in exactly the\n * deployments careful enough not to connect as a superuser, is the failure this\n * whole slice exists to prevent one layer up.\n */\nexport async function purgeAuditBefore(\n pool: Pool,\n windows: Partial<Record<AuditTable, string>>,\n opts: { retentionRole?: string; batch?: number } = {},\n): Promise<Partial<Record<AuditTable, number>>> {\n // Validated BEFORE a connection is taken, so a typo'd window fails without\n // having deleted from an earlier table in the loop.\n for (const table of AUDIT_TABLES) {\n const before = windows[table];\n if (before !== undefined) assertIso8601(before, `timestamp for ${table}`);\n }\n const purged: Partial<Record<AuditTable, number>> = {};\n for (const table of AUDIT_TABLES) {\n const before = windows[table];\n if (before === undefined) continue;\n // The table name is a module constant, never caller input — `windows` is\n // keyed by a closed union and the loop drives from `AUDIT_TABLES`. In\n // batches, each its own transaction (spec: close-review-part-two).\n purged[table] = await purgeBefore(pool, { table, column: \"at\", before, ...opts });\n }\n return purged;\n}\n","import type {\n AccessEvent,\n AuditLog,\n ContextEvent,\n CostEvent,\n RecallEvent,\n RoutingEvent,\n Scope,\n} from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport {\n AUDIT_ACCESS_TABLE,\n AUDIT_CONTEXT_TABLE,\n AUDIT_COST_TABLE,\n AUDIT_RECALL_TABLE,\n AUDIT_ROUTING_TABLE,\n} from \"./audit-schema\";\nimport { assertIso8601 } from \"./schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresAuditLogOptions = ScopedStoreOptions;\n\n/**\n * Reference `AuditLog` sink — §6.8, spec 038. Same RLS discipline as every\n * other store in this package: each write runs inside a transaction that\n * assumes the app role and binds `alma.org`/`alma.uid` for the policies to\n * compare against.\n *\n * WRITES SYNCHRONOUSLY, and ships no buffering wrapper. The contract permits\n * either — \"buffer internally and return synchronously to stay off the critical\n * path, or return a promise and be awaited\" — and the cost of this choice is\n * real: a three-step turn with two tool calls emits one routing, three cost and\n * two access events, plus recall and context, so roughly eight round trips join\n * the turn's critical path.\n *\n * The alternative is worse in the way that matters. A buffered sink that loses\n * its buffer on a crash stops writing SILENTLY, which is exactly the failure\n * `AuditSinkError` was typed to catch (spec 027) and exactly what \"where trails\n * are written is swappable; THAT they are written is not\" forbids. A product\n * that wants the trade should own a visible wrapper, not inherit it from the\n * reference adapter.\n *\n * There is no read surface here, deliberately (spec 038). §7.3's thesis is that\n * \"what exactly did the model see about this user?\" is a query — but which\n * queries matter is not yet known, and a contract shaped before its consumers\n * exist is public API from its first release. The product queries its own\n * tables; the rule of two decides when one has earned promotion.\n */\nexport class PostgresAuditLog implements AuditLog {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresAuditLogOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async access(e: AccessEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_ACCESS_TABLE}\n (org, uid, at, tool, action, resource, session_id, turn_id)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.tool,\n e.action,\n e.resource ?? null,\n e.sessionId ?? null,\n e.turnId ?? null,\n ],\n ),\n );\n }\n\n async routing(e: RoutingEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_ROUTING_TABLE}\n (org, uid, at, tier, sensitivity, model_provider, model_id, rationale,\n session_id, turn_id)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9, $10)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.tier,\n e.sensitivity,\n e.model.provider,\n e.model.id,\n e.rationale,\n e.sessionId ?? null,\n e.turnId ?? null,\n ],\n ),\n );\n }\n\n async cost(e: CostEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_COST_TABLE}\n (org, uid, at, model_provider, model_id, input_tokens, output_tokens,\n cache_read_input_tokens, cache_write_input_tokens, cost_usd,\n caps_crossed, session_id, turn_id)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.model.provider,\n e.model.id,\n e.usage.inputTokens,\n e.usage.outputTokens,\n // Absent stays absent rather than becoming a reported zero — the\n // distinction `addUsage` is careful about, preserved at the boundary.\n e.usage.cacheReadInputTokens ?? null,\n e.usage.cacheWriteInputTokens ?? null,\n e.costUsd,\n [...(e.capsCrossed ?? [])],\n e.sessionId ?? null,\n e.turnId ?? null,\n ],\n ),\n );\n }\n\n async recall(e: RecallEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_RECALL_TABLE}\n (org, uid, at, session_id, turn_id, fact_ids, episode_ids,\n budget_tokens, estimated_tokens, dropped_tokens, truncated,\n degraded_tiers)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.sessionId,\n e.turnId,\n [...e.factIds],\n [...e.episodeIds],\n e.budgetTokens,\n e.estimatedTokens,\n e.droppedTokens ?? null,\n e.truncated,\n [...(e.degradedTiers ?? [])],\n ],\n ),\n );\n }\n\n async context(e: ContextEvent): Promise<void> {\n await this.#write(e.scope, (client) =>\n client.query(\n `insert into ${AUDIT_CONTEXT_TABLE}\n (org, uid, at, session_id, turn_id, step, delegate, changed, refused,\n before_system_blocks, before_system_chars, before_messages,\n before_message_blocks, before_message_chars, before_max_tokens,\n after_system_blocks, after_system_chars, after_messages,\n after_message_blocks, after_message_chars, after_max_tokens)\n values ($1, $2, $3::timestamptz, $4, $5, $6, $7, $8, $9,\n $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)`,\n [\n e.scope.org,\n e.scope.uid,\n instant(e.at),\n e.sessionId,\n e.turnId,\n e.step,\n e.delegate ?? false,\n [...e.changed],\n [...(e.refused ?? [])],\n e.before.systemBlocks,\n e.before.systemChars,\n e.before.messages,\n e.before.messageBlocks,\n e.before.messageChars,\n e.before.maxTokens,\n e.after.systemBlocks,\n e.after.systemChars,\n e.after.messages,\n e.after.messageBlocks,\n e.after.messageChars,\n e.after.maxTokens,\n ],\n ),\n );\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #write(scope: Scope, run: (client: PoolClient) => Promise<unknown>): Promise<void> {\n await inScope(this.#pool, this.#role, scope, async (client) => {\n await run(client);\n }, this.#timeoutMs);\n }\n}\n\n/**\n * Validates the event's ISO timestamp before it reaches SQL. Rejecting here\n * rather than letting Postgres parse it is the same choice the spend store\n * made for its day bucket: a server with a different `TimeZone` must not be\n * able to read a timestamp differently from the process that wrote it.\n */\nconst instant = (at: string): string => assertIso8601(at);\n","import {\n assertWellFormed,\n scopePath,\n type CompletedTurn,\n type LeaseOpts,\n type Scope,\n type TurnClaim,\n type TurnKey,\n type TurnLease,\n type TurnStore,\n} from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\nimport { TURN_CLAIMS_TABLE, TURN_LEASES_TABLE } from \"./turn-schema\";\n\nexport type PostgresTurnStoreOptions = ScopedStoreOptions;\n\n/**\n * Reference `TurnStore` adapter — spec 030. Same RLS discipline as every other\n * store in this package.\n *\n * The lease is ONE conditional upsert: the row is taken only when there is no\n * row or the existing one has expired, and Postgres serializes contenders on\n * the primary key, so exactly one concurrent caller sees a returned row. That\n * single statement is the whole mutual-exclusion argument — a read-then-write\n * would let two callers both observe a free session.\n *\n * Waiting is POLLING with backoff, not `LISTEN`/`NOTIFY` or an advisory lock.\n * An advisory lock is bound to the connection that took it, and a turn holds\n * its lease across many queries from a POOL — the lease has to outlive any one\n * connection, which is what makes it a row.\n */\nexport class PostgresTurnStore implements TurnStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresTurnStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async acquire(scope: Scope, sessionId: string, opts: LeaseOpts): Promise<TurnLease | null> {\n assertLeaseOpts(opts);\n scopePath(scope);\n const deadline = Date.now() + opts.waitMs;\n // Backoff rather than a tight loop: a contended session with a 30s wait\n // would otherwise be 600 round trips against a row nobody is releasing.\n let backoffMs = 25;\n for (;;) {\n const lease = await this.#tryAcquire(scope, sessionId, opts.ttlMs);\n if (lease !== null) return lease;\n const remaining = deadline - Date.now();\n if (remaining <= 0) return null;\n await new Promise((resolve) => setTimeout(resolve, Math.min(backoffMs, remaining)));\n backoffMs = Math.min(backoffMs * 2, 250);\n }\n }\n\n async #tryAcquire(scope: Scope, sessionId: string, ttlMs: number): Promise<TurnLease | null> {\n const token = crypto.randomUUID();\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<{ token: string; expires_at: Date }>(\n // The `where` on the conflict target is what makes this atomic: a live\n // lease makes the update match nothing, so no row comes back and the\n // caller lost. Expiry is compared against the SERVER's clock, so two\n // app instances with drifting clocks cannot disagree about whether a\n // lease is live — which is how the same session reaches two holders.\n `insert into ${TURN_LEASES_TABLE} (org, uid, session_id, token, expires_at)\n values ($1, $2, $3, $4, now() + make_interval(secs => $5::double precision))\n on conflict (org, uid, session_id) do update\n set token = excluded.token, expires_at = excluded.expires_at\n where ${TURN_LEASES_TABLE}.expires_at <= now()\n returning token, expires_at`,\n [scope.org, scope.uid, sessionId, token, ttlMs / 1000],\n );\n const row = rows[0];\n return row === undefined\n ? null\n : { token: row.token, expiresAt: row.expires_at.toISOString() };\n });\n }\n\n async release(scope: Scope, sessionId: string, lease: TurnLease): Promise<void> {\n await this.#inScope(scope, async (client) => {\n // Token-matched: a STALE holder deletes nothing rather than freeing the\n // session the next turn is holding (spec 030).\n await client.query(\n `delete from ${TURN_LEASES_TABLE}\n where org = $1 and uid = $2 and session_id = $3 and token = $4`,\n [scope.org, scope.uid, sessionId, lease.token],\n );\n });\n }\n\n async claim(key: TurnKey): Promise<TurnClaim> {\n return this.#inScope(key.scope, async (client) => {\n // Insert-if-absent, then read. Two statements in one transaction rather\n // than a CTE: the row may already exist in either of two states, and\n // being obviously correct is worth one extra round trip on a path that\n // runs once per turn.\n await client.query(\n `insert into ${TURN_CLAIMS_TABLE} (org, uid, session_id, idempotency_key)\n values ($1, $2, $3, $4)\n on conflict (org, uid, session_id, idempotency_key) do nothing`,\n [key.scope.org, key.scope.uid, key.sessionId, key.idempotencyKey],\n );\n const { rows } = await client.query<{ completed: CompletedTurn | null }>(\n `select completed from ${TURN_CLAIMS_TABLE}\n where org = $1 and uid = $2 and session_id = $3 and idempotency_key = $4`,\n [key.scope.org, key.scope.uid, key.sessionId, key.idempotencyKey],\n );\n const completed = rows[0]?.completed ?? null;\n // NULL is IN FLIGHT — reachable only after a crash, since the lease makes\n // concurrency impossible — and re-running is the correct answer there.\n return completed === null ? { status: \"fresh\" } : { status: \"replay\", completed };\n });\n }\n\n /**\n * PRECONDITION: every string in `completed`, keys included, is well-formed\n * UTF-16 — `jsonb` refuses a lone surrogate and the write fails (spec:\n * well-formed-text).\n *\n * The loop repairs it on the way in: `reply` is drawn from messages\n * `record()` passed through `toWellFormedDeep`. But that repair is\n * BEST-EFFORT by design — its `catch` keeps the unrepaired message, because\n * failing to repair must never cost more than not having tried — so a\n * pathologically nested payload can still arrive malformed.\n *\n * Now ENFORCED here and in the in-memory reference alike, by the same guard\n * `SessionStore.append` uses (spec 040). The adapters used to differ — that\n * store kept the lone surrogate, this one refused the write — which is the\n * gap spec 025's review recorded and two contracts then documented instead\n * of closing. The check runs BEFORE the UPDATE and regardless of whether a\n * row matches, because `$5::jsonb` is parsed either way; the in-memory\n * reference orders it the same for that reason. The first version of this\n * comment claimed the loop simply guaranteed it (spec 033).\n */\n async complete(key: TurnKey, completed: CompletedTurn): Promise<void> {\n // `jsonb` would refuse this anyway — the guard makes the failure the same\n // one the in-memory reference now gives, and raises it before a connection\n // is taken (spec 040).\n assertWellFormed(completed, \"completed\");\n await this.#inScope(key.scope, async (client) => {\n // UPDATE, never upsert: a session erased while its turn was still\n // running must not have the reply resurrected by that turn finishing.\n await client.query(\n `update ${TURN_CLAIMS_TABLE} set completed = $5::jsonb\n where org = $1 and uid = $2 and session_id = $3 and idempotency_key = $4`,\n [\n key.scope.org,\n key.scope.uid,\n key.sessionId,\n key.idempotencyKey,\n JSON.stringify(completed),\n ],\n );\n });\n }\n\n async abandon(key: TurnKey): Promise<void> {\n await this.#inScope(key.scope, async (client) => {\n await client.query(\n `delete from ${TURN_CLAIMS_TABLE}\n where org = $1 and uid = $2 and session_id = $3 and idempotency_key = $4`,\n [key.scope.org, key.scope.uid, key.sessionId, key.idempotencyKey],\n );\n });\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n await this.#inScope(scope, async (client) => {\n const params =\n sessionId === undefined ? [scope.org, scope.uid] : [scope.org, scope.uid, sessionId];\n const bySession = sessionId === undefined ? \"\" : \" and session_id = $3\";\n // Claims only — the lease guards a turn that may be in flight (spec: close-review-part-two).\n await client.query(\n `delete from ${TURN_CLAIMS_TABLE} where org = $1 and uid = $2${bySession}`,\n params,\n );\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\nfunction assertLeaseOpts(opts: LeaseOpts): void {\n for (const [name, value] of [\n [\"ttlMs\", opts.ttlMs],\n [\"waitMs\", opts.waitMs],\n ] as const) {\n // Mirrors the in-memory reference: a NaN interval reaches SQL as `NaN\n // seconds`, and every later expiry comparison against it is false — the\n // lease reads as live forever and the session is blocked until someone\n // deletes the row by hand.\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`${name} must be a non-negative finite number, got ${value}`);\n }\n }\n}\n","import type { Pool } from \"pg\";\n\nimport {\n assertRoleIdentifier,\n DEFAULT_RETENTION_ROLE,\n DEFAULT_RLS_ROLE,\n retentionPolicySql,\n rlsPolicySql,\n roleBootstrapSql,\n} from \"./schema\";\nimport { purgeBefore } from \"./scoped\";\n\n/**\n * Schema and migration for the turn store — spec 030.\n *\n * Two scope-stamped tables under the standard `{org, uid}` policy, through the\n * SAME shared generator every other Alma table uses: forking it would let\n * future RLS hardening skip these two (the finding spec: spend-store recorded).\n *\n * The lease is one row per session, replaced in place; the claim is one row\n * per idempotency key, holding the replayable turn as `jsonb`.\n */\n\nexport const TURN_LEASES_TABLE = \"alma_turn_leases\";\nexport const TURN_CLAIMS_TABLE = \"alma_turn_claims\";\n\n/**\n * DECISION (spec 030): `expires_at` is a `timestamptz` computed from the\n * SERVER's `now()`, not from the process clock. Every contender for one lease\n * must compare against one clock, and two app instances with a few seconds of\n * drift would otherwise disagree about whether a lease is live — which is\n * exactly the disagreement that hands the same session to two holders. This is\n * the opposite of the spend store's day bucket, which is derived in TS\n * precisely because it must match the reference implementation's reading of a\n * caller-supplied timestamp; here there is no caller timestamp to match.\n *\n * DECISION (spec 030): `completed` is nullable, and NULL means in flight. The\n * claim row is inserted before the turn runs and updated when it finishes, so\n * the row's existence is the claim and its content is the result.\n *\n * The grant carries `delete`: unlike spend counters, these rows hold content\n * (the reply verbatim) and §10 erasure must be able to remove them.\n *\n * DECISION (spec: erasure-reaches-the-claims): time-based retention of the\n * claims runs as the retention role, never as the app role — the two actors\n * spec 039 separated for the audit tables. The app role's delete is scoped\n * by RLS and serves erasure; the sweep crosses scopes and needs its own\n * policies, `for select` AND `for delete`, since a DELETE with a WHERE scans.\n */\nexport function turnStoreMigrationSql(\n role: string = DEFAULT_RLS_ROLE,\n retentionRole: string = DEFAULT_RETENTION_ROLE,\n): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(retentionRole);\n return `\ncreate table if not exists ${TURN_LEASES_TABLE} (\n org text not null,\n uid text not null,\n session_id text not null,\n token text not null,\n expires_at timestamptz not null,\n primary key (org, uid, session_id)\n);\n\ncreate table if not exists ${TURN_CLAIMS_TABLE} (\n org text not null,\n uid text not null,\n session_id text not null,\n idempotency_key text not null,\n completed jsonb,\n created_at timestamptz not null default now(),\n primary key (org, uid, session_id, idempotency_key)\n);\n\ncreate index if not exists alma_turn_claims_created\n on ${TURN_CLAIMS_TABLE} (created_at);\n${rlsPolicySql(TURN_LEASES_TABLE)}${rlsPolicySql(TURN_CLAIMS_TABLE)}\n${roleBootstrapSql(role)}\n${roleBootstrapSql(retentionRole)}\ngrant select, insert, update, delete\n on ${TURN_LEASES_TABLE}, ${TURN_CLAIMS_TABLE}\n to ${role};\n${retentionPolicySql(TURN_CLAIMS_TABLE, retentionRole, \"alma_turn_claims_retention\")}\n${retentionPolicySql(TURN_LEASES_TABLE, retentionRole, \"alma_turn_leases_retention\")}\n\ngrant select, delete\n on ${TURN_CLAIMS_TABLE}, ${TURN_LEASES_TABLE}\n to ${retentionRole};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateTurnStore(\n pool: Pool,\n opts: { role?: string; retentionRole?: string } = {},\n): Promise<void> {\n await pool.query(turnStoreMigrationSql(opts.role, opts.retentionRole));\n}\n\n/**\n * Deletes claims created before the cutoff — spec: erasure-reaches-the-claims.\n * The largest copy of content in this schema had no retention at all: one\n * reply per delivered message, forever.\n *\n * Same shape as `purgeAuditBefore`, for the same reason: a plain function\n * taking the pool, assuming the retention role for one transaction, crossing\n * scopes by nature and never through the RLS-bound `inScope` path. A claim\n * still in flight at that age belongs to a crashed turn, which spec 030\n * already makes re-runnable, so it goes too. Leases are untouched: they\n * expire on their own clock.\n */\nexport async function purgeTurnClaimsBefore(\n pool: Pool,\n before: string,\n opts: { retentionRole?: string; batch?: number } = {},\n): Promise<number> {\n return purgeBefore(pool, { table: TURN_CLAIMS_TABLE, column: \"created_at\", before, ...opts });\n}\n\n/**\n * Deletes leases that expired before the cutoff — spec: close-060-064-findings.\n * Erasure leaves leases alone (spec: close-review-part-two), and a holder that\n * crashed never releases; this bounds the orphans, as the retention role.\n */\nexport async function purgeExpiredLeases(\n pool: Pool,\n before: string,\n opts: { retentionRole?: string; batch?: number } = {},\n): Promise<number> {\n return purgeBefore(pool, { table: TURN_LEASES_TABLE, column: \"expires_at\", before, ...opts });\n}\n","import type { Pool } from \"pg\";\n\nimport { assertRoleIdentifier, DEFAULT_RLS_ROLE, rlsPolicySql, roleBootstrapSql } from \"./schema\";\n\n/**\n * Schema and migration for the routine store — spec: clock-tick. One\n * scope-stamped table; the routine as `jsonb`, the registration stamp as a\n * column. The app role reads and writes its own scope's rows; the tick\n * lists EVERY scope, so it runs as a role of its own with a policy that\n * admits all rows — the retention role's shape, for the same reason: FORCE\n * ROW LEVEL SECURITY binds the owner too.\n */\n\nexport const ROUTINES_TABLE = \"alma_routines\";\n\n/** Role the tick assumes to list every scope's routines — spec: clock-tick. */\nexport const DEFAULT_SCHEDULER_ROLE = \"alma_scheduler\";\n\nexport function routineStoreMigrationSql(\n role: string = DEFAULT_RLS_ROLE,\n schedulerRole: string = DEFAULT_SCHEDULER_ROLE,\n): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(schedulerRole);\n return `\ncreate table if not exists ${ROUTINES_TABLE} (\n org text not null,\n uid text not null,\n routine_id text not null,\n routine jsonb not null,\n registered_at timestamptz not null default now(),\n primary key (org, uid, routine_id)\n);\n${rlsPolicySql(ROUTINES_TABLE)}\n${roleBootstrapSql(role)}\n${roleBootstrapSql(schedulerRole)}\ngrant select, insert, update, delete\n on ${ROUTINES_TABLE}\n to ${role};\n\ndrop policy if exists alma_routines_schedule on ${ROUTINES_TABLE};\ncreate policy alma_routines_schedule on ${ROUTINES_TABLE}\n for select\n to ${schedulerRole}\n using (true);\n\ngrant select\n on ${ROUTINES_TABLE}\n to ${schedulerRole};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateRoutineStore(\n pool: Pool,\n opts: { role?: string; schedulerRole?: string } = {},\n): Promise<void> {\n await pool.query(routineStoreMigrationSql(opts.role, opts.schedulerRole));\n}\n","import type { Routine, RoutineStore, Scope, StoredRoutine } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { DEFAULT_SCHEDULER_ROLE, ROUTINES_TABLE } from \"./routine-store-schema\";\nimport { assertIso8601, assertRoleIdentifier } from \"./schema\";\nimport {\n inScope,\n inTransaction,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport interface PostgresRoutineStoreOptions extends ScopedStoreOptions {\n /** The role `list` assumes — the deployment actor. Default `alma_scheduler`. */\n schedulerRole?: string;\n}\n\ninterface RoutineRow {\n routine: Routine;\n registered_at: Date;\n}\n\n/**\n * Reference `RoutineStore` adapter — spec: clock-tick. Scoped writes and\n * reads as the app role, like every store here; `list` as the scheduler\n * role, the one cross-scope read, through the same transaction helper the\n * sweeps use.\n */\nexport class PostgresRoutineStore implements RoutineStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #schedulerRole: string;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresRoutineStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#schedulerRole = opts.schedulerRole ?? DEFAULT_SCHEDULER_ROLE;\n assertRoleIdentifier(this.#schedulerRole);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async register(routine: Routine & { registeredAt?: string }): Promise<void> {\n const { registeredAt, ...plain } = routine;\n if (registeredAt !== undefined) assertIso8601(registeredAt, \"registeredAt\");\n await this.#inScope(routine.scope, async (client) => {\n // The stamp is set once — supplied, or now — and a re-registration edits\n // the routine and keeps the anchor.\n await client.query(\n `insert into ${ROUTINES_TABLE} (org, uid, routine_id, routine, registered_at)\n values ($1, $2, $3, $4::jsonb, coalesce($5::timestamptz, now()))\n on conflict (org, uid, routine_id) do update set routine = excluded.routine`,\n [routine.scope.org, routine.scope.uid, routine.id, JSON.stringify(plain), registeredAt ?? null],\n );\n });\n }\n\n async cancel(scope: Scope, routineId: string): Promise<void> {\n await this.#inScope(scope, async (client) => {\n await client.query(\n `delete from ${ROUTINES_TABLE} where org = $1 and uid = $2 and routine_id = $3`,\n [scope.org, scope.uid, routineId],\n );\n });\n }\n\n async get(scope: Scope, routineId: string): Promise<StoredRoutine | null> {\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<RoutineRow>(\n `select routine, registered_at from ${ROUTINES_TABLE}\n where org = $1 and uid = $2 and routine_id = $3`,\n [scope.org, scope.uid, routineId],\n );\n return rows[0] === undefined ? null : toStored(rows[0]);\n });\n }\n\n async list(): Promise<StoredRoutine[]> {\n // The deployment read carries the store's timeout too (spec: close-060-064-findings).\n return inTransaction(this.#pool, this.#schedulerRole, async (client) => {\n const { rows } = await client.query<RoutineRow>(\n `select routine, registered_at from ${ROUTINES_TABLE} order by org, uid, routine_id`,\n );\n return rows.map(toStored);\n }, this.#timeoutMs);\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\nfunction toStored(row: RoutineRow): StoredRoutine {\n return { ...row.routine, registeredAt: row.registered_at.toISOString() };\n}\n","import { scopePath, type JobHandle, type RoutineRun, type RoutineRunOutcome, type RoutineRunStore, type Scope } from \"@alma-harness/core\";\nimport type { Pool, PoolClient } from \"pg\";\n\nimport { ROUTINE_RUNS_TABLE } from \"./routine-schema\";\nimport { assertIso8601 } from \"./schema\";\nimport {\n inScope,\n resolveRlsRole,\n resolveStatementTimeout,\n type ScopedStoreOptions,\n} from \"./scoped\";\n\nexport type PostgresRoutineRunStoreOptions = ScopedStoreOptions;\n\ninterface RunRow {\n routine_id: string;\n run_id: string;\n started_at: Date;\n finished_at: Date | null;\n outcome: RoutineRunOutcome;\n reason: string | null;\n cost_usd: number;\n session_id: string | null;\n turn_id: string | null;\n handle: JobHandle | null;\n delivery_hash: string | null;\n}\n\nconst COLUMNS = \"routine_id, run_id, started_at, finished_at, outcome, reason, cost_usd, session_id, turn_id, handle, delivery_hash\";\n\n/**\n * Reference `RoutineRunStore` adapter — spec: postgres-routine-runs. Same RLS\n * discipline as every other store. The three reads the runner makes — the\n * same fire again, today's count, the last run of an outcome — are one key\n * lookup and one indexed range scan; `since` is compared as `timestamptz`,\n * so a bound with an offset filters the same instants the reference does.\n */\nexport class PostgresRoutineRunStore implements RoutineRunStore {\n readonly #pool: Pool;\n readonly #role: string | null;\n readonly #timeoutMs: number | null;\n\n constructor(pool: Pool, opts: PostgresRoutineRunStoreOptions = {}) {\n this.#pool = pool;\n this.#role = resolveRlsRole(opts);\n this.#timeoutMs = resolveStatementTimeout(opts);\n }\n\n async record(run: RoutineRun): Promise<void> {\n scopePath(run.scope);\n assertIso8601(run.startedAt, \"startedAt\");\n if (run.finishedAt !== undefined) assertIso8601(run.finishedAt, \"finishedAt\");\n await this.#inScope(run.scope, async (client) => {\n await client.query(\n `insert into ${ROUTINE_RUNS_TABLE}\n (org, uid, routine_id, run_id, started_at, finished_at, outcome, reason, cost_usd,\n session_id, turn_id, handle, delivery_hash)\n values ($1, $2, $3, $4, $5::timestamptz, $6::timestamptz, $7, $8, $9, $10, $11, $12::jsonb, $13)\n on conflict (org, uid, routine_id, run_id) do update set\n started_at = excluded.started_at, finished_at = excluded.finished_at,\n outcome = excluded.outcome, reason = excluded.reason, cost_usd = excluded.cost_usd,\n session_id = excluded.session_id, turn_id = excluded.turn_id,\n handle = excluded.handle, delivery_hash = excluded.delivery_hash`,\n [\n run.scope.org,\n run.scope.uid,\n run.routineId,\n run.id,\n run.startedAt,\n run.finishedAt ?? null,\n run.outcome,\n run.reason ?? null,\n run.costUsd,\n run.sessionId ?? null,\n run.turnId ?? null,\n run.handle === undefined ? null : JSON.stringify(run.handle),\n run.deliveryHash ?? null,\n ],\n );\n });\n }\n\n async get(scope: Scope, routineId: string, runId: string): Promise<RoutineRun | null> {\n return this.#inScope(scope, async (client) => {\n const { rows } = await client.query<RunRow>(\n `select ${COLUMNS} from ${ROUTINE_RUNS_TABLE}\n where org = $1 and uid = $2 and routine_id = $3 and run_id = $4`,\n [scope.org, scope.uid, routineId, runId],\n );\n return rows[0] === undefined ? null : toRun(scope, rows[0]);\n });\n }\n\n async list(\n scope: Scope,\n routineId: string,\n opts: { since?: string; outcome?: RoutineRunOutcome; limit?: number } = {},\n ): Promise<RoutineRun[]> {\n scopePath(scope);\n if (opts.since !== undefined) assertIso8601(opts.since, \"since\");\n if (opts.limit !== undefined && opts.limit <= 0) return [];\n return this.#inScope(scope, async (client) => {\n const params: unknown[] = [scope.org, scope.uid, routineId];\n const where = [\"org = $1\", \"uid = $2\", \"routine_id = $3\"];\n if (opts.since !== undefined) {\n params.push(opts.since);\n where.push(`started_at >= $${params.length}::timestamptz`);\n }\n if (opts.outcome !== undefined) {\n params.push(opts.outcome);\n where.push(`outcome = $${params.length}`);\n }\n let limit = \"\";\n if (opts.limit !== undefined) {\n params.push(opts.limit);\n limit = ` limit $${params.length}`;\n }\n const { rows } = await client.query<RunRow>(\n `select ${COLUMNS} from ${ROUTINE_RUNS_TABLE}\n where ${where.join(\" and \")}\n order by started_at desc, run_id asc${limit}`,\n params,\n );\n return rows.map((row) => toRun(scope, row));\n });\n }\n\n /** Shared RLS binding — see `scoped.ts`. */\n async #inScope<T>(scope: Scope, fn: (client: PoolClient) => Promise<T>): Promise<T> {\n return inScope(this.#pool, this.#role, scope, fn, this.#timeoutMs);\n }\n}\n\n/** Absent stays ABSENT, never `null`: the two references must agree byte for byte. */\nfunction toRun(scope: Scope, row: RunRow): RoutineRun {\n const run: RoutineRun = {\n id: row.run_id,\n routineId: row.routine_id,\n scope: { org: scope.org, uid: scope.uid },\n startedAt: row.started_at.toISOString(),\n outcome: row.outcome,\n costUsd: row.cost_usd,\n };\n if (row.finished_at !== null) run.finishedAt = row.finished_at.toISOString();\n if (row.reason !== null) run.reason = row.reason;\n if (row.session_id !== null) run.sessionId = row.session_id;\n if (row.turn_id !== null) run.turnId = row.turn_id;\n if (row.handle !== null) run.handle = row.handle;\n if (row.delivery_hash !== null) run.deliveryHash = row.delivery_hash;\n return run;\n}\n","import type { Pool } from \"pg\";\n\nimport {\n assertRoleIdentifier,\n DEFAULT_RETENTION_ROLE,\n DEFAULT_RLS_ROLE,\n retentionPolicySql,\n rlsPolicySql,\n roleBootstrapSql,\n} from \"./schema\";\nimport { purgeBefore } from \"./scoped\";\n\n/**\n * Schema and migration for routine runs — spec: postgres-routine-runs. One\n * scope-stamped table under the shared policy generator, METADATA only:\n * never the text delivered (the hash is what a store may keep, spec:\n * routine-runner), so the app role gets no delete — a run record is retained\n * like a trail, and swept by age as the retention actor.\n */\n\nexport const ROUTINE_RUNS_TABLE = \"alma_routine_runs\";\n\nexport function routineRunStoreMigrationSql(\n role: string = DEFAULT_RLS_ROLE,\n retentionRole: string = DEFAULT_RETENTION_ROLE,\n): string {\n assertRoleIdentifier(role);\n assertRoleIdentifier(retentionRole);\n return `\ncreate table if not exists ${ROUTINE_RUNS_TABLE} (\n org text not null,\n uid text not null,\n routine_id text not null,\n run_id text not null,\n started_at timestamptz not null,\n finished_at timestamptz,\n outcome text not null,\n reason text,\n cost_usd double precision not null,\n session_id text,\n turn_id text,\n handle jsonb,\n delivery_hash text,\n primary key (org, uid, routine_id, run_id),\n constraint alma_routine_runs_outcome_check\n check (outcome in ('delivered', 'duplicate', 'submitted', 'waiting', 'refused', 'failed'))\n);\n\ncreate index if not exists alma_routine_runs_recent\n on ${ROUTINE_RUNS_TABLE} (org, uid, routine_id, started_at desc);\n\ncreate index if not exists alma_routine_runs_started\n on ${ROUTINE_RUNS_TABLE} (started_at);\n${rlsPolicySql(ROUTINE_RUNS_TABLE)}\n${roleBootstrapSql(role)}\n${roleBootstrapSql(retentionRole)}\ngrant select, insert, update\n on ${ROUTINE_RUNS_TABLE}\n to ${role};\n${retentionPolicySql(ROUTINE_RUNS_TABLE, retentionRole, \"alma_routine_runs_retention\")}\n\ngrant select, delete\n on ${ROUTINE_RUNS_TABLE}\n to ${retentionRole};\n`;\n}\n\n/** Idempotent; running it is the product's choice (typically at startup). */\nexport async function migrateRoutineRunStore(\n pool: Pool,\n opts: { role?: string; retentionRole?: string } = {},\n): Promise<void> {\n await pool.query(routineRunStoreMigrationSql(opts.role, opts.retentionRole));\n}\n\n/** Deletes run records started before the cutoff, as the retention role — the shape of every sweep here. */\nexport async function purgeRoutineRunsBefore(\n pool: Pool,\n before: string,\n opts: { retentionRole?: string; batch?: number } = {},\n): Promise<number> {\n return purgeBefore(pool, { table: ROUTINE_RUNS_TABLE, column: \"started_at\", before, ...opts });\n}\n"],"mappings":"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","names":["scopePath","scopePath","scopePath","scopePath","id","instant","assertWellFormed","scopePath","scopePath","assertWellFormed","scopePath","scopePath"]}
|