/** * Reading a ledger back — integrity, instruction versions, and the ADR-0020 approval tally. * * Split from `ledger.ts` when the file-size guard refused it at 419 lines. The seam is real rather than * arbitrary: `ledger.ts` **writes** one record at a time and fails closed when it cannot, while everything * here **reads** a whole file and must never fail at all — a report is a diagnostic, and a diagnostic that * throws on damaged input is useless on exactly the input it exists for. Every defect this half has had * (R-63's bias, R-64's three malformed shapes) was a *reporting* defect, and none of them could have * touched the write path. * * Re-exported from `./ledger` so the published subpath is unchanged. */ import type { ApprovalSource } from "../kernel/approval.ts"; export interface LedgerReport { /** False when the file is absent — a configuration state, not damage. */ exists: boolean; /** Capability-decision records (legacy, v2, or v3). */ records: number; /** Every parsed ledger event, including lifecycle and lease events. */ events: number; workspaceLeases: { acquired: number; uncontended: number; refused: number; released: number; releasedUnrecorded: number; lost: number; retained: number; timeout: number; recovered: number; }; lifecycle: { starting: number; running: number; completed: number; failed: number; }; /** Events of a kind an earlier version wrote and this one no longer builds; valid history, not corruption. */ retired: number; /** Lines that did not, with 1-based line numbers so the report is actionable. */ corrupt: Array<{ line: number; reason: string; }>; /** Records where an agent asked for more than it held — ADR-0008's designated signal. */ escalationAttempts: number; /** * How many records ran under each executor, plus how many name none — ADR-0031. * * **Added because the field was written and never read, which is R-51's shape exactly.** R-51 was * `definitionDigest`: recorded from the start, absent from every report, so the questions ADR-0018 advertised * needed hand-written `jq`. `executor` arrived the same way — `src/governance/ledger.ts` justifies making it *required* * with "reading it back is the only reason it exists", and nothing read it back. The README claims the * executor is "announced three times… per child in the ledger"; without this the third announcement was to * `jq` only. * * `unknown` counts pre-0.16 lines, which have no such field. Reported rather than folded into `process`, * because "written before the executor was recorded" and "ran as a subprocess" are different facts. */ executors: { herdr: number; process: number; unknown: number; }; /** * Every distinct set of instructions this ledger saw run, with how many spawns used it (R-51). * * ADR-0018 advertises that a record answers *"did these four children run the same instructions?"* and * *"has this definition changed since?"* — and until this existed **nothing read `definitionDigest` at * all**, so both questions required hand-written `jq` and the second was not even reproducible with * `sha256sum`, because the digest covers the body and not the frontmatter. A field no tool reads is a * field that quietly becomes decoration. * * Grouped by `name` + `sha256`, so two entries with one name are exactly the evidence that a definition * changed mid-ledger. Sorted by name then digest so two runs of the same fan-out produce a diffable * report, like the ids themselves. */ definitions: Array<{ name: string; source: string; sha256: string; spawns: number; }>; /** * Where the yes came from, per approved capability, tallied across the whole ledger. * * **This is the measurement ADR-0020 asks for.** That ADR keeps the persistence layer on R-25's fatigue * argument with *no number behind it*, and named the evidence that would settle it: counting `persisted` * against `prompt` over a few weeks of real use. It also said this "needs no new machinery" — true of the * data and false of the answer, which required hand-written `jq`. Same shape as R-51: a field no tool * reads becomes decoration, and a measurement nobody can run does not get run. * * **`bySource` counts RECORDS and is an upper bound, not an answer.** Deleting the persistence layer does * not turn every `persisted` record back into a prompt: precedence is `inherited → session → persisted → * prompt`, and `session` approvals live in memory and do not depend on the store at all. So a session that * spawns `deploy` twenty times under one persisted entry writes twenty `persisted` records, while without * the store it would raise **one** prompt and satisfy the other nineteen from the session cache. Reporting * twenty prompts avoided would overstate the layer's value twentyfold, on the one number that decides * whether to keep it — the same direction of bias `unattributed` exists to avoid, arrived at a different way. * * `distinctBySource` is the closer estimate: distinct `capability@subject` pairs, which bounds the cost of * deletion at one prompt per pair per session. The ledger carries no session id, so the exact figure is not * computable from it; both numbers are printed and labelled rather than one being presented as the truth. * * Counted from `approvalSources` **only**. `approvalSource` is deliberately not used as a fallback: before * 0.11.1 that scalar was written for the whole set even when the sources differed (R-46), so folding it in * would report humans as having been asked about capabilities they were never asked about — biasing the * one direction this measurement must not be biased in. Those records are counted as `unattributed` * instead, so the sample size is visible rather than silently smaller. */ approvals: { /** Raw record counts. An UPPER bound on prompts avoided — see above before quoting one. */ bySource: Record; /** Distinct `capability@subject` pairs per source. The closer estimate. */ distinctBySource: Record; /** Records carrying approvals from before per-capability sources existed. Not attributable; see above. */ unattributed: number; /** Records where a human was asked and said no — the fatigue argument's other half. */ humanDenied: number; /** * Distinct `capability@subject` pairs a human declined. * * Same reason `distinctBySource` exists: R-29 shares a decline across every concurrent caller, so one * click of *Deny* under an eight-wide fan-out writes eight `humanDenied` records. Reporting the raw * count as "times a human declined" is the per-record bias R-63 removed from `persisted`, left in place * on the number that argues hardest FOR the layer — which is the direction that flatters this package's * own gating and therefore the one to be most careful with. */ humanDeniedPairs: number; }; ok: boolean; } export declare function verifyLedger(path: string): Promise; //# sourceMappingURL=ledger-report.d.ts.map