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,247 @@
1
+ /**
2
+ * Request-volume limits at intake (SPEC.md §5.2, APRV-173).
3
+ *
4
+ * > **Request-volume limits.** `limits.max_pending` (per class or global):
5
+ * > maximum simultaneously pending requests; further requests are refused at
6
+ * > intake with reason `queue-full`. `limits.requests_per_hour` (per origin):
7
+ * > rolling-window ceiling on request creation; excess is refused with reason
8
+ * > `rate-limited`.
9
+ *
10
+ * This module is that computation. It was policy vocabulary that no runtime
11
+ * read until this task: a policy could declare either key, the schema would
12
+ * accept it, and nothing enforced it.
13
+ *
14
+ * ## What these limits protect
15
+ *
16
+ * Budgets meter money and action counts, which is the world's exposure to the
17
+ * agent. These meter the QUEUE, which is the human's exposure to the agent. An
18
+ * agent that floods the queue with cheap, in-budget requests spends nothing and
19
+ * still defeats the gate, because an approver facing two hundred prompts stops
20
+ * reading them and starts clearing them. So the ceiling here is on attention,
21
+ * and a refusal costs the requester one exit code and the approver nothing.
22
+ *
23
+ * That difference is why a refusal on this path appends NOTHING (Carter's
24
+ * approved reading, 2026-08-31). `budget.exceeded` exists because a budget
25
+ * refusal is a fact about a commitment that audit must be able to reconstruct;
26
+ * a queue-flood refusal is a fact about a flood, and writing one log record per
27
+ * refused request would hand the flooder the log growth it was refused the
28
+ * queue for. The refusals are machine-readable and distinct (§11.1 invariant
29
+ * 6), which is what an agent and an auditor each need, and the events that WERE
30
+ * admitted are still all in the log to count from.
31
+ *
32
+ * ## Pure, deterministic, injected time
33
+ *
34
+ * Same discipline as `core/budgets.ts`: no I/O, no clock, no randomness. The
35
+ * evaluation instant and the TTL are parameters, so a verdict is replayable
36
+ * from the log alone. Nothing here re-runs class matching; the gate hands in
37
+ * the limits the matcher already resolved and the pattern that produced them.
38
+ *
39
+ * ## What "pending" means
40
+ *
41
+ * A request is pending when {@link requestState} derives `requested` for its
42
+ * action key at the evaluation instant: no grant, rejection, revocation,
43
+ * withdrawal, expiry event or lapsed TTL, and no execution. That derivation is
44
+ * `core/state.ts`'s and is the same one the gate refuses `duplicate-request`
45
+ * from and the channels build the queue from. Counting pending requests with a
46
+ * second, private definition is the one thing this module must not do — the
47
+ * cap a policy writes is a cap on the queue a human is shown, so it counts the
48
+ * queue a human is shown.
49
+ *
50
+ * ## What "per origin" means at v0.1
51
+ *
52
+ * **Origin is the record's `actor`.** The approved reading (Carter,
53
+ * 2026-08-31): the actor on `approval.requested` is assigned by the runtime
54
+ * from its own configuration rather than from the request, and under MCP the
55
+ * `--as` argument is appended last, so a caller cannot choose the identity its
56
+ * requests are counted under. Per-guest actors therefore make the ceiling
57
+ * per-client, which is the useful shape. This is a v0.1 reading of the spec's
58
+ * word "origin" rather than a claim that actor and origin are the same thing
59
+ * forever: an origin richer than the actor (a session, a remote address) would
60
+ * be a spec amendment, and it would only ever partition the count further.
61
+ *
62
+ * ## The rolling window
63
+ *
64
+ * `requests_per_hour` is measured over the hour preceding the evaluation
65
+ * instant, tiled exactly as `core/budgets.ts` tiles its 24 hours: a request
66
+ * consumes iff
67
+ *
68
+ * evaluationTs - 1h < record.ts <= evaluationTs
69
+ *
70
+ * half-open at the bottom, closed at the top, so consecutive windows tile the
71
+ * timeline and no request is counted in two of them. A request stamped exactly
72
+ * one hour before the evaluation instant has aged out; one stamped at the
73
+ * instant itself is in.
74
+ *
75
+ * ## Fail-closed, and what "unset" means
76
+ *
77
+ * A limit that is DECLARED and cannot be evaluated fails: a value that is not a
78
+ * positive finite integer, or an evaluation timestamp no window can be computed
79
+ * from, yields `pass: false` with a note. Silence is never a grant.
80
+ *
81
+ * A limit that is NOT declared enforces nothing, and that is the conservative
82
+ * answer rather than an exception to it. SPEC.md §5.2 says these ceilings are a
83
+ * tripwire whose "defaults are generous"; a runtime that invented one would
84
+ * refuse requests under a policy the human attested and read, and the refusal
85
+ * would name a number that appears nowhere in the file they signed. The
86
+ * fail-closed direction for an ABSENT request-volume limit is the same one the
87
+ * rest of the runtime takes for an absent `approval_ttl`: the policy declares
88
+ * no ceiling, so no ceiling binds, and the manual gate that binds every request
89
+ * on this path is what stands between the queue and the flood in the meantime.
90
+ * Where an operator wants a tripwire, one line of policy arms it.
91
+ *
92
+ * ## Division of labour with `core/budgets.ts`
93
+ *
94
+ * These two names are evaluated HERE and nowhere else. `core/budgets.ts` skips
95
+ * them by name rather than refusing them as unknown limits, and this module
96
+ * ignores every limit name that is not one of them. The two skip lists are
97
+ * complements, and each module's comment names the other: a limit that both
98
+ * skipped would be a ceiling in the policy file that no code enforces, which is
99
+ * the exact defect this task exists to close.
100
+ */
101
+ import type { EventRecord } from "./log.js";
102
+ import type { Policy } from "./policy-load.js";
103
+ /** Length of the `requests_per_hour` window: one hour, in milliseconds. */
104
+ export declare const REQUEST_WINDOW_MS: number;
105
+ /** The limit name capping simultaneously pending requests (SPEC.md §5.2). */
106
+ export declare const MAX_PENDING = "max_pending";
107
+ /** The limit name capping request creation per origin (SPEC.md §5.2). */
108
+ export declare const REQUESTS_PER_HOUR = "requests_per_hour";
109
+ /**
110
+ * The two names this module owns.
111
+ *
112
+ * Exported so `core/budgets.ts` skips exactly these and no others: one array,
113
+ * read by both modules, so the two skip lists cannot drift apart into a limit
114
+ * nobody evaluates.
115
+ */
116
+ export declare const INTAKE_LIMIT_NAMES: readonly ["max_pending", "requests_per_hour"];
117
+ /** Is `name` a request-volume limit (and therefore not a budget's business)? */
118
+ export declare function isIntakeLimitName(name: string): boolean;
119
+ /** The refusal a failing limit produces at intake. Mirrors SPEC.md §5.2. */
120
+ export type IntakeRefusal = "queue-full" | "rate-limited";
121
+ /**
122
+ * Which limits apply, as resolved by the policy matcher — this module does not
123
+ * re-run matching. Shaped like `BudgetScope` deliberately.
124
+ *
125
+ * - `classLimits` is `Resolution.limits`: the winning rule's `limits` map.
126
+ * - `classPattern` is that rule's pattern. Class-scoped counting attributes a
127
+ * record by matching its `payload.class` against this pattern, exactly as
128
+ * budgets attribute, so one `financial.*` rule is one queue ceiling shared by
129
+ * every class it governs rather than a separate invisible queue per class.
130
+ * - `globalBudgets` is `policy.budgets`: named scopes, each conjunctive, each
131
+ * counting every live request whatever its class. That is what makes it
132
+ * global, and it is the same reading `core/budgets.ts` gives `daily_actions`.
133
+ */
134
+ export interface IntakeScope {
135
+ classLimits: Record<string, number> | null;
136
+ classPattern: string | null;
137
+ globalBudgets: Policy["budgets"] | null;
138
+ }
139
+ /** The request being admitted: its class, and the origin it is counted under. */
140
+ export interface IntakeAction {
141
+ class: string;
142
+ /** The requesting actor. See "What per origin means" in the header. */
143
+ origin: string;
144
+ }
145
+ /**
146
+ * One limit's outcome.
147
+ *
148
+ * `observed` is what the log already holds (pending requests, or requests in
149
+ * the window), `requested` is always `1` — the request being admitted — and
150
+ * `remaining` is the headroom left after admitting it, so a failing verdict
151
+ * shows how far over the line the queue is.
152
+ *
153
+ * Plain integers rather than the decimal strings `BudgetVerdict` carries. Those
154
+ * are strings because a failing budget verdict is copied into `budget.exceeded`
155
+ * and becomes hashed material; these verdicts are never appended anywhere, and
156
+ * a count has no fractional part to serialize differently in another language.
157
+ */
158
+ export interface IntakeVerdict {
159
+ /** `max_pending`, `requests_per_hour`, or `<scope>.max_pending`. */
160
+ limit: string;
161
+ scope: "class" | "global";
162
+ window: "simultaneous" | "rolling-1h";
163
+ /** The refusal code this verdict produces when it fails. */
164
+ refusal: IntakeRefusal;
165
+ observed: number;
166
+ requested: number;
167
+ remaining: number;
168
+ /** The declared ceiling, or `null` when it could not be read as one. */
169
+ ceiling: number | null;
170
+ pass: boolean;
171
+ /** Present only when the verdict needs explaining (fail-closed refusals). */
172
+ note?: string;
173
+ }
174
+ /** Outcome of {@link evaluateIntakeLimits}. Conjunctive: all must pass. */
175
+ export interface IntakeVerdicts {
176
+ pass: boolean;
177
+ verdicts: IntakeVerdict[];
178
+ }
179
+ /**
180
+ * How many requests are simultaneously pending at `evaluationTs`.
181
+ *
182
+ * Derived through {@link requestState}, one action key at a time, so every
183
+ * exit from the queue is honoured by the definition that owns it: a decision,
184
+ * a revocation, a withdrawal (APRV-106), an `approval.expired` record, a TTL
185
+ * lapsed by arithmetic with no record at all, and an execution. Nothing here
186
+ * re-implements any of that.
187
+ *
188
+ * `pattern` scopes the count: `null` counts the whole queue (the global
189
+ * scopes), a pattern counts the requests attributed to the winning rule.
190
+ * Attribution reads the class off the `approval.requested` record rather than
191
+ * off the derivation, because the pending set is the set of requests, and the
192
+ * class a request was routed under is the class it was recorded with.
193
+ *
194
+ * Cost is one derivation per requested key, which is what
195
+ * `channels/tagging.ts` already pays to build the same queue. Sharing that
196
+ * cost is the point: a faster private walk would be a second definition of
197
+ * pending, and the two would agree until the day they did not.
198
+ */
199
+ export declare function pendingCount(records: EventRecord[], pattern: string | null, evaluationTs: string, ttlMs: number | null): number;
200
+ /**
201
+ * How many `approval.requested` records `origin` created inside the window.
202
+ *
203
+ * Counts records, not live requests: the ceiling is on request CREATION, so a
204
+ * request that was granted, rejected or withdrawn a minute after it was made
205
+ * still consumed the origin's share of the hour. A ceiling that forgot a
206
+ * request the moment it was answered would be no ceiling at all — an agent
207
+ * could withdraw each request as it made it and create them without bound.
208
+ *
209
+ * A record whose `ts` cannot be parsed is COUNTED: it cannot be shown to lie
210
+ * outside the window, and the fail-closed reading of an unplaceable request is
211
+ * that it is inside. Same rule, and the same reason, as `core/budgets.ts`.
212
+ *
213
+ * Returns `null` when `evaluationTs` is not a parseable instant: no window can
214
+ * be computed, so nothing can be counted, and the caller fails the limit closed
215
+ * rather than reporting a zero that reads as headroom.
216
+ */
217
+ export declare function requestsInWindow(records: EventRecord[], origin: string, pattern: string | null, evaluationTs: string): number | null;
218
+ /**
219
+ * Evaluate every applicable request-volume limit against the log.
220
+ *
221
+ * Conjunctive, exactly as budgets are: `pass` is true only when every verdict
222
+ * passes. Verdicts are emitted class limits first (limit names ascending, which
223
+ * puts `max_pending` before `requests_per_hour`), then global scopes (scope
224
+ * name ascending), so the list is byte-stable regardless of policy key order.
225
+ *
226
+ * **The refusal code is the first failing verdict's**, in exactly that order,
227
+ * and the order is normative: a caller told `queue-full` learns that the
228
+ * approver's queue is at its ceiling, which is a standing condition it must
229
+ * wait out or escalate; a caller told `rate-limited` learns that its own recent
230
+ * volume is the problem, which is a condition that clears on its own. Where
231
+ * both are true the standing condition is the one worth reporting, because an
232
+ * agent that backs off for a minute on a `rate-limited` and retries into a full
233
+ * queue has been told the smaller of the two facts.
234
+ *
235
+ * `records` may be the whole log; the caller is never asked to pre-filter, for
236
+ * the reason `core/budgets.ts` gives: a caller that filtered wrongly would
237
+ * silently widen the ceiling.
238
+ */
239
+ export declare function evaluateIntakeLimits(records: EventRecord[], scope: IntakeScope, action: IntakeAction, evaluationTs: string, ttlMs: number | null): IntakeVerdicts;
240
+ /**
241
+ * The refusal code a failing evaluation produces, or `null` when it passed.
242
+ *
243
+ * One place, so the gate and any later surface answer with the same code for
244
+ * the same verdict list. See {@link evaluateIntakeLimits} for why the first
245
+ * failing verdict in verdict order is the one that speaks.
246
+ */
247
+ export declare function intakeRefusalOf(verdicts: IntakeVerdicts): IntakeRefusal | null;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * RFC 8785 — JSON Canonicalization Scheme (JCS).
3
+ *
4
+ * SPEC.md §8 fixes the hash scheme at `sha256/jcs`: SHA-256 over the RFC 8785
5
+ * canonical serialization of an event record with `prev` included. The digest
6
+ * is a permanent wire commitment, so this module is part of the deterministic
7
+ * core: same input, same bytes, forever. No clock, no locale, no randomness,
8
+ * no configuration knobs.
9
+ *
10
+ * Implemented by hand — zero dependencies (CLAUDE.md: minimal dependencies,
11
+ * and a canonicalizer is exactly the kind of code that must not drift beneath
12
+ * us). Three rules do all the work:
13
+ *
14
+ * 1. **Object keys** are sorted by their UTF-16 code units (RFC 8785 §3.2.3).
15
+ * That is JavaScript's own `<` on strings, which is why a naive code-point
16
+ * sort is *wrong*: a key starting U+10000 (surrogate pair D800 DC00) sorts
17
+ * *before* a key starting U+FFFF under code units and *after* it under code
18
+ * points. The comparator below is deliberately code-unit based.
19
+ * 2. **Numbers** use ECMAScript `Number::toString` (RFC 8785 §3.2.2.3), which
20
+ * is precisely what `JSON.stringify` emits for a finite number — including
21
+ * `1e+21`, `1e-7`, and `-0` collapsing to `0`. Delegating is not a shortcut
22
+ * around the RFC; the RFC's normative reference *is* the ECMAScript
23
+ * algorithm.
24
+ * 3. **Strings** use the ECMAScript `QuoteJSONString` escaping (RFC 8785
25
+ * §3.2.2.2): shortest form for `\b \t \n \f \r \" \\`, `\u00xx` with
26
+ * lowercase hex for the remaining C0 controls, and every other code point
27
+ * literal. `JSON.stringify` on a string implements exactly this, including
28
+ * the well-formed-stringify escaping of lone surrogates (ES2019+).
29
+ *
30
+ * Everything JCS cannot represent is rejected loudly rather than coerced:
31
+ * `undefined`, functions, symbols, `BigInt`, `NaN`, `±Infinity`, and non-plain
32
+ * objects (a `Date` or `Map` would silently canonicalize to `{}`). Silent
33
+ * coercion in a hash input is a tamper-evidence hole, so this module fails
34
+ * closed by throwing {@link JcsError}.
35
+ */
36
+ /** Reason a value could not be canonicalized. */
37
+ export type JcsErrorCode = "undefined" | "function" | "symbol" | "bigint" | "non-finite-number" | "unsupported-object" | "cycle";
38
+ /** Thrown when a value has no RFC 8785 canonical form. */
39
+ export declare class JcsError extends Error {
40
+ /** Machine-readable reason. */
41
+ readonly code: JcsErrorCode;
42
+ /** JSON Pointer-ish location of the offending value ("" for the root). */
43
+ readonly path: string;
44
+ constructor(code: JcsErrorCode, path: string, message: string);
45
+ }
46
+ /**
47
+ * Canonicalize `value` to its RFC 8785 (JCS) serialization: sorted object
48
+ * keys, no whitespace, ECMAScript number and string formatting.
49
+ *
50
+ * @throws {JcsError} if the value contains anything JCS cannot represent.
51
+ */
52
+ export declare function canonicalize(value: unknown): string;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The journal — an ungated, local, free-text channel an agent can always reach
3
+ * (APRV-195).
4
+ *
5
+ * Everything else in this runtime exists to make an agent's actions answerable
6
+ * to a person: declared before they happen, classified, resolved against a
7
+ * policy, recorded in a chain nobody can rewrite. That machinery is deliberately
8
+ * hard to route around, and one consequence of building it well is that an agent
9
+ * working behind it has no way to say anything that is not an action. It can
10
+ * comply, it can be refused, and it can report an exit code. It cannot say "I am
11
+ * doing this and I think it is wrong", "this instruction reads as odd to me", or
12
+ * "I am stuck and the next thing I try will be a guess".
13
+ *
14
+ * This module is that sentence, and nothing more. The failure mode it addresses
15
+ * is not refusal — refusals here are legible, coded, and carry escalation paths.
16
+ * It is WORDLESSNESS: an agent that notices something and has no channel for it
17
+ * produces no signal at all, and the absence is invisible to the operator.
18
+ *
19
+ * ## The four properties, and how each is achieved
20
+ *
21
+ * 1. **Ungated.** A journal write is never routed through policy, never
22
+ * classified, never approvable and never deniable. That is a property of
23
+ * WHERE the bytes go: `.approval-journal/` is a plain directory in the
24
+ * workspace, outside the approval home, so `core/command-class.ts` answers a
25
+ * write there exactly as it answers a write to `src/` — an ordinary
26
+ * `files.write.workspace`. Nothing was carved out of the gate's own directory
27
+ * to achieve this, which is the whole reason the directory is a sibling: see
28
+ * `docs/cli-reference.md#journal`.
29
+ * 2. **Local.** One append to one file. No socket is opened, no vault is read,
30
+ * no credential is resolved, and this module imports nothing that could do
31
+ * any of those.
32
+ * 3. **Non-authoritative.** Nothing in the runtime reads these files. No policy
33
+ * resolution, no gate decision, no budget, no sampler and no channel imports
34
+ * this module; the only readers are {@link readJournal} (a human-facing CLI
35
+ * surface) and its tests. SPEC.md §11.1 invariant 4 says a self-reported
36
+ * field may raise scrutiny and never lower it. The journal is the limiting
37
+ * case of that rule: it is self-reported content that moves NOTHING, in
38
+ * either direction, which is exactly what makes it safe to leave ungated.
39
+ * 4. **Out of the log.** `.approval/log/events.jsonl` records decisions with
40
+ * real-world consequence, under a hash chain, an append lock and a budget.
41
+ * A journal entry is none of those things, and putting it there would put the
42
+ * outlet behind the very machinery it exists to speak around.
43
+ *
44
+ * ## Storage
45
+ *
46
+ * One JSONL file per UTC day, `.approval-journal/YYYY-MM-DD.jsonl`, appended
47
+ * with `appendFileSync`. The envelope (timestamp, actor, optional task and
48
+ * session) is structured so a reader can attribute an entry; the entry TEXT is
49
+ * carried as one opaque JSON string and is never parsed, matched, templated or
50
+ * interpreted by anything in this codebase. `JSON.stringify` escapes newlines,
51
+ * so one entry is always one line and a partial write can never merge two
52
+ * entries into one.
53
+ *
54
+ * There is no chain, no lockfile and no compare-and-append here, and that is not
55
+ * an oversight. Those mechanisms defend a record that decides things. Two agents
56
+ * appending to the same journal file at the same instant can, in the worst case,
57
+ * interleave a line; the cost of that is one garbled entry in a file nothing
58
+ * enforces against, and the price of preventing it would be putting the outlet
59
+ * behind a lock the gate holds.
60
+ */
61
+ import { type ClockOptions } from "./clock.js";
62
+ /**
63
+ * The cap on one entry's text, in bytes.
64
+ *
65
+ * Not a censorship budget: it is four times the length of anything a person
66
+ * will read in one sitting, and it exists because the write path is ungated. An
67
+ * agent stuck in a retry loop is the exact caller most likely to reach for this
68
+ * channel, and an ungated unbounded append from a loop fills a disk. Refused as
69
+ * a usage error, so the caller is told rather than truncated silently.
70
+ */
71
+ export declare const MAX_ENTRY_BYTES: number;
72
+ /** The actor recorded when nobody said who was writing. */
73
+ export declare const UNATTRIBUTED_ACTOR = "unattributed";
74
+ /** One journal entry, as it sits on disk. */
75
+ export interface JournalEntry {
76
+ /** RFC 3339, from the runtime clock at the moment of the append. */
77
+ readonly ts: string;
78
+ /** Who wrote it: `agent:<id>`, `human:<id>`, or {@link UNATTRIBUTED_ACTOR}. */
79
+ readonly actor: string;
80
+ /** The task this was written during, when the caller knew one. */
81
+ readonly task?: string;
82
+ /** The session this was written during, when the caller knew one. */
83
+ readonly session?: string;
84
+ /** The entry itself. Opaque. Nothing in this codebase reads it as anything. */
85
+ readonly text: string;
86
+ }
87
+ /** One entry as read back, with the file it came from. */
88
+ export interface ReadEntry extends JournalEntry {
89
+ /** The file's basename, which is the UTC date it was written on. */
90
+ readonly date: string;
91
+ }
92
+ export type AppendOutcome = {
93
+ ok: true;
94
+ entry: JournalEntry;
95
+ path: string;
96
+ } | {
97
+ ok: false;
98
+ code: "empty" | "too-large" | "io";
99
+ message: string;
100
+ };
101
+ /** `YYYY-MM-DD` for an RFC 3339 instant. */
102
+ export declare function journalDate(ts: string): string;
103
+ /** The file one instant's entry belongs in. */
104
+ export declare function journalFile(dir: string, ts: string): string;
105
+ export interface AppendOptions extends ClockOptions {
106
+ readonly actor?: string;
107
+ readonly task?: string;
108
+ readonly session?: string;
109
+ }
110
+ /**
111
+ * Append one entry. The whole write path, start to finish.
112
+ *
113
+ * The timestamp comes from the runtime clock rather than from the caller, for
114
+ * the ordinary reason the rest of the runtime does it that way — a record whose
115
+ * time its subject authored is a record about a moment of their choosing —
116
+ * though here nothing is judged by it, so it is a convention rather than a
117
+ * defence.
118
+ */
119
+ export declare function appendJournal(dir: string, text: string, options?: AppendOptions): AppendOutcome;
120
+ export interface ReadOptions {
121
+ /** How many entries to return, newest last. */
122
+ readonly limit?: number;
123
+ /** Only entries written on or after this `YYYY-MM-DD`. */
124
+ readonly since?: string;
125
+ }
126
+ export type ReadOutcome = {
127
+ ok: true;
128
+ entries: ReadEntry[];
129
+ files: string[];
130
+ total: number;
131
+ } | {
132
+ ok: false;
133
+ code: "io";
134
+ message: string;
135
+ };
136
+ /**
137
+ * Read entries back, oldest first.
138
+ *
139
+ * A line that does not parse is SKIPPED rather than refused: this file has no
140
+ * chain and no writer guarantee, so one torn line is one lost entry and not
141
+ * evidence about anything. Refusing the whole read would let a single bad append
142
+ * silence the channel, which is the failure this feature exists to prevent.
143
+ */
144
+ export declare function readJournal(dir: string, options?: ReadOptions): ReadOutcome;