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.
Files changed (266) hide show
  1. package/README.md +584 -553
  2. package/SPEC.md +42 -13
  3. package/dist/src/adapters/agentmail.d.ts +426 -0
  4. package/dist/src/adapters/agentmail.js +2 -2
  5. package/dist/src/adapters/conformance.d.ts +149 -0
  6. package/dist/src/adapters/contract.d.ts +628 -0
  7. package/dist/src/adapters/contract.js +110 -16
  8. package/dist/src/adapters/contract.js.map +1 -1
  9. package/dist/src/adapters/email.d.ts +324 -0
  10. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  11. package/dist/src/adapters/public.d.ts +11 -0
  12. package/dist/src/adapters/public.js +11 -0
  13. package/dist/src/adapters/public.js.map +1 -0
  14. package/dist/src/adapters/registry.d.ts +59 -0
  15. package/dist/src/adapters/registry.js +2 -1
  16. package/dist/src/adapters/registry.js.map +1 -1
  17. package/dist/src/adapters/smtp.d.ts +213 -0
  18. package/dist/src/adapters/vault-provider.d.ts +114 -0
  19. package/dist/src/adapters/vault-provider.js +3 -3
  20. package/dist/src/adapters/zzz.d.ts +66 -0
  21. package/dist/src/adapters/zzz.js +299 -0
  22. package/dist/src/adapters/zzz.js.map +1 -0
  23. package/dist/src/channels/batch.d.ts +109 -0
  24. package/dist/src/channels/cli.d.ts +193 -0
  25. package/dist/src/channels/conformance.d.ts +92 -0
  26. package/dist/src/channels/contract.d.ts +623 -0
  27. package/dist/src/channels/payload-view.d.ts +35 -0
  28. package/dist/src/channels/render-queue.d.ts +149 -0
  29. package/dist/src/channels/tagging.d.ts +196 -0
  30. package/dist/src/channels/telegram.d.ts +1832 -0
  31. package/dist/src/channels/web.d.ts +341 -0
  32. package/dist/src/cli/adapter.d.ts +90 -0
  33. package/dist/src/cli/adapter.js +25 -15
  34. package/dist/src/cli/adapter.js.map +1 -1
  35. package/dist/src/cli/amend.d.ts +59 -0
  36. package/dist/src/cli/args.d.ts +43 -0
  37. package/dist/src/cli/attest.d.ts +41 -0
  38. package/dist/src/cli/audit-card.d.ts +62 -0
  39. package/dist/src/cli/audit.d.ts +59 -0
  40. package/dist/src/cli/channel-telegram.d.ts +806 -0
  41. package/dist/src/cli/channel-web.d.ts +131 -0
  42. package/dist/src/cli/channel.d.ts +71 -0
  43. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  44. package/dist/src/cli/codex.d.ts +2 -0
  45. package/dist/src/cli/codex.js +172 -0
  46. package/dist/src/cli/codex.js.map +1 -0
  47. package/dist/src/cli/coverage.d.ts +61 -0
  48. package/dist/src/cli/daemon.d.ts +120 -0
  49. package/dist/src/cli/doctor.d.ts +129 -0
  50. package/dist/src/cli/doctor.js +119 -5
  51. package/dist/src/cli/doctor.js.map +1 -1
  52. package/dist/src/cli/env.d.ts +65 -0
  53. package/dist/src/cli/execute.d.ts +202 -0
  54. package/dist/src/cli/exit-codes.d.ts +73 -0
  55. package/dist/src/cli/feedback.d.ts +60 -0
  56. package/dist/src/cli/gate-window.d.ts +40 -0
  57. package/dist/src/cli/gate.d.ts +68 -0
  58. package/dist/src/cli/git-scope.d.ts +190 -0
  59. package/dist/src/cli/gloss-attach.d.ts +85 -0
  60. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  61. package/dist/src/cli/gloss-codex.d.ts +24 -0
  62. package/dist/src/cli/gloss-options.d.ts +42 -0
  63. package/dist/src/cli/gloss.d.ts +265 -0
  64. package/dist/src/cli/help.d.ts +103 -0
  65. package/dist/src/cli/help.js +173 -51
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook-codex.d.ts +78 -0
  68. package/dist/src/cli/hook-codex.js +167 -0
  69. package/dist/src/cli/hook-codex.js.map +1 -0
  70. package/dist/src/cli/hook.d.ts +331 -0
  71. package/dist/src/cli/hook.js +186 -80
  72. package/dist/src/cli/hook.js.map +1 -1
  73. package/dist/src/cli/import.d.ts +35 -0
  74. package/dist/src/cli/init.d.ts +84 -0
  75. package/dist/src/cli/init.js +2 -2
  76. package/dist/src/cli/init.js.map +1 -1
  77. package/dist/src/cli/instructions.d.ts +23 -0
  78. package/dist/src/cli/journal.d.ts +41 -0
  79. package/dist/src/cli/log-advance.d.ts +287 -0
  80. package/dist/src/cli/log-advance.js +102 -11
  81. package/dist/src/cli/log-advance.js.map +1 -1
  82. package/dist/src/cli/log-anchor.d.ts +176 -0
  83. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  84. package/dist/src/cli/log-sync.d.ts +243 -0
  85. package/dist/src/cli/log-verbs.d.ts +16 -0
  86. package/dist/src/cli/log-verbs.js +7 -1
  87. package/dist/src/cli/log-verbs.js.map +1 -1
  88. package/dist/src/cli/long-help.d.ts +70 -0
  89. package/dist/src/cli/main.d.ts +77 -0
  90. package/dist/src/cli/main.js +155 -5
  91. package/dist/src/cli/main.js.map +1 -1
  92. package/dist/src/cli/mcp.d.ts +52 -0
  93. package/dist/src/cli/paths.d.ts +56 -0
  94. package/dist/src/cli/payload.d.ts +58 -0
  95. package/dist/src/cli/policy.d.ts +43 -0
  96. package/dist/src/cli/preflight.d.ts +363 -0
  97. package/dist/src/cli/preflight.js +294 -7
  98. package/dist/src/cli/preflight.js.map +1 -1
  99. package/dist/src/cli/progress.d.ts +78 -0
  100. package/dist/src/cli/prompt.d.ts +209 -0
  101. package/dist/src/cli/quickstart.d.ts +46 -0
  102. package/dist/src/cli/quickstart.js +297 -0
  103. package/dist/src/cli/quickstart.js.map +1 -0
  104. package/dist/src/cli/records.d.ts +34 -0
  105. package/dist/src/cli/render.d.ts +22 -0
  106. package/dist/src/cli/sandbox.d.ts +51 -0
  107. package/dist/src/cli/scaffold.d.ts +79 -0
  108. package/dist/src/cli/setup-adapter.d.ts +137 -0
  109. package/dist/src/cli/setup-adapter.js +38 -4
  110. package/dist/src/cli/setup-adapter.js.map +1 -1
  111. package/dist/src/cli/setup-channel.d.ts +117 -0
  112. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  113. package/dist/src/cli/setup-common.d.ts +275 -0
  114. package/dist/src/cli/setup-flow.d.ts +287 -0
  115. package/dist/src/cli/setup-service.d.ts +96 -0
  116. package/dist/src/cli/setup.d.ts +202 -0
  117. package/dist/src/cli/style.d.ts +320 -0
  118. package/dist/src/cli/token.d.ts +39 -0
  119. package/dist/src/cli/up.d.ts +155 -0
  120. package/dist/src/cli/up.js +4 -2
  121. package/dist/src/cli/up.js.map +1 -1
  122. package/dist/src/cli/usage.d.ts +37 -0
  123. package/dist/src/cli/values.d.ts +40 -0
  124. package/dist/src/cli/vault.d.ts +59 -0
  125. package/dist/src/cli/vault.js +2 -2
  126. package/dist/src/cli/vault.js.map +1 -1
  127. package/dist/src/cli/verb-registry.d.ts +76 -0
  128. package/dist/src/cli/verb-registry.js +176 -8
  129. package/dist/src/cli/verb-registry.js.map +1 -1
  130. package/dist/src/cli/wordmark.d.ts +31 -0
  131. package/dist/src/cli/wordmark.js +2 -2
  132. package/dist/src/codex/doctor.d.ts +13 -0
  133. package/dist/src/codex/doctor.js +41 -0
  134. package/dist/src/codex/doctor.js.map +1 -0
  135. package/dist/src/codex/manifest.d.ts +49 -0
  136. package/dist/src/codex/manifest.js +103 -0
  137. package/dist/src/codex/manifest.js.map +1 -0
  138. package/dist/src/codex/templates.d.ts +41 -0
  139. package/dist/src/codex/templates.js +319 -0
  140. package/dist/src/codex/templates.js.map +1 -0
  141. package/dist/src/codex/trust.d.ts +19 -0
  142. package/dist/src/codex/trust.js +183 -0
  143. package/dist/src/codex/trust.js.map +1 -0
  144. package/dist/src/codex/workspace-plan.d.ts +131 -0
  145. package/dist/src/codex/workspace-plan.js +561 -0
  146. package/dist/src/codex/workspace-plan.js.map +1 -0
  147. package/dist/src/core/actor.d.ts +2 -0
  148. package/dist/src/core/actor.js +5 -0
  149. package/dist/src/core/actor.js.map +1 -0
  150. package/dist/src/core/advance-cycle.d.ts +170 -0
  151. package/dist/src/core/agents-md.d.ts +276 -0
  152. package/dist/src/core/apply-patch.d.ts +49 -0
  153. package/dist/src/core/apply-patch.js +266 -0
  154. package/dist/src/core/apply-patch.js.map +1 -0
  155. package/dist/src/core/attest.d.ts +420 -0
  156. package/dist/src/core/attest.js +13 -1
  157. package/dist/src/core/attest.js.map +1 -1
  158. package/dist/src/core/audit.d.ts +492 -0
  159. package/dist/src/core/budgets.d.ts +238 -0
  160. package/dist/src/core/checkpoint.d.ts +500 -0
  161. package/dist/src/core/child-env.d.ts +88 -0
  162. package/dist/src/core/clock.d.ts +52 -0
  163. package/dist/src/core/command-class.d.ts +543 -0
  164. package/dist/src/core/command-class.js +43 -8
  165. package/dist/src/core/command-class.js.map +1 -1
  166. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  167. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  168. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  169. package/dist/src/core/coverage.d.ts +217 -0
  170. package/dist/src/core/credential-spec.d.ts +72 -0
  171. package/dist/src/core/dark-session.d.ts +331 -0
  172. package/dist/src/core/decision-refusal.d.ts +185 -0
  173. package/dist/src/core/env-file.d.ts +450 -0
  174. package/dist/src/core/execute.d.ts +858 -0
  175. package/dist/src/core/execute.js +44 -6
  176. package/dist/src/core/execute.js.map +1 -1
  177. package/dist/src/core/frontmatter.d.ts +78 -0
  178. package/dist/src/core/gate-window.d.ts +312 -0
  179. package/dist/src/core/gate.d.ts +1364 -0
  180. package/dist/src/core/gate.js +68 -13
  181. package/dist/src/core/gate.js.map +1 -1
  182. package/dist/src/core/git-run.d.ts +73 -0
  183. package/dist/src/core/harness-version.d.ts +157 -0
  184. package/dist/src/core/harness-version.js +2 -1
  185. package/dist/src/core/harness-version.js.map +1 -1
  186. package/dist/src/core/harness-wait.d.ts +55 -0
  187. package/dist/src/core/head-retry.d.ts +107 -0
  188. package/dist/src/core/instance.d.ts +253 -0
  189. package/dist/src/core/intake-limits.d.ts +247 -0
  190. package/dist/src/core/jcs.d.ts +52 -0
  191. package/dist/src/core/journal.d.ts +144 -0
  192. package/dist/src/core/live-draw.d.ts +436 -0
  193. package/dist/src/core/log-reconcile.d.ts +89 -0
  194. package/dist/src/core/log-subscribe.d.ts +36 -0
  195. package/dist/src/core/log-subscribe.js +162 -0
  196. package/dist/src/core/log-subscribe.js.map +1 -0
  197. package/dist/src/core/log.d.ts +278 -0
  198. package/dist/src/core/loop.d.ts +274 -0
  199. package/dist/src/core/loop.js +11 -0
  200. package/dist/src/core/loop.js.map +1 -1
  201. package/dist/src/core/md-fence.d.ts +41 -0
  202. package/dist/src/core/money.d.ts +147 -0
  203. package/dist/src/core/payload-census.d.ts +74 -0
  204. package/dist/src/core/payload-store.d.ts +175 -0
  205. package/dist/src/core/payload.d.ts +71 -0
  206. package/dist/src/core/policy-diff.d.ts +292 -0
  207. package/dist/src/core/policy-diff.js +27 -4
  208. package/dist/src/core/policy-diff.js.map +1 -1
  209. package/dist/src/core/policy-expectations.d.ts +199 -0
  210. package/dist/src/core/policy-explain.d.ts +150 -0
  211. package/dist/src/core/policy-explain.js +31 -3
  212. package/dist/src/core/policy-explain.js.map +1 -1
  213. package/dist/src/core/policy-load.d.ts +527 -0
  214. package/dist/src/core/policy-load.js +15 -3
  215. package/dist/src/core/policy-load.js.map +1 -1
  216. package/dist/src/core/policy-match.d.ts +281 -0
  217. package/dist/src/core/policy-match.js +20 -9
  218. package/dist/src/core/policy-match.js.map +1 -1
  219. package/dist/src/core/policy-proposal.d.ts +265 -0
  220. package/dist/src/core/prompt-layout.d.ts +221 -0
  221. package/dist/src/core/protected-path-guard.d.ts +453 -0
  222. package/dist/src/core/protected-path-guard.js +514 -35
  223. package/dist/src/core/protected-path-guard.js.map +1 -1
  224. package/dist/src/core/registration.d.ts +25 -0
  225. package/dist/src/core/reindex.d.ts +99 -0
  226. package/dist/src/core/sampler.d.ts +313 -0
  227. package/dist/src/core/sandbox.d.ts +290 -0
  228. package/dist/src/core/seal.d.ts +165 -0
  229. package/dist/src/core/state.d.ts +505 -0
  230. package/dist/src/core/task-file.d.ts +185 -0
  231. package/dist/src/core/telegram-config.d.ts +93 -0
  232. package/dist/src/core/token.d.ts +409 -0
  233. package/dist/src/core/token.js +21 -38
  234. package/dist/src/core/token.js.map +1 -1
  235. package/dist/src/core/validate.d.ts +138 -0
  236. package/dist/src/core/values.d.ts +137 -0
  237. package/dist/src/core/vault.d.ts +291 -0
  238. package/dist/src/core/verified-snapshot.d.ts +204 -0
  239. package/dist/src/core/verify.d.ts +336 -0
  240. package/dist/src/core/version.d.ts +8 -0
  241. package/dist/src/core/wysiwys.d.ts +370 -0
  242. package/dist/src/daemon/advance-child.d.ts +39 -0
  243. package/dist/src/daemon/advance.d.ts +466 -0
  244. package/dist/src/daemon/audit.d.ts +87 -0
  245. package/dist/src/daemon/daemon.d.ts +1180 -0
  246. package/dist/src/daemon/dark-session.d.ts +64 -0
  247. package/dist/src/daemon/draw-child.d.ts +36 -0
  248. package/dist/src/daemon/draw.d.ts +154 -0
  249. package/dist/src/daemon/git-evidence.d.ts +173 -0
  250. package/dist/src/daemon/git-evidence.js +1 -1
  251. package/dist/src/daemon/projection.d.ts +180 -0
  252. package/dist/src/daemon/prune.d.ts +207 -0
  253. package/dist/src/mcp/http.d.ts +113 -0
  254. package/dist/src/mcp/server.d.ts +265 -0
  255. package/dist/src/mcp/server.js +9 -1
  256. package/dist/src/mcp/server.js.map +1 -1
  257. package/docs/adapter-api.md +106 -0
  258. package/docs/cli-reference.md +389 -36
  259. package/docs/codex-enforced-session.md +30 -0
  260. package/package.json +12 -2
  261. package/schema/codex-instance.schema.json +82 -0
  262. package/schema/event.schema.json +2 -1
  263. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  264. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  265. package/schema/policy.schema.json +21 -1
  266. package/templates/codex/README.md +9 -0
