approval-md 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +584 -553
- package/SPEC.md +42 -13
- package/dist/src/adapters/agentmail.d.ts +426 -0
- package/dist/src/adapters/agentmail.js +2 -2
- package/dist/src/adapters/conformance.d.ts +149 -0
- package/dist/src/adapters/contract.d.ts +628 -0
- package/dist/src/adapters/contract.js +110 -16
- package/dist/src/adapters/contract.js.map +1 -1
- package/dist/src/adapters/email.d.ts +324 -0
- package/dist/src/adapters/env-passphrase.d.ts +93 -0
- package/dist/src/adapters/public.d.ts +11 -0
- package/dist/src/adapters/public.js +11 -0
- package/dist/src/adapters/public.js.map +1 -0
- package/dist/src/adapters/registry.d.ts +59 -0
- package/dist/src/adapters/registry.js +2 -1
- package/dist/src/adapters/registry.js.map +1 -1
- package/dist/src/adapters/smtp.d.ts +213 -0
- package/dist/src/adapters/vault-provider.d.ts +114 -0
- package/dist/src/adapters/vault-provider.js +3 -3
- package/dist/src/adapters/zzz.d.ts +66 -0
- package/dist/src/adapters/zzz.js +299 -0
- package/dist/src/adapters/zzz.js.map +1 -0
- package/dist/src/channels/batch.d.ts +109 -0
- package/dist/src/channels/cli.d.ts +193 -0
- package/dist/src/channels/conformance.d.ts +92 -0
- package/dist/src/channels/contract.d.ts +623 -0
- package/dist/src/channels/payload-view.d.ts +35 -0
- package/dist/src/channels/render-queue.d.ts +149 -0
- package/dist/src/channels/tagging.d.ts +196 -0
- package/dist/src/channels/telegram.d.ts +1832 -0
- package/dist/src/channels/web.d.ts +341 -0
- package/dist/src/cli/adapter.d.ts +90 -0
- package/dist/src/cli/adapter.js +25 -15
- package/dist/src/cli/adapter.js.map +1 -1
- package/dist/src/cli/amend.d.ts +59 -0
- package/dist/src/cli/args.d.ts +43 -0
- package/dist/src/cli/attest.d.ts +41 -0
- package/dist/src/cli/audit-card.d.ts +62 -0
- package/dist/src/cli/audit.d.ts +59 -0
- package/dist/src/cli/channel-telegram.d.ts +806 -0
- package/dist/src/cli/channel-web.d.ts +131 -0
- package/dist/src/cli/channel.d.ts +71 -0
- package/dist/src/cli/checkpoint-tap.d.ts +169 -0
- package/dist/src/cli/codex.d.ts +2 -0
- package/dist/src/cli/codex.js +172 -0
- package/dist/src/cli/codex.js.map +1 -0
- package/dist/src/cli/coverage.d.ts +61 -0
- package/dist/src/cli/daemon.d.ts +120 -0
- package/dist/src/cli/doctor.d.ts +129 -0
- package/dist/src/cli/doctor.js +119 -5
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/env.d.ts +65 -0
- package/dist/src/cli/execute.d.ts +202 -0
- package/dist/src/cli/exit-codes.d.ts +73 -0
- package/dist/src/cli/feedback.d.ts +60 -0
- package/dist/src/cli/gate-window.d.ts +40 -0
- package/dist/src/cli/gate.d.ts +68 -0
- package/dist/src/cli/git-scope.d.ts +190 -0
- package/dist/src/cli/gloss-attach.d.ts +85 -0
- package/dist/src/cli/gloss-codex-child.d.ts +9 -0
- package/dist/src/cli/gloss-codex.d.ts +24 -0
- package/dist/src/cli/gloss-options.d.ts +42 -0
- package/dist/src/cli/gloss.d.ts +265 -0
- package/dist/src/cli/help.d.ts +103 -0
- package/dist/src/cli/help.js +173 -51
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-codex.d.ts +78 -0
- package/dist/src/cli/hook-codex.js +167 -0
- package/dist/src/cli/hook-codex.js.map +1 -0
- package/dist/src/cli/hook.d.ts +331 -0
- package/dist/src/cli/hook.js +186 -80
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/import.d.ts +35 -0
- package/dist/src/cli/init.d.ts +84 -0
- package/dist/src/cli/init.js +2 -2
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.d.ts +23 -0
- package/dist/src/cli/journal.d.ts +41 -0
- package/dist/src/cli/log-advance.d.ts +287 -0
- package/dist/src/cli/log-advance.js +102 -11
- package/dist/src/cli/log-advance.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +176 -0
- package/dist/src/cli/log-checkpoint.d.ts +22 -0
- package/dist/src/cli/log-sync.d.ts +243 -0
- package/dist/src/cli/log-verbs.d.ts +16 -0
- package/dist/src/cli/log-verbs.js +7 -1
- package/dist/src/cli/log-verbs.js.map +1 -1
- package/dist/src/cli/long-help.d.ts +70 -0
- package/dist/src/cli/main.d.ts +77 -0
- package/dist/src/cli/main.js +155 -5
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/mcp.d.ts +52 -0
- package/dist/src/cli/paths.d.ts +56 -0
- package/dist/src/cli/payload.d.ts +58 -0
- package/dist/src/cli/policy.d.ts +43 -0
- package/dist/src/cli/preflight.d.ts +363 -0
- package/dist/src/cli/preflight.js +294 -7
- package/dist/src/cli/preflight.js.map +1 -1
- package/dist/src/cli/progress.d.ts +78 -0
- package/dist/src/cli/prompt.d.ts +209 -0
- package/dist/src/cli/quickstart.d.ts +46 -0
- package/dist/src/cli/quickstart.js +297 -0
- package/dist/src/cli/quickstart.js.map +1 -0
- package/dist/src/cli/records.d.ts +34 -0
- package/dist/src/cli/render.d.ts +22 -0
- package/dist/src/cli/sandbox.d.ts +51 -0
- package/dist/src/cli/scaffold.d.ts +79 -0
- package/dist/src/cli/setup-adapter.d.ts +137 -0
- package/dist/src/cli/setup-adapter.js +38 -4
- package/dist/src/cli/setup-adapter.js.map +1 -1
- package/dist/src/cli/setup-channel.d.ts +117 -0
- package/dist/src/cli/setup-checkpoint.d.ts +57 -0
- package/dist/src/cli/setup-common.d.ts +275 -0
- package/dist/src/cli/setup-flow.d.ts +287 -0
- package/dist/src/cli/setup-service.d.ts +96 -0
- package/dist/src/cli/setup.d.ts +202 -0
- package/dist/src/cli/style.d.ts +320 -0
- package/dist/src/cli/token.d.ts +39 -0
- package/dist/src/cli/up.d.ts +155 -0
- package/dist/src/cli/up.js +4 -2
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/usage.d.ts +37 -0
- package/dist/src/cli/values.d.ts +40 -0
- package/dist/src/cli/vault.d.ts +59 -0
- package/dist/src/cli/vault.js +2 -2
- package/dist/src/cli/vault.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +76 -0
- package/dist/src/cli/verb-registry.js +176 -8
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +31 -0
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/codex/doctor.d.ts +13 -0
- package/dist/src/codex/doctor.js +41 -0
- package/dist/src/codex/doctor.js.map +1 -0
- package/dist/src/codex/manifest.d.ts +49 -0
- package/dist/src/codex/manifest.js +103 -0
- package/dist/src/codex/manifest.js.map +1 -0
- package/dist/src/codex/templates.d.ts +41 -0
- package/dist/src/codex/templates.js +319 -0
- package/dist/src/codex/templates.js.map +1 -0
- package/dist/src/codex/trust.d.ts +19 -0
- package/dist/src/codex/trust.js +183 -0
- package/dist/src/codex/trust.js.map +1 -0
- package/dist/src/codex/workspace-plan.d.ts +131 -0
- package/dist/src/codex/workspace-plan.js +561 -0
- package/dist/src/codex/workspace-plan.js.map +1 -0
- package/dist/src/core/actor.d.ts +2 -0
- package/dist/src/core/actor.js +5 -0
- package/dist/src/core/actor.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +170 -0
- package/dist/src/core/agents-md.d.ts +276 -0
- package/dist/src/core/apply-patch.d.ts +49 -0
- package/dist/src/core/apply-patch.js +266 -0
- package/dist/src/core/apply-patch.js.map +1 -0
- package/dist/src/core/attest.d.ts +420 -0
- package/dist/src/core/attest.js +13 -1
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +492 -0
- package/dist/src/core/budgets.d.ts +238 -0
- package/dist/src/core/checkpoint.d.ts +500 -0
- package/dist/src/core/child-env.d.ts +88 -0
- package/dist/src/core/clock.d.ts +52 -0
- package/dist/src/core/command-class.d.ts +543 -0
- package/dist/src/core/command-class.js +43 -8
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
- package/dist/src/core/coverage-sources/gh.d.ts +48 -0
- package/dist/src/core/coverage-sources/git.d.ts +101 -0
- package/dist/src/core/coverage.d.ts +217 -0
- package/dist/src/core/credential-spec.d.ts +72 -0
- package/dist/src/core/dark-session.d.ts +331 -0
- package/dist/src/core/decision-refusal.d.ts +185 -0
- package/dist/src/core/env-file.d.ts +450 -0
- package/dist/src/core/execute.d.ts +858 -0
- package/dist/src/core/execute.js +44 -6
- package/dist/src/core/execute.js.map +1 -1
- package/dist/src/core/frontmatter.d.ts +78 -0
- package/dist/src/core/gate-window.d.ts +312 -0
- package/dist/src/core/gate.d.ts +1364 -0
- package/dist/src/core/gate.js +68 -13
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/git-run.d.ts +73 -0
- package/dist/src/core/harness-version.d.ts +157 -0
- package/dist/src/core/harness-version.js +2 -1
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +55 -0
- package/dist/src/core/head-retry.d.ts +107 -0
- package/dist/src/core/instance.d.ts +253 -0
- package/dist/src/core/intake-limits.d.ts +247 -0
- package/dist/src/core/jcs.d.ts +52 -0
- package/dist/src/core/journal.d.ts +144 -0
- package/dist/src/core/live-draw.d.ts +436 -0
- package/dist/src/core/log-reconcile.d.ts +89 -0
- package/dist/src/core/log-subscribe.d.ts +36 -0
- package/dist/src/core/log-subscribe.js +162 -0
- package/dist/src/core/log-subscribe.js.map +1 -0
- package/dist/src/core/log.d.ts +278 -0
- package/dist/src/core/loop.d.ts +274 -0
- package/dist/src/core/loop.js +11 -0
- package/dist/src/core/loop.js.map +1 -1
- package/dist/src/core/md-fence.d.ts +41 -0
- package/dist/src/core/money.d.ts +147 -0
- package/dist/src/core/payload-census.d.ts +74 -0
- package/dist/src/core/payload-store.d.ts +175 -0
- package/dist/src/core/payload.d.ts +71 -0
- package/dist/src/core/policy-diff.d.ts +292 -0
- package/dist/src/core/policy-diff.js +27 -4
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-expectations.d.ts +199 -0
- package/dist/src/core/policy-explain.d.ts +150 -0
- package/dist/src/core/policy-explain.js +31 -3
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +527 -0
- package/dist/src/core/policy-load.js +15 -3
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +281 -0
- package/dist/src/core/policy-match.js +20 -9
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +265 -0
- package/dist/src/core/prompt-layout.d.ts +221 -0
- package/dist/src/core/protected-path-guard.d.ts +453 -0
- package/dist/src/core/protected-path-guard.js +514 -35
- package/dist/src/core/protected-path-guard.js.map +1 -1
- package/dist/src/core/registration.d.ts +25 -0
- package/dist/src/core/reindex.d.ts +99 -0
- package/dist/src/core/sampler.d.ts +313 -0
- package/dist/src/core/sandbox.d.ts +290 -0
- package/dist/src/core/seal.d.ts +165 -0
- package/dist/src/core/state.d.ts +505 -0
- package/dist/src/core/task-file.d.ts +185 -0
- package/dist/src/core/telegram-config.d.ts +93 -0
- package/dist/src/core/token.d.ts +409 -0
- package/dist/src/core/token.js +21 -38
- package/dist/src/core/token.js.map +1 -1
- package/dist/src/core/validate.d.ts +138 -0
- package/dist/src/core/values.d.ts +137 -0
- package/dist/src/core/vault.d.ts +291 -0
- package/dist/src/core/verified-snapshot.d.ts +204 -0
- package/dist/src/core/verify.d.ts +336 -0
- package/dist/src/core/version.d.ts +8 -0
- package/dist/src/core/wysiwys.d.ts +370 -0
- package/dist/src/daemon/advance-child.d.ts +39 -0
- package/dist/src/daemon/advance.d.ts +466 -0
- package/dist/src/daemon/audit.d.ts +87 -0
- package/dist/src/daemon/daemon.d.ts +1180 -0
- package/dist/src/daemon/dark-session.d.ts +64 -0
- package/dist/src/daemon/draw-child.d.ts +36 -0
- package/dist/src/daemon/draw.d.ts +154 -0
- package/dist/src/daemon/git-evidence.d.ts +173 -0
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +180 -0
- package/dist/src/daemon/prune.d.ts +207 -0
- package/dist/src/mcp/http.d.ts +113 -0
- package/dist/src/mcp/server.d.ts +265 -0
- package/dist/src/mcp/server.js +9 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/adapter-api.md +106 -0
- package/docs/cli-reference.md +389 -36
- package/docs/codex-enforced-session.md +30 -0
- package/package.json +12 -2
- package/schema/codex-instance.schema.json +82 -0
- package/schema/event.schema.json +2 -1
- package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
- package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
- package/schema/policy.schema.json +21 -1
- package/templates/codex/README.md +9 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verified-head snapshot (APRV-188): what a process that already walked the
|
|
3
|
+
* log publishes, so the next process does not have to walk it again.
|
|
4
|
+
*
|
|
5
|
+
* ## The cost this removes
|
|
6
|
+
*
|
|
7
|
+
* Every hook-gated tool call is a fresh process with an empty
|
|
8
|
+
* {@link VerifiedReadCache}, so it verifies the log from genesis before it may
|
|
9
|
+
* decide anything. APRV-186 and APRV-206 made that walk as cheap as a walk can
|
|
10
|
+
* be; what they could not change is that it is O(records) per gated command.
|
|
11
|
+
* Measured on this repository's shape at 10k records: 65 ms of the 371 ms a
|
|
12
|
+
* gated invocation costs, growing at 6.5 ms per thousand records, which is the
|
|
13
|
+
* term that turns into seconds as a log grows. The daemon, meanwhile, holds a
|
|
14
|
+
* warm cache and re-verifies only the tail on every tick. This module is how
|
|
15
|
+
* what it verified reaches the next hook process.
|
|
16
|
+
*
|
|
17
|
+
* ## Why a file and not a socket
|
|
18
|
+
*
|
|
19
|
+
* The hook's request path is synchronous end to end — `commandHook` returns an
|
|
20
|
+
* exit code, and the wait loop sleeps synchronously — so a `node:net` client
|
|
21
|
+
* could not be awaited from it, and a `spawnSync` helper to do the awaiting
|
|
22
|
+
* would cost more node startup (20-40 ms) than the walk it saves. A published
|
|
23
|
+
* file needs one `readFileSync`. It also spawns no child and opens no socket,
|
|
24
|
+
* so there is no environment to scrub (APRV-205) and no fd to leak.
|
|
25
|
+
*
|
|
26
|
+
* ## What a snapshot is, and what it is emphatically not
|
|
27
|
+
*
|
|
28
|
+
* It is an **endorsement of bytes**. It says: *the first `byte_length` bytes of
|
|
29
|
+
* this log, whose SHA-256 is this, verified clean, and they end at this head.*
|
|
30
|
+
* It carries no records, and a reader never learns anything from it that it
|
|
31
|
+
* cannot check against the log file in its own hands. A reader that cannot
|
|
32
|
+
* re-prove every one of those claims ignores the file and walks the log, which
|
|
33
|
+
* is the behaviour with no snapshot at all.
|
|
34
|
+
*
|
|
35
|
+
* ## Global invariant 1, "enforcement paths read only verified records"
|
|
36
|
+
*
|
|
37
|
+
* This is the invariant the module touches, so the argument is written out
|
|
38
|
+
* rather than assumed. The structure is APRV-43's, with the verifier in another
|
|
39
|
+
* process:
|
|
40
|
+
*
|
|
41
|
+
* - {@link admitSnapshot} hashes the prefix bytes **it read itself** and
|
|
42
|
+
* requires the digest the snapshot names. That is what makes "the bytes the
|
|
43
|
+
* publisher verified" and "the bytes on disk now" the same bytes; it is the
|
|
44
|
+
* identical proof the in-process cache pays on every cached read, and no
|
|
45
|
+
* amount of `stat` substitutes for it.
|
|
46
|
+
* - The reader parses the prefix lines itself and re-derives the head and the
|
|
47
|
+
* line count from its own parse. A snapshot naming a different head, or a
|
|
48
|
+
* different number of lines, is rejected by the reader's own arithmetic.
|
|
49
|
+
* - The reader then re-checks the chain links (`alg`, `seq` succession, `prev`
|
|
50
|
+
* linkage, hash shape) over those parsed records. This is a deliberate
|
|
51
|
+
* SUBSET of `core/verify.ts`'s ladder and never a replacement for it: it can
|
|
52
|
+
* only reject. `core/verify.ts` remains the one implementation of the
|
|
53
|
+
* verdict, and the appended tail beyond the prefix is walked by it in full.
|
|
54
|
+
* - What is therefore taken on the publisher's word is exactly two checks over
|
|
55
|
+
* bytes already proved identical: the `event` schema validation and the
|
|
56
|
+
* per-record hash recompute.
|
|
57
|
+
*
|
|
58
|
+
* **Why that residue is not a new capability.** A snapshot can only endorse
|
|
59
|
+
* bytes that are already in the log file. To exploit the residue an attacker
|
|
60
|
+
* must write `verified-head.json`, which sits in the same directory as
|
|
61
|
+
* `events.jsonl` under the same permissions — and the chain is unkeyed, so an
|
|
62
|
+
* attacker who can write that directory can simply recompute a self-consistent
|
|
63
|
+
* forged log, which passes a *cold* walk too. The snapshot grants nothing that
|
|
64
|
+
* write access to the log directory did not already grant. The ownership and
|
|
65
|
+
* permission checks in {@link readSnapshot} are what keep that sentence true
|
|
66
|
+
* under a loose umask: a snapshot any other user could have written is refused
|
|
67
|
+
* before it is read.
|
|
68
|
+
*
|
|
69
|
+
* Fail closed throughout. Every check here can only reject, every rejection
|
|
70
|
+
* falls back to the cold walk, and nothing in this module can turn a log that
|
|
71
|
+
* does not verify into a verdict — the reader either proves the prefix or walks
|
|
72
|
+
* it.
|
|
73
|
+
*/
|
|
74
|
+
import type { LogHead } from "./log.js";
|
|
75
|
+
import type { VerifiedPrefix } from "./verify.js";
|
|
76
|
+
/** The snapshot format version. A reader admits exactly this one. */
|
|
77
|
+
export declare const SNAPSHOT_VERSION = 1;
|
|
78
|
+
/**
|
|
79
|
+
* The published snapshot, as it sits on disk.
|
|
80
|
+
*
|
|
81
|
+
* Field names are snake_case to match every other file this project writes.
|
|
82
|
+
* There is no JSON Schema for it, deliberately: schemas in `schema/` describe
|
|
83
|
+
* the log's records, which are evidence, and this file is not evidence. It is
|
|
84
|
+
* validated field by field by {@link parseSnapshot}, strictly, in one place.
|
|
85
|
+
*/
|
|
86
|
+
export interface VerifiedSnapshot {
|
|
87
|
+
v: number;
|
|
88
|
+
/** Resolved path of the log these bytes belong to. */
|
|
89
|
+
log: string;
|
|
90
|
+
/** Resolved schema directory the publisher verified against ("" = default). */
|
|
91
|
+
schema_dir: string;
|
|
92
|
+
/** Length of the endorsed prefix in bytes. Always ends just after a newline. */
|
|
93
|
+
byte_length: number;
|
|
94
|
+
/** SHA-256 (hex) over exactly those bytes. The proof a reader re-computes. */
|
|
95
|
+
sha256: string;
|
|
96
|
+
/** Complete lines in the prefix, so a resumed walk numbers lines absolutely. */
|
|
97
|
+
lines: number;
|
|
98
|
+
/** The prefix's chain head. A reader re-derives it and must agree. */
|
|
99
|
+
head: LogHead;
|
|
100
|
+
/** When it was published. Diagnostics only; no check depends on it. */
|
|
101
|
+
verified_at: string;
|
|
102
|
+
/** Publisher pid. Diagnostics only; no check depends on it. */
|
|
103
|
+
pid: number;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Where the snapshot for `logPath` lives: beside the log, named for what it is.
|
|
107
|
+
*
|
|
108
|
+
* Derived, never configured. One log has one snapshot, and a reader that had to
|
|
109
|
+
* be *told* where to look could be pointed at a snapshot for another log — a
|
|
110
|
+
* mistake the `log` field would catch and that this removes entirely.
|
|
111
|
+
*/
|
|
112
|
+
export declare function snapshotPathFor(logPath: string): string;
|
|
113
|
+
/** Forget what this process published. For tests, and for {@link clearSnapshot}. */
|
|
114
|
+
export declare function forgetPublishedSnapshots(logPath?: string): void;
|
|
115
|
+
/**
|
|
116
|
+
* Publish a snapshot for a log the caller has just verified clean.
|
|
117
|
+
*
|
|
118
|
+
* `raw` must be the exact bytes the caller verified, `sha256` their digest,
|
|
119
|
+
* `head` the head that walk produced and `lines` its record count: this function
|
|
120
|
+
* checks none of that and cannot — it is the caller's own verification being
|
|
121
|
+
* published. Every caller is therefore immediately after a `clean` verdict over
|
|
122
|
+
* these bytes. The digest is a parameter rather than a recomputation because the
|
|
123
|
+
* caller has just proved it (APRV-206's argument, and APRV-211's measurement:
|
|
124
|
+
* hashing megabytes twice per read was 45% of a tick).
|
|
125
|
+
*
|
|
126
|
+
* The write is atomic (a temp file in the same directory, then `rename`) so a
|
|
127
|
+
* reader never sees half a snapshot, and mode 0600 so the ownership argument in
|
|
128
|
+
* the module header holds. Any failure is swallowed: a snapshot is an
|
|
129
|
+
* optimization, and a daemon must not die because a cache file could not be
|
|
130
|
+
* written. The return value says whether it landed, for the tests and the
|
|
131
|
+
* doctor row; `false` also covers "these exact bytes are already published",
|
|
132
|
+
* which is a write skipped rather than a write that failed.
|
|
133
|
+
*/
|
|
134
|
+
export declare function publishSnapshot(logPath: string, raw: Uint8Array, digest: string, lines: number, head: LogHead, schemaDir: string | undefined, now?: () => string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* {@link publishSnapshot} for a caller that never held the whole file.
|
|
137
|
+
*
|
|
138
|
+
* The incremental read (APRV-217) reads the head line and the appended bytes
|
|
139
|
+
* and proves the digest from a carried hash state, so it has the two facts this
|
|
140
|
+
* function needs — how long the verified prefix is and whether it ends at a
|
|
141
|
+
* line boundary — without a buffer of the log to derive them from. Nothing else
|
|
142
|
+
* differs: the same suppression memo, the same atomic 0600 write, the same
|
|
143
|
+
* snapshot fields. `publishSnapshot` is this function with those two facts read
|
|
144
|
+
* off a buffer.
|
|
145
|
+
*/
|
|
146
|
+
export declare function publishVerifiedPrefix(logPath: string, byteLength: number, endsWithNewline: boolean, digest: string, lines: number, head: LogHead, schemaDir: string | undefined, now?: () => string): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Why a snapshot was not used. Every value means the same thing operationally —
|
|
149
|
+
* walk the log — and exists so the doctor row and the tests can say which check
|
|
150
|
+
* rejected it.
|
|
151
|
+
*/
|
|
152
|
+
export type SnapshotRefusal = "absent" | "unreadable" | "not-a-file" | "foreign-owner" | "loose-permissions" | "malformed" | "version" | "other-log" | "other-schema-dir" | "shorter-file" | "not-line-aligned" | "digest-mismatch" | "line-count-mismatch" | "head-mismatch" | "chain-broken";
|
|
153
|
+
export interface SnapshotRejected {
|
|
154
|
+
ok: false;
|
|
155
|
+
reason: SnapshotRefusal;
|
|
156
|
+
detail: string;
|
|
157
|
+
}
|
|
158
|
+
export type SnapshotRead = {
|
|
159
|
+
ok: true;
|
|
160
|
+
snapshot: VerifiedSnapshot;
|
|
161
|
+
} | SnapshotRejected;
|
|
162
|
+
/**
|
|
163
|
+
* Read the snapshot for `logPath`, refusing one this user did not write.
|
|
164
|
+
*
|
|
165
|
+
* The ownership and permission checks are taken from the open file descriptor
|
|
166
|
+
* rather than from the path, so what is checked and what is read are the same
|
|
167
|
+
* inode: a snapshot swapped between the `stat` and the read cannot be admitted
|
|
168
|
+
* on the strength of the file that used to be there.
|
|
169
|
+
*/
|
|
170
|
+
export declare function readSnapshot(logPath: string): SnapshotRead;
|
|
171
|
+
export type SnapshotAdmission = {
|
|
172
|
+
ok: true;
|
|
173
|
+
prefix: VerifiedPrefix;
|
|
174
|
+
/**
|
|
175
|
+
* SHA-256 of the endorsed prefix, as this call re-proved it. Handed back
|
|
176
|
+
* so a caller that caches the prefix does not hash the same bytes twice.
|
|
177
|
+
*/
|
|
178
|
+
digest: string;
|
|
179
|
+
} | SnapshotRejected;
|
|
180
|
+
/**
|
|
181
|
+
* Decide whether `snapshot` may stand for the first bytes of `raw`, and return
|
|
182
|
+
* the {@link VerifiedPrefix} a resumed walk chains onto when it may.
|
|
183
|
+
*
|
|
184
|
+
* `raw` is the caller's own read of the whole log. Every check below is made
|
|
185
|
+
* against those bytes; nothing is re-`stat`ed, re-read, or taken from the
|
|
186
|
+
* snapshot without being re-derived. The order is cheapest-first, but only the
|
|
187
|
+
* digest is load-bearing — everything before it rejects an obviously
|
|
188
|
+
* inapplicable snapshot without hashing megabytes, and everything after it
|
|
189
|
+
* re-derives from the reader's own parse what the snapshot merely claimed.
|
|
190
|
+
*/
|
|
191
|
+
export declare function admitSnapshot(logPath: string, raw: Uint8Array, snapshot: VerifiedSnapshot, schemaDir: string | undefined): SnapshotAdmission;
|
|
192
|
+
/**
|
|
193
|
+
* {@link readSnapshot} then {@link admitSnapshot}: the whole path a reader takes.
|
|
194
|
+
*
|
|
195
|
+
* Returns the prefix to resume behind, or the reason there is none. Every reason
|
|
196
|
+
* means "walk the log", which is what the caller does with no snapshot at all.
|
|
197
|
+
*/
|
|
198
|
+
export declare function snapshotPrefix(logPath: string, raw: Uint8Array, schemaDir: string | undefined): SnapshotAdmission;
|
|
199
|
+
/** Read the snapshot's own text, for the doctor row. Never throws. */
|
|
200
|
+
export declare function snapshotSummary(logPath: string): SnapshotRead;
|
|
201
|
+
/** Remove a snapshot. Used by tests and by anything that invalidates one. */
|
|
202
|
+
export declare function clearSnapshot(logPath: string): void;
|
|
203
|
+
/** Read the log's bytes, for callers that want the same view a reader admits. */
|
|
204
|
+
export declare function logBytes(logPath: string): Uint8Array | null;
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash-chain verification for `.approval/log/events.jsonl` (SPEC.md §8:
|
|
3
|
+
* "`approval log verify` MUST detect any mutation or truncation").
|
|
4
|
+
*
|
|
5
|
+
* This module is the read side of the append-only log. It re-derives every
|
|
6
|
+
* record's digest from its canonical serialization and walks the `prev` chain
|
|
7
|
+
* and the `seq` succession end to end, reporting the *first* place the log
|
|
8
|
+
* stops being self-consistent.
|
|
9
|
+
*
|
|
10
|
+
* **Read-only, by definition.** Verification opens the log for reading and
|
|
11
|
+
* nothing else: it never truncates, never rewrites, never repairs, and never
|
|
12
|
+
* creates a lockfile. A torn tail is *reported*, never auto-truncated — a log
|
|
13
|
+
* that heals itself is a log that can be made to forget. Repair is a human
|
|
14
|
+
* decision, made explicitly: a later CLI task may offer it behind an explicit
|
|
15
|
+
* flag, but the core provides detection only.
|
|
16
|
+
*
|
|
17
|
+
* **What verification detects.** Any mutation of a record without a full
|
|
18
|
+
* recompute of every descendant; truncation of the tail (with an external
|
|
19
|
+
* anchor); deletion, insertion, reorder, or splice anywhere in the chain;
|
|
20
|
+
* `alg` tampering; a malformed or schema-invalid line; a duplicated or skipped
|
|
21
|
+
* `seq`; a non-genesis first record; and — given an anchor — a fully
|
|
22
|
+
* recomputed forged suffix.
|
|
23
|
+
*
|
|
24
|
+
* **The detection boundary — state it plainly.** A hash chain is
|
|
25
|
+
* tamper-*evident*, not tamper-*proof*. A forger who rewrites record N and then
|
|
26
|
+
* recomputes every descendant (new hash, fixed `prev`) produces a file that is
|
|
27
|
+
* internally self-consistent in every respect, and `verify(logPath)` on that
|
|
28
|
+
* file alone CANNOT distinguish it from an honest log. The same is true of
|
|
29
|
+
* dropping records off the tail: the surviving prefix is a valid chain. Closing
|
|
30
|
+
* that gap requires something the forger does not control:
|
|
31
|
+
*
|
|
32
|
+
* - an externally anchored head — pass {@link VerifyOptions.expectedHead} with
|
|
33
|
+
* a `(seq, hash)` pair recorded elsewhere (a channel message, another host, a
|
|
34
|
+
* human's notes); a mismatch is reported as `head-mismatch`;
|
|
35
|
+
* - a retained copy of the log held outside the writer's reach;
|
|
36
|
+
* - the optional per-event git commits of SPEC.md §8, where the daemon commits
|
|
37
|
+
* each record under its own identity, giving signed, distributed evidence.
|
|
38
|
+
*
|
|
39
|
+
* Three questions that keep arriving separately have one answer (APRV-141, F7):
|
|
40
|
+
* the digest covers each record's CANONICAL CONTENT, its RFC 8785
|
|
41
|
+
* serialization, and never the bytes of the line it was read from, so a
|
|
42
|
+
* re-serialization that changes only byte format verifies clean, a line
|
|
43
|
+
* carrying a duplicate key is settled by the JSON parse that precedes
|
|
44
|
+
* canonicalization rather than by the chain, and an unanchored tail truncation
|
|
45
|
+
* leaves a valid prefix that nothing inside the file can contradict.
|
|
46
|
+
*
|
|
47
|
+
* **Anomalies, which are reported and never enforced (APRV-40).** SPEC.md §8
|
|
48
|
+
* requires that "verification treats gate-type events with implausible skew
|
|
49
|
+
* relative to their neighbors as a reportable anomaly, never silently accepted".
|
|
50
|
+
* Every non-corrupt result therefore carries {@link VerifyResult.anomalies}, a
|
|
51
|
+
* list that says nothing about integrity: a clean log with anomalies is clean,
|
|
52
|
+
* exits 0, and authorizes exactly what it authorized before. The separation is
|
|
53
|
+
* the point. Chain integrity is a proof, skew is a judgment, and folding a
|
|
54
|
+
* judgment into a proof would turn `log verify` into a check people learn to
|
|
55
|
+
* pass a flag to silence. See {@link chainAnomalies}.
|
|
56
|
+
*
|
|
57
|
+
* Determinism: the result is a pure function of (log bytes, schema files,
|
|
58
|
+
* options). No clock, no network, no cross-call state — the anomaly pass reads
|
|
59
|
+
* the records' own timestamps and never the current time.
|
|
60
|
+
*/
|
|
61
|
+
import { type EventRecord, type LogHead } from "./log.js";
|
|
62
|
+
import { type ValidateOptions } from "./validate.js";
|
|
63
|
+
/**
|
|
64
|
+
* A chain head: the last record's position and digest.
|
|
65
|
+
*
|
|
66
|
+
* Defined in `core/log.ts` (the writer needs it for its compare-and-append
|
|
67
|
+
* precondition and cannot import this module) and re-exported here, where
|
|
68
|
+
* readers expect to find it. One definition, one meaning.
|
|
69
|
+
*/
|
|
70
|
+
export type { LogHead };
|
|
71
|
+
/**
|
|
72
|
+
* The skew allowance, in milliseconds, before a backwards step between two
|
|
73
|
+
* gate-typed records is reported.
|
|
74
|
+
*
|
|
75
|
+
* ### Why 2 seconds, and why any number at all
|
|
76
|
+
*
|
|
77
|
+
* Gate-typed timestamps are stamped by `core/clock.ts` at the write boundary. In
|
|
78
|
+
* one process they come from one `Date.now()` and never go backwards. Across
|
|
79
|
+
* processes and hosts — the daemon on one machine, a CLI verb on another, both
|
|
80
|
+
* appending to a shared log — they come from separate wall clocks, and two
|
|
81
|
+
* healthy NTP-disciplined clocks routinely disagree by tens of milliseconds and
|
|
82
|
+
* occasionally by a few hundred during a step correction. A tolerance of zero
|
|
83
|
+
* would therefore report ordinary distributed operation as an anomaly, which is
|
|
84
|
+
* the fastest way to make an anomaly channel ignored.
|
|
85
|
+
*
|
|
86
|
+
* 2000 ms is chosen as roughly an order of magnitude above the disagreement a
|
|
87
|
+
* synchronized fleet actually exhibits, and two to three orders of magnitude
|
|
88
|
+
* below the skew that a *useful* lie requires. The thing this check exists to
|
|
89
|
+
* catch is a timestamp placed to change a judgment: a `ts` inside a lapsed TTL
|
|
90
|
+
* (minutes to hours), or one moved outside a rolling budget window (hours). No
|
|
91
|
+
* attack is bought by 1.9 seconds, and no healthy fleet needs 2.1.
|
|
92
|
+
*
|
|
93
|
+
* It is the DEFAULT rather than the only value since APRV-58: an operator
|
|
94
|
+
* running a single host may tighten it to 250 ms, one running across a WAN with
|
|
95
|
+
* poor time discipline may loosen it to 5 s, and `audit.skew_tolerance` in the
|
|
96
|
+
* policy is where they say so. A policy that declares nothing (or that fails to
|
|
97
|
+
* load at all) leaves this number in force, so the reference runtime still has
|
|
98
|
+
* exactly one value that every reader can see.
|
|
99
|
+
*
|
|
100
|
+
* Widening the tolerance permits nothing: the threshold is report-only in both
|
|
101
|
+
* directions, so a loosened value hides evidence from a human and cannot make
|
|
102
|
+
* any action allowed that was refused before.
|
|
103
|
+
*/
|
|
104
|
+
export declare const GATE_TS_SKEW_TOLERANCE_MS = 2000;
|
|
105
|
+
/**
|
|
106
|
+
* `audit.skew_tolerance` in milliseconds, or the default when it says nothing.
|
|
107
|
+
*
|
|
108
|
+
* Fails closed to the default exactly as `daemon/prune.ts`'s retention read
|
|
109
|
+
* does: a policy that cannot be loaded configures nothing, and for THIS key the
|
|
110
|
+
* safe fallback is the shipped number rather than zero — a zero allowance would
|
|
111
|
+
* report every ordinary clock disagreement as an anomaly, and an anomaly channel
|
|
112
|
+
* that cries wolf is one operators stop reading. An unparseable duration never
|
|
113
|
+
* reaches here: the schema's duration pattern rejects it and `loadPolicy` fails
|
|
114
|
+
* the whole policy, precisely as a bad `defaults.approval_ttl` does.
|
|
115
|
+
*/
|
|
116
|
+
export declare function skewToleranceMsOf(policy: {
|
|
117
|
+
dir?: string;
|
|
118
|
+
file?: string;
|
|
119
|
+
}, schemaDir?: string): number;
|
|
120
|
+
/**
|
|
121
|
+
* Machine-readable anomaly kinds. Closed union, additive-only, each pinned by a
|
|
122
|
+
* test — the same contract every other frozen union in this codebase carries.
|
|
123
|
+
*/
|
|
124
|
+
export declare const CHAIN_ANOMALY_KINDS: readonly [
|
|
125
|
+
/**
|
|
126
|
+
* A gate-typed record's `ts` is earlier than the previous gate-typed record's
|
|
127
|
+
* `ts` by more than {@link GATE_TS_SKEW_TOLERANCE_MS}.
|
|
128
|
+
*
|
|
129
|
+
* One kind covers both directions SPEC.md §8 describes. "Earlier than its
|
|
130
|
+
* predecessor" and "later than its successor" are the same disagreement seen
|
|
131
|
+
* from the two ends of one adjacent pair, and reporting it twice would double
|
|
132
|
+
* every entry without adding a fact.
|
|
133
|
+
*/
|
|
134
|
+
"gate-ts-regression"];
|
|
135
|
+
export type ChainAnomalyKind = (typeof CHAIN_ANOMALY_KINDS)[number];
|
|
136
|
+
/** One reportable oddity in a log that verifies. Never a verdict. */
|
|
137
|
+
export interface ChainAnomaly {
|
|
138
|
+
kind: ChainAnomalyKind;
|
|
139
|
+
/** The record the anomaly is reported against. */
|
|
140
|
+
seq: number;
|
|
141
|
+
ts: string;
|
|
142
|
+
event: string;
|
|
143
|
+
/** The gate-typed record it was compared with. */
|
|
144
|
+
previousSeq: number;
|
|
145
|
+
previousTs: string;
|
|
146
|
+
/** How far back the step is, in milliseconds. Always positive. */
|
|
147
|
+
skewMs: number;
|
|
148
|
+
message: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Timestamp anomalies among the gate-typed records of a verified chain.
|
|
152
|
+
*
|
|
153
|
+
* Pure: a function of the records alone, with no clock and no I/O. Comparison is
|
|
154
|
+
* between each gate-typed record and the previous **gate-typed** record, not the
|
|
155
|
+
* previous record of any kind, for the reason {@link isGateTyped} states.
|
|
156
|
+
*
|
|
157
|
+
* This changes no verdict. A log full of anomalies is still `clean` if its chain
|
|
158
|
+
* verifies, still exits 0, and still authorizes exactly what it authorized
|
|
159
|
+
* before. Skew is evidence for a human to weigh, and a verifier that refused on
|
|
160
|
+
* it would be refusing on a heuristic — which is how a tamper-evidence tool
|
|
161
|
+
* starts being run with a flag that turns it off.
|
|
162
|
+
*/
|
|
163
|
+
export declare function chainAnomalies(records: readonly EventRecord[], toleranceMs?: number): ChainAnomaly[];
|
|
164
|
+
/** Machine-readable reason a log failed verification. Closed set. */
|
|
165
|
+
export type VerifyFailureReason = "malformed-line" | "schema-invalid" | "bad-alg" | "hash-mismatch" | "prev-mismatch" | "seq-gap" | "seq-duplicate" | "not-genesis" | "head-mismatch";
|
|
166
|
+
/**
|
|
167
|
+
* Outcome of a verification run. A discriminated union on `status`:
|
|
168
|
+
*
|
|
169
|
+
* - `clean` — every complete line verified; `head` is `null` for an empty or
|
|
170
|
+
* absent log.
|
|
171
|
+
* - `torn-tail` — the file's final line is torn (it is not newline-terminated,
|
|
172
|
+
* i.e. a writer died mid-line) while every complete line before it verifies.
|
|
173
|
+
* This is the crashed-write signature and is deliberately distinct from
|
|
174
|
+
* corruption: it is an incomplete write, not evidence of tampering.
|
|
175
|
+
* - `corrupt` — everything else, reported at the first offending record.
|
|
176
|
+
*/
|
|
177
|
+
export type VerifyResult = {
|
|
178
|
+
status: "clean";
|
|
179
|
+
records: number;
|
|
180
|
+
head: LogHead | null;
|
|
181
|
+
anomalies: ChainAnomaly[];
|
|
182
|
+
} | {
|
|
183
|
+
status: "torn-tail";
|
|
184
|
+
records: number;
|
|
185
|
+
intactThroughSeq: number;
|
|
186
|
+
message: string;
|
|
187
|
+
anomalies: ChainAnomaly[];
|
|
188
|
+
} | {
|
|
189
|
+
status: "corrupt";
|
|
190
|
+
firstBadSeq: number | null;
|
|
191
|
+
reason: VerifyFailureReason;
|
|
192
|
+
message: string;
|
|
193
|
+
anomalies: ChainAnomaly[];
|
|
194
|
+
};
|
|
195
|
+
/** Options accepted by {@link verify}. */
|
|
196
|
+
export interface VerifyOptions extends ValidateOptions {
|
|
197
|
+
/**
|
|
198
|
+
* Externally anchored head. When supplied, a log that verifies internally is
|
|
199
|
+
* additionally required to end at exactly this `(seq, hash)`. This is the
|
|
200
|
+
* only defence against tail truncation and against a fully recomputed forged
|
|
201
|
+
* suffix; see the detection boundary in the module header.
|
|
202
|
+
*/
|
|
203
|
+
expectedHead?: LogHead;
|
|
204
|
+
/**
|
|
205
|
+
* Where the policy carrying `audit.skew_tolerance` lives (APRV-58), with
|
|
206
|
+
* `loadPolicy`'s semantics. Supplied by a caller that has a policy to hand;
|
|
207
|
+
* absent means {@link GATE_TS_SKEW_TOLERANCE_MS}.
|
|
208
|
+
*
|
|
209
|
+
* It reaches exactly one thing, the anomaly threshold, and anomalies change no
|
|
210
|
+
* verdict. Verification's answer to "does this chain hold" is a function of
|
|
211
|
+
* the log bytes and the schemas, and no policy — loadable, absent, hostile, or
|
|
212
|
+
* edited — can move it.
|
|
213
|
+
*/
|
|
214
|
+
policy?: {
|
|
215
|
+
dir?: string;
|
|
216
|
+
file?: string;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* The tolerance, already resolved, in milliseconds. Overrides
|
|
220
|
+
* {@link VerifyOptions.policy} when both are given, and exists so the pure
|
|
221
|
+
* paths and the tests can state a threshold without a file on disk.
|
|
222
|
+
*/
|
|
223
|
+
skewToleranceMs?: number;
|
|
224
|
+
/**
|
|
225
|
+
* Called as the walk advances, so a caller can say what is taking so long
|
|
226
|
+
* (APRV-167).
|
|
227
|
+
*
|
|
228
|
+
* `approval policy amend` sat silent for thirty-three seconds re-verifying a
|
|
229
|
+
* three-thousand-record chain before it printed its first line, read as
|
|
230
|
+
* frozen, and was once abandoned mid-ceremony — which left the repository's
|
|
231
|
+
* gate fail-closed for every agent session until somebody tried again. The
|
|
232
|
+
* work was never the problem; the silence was.
|
|
233
|
+
*
|
|
234
|
+
* It OBSERVES and never decides. Nothing in the walk consults it, no verdict,
|
|
235
|
+
* message, line number or record depends on whether it was supplied, and a
|
|
236
|
+
* listener that throws would fail the verification of a sound log — so
|
|
237
|
+
* callers must not throw from it. Counts are absolute over the whole log
|
|
238
|
+
* (a resumed read behind a {@link VerifiedPrefix} reports the same numbers a
|
|
239
|
+
* cold one does) and strictly increasing within one run. The final call
|
|
240
|
+
* reports `done === total` when the chain held, and the length of the
|
|
241
|
+
* verified prefix when it did not — a meter must not claim work that was
|
|
242
|
+
* refused.
|
|
243
|
+
*/
|
|
244
|
+
onProgress?: VerifyProgressListener;
|
|
245
|
+
}
|
|
246
|
+
/** How far along a verification walk is. Counts are records, not bytes. */
|
|
247
|
+
export interface VerifyProgress {
|
|
248
|
+
/** Records verified so far. Monotonically increasing within one run. */
|
|
249
|
+
done: number;
|
|
250
|
+
/** Records this run will cover in total, known before the walk starts. */
|
|
251
|
+
total: number;
|
|
252
|
+
}
|
|
253
|
+
export type VerifyProgressListener = (progress: VerifyProgress) => void;
|
|
254
|
+
/**
|
|
255
|
+
* How many records pass between progress calls.
|
|
256
|
+
*
|
|
257
|
+
* Count-based rather than time-based, and reported here rather than throttled
|
|
258
|
+
* here: this module has no clock, a listener that fires on a deterministic
|
|
259
|
+
* schedule is a listener a test can assert on exactly, and the decision about
|
|
260
|
+
* how often a HUMAN should see a repaint belongs to the thing holding the
|
|
261
|
+
* terminal (`cli/progress.ts`), which is the only layer that knows whether
|
|
262
|
+
* there is one.
|
|
263
|
+
*/
|
|
264
|
+
export declare const PROGRESS_INTERVAL = 250;
|
|
265
|
+
/**
|
|
266
|
+
* A verification run plus the records it verified.
|
|
267
|
+
*
|
|
268
|
+
* `records` holds every record the walk accepted, in log order: the whole log
|
|
269
|
+
* when `result.status` is `clean`, the intact prefix when it is `torn-tail`, and
|
|
270
|
+
* the prefix before the first failure when it is `corrupt` (where it carries no
|
|
271
|
+
* authority and callers must ignore it).
|
|
272
|
+
*/
|
|
273
|
+
export interface VerifiedLog {
|
|
274
|
+
result: VerifyResult;
|
|
275
|
+
records: EventRecord[];
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* {@link verify}, returning the verified records alongside the verdict.
|
|
279
|
+
*
|
|
280
|
+
* This exists so that a reader which needs *both* — the gate, the token module,
|
|
281
|
+
* the executor, via `core/state.ts` — can have one walk produce both, rather
|
|
282
|
+
* than verifying with this module and then parsing the same bytes a second time
|
|
283
|
+
* with a private walk that could disagree with it (APRV-20 finding S1).
|
|
284
|
+
*/
|
|
285
|
+
export declare function verifyWithRecords(logPath: string, options?: VerifyOptions): VerifiedLog;
|
|
286
|
+
/**
|
|
287
|
+
* A prefix of a log that *this process* has already verified in full, together
|
|
288
|
+
* with the evidence needed to resume behind it.
|
|
289
|
+
*
|
|
290
|
+
* Handing one of these to {@link verifyText} skips re-verification of the prefix
|
|
291
|
+
* entirely, which is sound only under the caller's obligation stated on
|
|
292
|
+
* {@link records}: the bytes now on disk in `[0, byteLength)` must be proved
|
|
293
|
+
* byte-identical to the bytes that produced these records. `core/state.ts` is
|
|
294
|
+
* the only caller, and it discharges that obligation by re-hashing the prefix
|
|
295
|
+
* bytes on every use. Verification is a pure function of (bytes, schemas,
|
|
296
|
+
* options), so identical bytes re-verify identically by construction; nothing
|
|
297
|
+
* weaker (a matching size, a matching mtime, a matching head line) implies it.
|
|
298
|
+
*/
|
|
299
|
+
export interface VerifiedPrefix {
|
|
300
|
+
/** Byte length of the prefix. Always immediately after a newline. */
|
|
301
|
+
byteLength: number;
|
|
302
|
+
/** Number of complete lines in the prefix, for file-absolute line numbers. */
|
|
303
|
+
lines: number;
|
|
304
|
+
/** The prefix's chain head, which the resumed walk chains onto. */
|
|
305
|
+
head: LogHead;
|
|
306
|
+
/** The records the prefix verified to, in log order. */
|
|
307
|
+
records: readonly EventRecord[];
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* {@link verifyWithRecords} over text already in hand, optionally resuming
|
|
311
|
+
* behind a {@link VerifiedPrefix}.
|
|
312
|
+
*
|
|
313
|
+
* With `prefix === null`, `text` is the whole log and the walk starts at
|
|
314
|
+
* genesis: this is exactly what {@link verifyWithRecords} does, and the two
|
|
315
|
+
* share every check, message, and line number as a result. With a `prefix`,
|
|
316
|
+
* `text` is the *remainder* of the file (the bytes from `prefix.byteLength` on)
|
|
317
|
+
* and the walk chains onto `prefix.head` with line numbers offset by
|
|
318
|
+
* `prefix.lines`, so a resumed verdict is textually identical to the cold one.
|
|
319
|
+
*
|
|
320
|
+
* Exported for `core/state.ts`'s verified-read cache and for nothing else. Every
|
|
321
|
+
* schema check, hash recompute, `seq` succession check, and `prev` link check
|
|
322
|
+
* still runs on every record the walk covers; resuming changes only *which*
|
|
323
|
+
* records are covered, never how.
|
|
324
|
+
*/
|
|
325
|
+
export declare function verifyText(logPath: string, text: string, options?: VerifyOptions, prefix?: VerifiedPrefix | null): VerifiedLog;
|
|
326
|
+
/**
|
|
327
|
+
* Verify the hash chain of the log at `logPath`.
|
|
328
|
+
*
|
|
329
|
+
* An absent file is an empty log, which is clean with zero records and a `null`
|
|
330
|
+
* head — an audit trail that has recorded nothing is not evidence of tampering.
|
|
331
|
+
*
|
|
332
|
+
* The file is opened for reading only; see the module header for the recovery
|
|
333
|
+
* stance and the detection boundary. This is {@link verifyWithRecords} with the
|
|
334
|
+
* records dropped — one walk, one implementation.
|
|
335
|
+
*/
|
|
336
|
+
export declare function verify(logPath: string, options?: VerifyOptions): VerifyResult;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version constants for the approval.md runtime.
|
|
3
|
+
*
|
|
4
|
+
* `SPEC_VERSION` tracks the version of SPEC.md this build implements.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SPEC_VERSION = "0.1.0-draft";
|
|
7
|
+
/** Policy schema version accepted in `APPROVAL.md` frontmatter (`version:`). */
|
|
8
|
+
export declare const POLICY_VERSION = "0.1";
|