/** * Cross-Session Task Registry, housekeeping (content validation + reaping). * * Module-private companion to registry.ts: the pure, I/O-free half of the * persisted task graph's store obligations, parse-and-validate-by-content, * the bound constants (each a count cap AND an age TTL), and the idempotent * reap over a snapshot. Kept out of registry.ts so neither file carries two * jobs at once. Nothing here is re-exported from the package's public surface. */ import type { SessionTaskGraphSnapshot } from './types.js'; /** Current schema version for the persisted graph file. */ export declare const GRAPH_SCHEMA_VERSION = 1; /** Periodic sweep interval (1 hour). Reaping must not be startup-only: a daemon-hosted registry can stay up for weeks. */ export declare const SWEEP_INTERVAL_MS: number; /** * How long an unreadable graph file preserved aside is kept before deletion. * * 14 days: long enough that a crash noticed on Monday can still be * investigated the following week, short enough that a repeatedly-crashing * host does not keep forensic litter forever. Only ever one such file exists * (the quarantine name is fixed, so a later rename replaces it), so this is a * count cap of one plus an age TTL. */ export declare const QUARANTINE_RETENTION_MS: number; /** Suffix for a graph file preserved aside because it could not be trusted. */ export declare const QUARANTINE_SUFFIX = ".unrecognized"; /** * What a single reap pass reclaimed. Every field is a count, the registry * never logs graph contents, only how much of it was removed and why. */ export interface CrossSessionGraphReapSummary { /** Refs dropped because their owning session no longer exists. */ readonly refsMissingSession: number; /** Refs dropped because they exceeded {@link REF_MAX_AGE_MS} since their last update. */ readonly refsExpired: number; /** * Refs from the pre-binding `'local'` namespace dropped by age. Counted apart * from {@link refsExpired} so the one-way drain-down of the legacy store is * visible in its own right, it is a migration finishing, not routine expiry. */ readonly refsLegacyNamespaceExpired: number; /** Refs dropped because the graph exceeded {@link MAX_PERSISTED_REFS}. */ readonly refsOverCap: number; /** Records dropped during parsing because their persisted shape did not validate. */ readonly refsMalformed: number; /** Edges dropped because one or both endpoints were reaped. */ readonly edgesDangling: number; /** Handoffs dropped because their session or task ref no longer exists. */ readonly handoffsOrphaned: number; /** Handoffs retired after firing (acknowledged) or after ageing out unacknowledged. */ readonly handoffsRetired: number; /** Handoffs dropped because the graph exceeded {@link MAX_PERSISTED_HANDOFFS}. */ readonly handoffsOverCap: number; /** Handoff records dropped during parsing because their persisted shape did not validate. */ readonly handoffsMalformed: number; /** Sum of every count above. Zero means the pass reclaimed nothing. */ readonly total: number; } export declare const EMPTY_REAP_SUMMARY: CrossSessionGraphReapSummary; /** The verdict of reading the persisted graph file. */ type GraphFileVerdict = { readonly kind: 'ok'; readonly snapshot: SessionTaskGraphSnapshot; readonly refsMalformed: number; readonly handoffsMalformed: number; } | { readonly kind: 'corrupt'; readonly detail: string; } | { readonly kind: 'future'; readonly version: number; }; /** * Parse and content-validate the persisted graph file. * * VERSION POLICY, tolerate backward, fail closed forward. * * The previous behaviour was a strict `version !== GRAPH_SCHEMA_VERSION` * equality check, which means the first schema bump silently discards every * graph already on disk. A strict envelope-version mismatch that rejects * everything has already caused a real incident in this codebase this cycle * (a catalog cache envelope version mismatch rejected every fixture), and the * failure is invisible: the user just finds their cross-session graph empty. * * So: any version at or below the current one is READ, because every field the * registry actually uses is validated record by record right here, an older * envelope that no longer matches simply loses the records that fail * validation, and those losses are counted and disclosed. A version ABOVE the * current one is rejected (a newer runtime may have written fields whose * meaning we would misinterpret) and the file is preserved aside rather than * overwritten, so the newer runtime's data survives. */ export declare function parseGraphFile(text: string): GraphFileVerdict; interface GraphReapOptions { /** "Does this session still exist?", absent means owner-existence reaping is skipped (age/count bounds still apply). */ readonly sessionExists?: ((sessionId: string) => boolean) | undefined; readonly now: number; } /** * Pure reap over a snapshot. Idempotent by construction: every survivor * satisfies every predicate, so a second pass over the returned snapshot * reclaims nothing. */ export declare function reapGraphSnapshot(snapshot: SessionTaskGraphSnapshot, options: GraphReapOptions): { snapshot: SessionTaskGraphSnapshot; summary: CrossSessionGraphReapSummary; }; export {}; //# sourceMappingURL=registry-housekeeping.d.ts.map