/** * Validate an engine-minted id (sessionId / tool-result ref) before using it as a path component. These are * `uuidv7` / `tr__` — already filename-safe — but a strict guard is cheap * defense-in-depth: reject `.`/`..`/empty/separators so nothing can traverse out of the store dir. */ export declare function sanitizePathComponent(raw: string): string; /** * Map a model-influenced memory `scope` to a safe directory name (CC `sanitizePath` parity). Replace every * non-alphanumeric run with `-`; if the result is over 200 chars, append a short content hash so two long * scopes can't collide. The ORIGINAL scope is the partition key the engine passes — this only affects the * on-disk dir name, never the stored data. Never `path.join` a raw scope. */ export declare function sanitizeScope(scope: string): string; /** * Resolve the data root (CC `getClaudeConfigHomeDir` analog): `$AGENT_DATA_DIR ?? ~/.ai-agent`, * NFC-normalized, and `realpath`-canonicalized once the dir exists. Creates the dir (0o700) if absent. */ export declare function resolveDataRoot(explicit?: string): string; /** Ensure a directory exists with 0o700 perms (idempotent). */ /** * Atomically CREATE `target` carrying `content` — it appears in ONE step already fully written, no empty/partial * window. Fixes the create-then-write race (codex review BUG-1/BUG-3): a reader/pruner can see a half-written * file, and a crash mid-write leaves a zero-byte target a retry would silently keep. Writes a temp in `target`'s * OWN dir (same FS → `link` is atomic) + fsync, then `linkSync`-publishes it; throws `EEXIST` if `target` exists. */ export declare function writeThenLink(target: string, content: string | Uint8Array): void; export declare function ensureDir(dir: string): void; /** * The 5-step atomic whole-file replace (§2.1). Skipping any of steps 3/5/6 is the classic corruption bug: * 1. open a temp under `root/tmp/` (same FS → rename is atomic), * 2. write the bytes, * 3. fsync the DATA (else ext4 "zero-length file after crash"), * 4. close → rename(tmp, target) (atomic intra-FS namespace swap), * 5. fsync the containing DIRECTORY (persists the rename itself). * On any failure the temp is unlinked so a crashed write leaves no scratch behind. */ export declare function atomicWriteFile(tmpDir: string, target: string, bytes: string): void; /** * Read a JSONL file and return every COMPLETE, parseable record — the universal torn-tail recovery (§2.2): * - a trailing line with no terminating `\n` (a crash mid-write) is discarded, * - any line (interior or tail) that fails `JSON.parse` is skipped (never throws — `listRemoteAgentMetadata` * parity), so a single corrupt interior line never poisons the whole replay. * A missing file → `[]`. This is why JSONL beats one big JSON blob: a torn tail can never corrupt the store. */ export declare function readJsonlRecords(path: string): T[]; /** * An append-only JSONL log opened once with `O_APPEND` and held for the file's lifetime. One record = * one `JSON.stringify(...) + "\n"` written in a SINGLE `writeSync` call (never split across writes → the * only torn line possible is a crash tail, which {@link readJsonlRecords} drops). `fsync` is opt-in per * append (the checkpoint commit point ALWAYS fsyncs; session/memory cadence is the caller's `fsyncEvery`). */ export declare class AppendLog { private fd; constructor(path: string); /** Append one record (single write of `json + "\n"`). `fsync:true` makes the record durable before return. */ append(record: unknown, fsync: boolean): void; close(): void; } /** * The SINGLE coarse boot guard (§2.4): an `O_EXCL` PID file at `root/LOCK` that forbids two processes * sharing a data dir. A second instance fails fast ("another instance owns this data dir"). A STALE lock * (the writing PID is dead) is pruned and re-acquired — `proper-lockfile`/CC `concurrentSessions` parity. * * This is the ONLY legitimate file lock in the backend: the once-only CAS is in-process (one event loop + * a per-token async mutex), so there is NO per-operation flock — this fence just guarantees the * in-process model's premise (a single writer to the dir) holds. Cross-process CORRECT concurrency is the * Pg/TiDB backend's job, by design. */ export declare class BootLock { private readonly lockPath; private held; constructor(lockPath: string); /** Acquire the lock or throw. Prunes a stale lock whose recorded PID is not running. */ acquire(): void; private writeLock; private readLockPid; /** Release the lock (only if we hold it). Best-effort; never throws. */ release(): void; } /** * design/84 Seam B (TOC profile) — a per-scope CONSOLIDATION lock factory for the file backend's * {@link import("../../core/consolidate-scope.js").ConsolidateScopeDeps.acquire} injection point. A single * machine may run a periodic-consolidation timer AND a task-end inline pass concurrently; this advisory lock * stops two processes consolidating the SAME scope at once (the in-process model the file backend assumes — * see {@link BootLock} — does not cover a second OS process with its own timer). * * `acquire(scope)` writes `lockDir/.consolidate.lock` carrying THIS pid (atomic * {@link writeThenLink}, so the lock appears with its pid already in it — no empty-window prune race). It * returns a `release` callback on success, or `undefined` when a LIVE owner already holds it (the caller * treats `undefined` as "busy → skip this pass", a no-op). A STALE lock (recorded pid is dead) is pruned and * re-acquired once. The implementation lives HERE (the deployment shell's persist/exec axis), NOT core — core * only DEFINES the injection point (the constitutional split: gate-presence in core, mechanism in the profile). */ export declare function createFileConsolidationLock(lockDir: string): (scope: string) => (() => void) | undefined; //# sourceMappingURL=fs-atomic.d.ts.map