/** * Reversible quarantine of auto-generated BRAIN rows (T13951, owner decision * D11179). * * Three kinds of rows were written by machinery, not by anyone recording * knowledge: dispatch traces (`brain_patterns`, one per agent resolution until * T13946), "Doc attached" records (`brain_observations`, until T13985) and the * content-free task-start/complete stubs. Recall already ignores them * (`eligibility.ts#autoRowExclusionClause`); this module retires them from the * active store by setting `invalid_at` only. Nothing is deleted. * * Safety contract (lead conditions on D11179): * - Dry run by default: per-store, per-table counts and the exact predicates. * - Apply refuses unless the caller passes the counts of a dry run taken in the * same state, so it never touches more rows than were approved. * - The receipt (ids per store and table, timestamp, predicates, counts) is * written durably BEFORE the first write, so a crash mid-apply leaves a * receipt restore can use. * - Restore clears `invalid_at` only where it still equals the receipt's * timestamp, so it is idempotent and never undoes a later invalidation. * - Writes go through the canonical accessors, in batches, and sync. * * Code placed in `packages/core/` per Package-Boundary Check — verified against AGENTS.md. * * @module memory/auto-row-quarantine * @task T13951 */ import type { DatabaseSync } from 'node:sqlite'; import { type AutoRowRuleId } from './auto-row-rules.js'; /** Brain stores the quarantine covers, reported separately. */ export declare const QUARANTINE_SCOPES: readonly ["project", "global"]; /** A brain store. */ export type QuarantineScope = (typeof QUARANTINE_SCOPES)[number]; /** Memory tables holding auto rows. */ export declare const QUARANTINE_TABLES: readonly ["patterns", "observations"]; /** A memory table holding auto rows. */ export type QuarantineTable = (typeof QUARANTINE_TABLES)[number]; /** Counts per table, and per rule, for one store. */ export type QuarantineCounts = Record; }>; /** A count per store and table. */ export type QuarantineTally = Record>; /** Row ids per table for one store. */ export type QuarantineIds = Record; /** The dry-run report: what an apply in this state would quarantine. */ export interface AutoRowQuarantinePlan { /** Counts per store and table. */ counts: Record; /** The exact predicates, so the owner approves what will match. */ predicates: Record; /** Ids per store and table (kept out of CLI output; written to the receipt). */ ids: Record; /** Short digest of the exact id set; `--expect-digest` binds an apply to it. */ digest: string; } /** Durable record of an apply, written before the first write. */ export interface AutoRowQuarantineReceipt { /** Receipt format. */ version: 1; /** The `invalid_at` value written; restore matches on it. */ quarantinedAt: string; /** `started` until every batch committed, then `complete`. */ status: 'started' | 'complete'; /** Predicates in force. */ predicates: Record; /** Approved counts per store and table. */ counts: QuarantineTally; /** Ids per store and table. */ ids: Record; /** Digest of the approved id set. */ digest: string; /** Rows actually quarantined, once complete. */ quarantined?: QuarantineTally; /** Planned rows a batch skipped because they stopped matching (e.g. verified mid-apply). */ skipped?: Record; } /** Writes a store's `invalid_at` for listed rows; returns the ids that changed. */ export type InvalidAtWriter = (table: QuarantineTable, ids: readonly string[], invalidAt: string | null, expected: string | null, guardRules?: readonly AutoRowRuleId[]) => Promise; /** Read handle and accessor-backed writer for one store. */ export interface QuarantineStore { /** Read handle for the store. */ db: DatabaseSync; /** Accessor-backed `invalid_at` writer. */ write: InvalidAtWriter; } /** Default rows per write transaction. */ export declare const QUARANTINE_BATCH_SIZE = 500; /** Error with a stable code for the CLI envelope. */ export declare class AutoRowQuarantineError extends Error { readonly code: 'E_QUARANTINE_EXPECT_MISMATCH' | 'E_QUARANTINE_RECEIPT'; /** * @param code - Stable error code. * @param message - Human-readable explanation. */ constructor(code: 'E_QUARANTINE_EXPECT_MISMATCH' | 'E_QUARANTINE_RECEIPT', message: string); } /** * Open the project and global brain stores with accessor-backed writers. * * @param projectRoot - Project whose brain is the `project` store. * @returns Read handles and writers for both stores. * @example * ```ts * const stores = await openQuarantineStores(projectRoot); * ``` */ export declare function openQuarantineStores(projectRoot: string): Promise>; /** * Dry run: what an apply would quarantine in each store, with the predicates. * * @param stores - Project and global stores. * @returns Counts, predicates and ids. Nothing is written. * @example * ```ts * const plan = planAutoRowQuarantine(await openQuarantineStores(root)); * ``` */ export declare function planAutoRowQuarantine(stores: Record): AutoRowQuarantinePlan; /** * Short, stable digest of an id set (sha256 of the sorted ids per store and * table, first 16 hex characters). Approval binds to it, so an apply cannot * quarantine a different set of rows with the same counts. * * @param ids - Ids per store and table. * @returns 16 hex characters. * @example * ```ts * digestIds(plan.ids); // '3f2a9c0d1b7e4a55' * ``` */ export declare function digestIds(ids: Record): string; /** * Parse `--expect` counts: `project:patterns=N,project:observations=N,global:...`. * Every store/table pair must be given, once. * * @param text - The flag value. * @returns Expected counts per store and table. * @throws AutoRowQuarantineError when a pair is missing, repeated or malformed. * @example * ```ts * parseQuarantineExpect('project:patterns=3,project:observations=1,global:patterns=0,global:observations=0'); * ``` */ export declare function parseQuarantineExpect(text: string): QuarantineTally; /** * Default receipt path for an apply at `quarantinedAt` (gitignored backups). * * @param projectRoot - Project root. * @param quarantinedAt - ISO timestamp of the apply. * @returns Absolute receipt path. * @example * ```ts * defaultQuarantineReceiptPath(root, '2026-10-24T00:00:00.000Z'); * ``` */ export declare function defaultQuarantineReceiptPath(projectRoot: string, quarantinedAt: string): string; /** * Apply the quarantine after checking the approved counts still hold. * * @param stores - Project and global stores. * @param expect - Counts from the approved dry run; any difference refuses. * @param expectDigest - Id-set digest from the approved dry run; any difference refuses. * @param receiptPath - Where the receipt is written (before the first write). * @param options - `batchSize` per write transaction; `now` for tests. * @returns The completed receipt. * @throws AutoRowQuarantineError when the current counts differ from `expect`. * @example * ```ts * const receipt = await applyAutoRowQuarantine(stores, expect, path); * ``` */ export declare function applyAutoRowQuarantine(stores: Record, expect: QuarantineTally, expectDigest: string, receiptPath: string, options?: { batchSize?: number; now?: Date; }): Promise; /** * Restore a quarantine from its receipt: clear `invalid_at` on the listed rows * that still carry the receipt's timestamp. Idempotent; works on a receipt left * `started` by an interrupted apply. * * @param stores - Project and global stores. * @param receiptPath - The receipt an apply wrote. * @param options - `batchSize` per write transaction. * @returns Rows restored per store and table. * @throws AutoRowQuarantineError when the receipt is unreadable or malformed. * @example * ```ts * const restored = await restoreAutoRowQuarantine(stores, path); * ``` */ export declare function restoreAutoRowQuarantine(stores: Record, receiptPath: string, options?: { batchSize?: number; }): Promise; //# sourceMappingURL=auto-row-quarantine.d.ts.map