/** * Session-end SQLite snapshot — requested after the session is persisted, * taken in a detached child process. * * ADR-013 / T5158: `.cleo/cleo.db` is not tracked in git, so ending a session * writes a point-in-time snapshot to `.cleo/backups/sqlite/`. Both session-end * paths call {@link requestSessionEndSnapshot} as their LAST step, after the * session row is written as ended: * * - the CLI / dispatch path (`cleo session end`, the Claude Code SessionEnd hook, * orchestrate handoff, safestop, GC): `session/engine-ops.ts` `sessionEnd`; * - the SDK path: `sessions/index.ts` `endSession`. * * The request spawns a detached, unref'd worker and returns — the command that * ended the session never waits for a VACUUM. At most one worker is QUEUED per * project: a per-project marker ({@link SESSION_END_WORKER_MARKER}) exists from * spawn until the worker holds the gate lock. A request that finds a live * marker spawns nothing ("coalesced", logged): the queued worker drops the * marker only once it holds the lock, and then snapshots UNCONDITIONALLY, so * its snapshot contains every write made before any request that coalesced * onto it. A generation claimed by some other snapshot while the worker waited * proves nothing about those requests, so the worker never counts itself * covered (T12508 round 5). A burst therefore keeps at most two workers alive * (one running, one queued) and produces at most two snapshots. * * After the snapshot the same run takes the hourly global backup and the * daily audit retention prune (`autoPruneAuditLog`, T13828). * * The worker appends its outcome as one JSON line to * `.cleo/logs/session-end-snapshot.log`; coalesced requests are logged there * too. * * `CLEO_SESSION_END_SNAPSHOT=inline` runs the snapshot in-process instead * (bounded lock wait, generation coverage), as does a failure to spawn the * worker. Under vitest the default is `inline`, so unit tests do not leave * detached processes behind. * * @task T12508 */ import { type SnapshotGateResult } from '../store/snapshot-gate.js'; /** Basename of the per-project "a worker is queued" marker in the backup dir. */ export declare const SESSION_END_WORKER_MARKER = ".session-end-worker.pending"; /** * A marker older than this is stale whatever its holder says. The worker * drops the marker as soon as it holds the gate lock, and it waits at most * about 90 s for that lock, so a live marker is always younger. */ export declare const SESSION_END_MARKER_STALE_MS: number; /** How a session-end snapshot was requested. */ export interface SessionEndSnapshotRequest { /** * `detached`: a worker was spawned to take the snapshot. `coalesced`: a * worker was already queued (it has not taken the lock yet), so its * snapshot will contain this session's writes — no new worker. `inline`: * this process took the snapshot. */ readonly mode: 'detached' | 'coalesced' | 'inline'; /** Snapshot generation observed when the request was made (`null` = unknown). */ readonly seenGeneration: number | null; /** PID of the detached child. */ readonly pid?: number; /** Gate outcome, for an inline snapshot. */ readonly result?: SnapshotGateResult | null; } /** Options for {@link snapshotAfterSessionEnd}. */ export interface SessionEndSnapshotOptions { /** Generation observed when the request was made; defaults to "now". */ readonly seenGeneration?: number; /** Never treat a prefix as covered (the worker; see the module comment). */ readonly alwaysSnapshot?: boolean; /** Lock retries; defaults to {@link SESSION_END_LOCK_WAIT_RETRIES} (about 6.5 s). */ readonly lockWaitRetries?: number; /** Called under the gate lock before the generation is claimed. */ readonly onLockAcquired?: () => void; } /** * Take the session-end snapshot in THIS process through the gate's * `required` mode. Used by the detached worker and by the inline fallback. * * Never throws. A result other than a written or covered snapshot is logged * at warn level with its cause. * * @param projectRoot - Project root of the ended session. * @param opts - Request generation and lock wait. * @returns The gate outcome, or `null` when no snapshot could be attempted. * @task T12508 */ export declare function snapshotAfterSessionEnd(projectRoot: string, opts?: SessionEndSnapshotOptions): Promise; /** Absolute path of the worker-pending marker for a project. */ export declare function sessionEndWorkerMarkerPath(projectRoot: string): string; /** * Remove the worker-pending marker if it still carries `token` — i.e. it was * created for this worker and not replaced after being judged stale — and the * worker's sidecar. Called by the worker under the gate lock, before it * snapshots, and again when it exits. Never throws. * * @param projectRoot - Project whose marker to release. * @param token - Token the worker was spawned with. * @task T12508 */ export declare function releaseSessionEndWorkerMarker(projectRoot: string, token: string): void; /** * Request the session-end snapshot. Call it as the LAST step of ending a * session, after the session row is persisted as ended. * * Unless a worker is already queued for this project, spawns a detached, * unref'd worker that takes the snapshot, and returns immediately. A request * that finds a live marker spawns nothing and is logged as coalesced (see the * module comment for why that is safe). Falls back to an inline snapshot * (bounded lock wait) when the worker cannot be spawned. Never throws. * * @param projectRoot - Project root of the ended session. * @returns How the snapshot was requested. * @task T12508 */ export declare function requestSessionEndSnapshot(projectRoot: string): Promise; //# sourceMappingURL=session-end-snapshot.d.ts.map