/** * Persistence for background-job lifecycle state (tombstones, deletion * epochs, alias high-water marks) over the optional v2 host storage * domain (`ctx.storage`). * * Design invariants: * - **Write-through.** `recordBackgroundJobSuppression` / * `clearBackgroundJobSuppression` (background-job-store.ts) call this * module on every mutation so the persisted state tracks the in-process * ledger. Writes are queued fire-and-forget: a crash between the * in-memory mutation and the queue flush loses that persisted entry — * an accepted degradation to process-local behavior. A task deleted and * then legitimately * relaunched must NOT be ghost-skipped after a restart — clearing the * tombstone on relaunch is load-bearing (the deletion EPOCH survives a * clear so generation fencing keeps working). * - **Seeding is backend-only.** Fresh boards/ledgers seed from what a * real backend returned via `loadInitialBackgroundJobPersistence`. * Without a backend (v1 hosts, hosts without the domain) the module is * a pure in-memory no-op sink: zero behavior change, and no * cross-board contamination inside one process. * - **Bounded growth.** Persisted tombstones (and their epoch entries) * are capped at `MAX_PERSISTED_TOMBSTONES` most-recent by recorded * time. Evicting an ancient tombstone can at worst resurrect an * equally ancient deleted run — the same trade a fresh process makes * today. * - **Serialized writes.** Every storage mutation for one key is chained * through an in-process queue, so concurrent callers never race a * read-modify-write on the same key. Queued writes are also fenced * across reconfigurations: a write enqueued before a configure() call * refuses to execute afterwards, so it can never land on — and reorder * against new-epoch writes on — the replacement backend. Writes are * fire-and-forget with logged (never thrown) failures — persistence * loss degrades to today's process-local behavior. * * Alias counters persist the last-seen counter per * `:`; a post-restart board seeds its counters * from these high-water marks so a new alias never collides with a * historical one. The alias→taskID mapping itself is NOT restored: old * aliases resolve as not-found after a restart, which is the intended * improvement over silently reusing them for unrelated tasks. */ /** Subset of the v2 `StorageDomain` this module consumes (see * `V2Context['storage']` in src/v2/types.ts). */ export interface BackgroundJobStorageBackend { get(key: string): Promise | unknown; set(key: string, value: unknown): Promise | unknown; remove(key: string): Promise | unknown; scan(options: { prefix: string; after?: string; limit?: number; }): Promise<{ entries: Array<{ key: string; value: unknown; }>; next?: string; }>; } /** Persisted tombstone entries self-cap at this many most-recent items. */ export declare const MAX_PERSISTED_TOMBSTONES = 500; export interface PersistedTombstoneEntry { taskID: string; epoch: number; recordedAt: number; } export interface PersistedBackgroundJobState { /** taskID → persisted tombstone entry (currently suppressed runs). */ tombstones: Map; /** taskID → deletion epoch (survives clear-on-relaunch). */ deletionEpochs: Map; /** Highest deletion epoch seen; keeps future epochs monotonic. */ nextEpoch: number; /** `:` → last-seen alias counter. */ aliasHighWaterMarks: Map; } /** * Configure the persistence sink. `undefined` (or an absent call — the v1 * default) selects the pure memory fallback: writes become no-ops and * nothing is ever seeded. Re-configuring resets all module state; callers * simulating a restart re-configure and then `await * loadInitialBackgroundJobPersistence()`. */ export declare function configureBackgroundJobPersistence(storage: BackgroundJobStorageBackend | undefined): void; /** * Scan the storage backend (following the paginated `next` cursor) into * the module's seed state. No backend → returns empty state and seeds * nothing. The returned snapshot is the same object later boards/ledgers * seed from. */ export declare function loadInitialBackgroundJobPersistence(): Promise; /** Seed snapshot for fresh boards/ledgers (backend-loaded state only). */ export declare function persistedBackgroundJobState(): PersistedBackgroundJobState; /** * Record a suppression tombstone (write-through from * `recordBackgroundJobSuppression`). The epoch comes from the ledger so * in-memory and persisted epochs stay identical. */ export declare function recordSuppression(taskID: string, epoch?: number): void; /** * Clear the suppression tombstone (write-through from * `clearBackgroundJobSuppression`). The deletion EPOCH is deliberately * kept — a relaunch must not be ghost-skipped after a restart, but its * generation fencing must survive. */ export declare function clearSuppression(taskID: string): void; /** Alias counter high-water mark: the max of the backend-restored * snapshot and every value persisted in this process. Seeding from the * live max too means two concurrently-live boards sharing one * `` never collide even before a restart replays the * backend. */ export declare function aliasHighWaterMark(parentSessionID: string, prefix: string): number; /** * Persist the last-seen alias counter. Monotonic: a stale writer can * never regress the high-water mark. Serialized per key so concurrent * launches never race the same entry. */ export declare function bumpAliasHighWaterMark(parentSessionID: string, prefix: string, counter: number): void;