@@ -0,0 +1,505 @@
1
+ /**
2
+ * Derived state: the one place the runtime turns the log into answers
3
+ * (SPEC.md §6.3, §7, §8).
4
+ *
5
+ * Everything the gate, the token module, and the executor believe about an
6
+ * action comes from here. The module exists for two reasons, both structural,
7
+ * both from the APRV-20 review:
8
+ *
9
+ * 1. **Verified reads (finding S1).** {@link readVerifiedRecords} is the only
10
+ * sanctioned way for a decision-making module to read the log. It runs the
11
+ * *full* chain verification — hash recompute, schema validation, `prev`/`seq`
12
+ * walk — by calling `core/verify.ts`'s {@link verifyWithRecords}, and refuses
13
+ * a corrupt log outright. Before this, the gate parsed lines as JSON and
14
+ * trusted them: a forged or spliced record could authorize an action, and the
15
+ * corruption would surface only when someone ran `approval log verify`. A
16
+ * permission system that reads its own evidence without checking it is not a
17
+ * permission system.
18
+ *
19
+ * APRV-43 lifted the linear cost that S1 accepted; see "The verified-read
20
+ * cache" below. What it did not lift is the rule: a decision is still made
21
+ * only from records this process verified in full, against bytes it proved
22
+ * unchanged.
23
+ *
24
+ * 2. **One derivation, no cycle (finding S4).** {@link requestState} used to
25
+ * live in `core/gate.ts`, which `core/token.ts` imported — while
26
+ * `core/gate.ts` imported `core/token.ts` to mint at grant. That import cycle
27
+ * made "which module owns approval state?" unanswerable. The derivation lives
28
+ * here now; `gate.ts`, `token.ts`, and `execute.ts` all import it, and the
29
+ * only remaining edge between them is the intended one, gate → token, at the
30
+ * mint seam. `gate.ts` re-exports the moved names so existing importers (the
31
+ * CLI, the tests) are unaffected.
32
+ *
33
+ * ## The verified-read cache (APRV-43), and why it is not a bypass
34
+ *
35
+ * A daemon re-reads the log on every watch event. Re-verifying from genesis
36
+ * every time makes a session quadratic in the log it is watching, so
37
+ * {@link readVerifiedRecords} keeps a process-lifetime, memory-only cache of the
38
+ * last log it verified clean: the prefix bytes' length, a SHA-256 over those
39
+ * bytes, the head line's bytes at their offset, the chain head, and the records
40
+ * the walk produced. On the next read of the same path, a prefix proved
41
+ * byte-identical is not re-walked; only the appended suffix is verified, chained
42
+ * onto the cached head.
43
+ *
44
+ * **Global invariant 1 ("enforcement paths read only verified records") is what
45
+ * this touches, so the argument is written out rather than assumed.**
46
+ *
47
+ * The tempting design is the cheap one: remember the head line and its offset,
48
+ * and on a re-read accept the prefix if the head line is byte-identical where it
49
+ * was. That design is unsound, and the specific attack says why. Append-only
50
+ * growth is a convention the *writer* honors; it is not a property of the file.
51
+ * An attacker with write access can mutate a record strictly before the head
52
+ * without changing its length — swap two characters of a `summary`, flip a digit
53
+ * of `est_cost_usd` — leaving the file size, the head line, and the head line's
54
+ * offset all identical. Nothing in the suffix walk touches those bytes. The
55
+ * forged record would be handed to the gate as verified, and a hash chain would
56
+ * have been defeated by a cache. "Byte-identical head at the same offset" does
57
+ * not imply "unchanged prefix", and no amount of `stat` makes it imply that.
58
+ *
59
+ * So the cache pays for what it claims: it stores a SHA-256 over the entire
60
+ * verified prefix and **re-hashes those bytes on every cached read**. A match
61
+ * proves the prefix on disk is bit-for-bit the prefix this process verified in
62
+ * full, in this process lifetime. Verification is a pure function of (bytes,
63
+ * schema files, options) — `core/verify.ts` says so and holds no state — so
64
+ * identical bytes verify identically, and replaying the walk over them could
65
+ * only reproduce the records already held. The cache therefore never *admits* a
66
+ * record: it declines to recompute a conclusion it has already computed from
67
+ * bytes it has just re-proved. Every record the caller receives was walked
68
+ * through the full check ladder (parse, `alg`, schema, hash recompute, `seq`
69
+ * succession, `prev` link) by this process, over exactly these bytes.
70
+ *
71
+ * **APRV-217 makes the last sentence conditional, and only under a policy that
72
+ * says so.** Under the default proof (`full`) the paragraph above is exactly
73
+ * what happens on every cached read. Under `incremental`, an operator's policy
74
+ * trades "re-hash the whole prefix on every read" for "hash the appended bytes,
75
+ * and re-hash the whole prefix on a cadence" — same guards, same walk, same
76
+ * verdicts, a bounded window in which an in-place rewrite of the prefix would be
77
+ * served from cache. The argument for that trade, and what it costs, is in the
78
+ * "incremental prefix proof" section below and in
79
+ * `docs/proposals/incremental-prefix-proof.md`.
80
+ *
81
+ * That is still a large win, because the two costs are not comparable: hashing
82
+ * bytes is a single linear pass at memory bandwidth, while the walk it replaces
83
+ * is a JSON parse, an Ajv schema validation, a JCS canonicalization, and a
84
+ * SHA-256 *per record*. The cache trades a full re-verification for a hash of
85
+ * the same bytes and a real verification of the new tail.
86
+ *
87
+ * Everything else the cache records is a discard trigger and never a licence:
88
+ *
89
+ * - **Size** shrinking below the cached prefix discards the entry. A shorter
90
+ * file cannot contain the prefix, and a truncated log must be re-read cold so
91
+ * that the head reported is the file's head, never the remembered one.
92
+ * - **mtime** is a staleness hint with no evidentiary weight. It can only cause
93
+ * a discard (a same-size file whose mtime moved is suspicious), never skip a
94
+ * check. Correctness does not depend on its granularity, on the clock being
95
+ * monotonic, or on the filesystem storing it at all: delete the mtime
96
+ * comparison and the cache is exactly as sound.
97
+ * - **The head line's bytes at their recorded offset** are compared before the
98
+ * prefix hash. This is a fast rejection of the ordinary tamper, not the proof;
99
+ * the prefix hash covers those same bytes and is what the soundness argument
100
+ * rests on.
101
+ * - **The schema directory** is part of the key. Records verified against one
102
+ * schema set are not evidence under another.
103
+ *
104
+ * Only a `clean` verdict populates the cache. A torn tail or a corrupt log
105
+ * leaves the previous entry in place unused (it is discarded on the mismatch
106
+ * that revealed the damage), so nothing derived from a broken read can ever be
107
+ * resumed from. And the cache is memory-only and process-lifetime: no file, no
108
+ * shared state between processes, nothing an attacker can pre-seed. A CLI
109
+ * invocation is a fresh process with an empty cache and behaves exactly as it
110
+ * did before this existed.
111
+ *
112
+ * Records handed out from the cache are deep-frozen, because a caller that
113
+ * mutated a returned record would otherwise corrupt the next reader's evidence.
114
+ * Freezing makes the aliasing safe instead of merely unlikely.
115
+ *
116
+ * Determinism: `requestState` and everything downstream of it read no clock, no
117
+ * network, and no cache; `ts` is a parameter everywhere, so a derivation can be
118
+ * replayed from the log exactly as it was made. The cache sits strictly above
119
+ * that line, in the read path, and is observationally invisible: for the same
120
+ * bytes it returns the same records and the same verdict as a cold read, which
121
+ * `tests/state-cache.test.ts` asserts scenario by scenario.
122
+ */
123
+ import { type EventRecord, type LogHead } from "./log.js";
124
+ import { type VerifiedLog, type VerifyOptions } from "./verify.js";
125
+ /**
126
+ * Why a verified read refused. These names are shared verbatim by the gate, the
127
+ * token module, and the executor, so the CLI maps all three onto the frozen exit
128
+ * table with one function.
129
+ */
130
+ export type LogReadRefusalCode =
131
+ /** The log could not be opened (permissions, a directory, a broken path). */
132
+ "log-unreadable"
133
+ /** The log's final line is unterminated: the signature of a crashed write. */
134
+ | "log-torn-tail"
135
+ /**
136
+ * The chain does not verify: a mutated, spliced, reordered, or schema-invalid
137
+ * record. Distinct from `log-unreadable` because the facts are different — one
138
+ * is a filesystem problem, the other is evidence of tampering — and the
139
+ * repairs are different. `approval log verify` owns the detailed vocabulary
140
+ * (`hash-mismatch`, `seq-gap`, …); this code says only "the log is not
141
+ * trustworthy, so nothing may be authorized from it".
142
+ */
143
+ | "log-corrupt";
144
+ /** A refused read. Structurally a subset of every consumer's refusal shape. */
145
+ export interface LogReadRefusal {
146
+ ok: false;
147
+ code: LogReadRefusalCode;
148
+ message: string;
149
+ }
150
+ /** A successful verified read: the records and the head they end at. */
151
+ export interface VerifiedRecords {
152
+ ok: true;
153
+ records: EventRecord[];
154
+ /**
155
+ * The chain head — `(seq, hash)` of the last record, or `null` for an empty
156
+ * log. This is what a caller passes as `appendEvent`'s `expectedHead`, so that
157
+ * a decision made from these records cannot be appended onto a log that moved
158
+ * underneath it (APRV-20 finding B1).
159
+ */
160
+ head: LogHead | null;
161
+ }
162
+ export type ReadRecordsResult = VerifiedRecords | LogReadRefusal;
163
+ /** Options accepted by {@link readVerifiedRecords}. */
164
+ export interface ReadVerifiedOptions extends VerifyOptions {
165
+ /**
166
+ * Verified-read cache to use.
167
+ *
168
+ * Defaults to {@link processReadCache}, so a repeat reader (the daemon's watch
169
+ * loop) is accelerated with no wiring and a one-shot CLI process cannot tell
170
+ * the difference. Pass an own {@link VerifiedReadCache} to keep a private one,
171
+ * or `null` to force a cold verification from genesis — which is what an
172
+ * explicit audit wants, and what the equivalence tests compare against.
173
+ */
174
+ cache?: VerifiedReadCache | null;
175
+ /**
176
+ * Publish a verified-head snapshot beside the log after a clean read
177
+ * (APRV-188).
178
+ *
179
+ * Off by default, and set by exactly one caller: the daemon, whose warm cache
180
+ * makes it the process that has already done the walk every hook process
181
+ * would otherwise repeat. It is set on the READ rather than done afterwards
182
+ * so the bytes endorsed are the bytes verified — a publisher that re-read the
183
+ * file to hash it could endorse a digest of bytes nobody walked.
184
+ */
185
+ publishSnapshot?: boolean;
186
+ /**
187
+ * Which prefix proof a cached read runs (APRV-217).
188
+ *
189
+ * Absent means {@link FULL_READ_PROOF}: today's behaviour, byte for byte, for
190
+ * every caller that does not ask otherwise. It is set by exactly one kind of
191
+ * caller — a long-lived process (the daemon) that was handed a policy or a
192
+ * flag naming `incremental`. One-shot processes, the hook included, never set
193
+ * it, and a `cache: null` read ignores it entirely.
194
+ */
195
+ readProof?: ReadProof;
196
+ }
197
+ /**
198
+ * Set (or clear, with `null`) the default proof for this process's reads.
199
+ *
200
+ * Called by exactly one kind of caller: the two long-lived operator verbs, at
201
+ * startup, after they have resolved the flag against the policy.
202
+ */
203
+ export declare function useReadProof(proof: ReadProof | null): void;
204
+ /** The default proof in force here. Diagnostics and tests. */
205
+ export declare function readProofInForce(): ReadProof;
206
+ /** Opt this process into (or out of) snapshot-resumed reads. */
207
+ export declare function useVerifiedSnapshots(enabled: boolean): void;
208
+ /** Whether snapshot-resumed reads are enabled here. Diagnostics and tests. */
209
+ export declare function verifiedSnapshotsEnabled(): boolean;
210
+ /** The head of a record list: the last record's `(seq, hash)`, or `null`. */
211
+ export declare function headOf(records: EventRecord[]): LogHead | null;
212
+ /** Which prefix proof a read runs. `full` is today's, byte for byte. */
213
+ export type ReadProofMode = "full" | "incremental";
214
+ /** Reads between full re-proofs under `incremental` (SPEC-signed default). */
215
+ export declare const DEFAULT_FULL_REPROOF_EVERY = 50;
216
+ /** Milliseconds between full re-proofs under `incremental`. */
217
+ export declare const DEFAULT_FULL_REPROOF_AFTER_MS = 60000;
218
+ /** The proof a read runs, with the cadence that bounds `incremental`. */
219
+ export interface ReadProof {
220
+ mode: ReadProofMode;
221
+ /** A full re-proof runs at least this often, counted in reads. */
222
+ everyReads: number;
223
+ /** …and at least this often in wall-clock milliseconds. */
224
+ afterMs: number;
225
+ }
226
+ /**
227
+ * What a reader gets when it asks for nothing: today's proof on every read.
228
+ *
229
+ * The default is `full` because it is the behaviour this repository has today
230
+ * and the one an operator has attested to. `incremental` is reached only by a
231
+ * caller that was handed a policy (or a flag) saying so.
232
+ */
233
+ export declare const FULL_READ_PROOF: ReadProof;
234
+ /** Bytes hashed by the verified-read cache so far. Diagnostics and tests. */
235
+ export declare function hashedByteCount(): number;
236
+ /** Reset {@link hashedByteCount}. Tests only. */
237
+ export declare function resetHashedByteCount(): void;
238
+ /**
239
+ * What one read may do with the published snapshot beside the log (APRV-188).
240
+ *
241
+ * Both halves default to off. `consume` is set from the process-wide switch
242
+ * `useVerifiedSnapshots` (the hook turns it on); `publish` is set per call by
243
+ * the daemon.
244
+ */
245
+ export interface SnapshotUse {
246
+ consume?: boolean;
247
+ publish?: boolean;
248
+ }
249
+ /**
250
+ * Process-lifetime, memory-only store of last-verified log state.
251
+ *
252
+ * Nothing here is written to disk and nothing is shared between processes. An
253
+ * instance is safe to construct per caller (the daemon may want its own); the
254
+ * default is {@link processReadCache}, which is why a repeat reader gets the
255
+ * acceleration without asking for it and a one-shot CLI process cannot notice
256
+ * it exists.
257
+ */
258
+ export declare class VerifiedReadCache {
259
+ #private;
260
+ /** Forget everything. Tests use this to force a genuinely cold read. */
261
+ clear(): void;
262
+ /** How many logs are remembered. Diagnostics and tests only. */
263
+ get size(): number;
264
+ /**
265
+ * Reads that reused a proved prefix, and reads that verified from genesis.
266
+ *
267
+ * Diagnostics, and the one way a test can tell the two paths apart: reusing a
268
+ * prefix is *designed* to be invisible in the result, so a test that wants to
269
+ * assert "this tamper discarded the cache" has nothing else to look at.
270
+ */
271
+ get stats(): {
272
+ hits: number;
273
+ misses: number;
274
+ resumed: number;
275
+ fullReproofs: number;
276
+ };
277
+ /**
278
+ * Require a full re-proof of `logPath` on this cache's next read (APRV-217).
279
+ *
280
+ * Called by `core/log.ts` after a successful append, through the listener it
281
+ * registers below. Can only add work: the next read hashes the whole prefix
282
+ * exactly as a `full` read does.
283
+ */
284
+ requireFullReproof(logPath: string): void;
285
+ /**
286
+ * Verify `logPath`, reusing a proved-identical prefix when there is one.
287
+ *
288
+ * The whole file is read once, and every decision is made from that single
289
+ * snapshot: nothing is re-`stat`ed and re-read behind its own conclusion.
290
+ */
291
+ read(logPath: string, options: VerifyOptions, snapshot?: SnapshotUse, proof?: ReadProof): VerifiedLog;
292
+ }
293
+ /**
294
+ * The cache {@link readVerifiedRecords} uses when a caller names none.
295
+ *
296
+ * Shared per process, which is what makes a watch loop fast without any wiring:
297
+ * the daemon calls `readVerifiedRecords` exactly as every other consumer does.
298
+ */
299
+ export declare const processReadCache: VerifiedReadCache;
300
+ /**
301
+ * Read the log and refuse unless the whole chain verifies.
302
+ *
303
+ * An absent log is an empty log (nothing has happened yet), exactly as
304
+ * `appendEvent` and `approval log verify` treat it.
305
+ *
306
+ * The file is opened once as a readability probe *before* verification so that
307
+ * "I could not open this file" stays an I/O fact (`log-unreadable`) and never
308
+ * arrives dressed as corruption — the same split the CLI's exit table draws, and
309
+ * the one thing `verify()` alone cannot express, since from inside the chain
310
+ * walker an unreadable log is indistinguishable from a broken one.
311
+ *
312
+ * Torn-tail behavior is unchanged from the pre-APRV-20 reader: the tear is
313
+ * reported as `log-torn-tail` and nothing is repaired, because truncating a torn
314
+ * line is a human decision.
315
+ *
316
+ * Reads go through the verified-read cache by default (see the module header):
317
+ * a prefix re-proved byte-identical is not re-walked, the appended suffix is
318
+ * verified in full, and any mismatch falls back to genesis. The result is the
319
+ * result of a cold read on the same bytes, always. `cache: null` opts out.
320
+ */
321
+ export declare function readVerifiedRecords(logPath: string, options?: ReadVerifiedOptions): ReadRecordsResult;
322
+ /**
323
+ * One action's approval state, derived from the log.
324
+ *
325
+ * These are the gate's names for the approval lifecycle of SPEC.md §6.3. The
326
+ * envelope's `state:` enum is the projection of the same thing over a whole
327
+ * task (`proposed`/`awaiting`/`approved`/…); the mapping is one-to-one for the
328
+ * action-scoped states and is applied by the projection layer, not here.
329
+ */
330
+ export type RequestState =
331
+ /** No `approval.requested` for this key. */
332
+ "none"
333
+ /** Requested and undecided — the envelope's `awaiting`. */
334
+ | "requested"
335
+ /** A human granted it — the envelope's `approved`. */
336
+ | "granted" | "rejected" | "revoked"
337
+ /** TTL lapsed, by event or by arithmetic. */
338
+ | "expired"
339
+ /**
340
+ * The requester took the question back while it was still pending (APRV-106,
341
+ * amended SPEC.md §6.3).
342
+ *
343
+ * Terminal, and terminal in the same sense `rejected` is: the gate refuses a
344
+ * grant, a rejection, a revocation or a second withdrawal afterwards. It is
345
+ * NOT a decision — nobody answered — which is why the projection layer reads
346
+ * it as "back to proposed" rather than as a refusal, and why no authorization
347
+ * is implied in either direction.
348
+ */
349
+ | "withdrawn";
350
+ /** The three terminal decisions a human can record, plus runtime expiry. */
351
+ export type Decision = "grant" | "reject" | "revoke";
352
+ /**
353
+ * Why a requester retracted a pending request (amended SPEC.md §6.3, APRV-106).
354
+ *
355
+ * Closed, and mirrored by `event.schema.json`: a withdrawal that does not say
356
+ * why is a fact audit cannot use. `timeout` is the one the hook writes — the
357
+ * requester stopped waiting, so a decision on this request can no longer be
358
+ * consumed by anyone.
359
+ */
360
+ export declare const WITHDRAW_REASONS: readonly ["timeout", "cancelled", "superseded"];
361
+ export type WithdrawReason = (typeof WITHDRAW_REASONS)[number];
362
+ /** Is `value` one of the closed withdrawal reasons? */
363
+ export declare function isWithdrawReason(value: unknown): value is WithdrawReason;
364
+ /**
365
+ * What the request declared, copied out of the `approval.requested` payload.
366
+ *
367
+ * ## The scrutiny ratchet (amended SPEC.md §11), stated where it binds
368
+ *
369
+ * `summary` and `est_cost_usd` are **claimed** fields: the party under
370
+ * oversight authored them. So is `route.confidence`, which this derivation
371
+ * deliberately does not carry at all — nothing in the resolution, sampling, or
372
+ * refusal paths reads it, and `tests/ratchet.test.ts` scans the source to keep
373
+ * it that way. Claimed fields MAY raise scrutiny, inform display, or feed
374
+ * retrospective analysis; they MUST NOT reduce sampling probability, downgrade
375
+ * a resolved autonomy level, or shortcut any refusal path. `est_cost_usd: 0`
376
+ * therefore still consumes one action of every action-count budget, and a
377
+ * confident summary buys nothing. Scrutiny only ratchets upward on self-report.
378
+ *
379
+ * `class` and `payload_hash` sit on the other side of that line: `class` is
380
+ * matched against policy the human attested, and `payload_hash` is *checked*
381
+ * against the bytes an executor presents (`core/payload.ts`), so a false one
382
+ * refuses rather than relaxes.
383
+ */
384
+ export interface DeclaredAction {
385
+ class: string | null;
386
+ /**
387
+ * The declared cost as a canonical decimal USD string (APRV-121), or `null`
388
+ * when the request declared none. A record written before that change carries
389
+ * a JSON number and is normalized to the same string here, so every reader
390
+ * downstream sees one representation regardless of when its record was
391
+ * written.
392
+ */
393
+ est_cost_usd: string | null;
394
+ reversible: boolean | null;
395
+ summary: string | null;
396
+ /**
397
+ * The content binding of amended SPEC.md §6.2, copied from the registered
398
+ * declaration onto `approval.requested` so the grant can copy it in turn.
399
+ * `null` for a request that declared none — which the gate admits only off
400
+ * the manual path.
401
+ */
402
+ payload_hash: string | null;
403
+ /**
404
+ * `"harness"` when the requester declared that it will never run this action
405
+ * through `approval run` (APRV-106, the Claude Code hook): the harness
406
+ * executes the command itself and the gate's answer is a permission decision,
407
+ * not a key. A grant on such a request mints no execution token.
408
+ *
409
+ * On the ratchet's safe side (SPEC.md §11.1), and deliberately so. It is a
410
+ * self-reported field, and every effect it has REMOVES capability from the
411
+ * party that reported it: no token is minted, so `approval run` and
412
+ * `approval consume` refuse the key outright. There is no reading of a false
413
+ * `harness` claim that authorizes anything the truthful claim would not.
414
+ */
415
+ execution: "harness" | null;
416
+ /**
417
+ * The deadline the requester says it will wait until, ISO-8601, or `null`.
418
+ *
419
+ * Display only, and claimed: channels render it so an approver can see that
420
+ * an answer after this instant reaches nobody (APRV-106). Nothing in the
421
+ * gate, the token module, the budgets or the TTL reads it — the TTL is the
422
+ * policy's and is the only deadline that governs anything — so a requester
423
+ * that lies about it can only make its own request look MORE urgent than it
424
+ * is, never less.
425
+ */
426
+ wait_until: string | null;
427
+ /**
428
+ * The SHA-256 of the attested policy in force when the runtime evaluated the
429
+ * request (APRV-118, amended SPEC.md §5.2), or `null` for a record written
430
+ * before the field existed.
431
+ *
432
+ * Computed, never claimed: the requester has no parameter that reaches it, and
433
+ * the gate assigns it at the write boundary from its own read of the attested
434
+ * file, exactly as it assigns `ts`. The grant path compares it against the hash
435
+ * in force at decision time and refuses `policy-drift` on a difference, so a
436
+ * value that disagrees with the runtime's can only refuse a grant, never
437
+ * produce one.
438
+ */
439
+ policy_sha256: string | null;
440
+ }
441
+ /** Execution facts for the action key: the seq of each event, or `null`. */
442
+ export interface ExecutionFacts {
443
+ started: number | null;
444
+ completed: number | null;
445
+ failed: number | null;
446
+ }
447
+ /** The full derivation, so a caller never re-walks the log to explain itself. */
448
+ export interface RequestDerivation {
449
+ actionKey: string;
450
+ state: RequestState;
451
+ task: string | null;
452
+ requestSeq: number | null;
453
+ requestTs: string | null;
454
+ /**
455
+ * The actor of the `approval.requested` record that opened the current cycle
456
+ * (APRV-106). The gate compares a withdrawal's actor against it: only the
457
+ * party that asked may take the question back.
458
+ */
459
+ requestActor: string | null;
460
+ decision: "granted" | "rejected" | "revoked" | "expired" | "withdrawn" | null;
461
+ decisionSeq: number | null;
462
+ decisionTs: string | null;
463
+ /** An `approval.expired` record exists for the current request. */
464
+ expiredByEvent: boolean;
465
+ /** The TTL lapsed by arithmetic, with no `approval.expired` record. */
466
+ expiredLazily: boolean;
467
+ declared: DeclaredAction;
468
+ execution: ExecutionFacts;
469
+ }
470
+ /** A record's payload as a map, or `{}` when it has none. */
471
+ export declare function payloadOf(record: EventRecord): Record<string, unknown>;
472
+ /**
473
+ * Derive `actionKey`'s approval state from `records`.
474
+ *
475
+ * Pure: no I/O, no clock. `ts` is the moment the question is being asked and is
476
+ * **required** — lazy expiry is arithmetic on it, and a state function that read
477
+ * the clock could not be replayed.
478
+ *
479
+ * Sequencing rules, all of them deliberate:
480
+ *
481
+ * - An `approval.requested` **resets** the derivation. A key that was rejected
482
+ * or expired may be requested again; the new request starts a fresh cycle and
483
+ * the old decision no longer governs. (An action that has *executed* is a
484
+ * different matter — `core/gate.ts`'s `request` refuses that on idempotency
485
+ * grounds.)
486
+ * - The **first** decision after a request wins. The gate refuses to append a
487
+ * second one, so a log carrying two is a log written by something else; the
488
+ * fail-closed reading is that the earliest human decision stands rather than
489
+ * that a later append can overwrite it.
490
+ * - Execution facts accumulate across the whole log for the key, independent of
491
+ * the request cycle: an action that has executed has executed, and no
492
+ * subsequent request un-executes it.
493
+ * - Withdrawal (APRV-106): an `approval.withdrawn` settles the request like any
494
+ * other terminal event, and is the only one of them that records no decision.
495
+ * It participates in the "first settlement wins" rule above, so a withdrawal
496
+ * appended after a human's answer does not erase the answer; the gate refuses
497
+ * to append one at all in that case.
498
+ * - Expiry: an `approval.expired` record sets `expiredByEvent`. With no such
499
+ * record, `ttlMs !== null` and `ts > requestTs + ttlMs` sets `expiredLazily`.
500
+ * Both yield `state: "expired"`. An unparseable `requestTs` or `ts` also
501
+ * yields `expired`: liveness that cannot be demonstrated is not assumed. (The
502
+ * event schema's `date-time` format makes that unreachable through the real
503
+ * append path; it is a backstop, not a live branch.)
504
+ */
505
+ export declare function requestState(records: EventRecord[], actionKey: string, ts: string, ttlMs: number | null): RequestDerivation;