/** * Row uids (T12341): the merge key every syncing row carries. * * A table whose Gate A class syncs gets a `uid TEXT` column. The local primary * key (`T####`, an AC id, a session id, a composite edge key) stays the local * key, and local foreign keys keep pointing at it; the uid is what a merge * compares, as (project, uid). Spec: `cleo docs fetch t12341-uid-scheme`. * * ## How a uid is made * * - **Minted, new row** (this build writes it): a random UUIDv7 * ({@link mintRowUid}), set by the Drizzle `$defaultFn` or by the writer. * - **Minted, no uid yet** (an existing row, or one an older build or a raw-SQL * writer inserted): a DETERMINISTIC v7-layout uid. Its timestamp is the * row's birth; its other 74 bits are SHA-256 over (scope, table, key, birth, * the uids of its owning rows, and, for append-only tables, a frozen content * list) ({@link mintedRowUid}). A `randomOnly` table (portable-secret) gets a * random uid instead. * - **Natural** (edges, labels): a UUIDv8 over the key with every reference * replaced by the referenced row's uid ({@link naturalRowUid}); a pure * function of the row. Symmetric relation types hash their endpoints in * sorted order; the reversed twin of a symmetric edge stored both ways takes * the `natural-mirror` recipe. A reference whose row does not exist hashes * `dangling:` and is reported. * * Deterministic identity can coincide for different rows (the same `T100` * with the same `created_at` in two stores). Every minted row therefore also * carries a birth fingerprint ({@link birthFingerprint}, `birth_fp`), hashed * once from creation facts no edit changes. Same uid and same `birth_fp` is * the same row; same uid and a different `birth_fp` is a COLLISION * ({@link classifyUidMatch}), which the merge never merges. * * ## When uids are filled * * {@link prepareRowIdentity} runs on every open of a `cleo.db`, inside the * cold-open lease right after the migrations: * * 1. re-asserts the identity columns and indexes (idempotent; heals a * migration the journal probe stamped without running its index DDL); * 2. registers the deterministic SQL functions on the connection, re-links * acceptance criteria an older build recreated without their uid (the AC * uid graveyard, spec §6.5), then fills every NULL uid (minted tables, * owners first; then natural tables), the stored reference facts (`ac_uid`, * `ac_text_hash`) and the birth fingerprints, in one savepoint; * 3. installs one TEMP `AFTER INSERT` trigger per declared table on this * connection, so a row inserted here without them gets the same values the * open pass would give it, in the same statement. A trigger never fails the * insert: a value that would clash is left NULL and reported at the next * open. * * A row an older build inserts keeps NULL values until the next open by this * build: there is no persistent trigger that computes them (see the * migration's header). Row uids are on by default (T13305); nothing here * runs when the kill switch `CLEO_ROW_UID_FILL=0` is set * ({@link rowUidFillEnabled}). * * A failure never fails the open: it is logged, and the rows keep NULL values * until a later open succeeds. A uid's timestamp is ordering sugar only: HLC * (T12342) never reads time from a uid. * * @module * @task T12341 * @epic T12323 */ import type { DatabaseSync } from 'node:sqlite'; import type { RowIdentityShareSignal, RowIdentityShareState, TableScope } from '@cleocode/contracts'; import { BIRTH_FP_COLUMN, ROW_IDENTITY, rowIdentityColumns, rowIdentitySpec, UID_COLUMN } from './row-identity-registry.js'; export { BIRTH_FP_COLUMN, ROW_IDENTITY, rowIdentityColumns, rowIdentitySpec, UID_COLUMN }; /** Domain separator hashed into every deterministic uid. Changing it re-keys every row. */ export declare const ROW_UID_DOMAIN = "cleo/row-uid/v1"; /** Domain separator hashed into every birth fingerprint. */ export declare const ROW_BIRTH_DOMAIN = "cleo/row-birth/v1"; /** Per-connection SQL function computing a deterministic uid. */ export declare const ROW_UID_SQL_FUNCTION = "cleo_row_uid"; /** Per-connection SQL function computing a birth fingerprint. */ export declare const ROW_BIRTH_FP_SQL_FUNCTION = "cleo_row_birth_fp"; /** Per-connection SQL function returning a fresh random UUIDv7 (`randomOnly` tables). */ export declare const ROW_RANDOM_UID_SQL_FUNCTION = "cleo_row_random_uid"; /** Per-connection SQL function hashing AC text like `acTextHash`. */ export declare const AC_TEXT_HASH_SQL_FUNCTION = "cleo_ac_text_hash"; /** Physical name of the AC uid graveyard (spec §6.5). */ export declare const AC_UID_GRAVEYARD = "tasks_ac_uid_graveyard"; /** * The literal-table writes the open pass makes, supplied by the tasks * chokepoint (`store/sqlite-data-accessor.ts`, which registers them when it * loads) so they stay enumerable by gate 28. */ export interface RowIdentityWriters { relinkAcUidNative(db: DatabaseSync, acId: string, uid: string, birthFp: string | null): number; clearAcUidGraveyardNative(db: DatabaseSync): void; writeRowIdentityMetaNative(db: DatabaseSync, key: string, value: string): void; fillIdentityColumnNative(db: DatabaseSync, table: string, column: string, valueSql: string, rowid?: number): number; clearBirthFpNative(db: DatabaseSync, table: string, rowid: number): void; /** Set the given identity columns of every row of `table` to NULL (T13231 full refill). */ clearIdentityColumnsNative(db: DatabaseSync, table: string, columns: readonly string[]): void; } /** * Register the chokepoint's row-identity writers (called once by * `store/sqlite-data-accessor.ts` at load). * * @param writers - The writers. */ export declare function registerRowIdentityWriters(writers: RowIdentityWriters): void; /** A SQL value as node:sqlite passes it to a function. */ export type UidInput = string | number | bigint | Uint8Array | null; /** * Unambiguous encoding of uid inputs: per value a type tag (0 null, 1 text, * 2 integer, 3 real, 4 blob), a big-endian u32 byte length, and the bytes. An * integer-valued number is tag 2 (SQLite `3` and `3.0` encode alike); any other * number is tag 3 with its ECMAScript shortest round-trip text. Frozen for * recipe v1. * * @param values - Inputs in hash order. * @returns The encoded bytes. */ export declare function encodeUidInputs(values: readonly UidInput[]): Buffer; /** * Epoch milliseconds of a stored timestamp, or `null` when absent or not a * strict ISO-8601 / SQLite timestamp. A value WITHOUT a zone is UTC: * `datetime('now')` writes `YYYY-MM-DD HH:MM:SS` meaning UTC, and a zoneless * `YYYY-MM-DDTHH:MM:SS` is read the same way, never as the device's local * time (`Date.parse` would, and a uid would then depend on the device's * timezone). Anything else (`Date.parse` extensions such as `Sep 24 2026`, * out-of-range fields) is `null`, i.e. an unparseable birth. Comparing that * text against ISO text is wrong within a day (`' '` sorts before `'T'`), so * every comparison and every uid birth goes through this function (T12329). * * @param value - Stored timestamp. * @returns Epoch milliseconds, or `null`. */ export declare function parseStoreTimestamp(value: UidInput | undefined): number | null; /** * A new uid for a row this build creates: a random UUIDv7. * * @param birthMs - The row's own birth, for a row imported with one (an * importer must never stamp import time into an old row's uid). * @returns Canonical lowercase UUID string. */ export declare function mintRowUid(birthMs?: number): string; /** * The deterministic uid of a minted row that has none: a v7 layout whose * timestamp is the row's birth and whose other 74 bits come from SHA-256 over * the row's identity. An unknown birth gives timestamp 0; the row's birth * fingerprint flags it (`birth:unknown`), and {@link rowIdentityFindings} * lists it. * * @param scope - Store scope. * @param table - Physical table name. * @param key - Local key values. * @param birth - Stored birth value (any format, or null). * @param ownerUids - Uids of the owning rows. * @param content - Frozen content values of an append-only row. * @returns Canonical lowercase UUID string. */ export declare function mintedRowUid(scope: TableScope, table: string, key: readonly UidInput[], birth: UidInput, ownerUids?: readonly UidInput[], content?: readonly UidInput[]): string; /** * The new uid of a minted child row when its owner is re-keyed (spec §6.4): * derived from STORED identity only (the child's old uid and the owner's new * uid), never from a local key or content, so every replica that computes it * gets the same value whatever display ids it holds. The child's timestamp * bits are kept; the other 74 bits come from SHA-256. * * @param scope - Store scope. * @param table - Physical table of the child. * @param oldChildUid - The child's uid before the re-key. * @param newOwnerUid - The owner's new uid. * @returns Canonical lowercase UUID string. */ export declare function rekeyedChildUid(scope: TableScope, table: string, oldChildUid: string, newOwnerUid: string): string; /** Recipe of a natural uid: the edge itself, or the reversed twin of a symmetric edge. */ export type NaturalRecipe = 'natural' | 'natural-mirror'; /** * The uid of a natural row: a UUIDv8 over (scope, table, key), with every * reference in the key already replaced by the referenced row's uid. * * @param scope - Store scope. * @param table - Physical table name. * @param parts - Key values, references as uids. * @param recipe - `natural-mirror` for the reversed twin of a symmetric edge. * @returns Canonical lowercase UUID string. */ export declare function naturalRowUid(scope: TableScope, table: string, parts: readonly UidInput[], recipe?: NaturalRecipe): string; /** * Canonical form of a text input to a birth fingerprint: Unicode NFC, line * endings as LF, surrounding whitespace trimmed. Two stores that hold the same * text in different normal forms fingerprint it identically. * * @param text - Stored text. * @returns The canonical text. */ export declare function canonicalText(text: string): string; /** * A minted row's birth fingerprint: 128 bits of SHA-256 over the canonical * birth (epoch ms when it parses, so a format-only difference fingerprints * alike; a flagged token otherwise) and the table's frozen birth facts, each * canonicalised ({@link canonicalText}). Computed ONCE, when the row first * gets its identity, from the row as it is then; never recomputed. It is * write-once and syncs with the row: a receiver stores the value it receives. * * @param table - Physical table name. * @param birth - Stored birth value. * @param facts - Birth fact values, in declared order. * @returns 32 lowercase hex characters. */ export declare function birthFingerprint(table: string, birth: UidInput, facts: readonly UidInput[]): string; /** How two rows that carry the same uid relate. */ export type UidMatch = 'same-row' | 'collision' | 'unknown'; /** * Classify two rows of one table that carry the same uid (merge contract, * spec §6.4): equal birth fingerprints are one row; different ones are a * COLLISION that must never be merged; a missing fingerprint must be filled * before the row is emitted. * * @param a - Birth fingerprint of one row. * @param b - Birth fingerprint of the other. * @returns The relation. */ export declare function classifyUidMatch(a: string | null, b: string | null): UidMatch; /** * Register the identity SQL functions on a connection: the deterministic uid * and birth fingerprint, the AC text hash, and a random v7 for `randomOnly` * tables. * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. */ export declare function registerRowUidFunction(db: DatabaseSync, scope: TableScope): void; /** * Re-assert the identity columns and indexes of every declared table present * in the store. Idempotent; the migration normally created them already. * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. * @returns Statements that had to be run (empty on a healthy store). */ export declare function ensureRowIdentitySchema(db: DatabaseSync, scope: TableScope): string[]; /** Findings the open pass reports (and `cleo doctor` can list). */ export interface RowIdentityFindings { /** Minted rows whose birth is missing or unparseable (uid timestamp 0), per table. */ readonly unknownBirth: Readonly>; /** Natural rows with a key reference to a row that does not exist, per table. */ readonly danglingRefs: Readonly>; /** Reversed twins of symmetric edges stored both ways (duplicates to drop), per table. */ readonly mirrorEdges: Readonly>; } /** What one fill pass did. */ export interface RowUidFillReport { /** Uids written per table. */ readonly filled: Readonly>; /** Stored reference facts written, per `table.column`. */ readonly refsFilled: Readonly>; /** Birth fingerprints written per table. */ readonly fingerprinted: Readonly>; /** Acceptance criteria re-linked to their uid from the graveyard. */ readonly relinked: number; /** Rows left without a uid or fingerprint, per table (a uniqueness clash). */ readonly unfilled: Readonly>; /** Rows the recipes flag. */ readonly findings: RowIdentityFindings; /** Schema statements the pass had to re-run. */ readonly healed: readonly string[]; /** * Pre-release identity values: `none` (the recipe marker is current, or * there were no values), `cleared` (values without the current recipe * marker were cleared and refilled), or `refused` (they were kept because * uids have already synced; spec §12.1). */ readonly refill: 'none' | 'cleared' | 'refused'; } /** * Fill the NULL uids of one declared table (the open pass's recipe). Requires * {@link registerRowUidFunction} on the connection. Used after a uid re-key, * which clears the uids of the natural rows keyed by the re-keyed row. * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. * @param table - Declared table. * @returns Uids written. */ export declare function fillTableUids(db: DatabaseSync, scope: TableScope, table: string, writers?: RowIdentityWriters): number; /** * Rows the recipes flag: minted rows with an unknown birth (uid timestamp 0), * natural rows with a dangling key reference, and reversed twins of symmetric * edges stored both ways. * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. * @returns Counts per table (tables without findings are absent). */ export declare function rowIdentityFindings(db: DatabaseSync, scope: TableScope): RowIdentityFindings; /** * Fill every NULL uid, stored reference fact and birth fingerprint of the * declared tables, in one savepoint, after re-linking graveyard AC uids. * Requires {@link registerRowUidFunction} on the connection. Idempotent: a * filled store writes nothing. * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. * @returns What was written and what could not be. */ export declare function fillRowUids(db: DatabaseSync, scope: TableScope, writers?: RowIdentityWriters): Omit; /** * Install the per-connection TEMP triggers that fill a uid, the stored * reference facts and the birth fingerprint on insert, in that order. Each * step is guarded so the trigger never fails the insert: a uid that would * clash is left NULL (a symmetric edge first tries its mirror recipe). * Requires {@link registerRowUidFunction}. * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. */ export declare function installRowUidTriggers(db: DatabaseSync, scope: TableScope): void; /** * Bring a freshly opened `cleo.db` connection's row identity up to date: * schema, graveyard re-link, fill, per-connection triggers. Never throws: a * failure is logged and the store opens with the values it has. * * @param db - Connection on a `cleo.db`, after its migrations ran. * @param scope - The store's scope. * @param options - `triggers: false` skips the per-connection triggers (a * dedicated migration connection: exodus refuses copies whose triggers call * an opaque function, so its rows are filled at the next open instead). * @returns The fill report, or `null` when skipped or failed. */ /** * Version of every identity recipe this build derives (uid, natural uid, * birth fingerprint, encoding). Written to {@link ROW_IDENTITY_META_TABLE} under * {@link ROW_IDENTITY_RECIPE_KEY} by the first fill; bumped by any change to a * recipe, a frozen content or birth-fact list, or the encoding. */ export declare const ROW_IDENTITY_RECIPE = "cleo/row-identity/v2"; /** * The recipe 9.25 shipped (v1). v2 changed only the birth facts of AC history * and evidence bindings (@refFp:ac_uid and ac_text_hash → none / ac_id, * T12802); a store filled * with v1 has those two tables' fingerprints re-derived (see * {@link v1ReleaseBirthFp}). */ export declare const ROW_IDENTITY_RECIPE_V1 = "cleo/row-identity/v1"; /** * The v1 fingerprint of an AC-history or binding row, to recognise a value * the 9.25 recipe derived: v1 hashed the criterion's fingerprint * (`@refFp:ac_uid`), or `ref:none` / `ref:missing`. Other tables: `null` * (their v1 and v2 recipes are the same). * * @param db - Connection on the project `cleo.db`. * @param table - Declared minted table. * @param row - The stored row. * @returns The v1 value, or `null`. * @task T12802 */ export declare function v1ReleaseBirthFp(db: DatabaseSync, table: string, row: Readonly>): string | null; /** * Local-only key/value table of the row-identity layer. Not * `tasks_schema_meta`: the twin collapse drops twin-only keys of that table on * its first run (T12535), which would lose the marker. */ export declare const ROW_IDENTITY_META_TABLE = "tasks_row_identity_meta"; /** Key holding the recipe version the store's identity values were derived with. */ export declare const ROW_IDENTITY_RECIPE_KEY = "row_identity_recipe"; /** * Key the sync layer (T12342/T12343) writes BEFORE the * first uid leaves the device. Once present, identity values are shared and * are never cleared locally. */ export declare const ROW_IDENTITY_SYNCED_KEY = "row_identity_synced"; /** * Re-create the tables, trigger and columns the uid migration adds when they * are missing: a store the journal probe stamped as migrated after only its * ADD COLUMNs had run (Scenario 3, Case A) otherwise never gets them. * Idempotent. * * @param db - Connection on the project `cleo.db`. * @returns Statements that had to be run. */ export declare function ensureIdentityTables(db: DatabaseSync): string[]; /** * Whether the store's identity values follow the current * {@link ROW_IDENTITY_RECIPE} (its marker, which the fill writes): no refill * is due. The recipe applies to project stores only; a global store is * always current. Read-only (a read-only handle is fine). * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. @defaultValue 'project' * @returns True when no recipe refill is owed. * @task T13231 */ export declare function rowIdentityRecipeCurrent(db: DatabaseSync, scope?: TableScope): boolean; /** * The birth fingerprint the pre-release v4/v5 build derived (the recipe that * reached live cleocode on 2026-09-28): raw birth text, raw facts, the task * title from the earliest `task_created` audit event, and an AC's OWNER UID * instead of its owner's fingerprint. Used only to recognise those values. * * @param db - Connection on the project `cleo.db`. * @param table - Minted table. * @param row - The stored row. * @returns The pre-release fingerprint, or `null` when the table had none. */ export declare function preReleaseBirthFp(db: DatabaseSync, table: string, row: Readonly>): string | null; /** Options of {@link rowIdentityShareState}. */ export interface RowIdentityShareOptions { /** * Cleo Nexus answered, on every linked origin, that it holds no checkpoint * and no journal segment of this project (`cleo doctor row-identity * --refill`): the `nexus-linked-no-vault` signal is resolved. Every other * signal still counts. */ readonly nexusCheckedNone?: boolean; } /** * Decide, strictly, whether a project store's row identity is unshared * (T13231). Every signal that the uids may have left the store, or that * local journal state references them, makes it `shared`; any record that * cannot be read makes it `unknown`: * * - the shared marker (`row_identity_synced`: a receive or a send happened); * - `sync.seal`/`sync.push`/`sync.pull` on; * - rows in any `_sync_*` table except `_sync_meta`, `_sync_replica` and * `_sync_clock` (captures, frames, row meta and repair baselines, undo * images, sealed ops, the ledger, inbox, conflicts: all keyed by uid; a * table added later counts by default); * - `suspect:` or `baseline:` keys in `_sync_meta` (journal state about this * store a refill would invalidate); * - identity aliases exist (a re-key, re-mint or split-brain import pointed * at uids); * - the store was rebound: a `_sync_replica` row not bound at `genesis`, or * more than one row (a copy, move or restore); * - a vault snapshot of this store root was pushed or restored (an entry in * `/nexus-vault.json` with a checkpoint or a push in flight); * - the project is linked to Cleo Nexus with no local vault entry for this * root: the path-keyed vault state cannot rule out a push from a moved path * or another CLEO_HOME, so this is `unknown` (resolved only by asking * Nexus: {@link RowIdentityShareOptions.nexusCheckedNone}). * * @param db - Connection on a project `cleo.db` (its file locates the root). * @param options - Whether Cleo Nexus was asked and answered "none". * @returns The state and the signals behind it. * @task T13231 */ export declare function rowIdentityShareState(db: DatabaseSync, options?: RowIdentityShareOptions): RowIdentityShareState; /** * Fold signals into a verdict: any `shared` signal makes it `shared`, else any * `unknown` one makes it `unknown`, else `unshared`. * * @param signals - Every signal found. * @returns The verdict. * @task T13231 */ export declare function shareStateOf(signals: readonly RowIdentityShareSignal[]): RowIdentityShareState; /** Meta key recording the snapshot taken before the last full identity refill (T13231). */ export declare const ROW_IDENTITY_REFILL_SNAPSHOT_KEY = "row_identity_refill_snapshot"; /** * Take the pre-refill snapshot when this open's refill will re-derive the * store from scratch (stale marker, identity values present, provably * unshared). A `VACUUM INTO` cannot run inside a transaction, so a caller that * runs the fill inside a bracket (identity-fill.ts) takes it first and passes * it in through `prepareRowIdentity`'s `refillSnapshot` option. * * @param db - Connection on a project `cleo.db`, outside any transaction. * @param scope - The store's scope. * @param share - Share-state options (whether Cleo Nexus answered "none"). * @returns The snapshot path, or `null` when no full refill is due. * @task T13231 */ export declare function snapshotIfFullRefillDue(db: DatabaseSync, scope: TableScope, share?: RowIdentityShareOptions): string | null; /** * What a full from-scratch refill clears: per declared table (alias tables * excluded), the rows carrying any identity value, plus the AC uid graveyard * rows under its table name. Read-only. * * @param db - Connection on a project `cleo.db` (read-only is fine). * @returns Row count per table; tables with nothing to clear are omitted. * @task T13231 */ export declare function fullRefillPlan(db: DatabaseSync): Record; /** Meta key recording a refused full refill (T13305). */ export declare const ROW_IDENTITY_REFUSED_KEY = "row_identity_refused"; /** How often a standing refusal is logged again (the doctor reports it in between). */ export declare const REFUSAL_WARN_INTERVAL_MS: number; /** * A recorded refusal of the full identity refill (T13305). While `state` is * `refused` for the current recipe and the store's share signals are * unchanged (`shareKey`), opens skip the refill pass. `cleared` means it was * reset (`cleo doctor row-identity --refill`, or a refill that ran). */ export interface RowIdentityRefusal { readonly state: 'refused' | 'cleared'; readonly recipe: string; /** Digest of the share verdict and its signals when refused. */ readonly shareKey: string; readonly shareState: 'shared' | 'unknown' | 'unshared'; readonly reasons: readonly string[]; /** When the refusal was first recorded. */ readonly at: string; /** When it was last logged. */ readonly warnedAt: string; } /** * The store's recorded refusal of the full identity refill, if any. * * @param db - Connection on a project `cleo.db` (read-only is fine). * @returns The refusal record, or `null`. * @task T13305 */ export declare function readRowIdentityRefusal(db: DatabaseSync): RowIdentityRefusal | null; /** * Clear a recorded refusal, so the next open (or this caller) re-evaluates * the full refill (`cleo doctor row-identity --refill`, T13305). * * @param db - Connection on a project `cleo.db`. * @param writers - The chokepoint writers. * @returns Whether a standing refusal was cleared. * @task T13305 */ export declare function clearRowIdentityRefusal(db: DatabaseSync, writers?: RowIdentityWriters): boolean; /** * Record that the store's identity values follow {@link ROW_IDENTITY_RECIPE}. * The fill writes it; {@link markRowIdentityShared} writes it too on the * first receive (a device that pulls before it ever fills). * * @param db - Connection on the project `cleo.db`. */ export declare function writeRecipeMarker(db: DatabaseSync, writers?: RowIdentityWriters): void; /** * Whether the store's identity values are shared with other devices * ({@link ROW_IDENTITY_SYNCED_KEY} present): from then on no local write may * clear or re-point a uid. * * @param db - Connection on the project `cleo.db`. * @returns True once identity has been sent or received. * @task T12806 */ export declare function rowIdentityShared(db: DatabaseSync): boolean; /** * Record that identity values are now SHARED with other devices: the recipe * marker, and {@link ROW_IDENTITY_SYNCED_KEY} (first write wins, with the * direction). Called on the first receive (a pull-first clone that never * filled) as well as before the first send; once present, the open pass * never clears an identity value (spec §12.1). * * @param db - Connection on the project `cleo.db`. * @param direction - `receive` (the merge applied a row or op) or `send`. * @param writers - The chokepoint writers. * @task T12746 */ export declare function markRowIdentityShared(db: DatabaseSync, direction: 'send' | 'receive', writers?: RowIdentityWriters): void; /** * Re-create the row-identity SCHEMA a store should have, on every open, * whatever the fill flag (T12878). DDL only: the identity tables, the * graveyard trigger, the columns an early table lacks, the uid columns and * their indexes. No row value is read or written. A store whose uid migration * was journaled without running its statements (journal Scenario 3 Case A: * live cleocode under 9.25, spec §12.2) gets the full schema here; the fill, * refill and TEMP triggers follow the fill flag ({@link prepareRowIdentity}). * * Never throws: a failure is logged and the open continues. * * @param db - Connection on a `cleo.db`, inside the cold-open lease. * @param scope - The store's scope. * @returns The DDL statements it ran (empty when the schema was complete). * @task T12878 */ export declare function healRowIdentitySchema(db: DatabaseSync, scope: TableScope, writers?: RowIdentityWriters | undefined): string[]; /** * Meta key of the heal receipt: when the open last re-created missing * identity schema, and what (T12878). Local-only bookkeeping of the heal * itself, in `tasks_row_identity_meta`; no row value of any other table is * written. `cleo doctor` (the `row_identity` check) shows it. */ export declare const ROW_IDENTITY_HEAL_KEY = "row_identity_schema_healed"; /** The receipt {@link healRowIdentitySchema} leaves when it re-created schema. */ export interface RowIdentityHealReceipt { readonly at: string; readonly scope?: TableScope; readonly statements: number; /** Objects re-created or columns added (table, index, trigger or `table.column`). */ readonly objects: readonly string[]; } /** * The last heal receipt of a store, read-only (`undefined` when the schema * never needed healing). * * @param db - Connection on a project `cleo.db` (read-only is fine). * @returns The receipt. * @task T12878 */ export declare function readRowIdentityHealReceipt(db: DatabaseSync): RowIdentityHealReceipt | undefined; /** * Every heal receipt a store kept, oldest first (read-only). * * @param db - Connection on a project `cleo.db` (read-only is fine). * @returns The receipts (empty when the schema never needed healing). * @task T12878 */ export declare function readRowIdentityHealHistory(db: DatabaseSync): RowIdentityHealReceipt[]; /** * Identity schema a project store lacks, read-only: missing identity tables, * the graveyard trigger, and the alias / graveyard columns an early table * lacks (T12878; the open heals them). * * The global store has no identity tables: only its declared uid columns * and indexes are checked (T12894). * * @param db - Connection on a `cleo.db` (read-only is fine). * @param scope - The store's scope (default `project`). * @returns What is missing (empty when complete). * @task T12878 * @task T12894 */ export declare function missingRowIdentitySchema(db: DatabaseSync, scope?: TableScope): string[]; /** * What the fill would do on this open, found by index probes alone (T12341 * C1). A store the fill has already run on answers `[]` and the open skips the * fill pass (and never loads its writers). Every probe is correct regardless * of rowid reuse: it looks for the rows themselves, never a rowid watermark. * * - `schema`: part of the identity schema, or a fill partial index, is missing * (the full pass creates it); * - `recipe`: the recipe marker is missing or stale (the refill runs, or is * refused and reported once uids have synced); * - `graveyard:`: an older build deleted acceptance criteria whose uids wait * to be re-linked; * - `uid:`: a row without a uid (the unique uid index, or the uid * primary key, holds the NULLs); * - `birth_fp:
`: a minted row without a birth fingerprint (the partial * index {@link birthFpNullIndex}); * - `ref:
.`: a NULL stored reference fact that now resolves * (its column index holds the NULLs; a reference that still dangles is not * work). * * Requires {@link registerRowUidFunction} on the connection (the stored * reference probe evaluates the AC text hash). * * @param db - Connection on a `cleo.db`. * @param scope - The store's scope. * @returns The pending work, `[]` when the identity is complete. * @task T12341 */ export declare function rowIdentityFillPending(db: DatabaseSync, scope: TableScope): string[]; export declare function prepareRowIdentity(db: DatabaseSync, scope: TableScope, options?: { readonly triggers?: boolean; readonly writers?: RowIdentityWriters; /** A pre-refill snapshot the caller took outside its transaction ({@link snapshotIfFullRefillDue}). */ readonly refillSnapshot?: string | null; /** Share-state options: Cleo Nexus answered "none" (`cleo doctor row-identity --refill`). */ readonly share?: RowIdentityShareOptions; /** The caller's {@link rowIdentityFillPending} result, to avoid probing twice. */ readonly pending?: readonly string[]; }): RowUidFillReport | null; /** * The AFTER UPDATE content-sync trigger for one brain FTS5 table, fired only * when an indexed column changes. * * @param table - The content table, e.g. `brain_observations`. * @returns The `CREATE TRIGGER IF NOT EXISTS` statement. */ export declare function brainFtsUpdateTriggerSql(table: string): string; /** * Rewrite every existing unscoped brain FTS update trigger to its scoped form. * A store without the trigger is left alone (the FTS tables are created * lazily by `ensureFts5Tables`). Idempotent; one `sqlite_master` read when * there is nothing to do. * * @param db - The store handle. * @returns The triggers rewritten. */ export declare function upgradeBrainFtsUpdateTriggers(db: DatabaseSync): string[]; //# sourceMappingURL=row-identity.d.ts.map