/** * The machine cache — **one** store for every machine-local fact the CLI keeps * about a file it wrote, keyed by that file's absolute path on this machine * (`machine-state-consolidation-plan.md`). * * One rule replaces a division of labour nobody could state without a paragraph: * * > Portable facts are committed and repo-relative. Machine facts are * > machine-local and absolute. * * The committed placement file (`docs/sidecar.ts:13-14`) stays the only durable * record — which item lives where *in this repo*, repo-relative, union-merged, * shared with teammates. Everything this file holds is what *this machine* last * observed, and losing all of it costs recomputation and a degraded import, never * a wrong write. * * **Why the absolute path is the key.** It is the only identifier available at * every moment one is needed: import discovers a file by scanning and has no item * id until the server answers (`rules/init-import.ts`), pull has an item id but, * for a `global` item, no repo to be relative to, and sync has both. It also has * no root to get wrong — the defect that blocked `guide-write-side-identity` * slice 4 existed only because a path was stored relative to one root and read * back against another — and two checkouts of one repo are two absolute paths, so * they cannot collide the way `guideBaselineScope`'s `project:` key * let them (`rules/guide-baseline.ts:101-107`). * * **Non-portability is the point.** This key never leaves the machine, and a * machine cache has no business being portable — the same reasoning that makes * the repo digest machine-local (`repo-id.ts:1-2`). * * **Isolation is key-derived here, not positional — so nothing enumerates this.** * The per-repo store this replaces was scoped by where it lived: `readSidecar(root)` * could only ever see `root`'s own `.auden/`. A machine-wide store has no such * guarantee, and what replaces it is that every lookup is for an absolute path * already inside the repo being synced, while the fused view stays driven by * enumerating *placement* (`readSidecar` in `docs/sidecar.ts` iterates the * placement map and looks this store up per entry). That is why this module exposes * `lookup` and `pathForItem` and no "all entries" accessor: a future call site that * listed the cache to answer "what does this machine manage" would fold other * checkouts' entries into a repo-scoped answer, and no single-repo test fixture * would catch it (CLAUDE.md → Simplicity: put the rule where a call site cannot * forget it). * * Kept free of citty/console so it unit-tests directly against a temp path. */ import * as v from 'valibot'; /** * How many entries the store retains, evicted least-recently-written through the * explicit `order` list — deterministic and testable without a clock. * * A bound is needed because nothing prunes by age: a machine accumulates * checkouts indefinitely. Evicting an entry costs that file its precondition on * the next pass, so it degrades exactly like a fresh machine — never a wrong * write. This covers docs as well as guides now that there is one store, so it is * set above the sum of both populations rather than at either one's old bound. */ export declare const MAX_MACHINE_CACHE_ENTRIES = 8192; declare const MachineCacheEntrySchema: v.ObjectSchema<{ /** * sha256 of the bytes this machine last wrote to (or read from) that path. * Absent means "cannot prove we wrote this", which must never read as a match * (`docs/sidecar.ts:24`). */ readonly hash: v.OptionalSchema, v.MinLengthAction]>, undefined>; /** * `ContextItemSchema.version` at the last successful sync, or the body-item * version this machine last agreed with the server about. Absent means there is * no precondition to send, which the server answers with its own conservative * guard rather than an overwrite. */ readonly itemVersion: v.OptionalSchema, v.IntegerAction, v.MinValueAction]>, undefined>; /** * The canonical item this file materializes — written **only** for a file no * committed placement can name, which today means a `global` one under the * user's home directory. A repo file's item id lives in the placement record, * which is the durable answer; duplicating it here would create a second shape * of one fact, and would make `pathForItem` multi-valued the moment one item is * materialized in two checkouts. */ readonly itemId: v.OptionalSchema, v.MinLengthAction]>, undefined>; }, undefined>; export type MachineCacheEntry = v.InferOutput; declare const MachineCacheFileSchema: v.ObjectSchema<{ readonly version: v.NumberSchema; /** Absolute keys, most-recently-written first. Keys absent from `entries` are ignored. */ readonly order: v.ArraySchema, undefined>; readonly entries: v.RecordSchema, v.ObjectSchema<{ /** * sha256 of the bytes this machine last wrote to (or read from) that path. * Absent means "cannot prove we wrote this", which must never read as a match * (`docs/sidecar.ts:24`). */ readonly hash: v.OptionalSchema, v.MinLengthAction]>, undefined>; /** * `ContextItemSchema.version` at the last successful sync, or the body-item * version this machine last agreed with the server about. Absent means there is * no precondition to send, which the server answers with its own conservative * guard rather than an overwrite. */ readonly itemVersion: v.OptionalSchema, v.IntegerAction, v.MinValueAction]>, undefined>; /** * The canonical item this file materializes — written **only** for a file no * committed placement can name, which today means a `global` one under the * user's home directory. A repo file's item id lives in the placement record, * which is the durable answer; duplicating it here would create a second shape * of one fact, and would make `pathForItem` multi-valued the moment one item is * materialized in two checkouts. */ readonly itemId: v.OptionalSchema, v.MinLengthAction]>, undefined>; }, undefined>, undefined>; }, undefined>; export type MachineCache = v.InferOutput; /** * The filesystem the cache touches, injectable for the same reason the sidecar's * is: this store is machine-wide, so a suite left on the real `fs` would read and * write the developer's own `~/.auden/state/` and let one test's fixture license * another's overwrite. */ export type MachineCacheDeps = { readFileFn?: (path: string, encoding: 'utf8') => Promise; writeFileFn?: (path: string, data: string) => Promise; mkdirFn?: (path: string, options: { recursive: true; }) => Promise; }; export declare const emptyMachineCache: () => MachineCache; /** * The storage key for a file on this machine. * * `resolve` normalizes separators and any `.`/`..` segments so the same file * reached two ways is one entry. Case is **not** folded: a case-insensitive * filesystem would want it folded and a case-sensitive one must not, and getting * that wrong on Linux would silently merge two genuinely different files. A * mis-keyed entry costs a lookup miss, which degrades to "no precondition" — the * safe direction — so the conservative choice is the correct one here. */ export declare function machineCacheKey(absolutePath: string): string; /** * What this machine last recorded for a file, or `undefined` when it has nothing. * * Undefined is the honest answer and the safe one: every caller treats it as "no * precondition, no proof we wrote this", which is what a fresh machine sees. */ export declare function lookup(store: MachineCache, absolutePath: string): MachineCacheEntry | undefined; /** * Where this machine put an item that no committed placement can name. * * The reverse lookup exists for exactly one population — `global` items, whose * home path is machine-specific and so is deliberately never committed * (`docs/sidecar.ts:107-112`). Repo items resolve through the placement file * instead, and because only the no-placement population writes `itemId`, this * stays single-valued. * * Returns the most recently written match, so that even if a caller ever did * write two, the answer is the live one rather than an arbitrary one. */ export declare function pathForItem(store: MachineCache, itemId: string): string | undefined; /** * Record what this machine just wrote at a path. Pure, so ordering and eviction * test without a filesystem. * * The entry replaces rather than merges: a caller that knows the hash but not the * version would otherwise resurrect a stale version alongside a fresh hash, and * the pair is only meaningful read together. */ export declare function record(store: MachineCache, absolutePath: string, entry: MachineCacheEntry): MachineCache; /** * Drop what this machine recorded for a path — used when the file it described is * pruned, so a later file at the same path is not credited with this one's hash. */ export declare function forget(store: MachineCache, absolutePath: string): MachineCache; /** * Read the store, degrading to empty on anything unexpected — missing file, * malformed JSON, a failing shape, or an unrecognised version. * * Degrading to empty is safe *because* an absent entry sends no precondition and * proves no write: the server applies its own guard and declines rather than * overwriting, and the docs phase falls through to version history. A reader that * guessed instead would invent the precondition the design rests on. */ export declare function readMachineCache(path: string, deps?: MachineCacheDeps): Promise; /** * Persist the store, trimmed to `MAX_MACHINE_CACHE_ENTRIES` by the `order` list. * * Never throws. An unwritable state file must not fail a sync or an import that * already succeeded on the server — the cost is that the next pass over those * files has no precondition and falls back to the conservative guard. */ export declare function writeMachineCache(path: string, store: MachineCache, deps?: MachineCacheDeps): Promise; export {}; //# sourceMappingURL=machine-cache.d.ts.map