/** * The machine-local guide baseline — "this machine last agreed with the server * at version N for this file" (`guide-versioned-identity-plan.md` → slice 2b-B, * D10 in `context-bundle-authoring-plan.md`). * * **The one question neither the server nor the content can answer.** Slice * 2b-A's guard is server-only: it sees that a body item's newest version came * from a browser and refuses to let disk bytes overwrite it. What it cannot see * is whether *this machine has already seen that edit* — pulled it, wrote it to * disk, and edited on top of it. So a guide the dashboard edited and the user * then re-synced from disk forks on every subsequent import, forever. The * baseline is the missing fact: an import carrying `expectedVersion` equal to * the version the server currently holds proves the local file descends from * it, and the update is safe whoever wrote that version. * * **Absence and staleness degrade in opposite directions, both away from an * overwrite.** No baseline — a fresh machine, a deleted `~/.auden/state/`, a * file never imported from here — sends no `expectedVersion`, and the server * falls back to exactly the guard it applies today: decline and fork. A * *stale* baseline is stronger evidence than none, because it says the remote * moved since this machine last agreed, so the server refuses the write and * reports a conflict the user resolves explicitly. Nothing here can produce a * silent overwrite, which is the property that makes the store safe to lose. * * **Why not in the repo.** A global guide (`~/.claude/CLAUDE.md`) belongs to no * checkout, so there is no repo to put its baseline in — and slice 4 of * `guide-write-side-identity` needs exactly that machine-level record. The * `.auden/docs.json` sidecar keeps its own baselines for bundle-managed docs and * is not touched here: retiring it is D10's separate transition, and this store * covers the import path the sidecar never did. * * Kept free of citty/console so it unit-tests directly against a temp path. */ import * as v from 'valibot'; import type { GuideDiscoveryRoot, GuideImportResult } from '@auden.to/protocol'; /** * How many baselines the store retains, evicted least-recently-written through * the explicit `order` list — deterministic and testable without a clock, the * same shape `usage-cache.ts` uses. * * A bound is needed because nothing prunes by age: entries are keyed by scope * and path, and a machine accumulates checkouts indefinitely. Evicting one costs * that file its precondition on the next import, so it declines-and-forks like a * fresh machine — never a wrong write. Set well above any plausible per-machine * guide count so eviction is a backstop rather than routine. */ export declare const MAX_GUIDE_BASELINES = 4096; declare const BaselineFileSchema: v.ObjectSchema<{ readonly version: v.NumberSchema; /** Most-recently-written first. Keys absent from `entries` are ignored. */ readonly order: v.ArraySchema, undefined>; readonly entries: v.RecordSchema, v.ObjectSchema<{ /** * The body-item version this machine last agreed with the server about. * Recorded only from an outcome where disk and server agree — see * `GuideImportOutcomeSchema`. */ readonly version: v.SchemaWithPipe, v.IntegerAction, v.MinValueAction]>; }, undefined>, undefined>; }, undefined>; export type GuideBaselineStore = v.InferOutput; export declare const emptyGuideBaselines: () => GuideBaselineStore; /** * Which identity a baseline belongs to, mirroring the server's resolution * scoping (`repoComponentFor`, `apps/dashboard/app/api/rules/import+api.ts`): * a global file belongs to no checkout and to no project, so it is keyed by the * machine alone; a project file is keyed by the identity the import actually * sends. * * The project id is preferred when the repo declares one, because that is what * the server prefers too and it survives a re-clone; the machine-local digest is * the fallback for a repo that has never run `auden init`. A repo that later * declares a project id changes scope once and loses its old baselines — one * degraded import per file, which is the same cost as a fresh machine and is why * this is a cache rather than a record. */ export declare function guideBaselineScope(identity: { projectId?: string | undefined; repoId?: string | undefined; }): string | undefined; /** The scope a `root: 'global'` payload is keyed under, on every machine. */ export declare const GLOBAL_BASELINE_SCOPE = "global"; /** * `(scope, root, path)` as one storage key. NUL separates unambiguously: `root` * is a picklist and `path` is parsed by `SafeRelativePosixPathSchema`, which * rejects control characters, so no two distinct triples collide. Written as an * escape rather than a literal for the reason the server's `projectionKey` is — * an invisible byte in source is one bad merge away from becoming a space. */ export declare function guideBaselineKey(scope: string, root: GuideDiscoveryRoot, path: string): string; /** * The scope a payload's result belongs to, decided the same way the server * decides its repo component: `root: 'global'` is machine-scoped whatever * identity the request carried. * * Applied here rather than at each call site so the client cannot key a global * file per checkout while the server keys it per user — which would make every * global baseline miss, silently, and leave 2b-A's fork in place for exactly the * population that most needs the precondition. */ export declare function scopeForRoot(root: GuideDiscoveryRoot, identity: { projectId?: string | undefined; repoId?: string | undefined; }): string | undefined; /** * 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 baseline sends no * `expectedVersion`: the server then applies its own guard and declines rather * than overwriting. A reader that guessed a version instead would invent the * precondition the whole design rests on. */ export declare function readGuideBaselines(path: string): Promise; /** * Persist the store, trimmed to `MAX_GUIDE_BASELINES` by the `order` list. * * Never throws. An unwritable state file must not fail an import that already * succeeded on the server — the cost is that the next import for those files has * no precondition and falls back to the server-only guard. */ export declare function writeGuideBaselines(path: string, store: GuideBaselineStore): Promise; /** * Fold one request's per-file outcomes into the store. * * **Only outcomes where disk and server agree record a baseline.** A `conflict` * reports the server's current version so the CLI can offer explicit * resolution, but recording it would hand the *next* import a matching * `expectedVersion` — turning a refused overwrite into an automatic one on the * following run. That would be worse than having no baseline at all, since the * refusal would look like it worked. * * Pure, so the ordering and eviction rules are testable without a filesystem. */ export declare function applyGuideImportResults(store: GuideBaselineStore, results: readonly GuideImportResult[], identity: { projectId?: string | undefined; repoId?: string | undefined; }): GuideBaselineStore; /** * The `expectedVersion` to send for a file, or `undefined` when this machine has * no baseline for it. Undefined is the honest answer and the safe one — see the * module comment. */ export declare function guideBaselineVersion(store: GuideBaselineStore, root: GuideDiscoveryRoot, path: string, identity: { projectId?: string | undefined; repoId?: string | undefined; }): number | undefined; export {}; //# sourceMappingURL=guide-baseline.d.ts.map