/** * Store-level `sync.*` flags (journal spec §5.1, Q8). * * Each flag is persisted per store in `_sync_meta` and is OFF by default. A * store that never enabled one has no `_sync_meta` at all, and reading the * flags never writes, so an all-off store is left byte-for-byte untouched. * * `CLEO_SYNC_=0` (for example `CLEO_SYNC_PUSH=0`) stops that flag's * behaviour in the current process. It never turns a flag on, and it never * installs or drops anything (M3). * * @task T12342 * @module store/sync/flags */ import type { DatabaseSync } from 'node:sqlite'; import type { SupersededStoreBareAccount, TableScope } from '@cleocode/contracts'; /** The journal's flags, in slice order. */ export declare const SYNC_FLAGS: readonly ["sync.capture", "sync.seal", "sync.push", "sync.pull", "sync.strict"]; /** One journal flag. */ export type SyncFlag = (typeof SYNC_FLAGS)[number]; /** Every flag's persisted state. */ export type SyncFlagState = Readonly>; /** * Flags whose slices are not finished: turning one on is refused unless a * test opts in (`allowUnreleased`). `sync.seal` stays here until S3b–S3d land * (T13032); remove a flag when its slice ships. */ export declare const UNRELEASED_FLAGS: ReadonlySet; /** The environment kill switch for a flag: `sync.push` → `CLEO_SYNC_PUSH`. */ export declare function killSwitchVar(flag: SyncFlag): string; /** * The persisted flags of a store. Read-only; all off when the store has no * `_sync_meta`. */ export declare function readSyncFlags(db: DatabaseSync): SyncFlagState; /** Whether any flag is persisted on (the kill switches do not count). */ export declare function anySyncFlagOn(db: DatabaseSync): boolean; /** * Whether a flag's behaviour runs in this process: persisted on, and not * stopped by its kill switch. * * @param env - The environment to read the kill switch from. */ export declare function isSyncFlagOn(db: DatabaseSync, flag: SyncFlag, env?: NodeJS.ProcessEnv): boolean; /** * The journal's sync set (§2.1): tables declared in ROW_IDENTITY that are * portable and not frozen-legacy. Defined here, below the capture machinery * (which imports this module), so the strand check can name it (T13225). * * @param scope - The store scope. */ export declare function syncSetTables(scope: TableScope): string[]; /** The remedy {@link LegacyOnlyStoreError} names while the store is a full strand. */ export declare const LEGACY_ONLY_REMEDY = "cleo doctor superseded-store --reconcile"; /** The remedy for a partial strand: plan, then add `--apply` (T13309). */ export declare const BARE_STRANDS_REMEDY = "cleo doctor superseded-store --reconcile --bare-strands"; /** * One bare legacy table holding rows its sync-set twin lacks (T13224, T13225). * The journal captures only the sync set, so it would never see them. */ export interface LegacyStrand { /** The bare legacy table, e.g. `sessions`. */ readonly bareTable: string; /** Its sync-set twin, e.g. `tasks_sessions`. */ readonly table: string; /** Bare rows whose key the twin lacks. */ readonly missing: number; /** * Bare `tasks` rows whose id a DIFFERENT live task holds (the T001 reuse * case): present by key, absent in substance. Always 0 for other tables. */ readonly shadowed: number; } /** * The store's bare legacy rows the journal would never see (T13224, T13225). * * A pair is a bare table outside the sync set whose consolidated target is a * sync-set table, so a bare table the runtime still reads (it is in the sync * set itself) is never a source. Rows are compared by the twin's primary key, * projected as the reconcile projects it ({@link legacyRowProjection}), the * same proof as the superseded-store survey. A pair whose keys cannot be * compared counts only while its twin is empty. Bare `tasks` rows shadowed by a * different live task with the same id ({@link taskIdCollisionsSql}) count too. * * Dead bare tables are written by nothing since consolidation, so a missing row * was never carried — unless a reconcile carried it and the runtime deleted it * from the twin since, the normal life of every reconciled store. A bare table * the store records as carried ({@link BARE_ACCOUNTS_TABLE}) whose key digest * still matches ({@link bareTableDigest}) is therefore not a strand (T13319, * T13320). The record lives in the store, never in files beside it, so a * restored pre-reconcile snapshot is refused again. * * @returns One entry per stranded pair; empty when the journal sees every row. */ export declare function legacyStrands(db: DatabaseSync): LegacyStrand[]; /** * The 18 cleocode ship commits the legacy `drizzle-tasks` migration * `20260520163324_t9755-backfill-legacy-ship-commits` seeds into the bare * `commits` table of EVERY store, after the consolidated schema (T12914). They * are not the store's history (cleocode's own `tasks_commits` holds them from * git), so a twin lacking them is not a strand: nothing is copied, and no * device journals another project's commits. A test pins this list to the * migration. */ export declare const LEGACY_SEEDED_COMMIT_SHAS: readonly string[]; /** * Adopt reconcile receipts written before T13319 into the store's record of * carried bare tables (T13320): a receipt file beside the store is audit * only, never authority, so a bare table one names is recorded only when it * compares clean RIGHT NOW (every bare key present in the twin, no shadowed * task). A table that does not — a restored pre-reconcile snapshot, or rows * deleted since the reconcile — stays a strand, for the bare-strands * reconcile (T13309) to settle. Runs when a flag is turned on. * * @param db - The live project store, writable. * @returns The bare tables adopted. */ export declare function adoptReconciledReceipts(db: DatabaseSync): string[]; /** Whether any bare legacy row is stranded from the journal ({@link legacyStrands}). */ export declare function isLegacyOnlyStore(db: DatabaseSync): boolean; /** * The remedy for `strands`: the full reconcile while `tasks_tasks` is empty * (it copies the bare family whole then), otherwise the bare-strands * reconcile ({@link BARE_STRANDS_REMEDY}). */ export declare function legacyStrandRemedy(db: DatabaseSync, strands: readonly LegacyStrand[]): string; /** Sync refused on a store with stranded bare rows ({@link legacyStrands}). */ export declare class LegacyOnlyStoreError extends Error { readonly code = "E_SYNC_LEGACY_ONLY_STORE"; /** * @param flag - The flag being enabled. * @param remedy - {@link legacyStrandRemedy} for the store. */ constructor(flag: string, remedy: string); } /** * {@link legacyStrands}, reused while no other connection has committed. * * @param db - The store connection the sealer uses. */ export declare function legacyStrandsCached(db: DatabaseSync): LegacyStrand[]; /** * The store's own record of the bare tables a reconcile carried (T13320): one * row per bare table with its key digest ({@link bareTableDigest}) when the * run verified. It lives IN the store (local-only, `_exodus_recovery_*`), so * it travels with backups and restores; a snapshot taken before the reconcile * carries none, and its stranded rows are refused again. */ export declare const BARE_ACCOUNTS_TABLE = "_exodus_recovery_bare_accounts"; /** * The row count and key digest of a bare legacy table, by which the store * records a reconcile carrying it (T13319). Its primary-key values are hashed in order * — the rows a strand is judged by — so a row added since changes it, while a * column the runtime adds to the dead table on open (its legacy upgrade does) * does not. A keyless table hashes every column. * * @param db - Connection holding the table. * @param schema - Schema the table lives in, e.g. `main`. * @param table - The bare table. */ export declare function bareTableDigest(db: DatabaseSync, schema: string, table: string): SupersededStoreBareAccount; /** * Record, in the store, that a verified reconcile run carried `accounts` * (T13320). Upserts per bare table, so a later run's digest replaces an * earlier one; `run` names the receipt directory that did it. * * @param db - The live project store, writable. * @param accounts - Key digests of the bare tables the run carried. * @param run - The reconcile's run directory name. * @param now - When the run verified. */ export declare function recordBareAccounts(db: DatabaseSync, accounts: readonly SupersededStoreBareAccount[], run: string, now?: Date): void; /** * Remove the store's record of the bare tables a reconcile run carried, when * that run is rolled back (T13309). * * @param db - The live project store, writable. * @param run - The reconcile's run directory name. * @returns Records removed. */ export declare function forgetBareAccounts(db: DatabaseSync, run: string): number; /** One record of {@link BARE_ACCOUNTS_TABLE}, as the store holds it. */ export interface BareAccountRecord { /** The bare legacy table. */ readonly table: string; /** Its row count when the run verified. */ readonly rows: number; /** Its key digest when the run verified ({@link bareTableDigest}). */ readonly digest: string; /** The reconcile's run directory name. */ readonly run: string; /** When the run verified (ISO 8601). */ readonly recordedAt: string; } /** * Every record of {@link BARE_ACCOUNTS_TABLE} in `db` (none without the table). * * @param db - The store. */ export declare function readBareAccounts(db: DatabaseSync): BareAccountRecord[]; /** * Settle the record of carried bare tables in a restored store (T13324). * * A vault restore places the snapshot's twins and record, but this machine's * bare tables (local-only; none on a new machine). Per bare table, the record * kept is the first of the store's own and `others` (this machine's, from * before the restore) whose row count and key digest match the bare table as * restored; a table neither matches loses its record, so its stranded rows are * judged again ({@link legacyStrands}). * * @param db - The restored store, writable. * @param others - This machine's records before the restore. * @returns The bare tables whose record was dropped. */ export declare function settleRestoredBareAccounts(db: DatabaseSync, others: readonly BareAccountRecord[]): string[]; /** * Persist a flag. Turning one on first applies the sync schema; turning one * off on a store without the schema writes nothing. * * Prerequisite checks (`cleo sync enable `) live with the slice that * gives the flag its behaviour; this is the storage primitive. * * @returns Whether the stored value changed. */ export declare function setSyncFlag(db: DatabaseSync, flag: SyncFlag, on: boolean, options?: { now?: Date; schemaRoot?: string; allowUnreleased?: boolean; }): boolean; //# sourceMappingURL=flags.d.ts.map