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
@@ -14,6 +14,10 @@ a gate refusal is exit 1 and not 2, approval events are exclusive to the manual
14
14
  path, the raw token is shown once, a channel is transport — are stated once at
15
15
  the top of `approval --help` and are not repeated here.
16
16
 
17
+ `approval --version`, `approval -v`, and `approval version` print the package
18
+ version and exit 0. These aliases apply only at the top level; a version-looking
19
+ flag after a verb remains that verb's argument.
20
+
17
21
  Each section below is what the corresponding `--help` points at with its
18
22
  `why: docs/cli-reference.md#…` footer.
19
23
 
@@ -35,11 +39,12 @@ resolved, and nothing is written.
35
39
 
36
40
  ## log
37
41
 
38
- The three subcommands open the log for reading only. `verify` walks the hash chain
42
+ Four subcommands open the log for reading only. `verify` walks the hash chain
39
43
  end to end and reports clean | torn-tail | corrupt, `tail` prints the last N
40
- records (default 10), and `export` streams every stored line to stdout, byte for
41
- byte. The default log path is `.approval/log/events.jsonl`, relative to the
42
- working directory.
44
+ records (default 10), `export` streams every stored line to stdout byte for
45
+ byte, and `follow` emits verified records after an exclusive cursor before
46
+ waiting for appends. The default log path is `.approval/log/events.jsonl`,
47
+ relative to the working directory.
43
48
 
44
49
  ## log verify
45
50
 
@@ -297,6 +302,47 @@ that is due to a refusal of anything: it is a warning on `log verify`, a
297
302
  doctor`'s `checkpoint` row. A gate that held up an action for want of a tap is a
298
303
  gate whose operator turns the check off.
299
304
 
305
+ ## log follow
306
+
307
+ `approval log follow --from <seq> --json` is the channel-independent decision
308
+ listener. `--from` is exclusive and defaults to zero, so a new consumer first
309
+ receives the entire verified log. Output is JSON Lines: one complete stored
310
+ event object per line. It emits every event type in chain order; a refund or
311
+ queue consumer selects the decisions relevant to its own work. The command then
312
+ remains in the foreground and exits 0 on SIGINT, SIGTERM, or a closed downstream
313
+ pipe. Signal cancellation may interrupt the final native stdout write. Consumers
314
+ must process only newline-terminated JSON records, discard any incomplete final
315
+ fragment, and reconnect from the cursor of the last complete record they
316
+ processed.
317
+
318
+ Filesystem notifications only prompt another read. On every notification wake
319
+ and every bounded 500ms fallback poll, the runtime rereads and verifies the
320
+ complete chain from genesis through the observed head, even when the log has not
321
+ changed. It emits nothing from a corrupt, torn, unreadable, truncated, or
322
+ cursor-mismatched snapshot. Pull backpressure means a slow consumer holds one
323
+ verified snapshot and no growing notification or event queue. Each check costs
324
+ O(N) verification time and O(N) snapshot memory for a log of N records. This
325
+ bounded initial implementation is not an incremental, low-overhead tail.
326
+
327
+ `--cursor-hash <64hex>` supplies the hash of record `--from`. Store both the
328
+ sequence and hash outside the log and pass both on reconnect. That binding
329
+ detects a truncated or replaced prefix. A sequence alone is a weaker bootstrap:
330
+ an internally valid rewritten prefix with the same sequence numbers cannot be
331
+ distinguished from the original, for the same reason an unanchored hash chain
332
+ cannot detect a fully recomputed forgery.
333
+
334
+ Delivery across reconnects is at least once. Apply the external effect first,
335
+ then persist the emitted event's `seq` and `hash`; a crash between those steps
336
+ replays the event. Consumers that require exactly-once external effects must
337
+ make their own effect idempotent or transact it with cursor storage. Persisting
338
+ the cursor before the effect instead risks silently losing that effect.
339
+
340
+ The stream uses the existing exit classes: 1 for corrupt or mismatched cursor,
341
+ 2 for usage, 3 for a torn tail, and 4 for I/O. Its error object is written to
342
+ stderr and no event from the refused batch is written to stdout. `log follow`
343
+ is deliberately absent from MCP because one unbounded call would occupy the
344
+ finite MCP call queue; run it as a separate CLI process.
345
+
300
346
  ## log tail
301
347
 
302
348
  The chain is verified first. On a torn tail the intact records are printed and the
@@ -439,6 +485,20 @@ range they cover, and pushed to a short-lived records branch that exists for
439
485
  exactly that commit. Main is protected here, so the commit reaches it through a
440
486
  pull request; `--pr` opens that pull request through the ordinary `gh` path.
441
487
 
488
+ `--co-author "Name <email>"` adds one validated `Co-authored-by` trailer to the
489
+ generated records commit and to the pull request body. When the day's pull
490
+ request already exists, the verb preserves its body and adds the trailer once.
491
+ This is display credit only. It does not set an event actor, name an approver,
492
+ grant authority, or derive an identity from the log. Omitting the flag preserves
493
+ the existing commit message, pull request body, and merge command byte for byte.
494
+
495
+ The merge queue ignored the custom auto-merge commit body observed on PR 378.
496
+ For a queued merge commit to retain this credit, the repository must use GitHub's
497
+ PR-body merge-message setting (`merge_commit_message=PR_BODY`); the package does
498
+ not change repository settings. The PR body is therefore the durable source the
499
+ queue can copy, rather than a claim that `gh pr merge --body` controls the final
500
+ queued merge.
501
+
442
502
  **You do not fetch or reset first (APRV-203).** The verb owns its own git
443
503
  preconditions: it fetches the base branch (the one you are standing on, or
444
504
  `--base <name>`), builds the commit on `origin/<base>` in a scratch index rather
@@ -536,7 +596,8 @@ is not manual:
536
596
  policy was read and understood, and it says ask.
537
597
  - `irreversibility-floor` — policy granted autonomous or supervised and SPEC §7's
538
598
  floor overrode it because `--reversible false` was given. `overridden` records
539
- what policy actually said.
599
+ what policy actually said. The floor remains the default when a class rule
600
+ omits `allow_irreversible` or writes `false`.
540
601
  - `load-failure` — the policy could not be loaded at all, so every class is
541
602
  manual. `loadFailure` carries a code and a message.
542
603
 
@@ -561,14 +622,26 @@ The exit codes, at length. `policy check|test` uses only 0, 2 and 4:
561
622
  key may contain, never something an agent can do.
562
623
 
563
624
  `--reversible` takes an explicit value because "unstated", "reversible" and
564
- "irreversible" are three different questions. Only the explicit `false` engages
565
- SPEC §7's irreversibility floor.
625
+ "irreversible" are three different questions. Explicit `false` asks for the
626
+ effective irreversible answer. It resolves to `manual` by default. A nonmanual
627
+ class retains its autonomy only when every equally most-specific matching rule
628
+ sets `allow_irreversible: true`. Lower-specificity rules do not vote, defaults
629
+ cannot opt in, and neither this flag nor any other action metadata can create
630
+ the permission. Manual remains manual and human-only remains human-only.
631
+
632
+ For an allowed `supervised-live` class, the existing live sampler decides
633
+ whether this action prompts before execution. Allowed `supervised` and
634
+ `supervised-retro` actions execute first and remain eligible for retrospective
635
+ review. Allowed autonomous actions proceed without either review path. Telegram
636
+ appears only when the effective path actually requests approval through that
637
+ channel; policy-authorized execution is never represented as a human grant.
566
638
 
567
639
  **`--json`** (one object on stdout):
568
640
 
569
641
  ```
570
642
  {"class":"vcs.push.main","reversible":null,
571
- "outcome":{"autonomy":"supervised","approvers":null,"limits":null},
643
+ "outcome":{"autonomy":"supervised","approvers":null,"limits":null,
644
+ "allowIrreversible":false},
572
645
  "provenance":"rule"|"default"|"inherited"|"fail-closed"|"floor",
573
646
  "manualBecause":null|"matched-rule"|"irreversibility-floor"|"load-failure",
574
647
  "loadFailure":null|{"code":"file-missing"|"no-block"|"multiple-blocks"|
@@ -576,6 +649,9 @@ SPEC §7's irreversibility floor.
576
649
  "message":"..."},
577
650
  "matched":null|{"pattern":"vcs.push.main","rule":{"autonomy":"supervised"}},
578
651
  "overridden":null|{"pattern":"read.web"|null,"autonomy":"autonomous"},
652
+ "irreversibility":"not-applicable"|"policy-allowed"|"floor-applied"|
653
+ "already-manual"|"human-only",
654
+ "irreversiblePatterns":["read.*"],
579
655
  "candidates":[{"pattern":"read.*","specificity":[1,1,2],
580
656
  "autonomy":"autonomous","winner":true,
581
657
  "tieBreak":"specificity"|"strictest-autonomy"|
@@ -1155,6 +1231,13 @@ hash must equal the declared `payload_hash` and it is filed in
1155
1231
  bytes from. Supply it here once and no channel needs `--payload-dir` or
1156
1232
  `--payloads` at all.
1157
1233
 
1234
+ When the policy permits an unattended action, supplied material is still checked
1235
+ against the registered action's task, class and payload hash and retained before
1236
+ `proceed:true` is returned. This creates no approval event and does not require
1237
+ an extra human grant. The retained bytes let later audits match the execution
1238
+ record to the actual edit. Missing bindings, mismatched material or a storage
1239
+ failure refuse the request; an existing valid payload is preserved.
1240
+
1158
1241
  **`--json`** (one object on stdout):
1159
1242
 
1160
1243
  ```
@@ -2529,6 +2612,16 @@ The checks, at length:
2529
2612
  where there is nothing to commit a key to. Neither fix deletes nor commits:
2530
2613
  `git rm --cached` for a key already in the index is named in the prose and
2531
2614
  left to you, along with revoking every action whose token is still unspent.
2615
+ - **codex-hook-wiring** — whether this checkout's `.codex/hooks.json` carries
2616
+ the reviewed approval.md profile for both `PreToolUse` and `PostToolUse`: the
2617
+ exact `Bash|apply_patch` matcher, a direct synchronous `approval hook codex`
2618
+ command, and a `600` second outer timeout. A PASS establishes only those JSON
2619
+ bytes on disk. Codex trust, loading, and observed execution remain separate
2620
+ facts checked through `/hooks` and the bounded smoke test. TOML-only hook
2621
+ configuration, or JSON combined with `.codex/config.toml`, SKIPS because
2622
+ doctor does not interpret or merge the TOML hook tables. Malformed JSON
2623
+ FAILS; a different valid Codex hook profile SKIPS as undetermined rather than
2624
+ being called broken.
2532
2625
 
2533
2626
  **`--json`** (one object on stdout):
2534
2627
 
@@ -2684,9 +2777,10 @@ and cannot do. It selects the SHAPE of an obligation that exists either way; it
2684
2777
  cannot remove one, delay one, or decide whether the denial happened. The only
2685
2778
  thing a false `reversible: true` buys is the shape whose discharge this runtime
2686
2779
  checks against the chain, which makes the claimant's own exit harder rather than
2687
- easier. The same reading applies to the irreversibility floor that keeps a
2688
- `reversible: false` action out of `supervised-retro` entirely: it is a floor, not
2689
- a proof — it acts on the acting party's own claim, so it catches the honest
2780
+ easier. The same reading applies to the irreversibility floor. By default it
2781
+ keeps a `reversible: false` action out of `supervised-retro`; an attested class
2782
+ rule may explicitly accept that consequence with `allow_irreversible: true`.
2783
+ The field acts on the acting party's own claim, so it catches the honest
2690
2784
  declaration and never the lie. What answers the lie is writing `manual` for the
2691
2785
  class, which no declaration can loosen.
2692
2786
 
@@ -3192,6 +3286,11 @@ would put a bot token into a shell history or a process listing.
3192
3286
 
3193
3287
  ## channel telegram listen
3194
3288
 
3289
+ This starts the standalone Telegram component. For normal operation, use
3290
+ [`approval up`](#up), which also runs the daemon. Do not run this listener beside
3291
+ `up` or another listener polling the same bot, even for a different policy
3292
+ project: competing `getUpdates` calls produce Telegram HTTP 409.
3293
+
3195
3294
  **Delivery is per cycle, not only at startup.** Before every `getUpdates` the
3196
3295
  listener re-derives the pending queue from the verified log and sends whatever
3197
3296
  it has not already sent, so a request appended while this listener is running
@@ -3456,6 +3555,43 @@ belong to a RUNNING listener: they are on its stderr as they happen, in its
3456
3555
  Which variables are read comes from the policy, so a renamed variable reads back
3457
3556
  as the name you set.
3458
3557
 
3558
+ ## quickstart
3559
+
3560
+ `approval quickstart [--dir <path>] [--api-base <url>]` is the human-only solo setup ceremony. It
3561
+ asks three decisions: the human identifier, terminal or Telegram, and which of
3562
+ five class families always ask. The default checklist selects `communicate.*`,
3563
+ `financial.*`, `files.delete.*`, `public.*`, and `vcs.push.main`.
3564
+
3565
+ The command validates the complete generated policy before writing it. It
3566
+ creates the same log directory, queue projection, and gitignore entries as
3567
+ `init`, writes `APPROVAL_HUMAN` through the existing `.approval/env` writer,
3568
+ and uses the existing Telegram setup path when selected. A token therefore
3569
+ follows the OS-keystore or no-echo path already documented under [setup channel
3570
+ telegram](#setup-channel-telegram). It refuses before prompting when a policy or
3571
+ `.approval` instance state already exists, so it cannot silently reuse a log,
3572
+ queue, environment map, vault, or channel setup from another ceremony.
3573
+
3574
+ Quickstart resolves this new instance's environment map explicitly, without
3575
+ borrowing ambient approval credentials, and runs a bounded doctor preflight.
3576
+ When `--api-base` is present, the same endpoint is used for Telegram setup and
3577
+ that preflight; a local or private Bot API selection never falls through to the
3578
+ public endpoint.
3579
+ The expected `attestation` failure is the only failed row accepted before the
3580
+ ceremony; any other failed row is printed and stops before attestation. It then
3581
+ prints the exact policy bytes and requires the operator to type `understood`.
3582
+ The append rechecks the live digest and refuses if the file changed after it was
3583
+ shown. An abort or failed step therefore leaves the generated policy unattested.
3584
+ The final `activate:` line includes `approval env --dir` with the absolute,
3585
+ shell-quoted target directory. It is required because no ordinary runtime
3586
+ command loads `.approval/env` implicitly, and it still names the right instance
3587
+ when the operator starts the next shell elsewhere.
3588
+
3589
+ This verb classifies `policy.core` and is omitted from MCP. Piped stdin and
3590
+ `--json` exit 2 before any write and print the manual sequence. The generated
3591
+ `defaults.autonomy: autonomous` applies to other classified reversible actions.
3592
+ Protected controls, fail-closed policy loading, the irreversibility floor, and
3593
+ unclassified-command refusal continue to apply.
3594
+
3459
3595
  ## init
3460
3596
 
3461
3597
  `init` holds no authority: the policy it writes authorizes nothing until a human
@@ -3541,7 +3677,11 @@ gate.self the "approval" CLI itself is pass-through
3541
3677
  ```
3542
3678
 
3543
3679
  Bash (Claude Code) and Shell (Cursor) commands are classified into SPEC.md §7
3544
- action classes. Claude file tools (Edit, Write, MultiEdit, NotebookEdit) and
3680
+ action classes. A `git push` that names `refs/tags/*`, a bare `v`-prefixed
3681
+ semantic-version-shaped tag, `tag <name>`, `--tags`, or `--follow-tags` is
3682
+ `release.publish`; force and mirror pushes remain `vcs.history.rewrite`, and
3683
+ ordinary branch pushes retain their branch or trunk class. Claude file tools
3684
+ (Edit, Write, MultiEdit, NotebookEdit) and
3545
3685
  Cursor Write/Delete are gated only when the file is policy-protected
3546
3686
  (`APPROVAL.md`, `.approval/`, `CLAUDE.md`, `AGENTS.md`, `.claude/settings*`,
3547
3687
  `.cursor/hooks.json`, `.cursor/hooks/`, `.cursor/agents/`,
@@ -4302,6 +4442,47 @@ when the log does not.
4302
4442
 
4303
4443
  ## up
4304
4444
 
4445
+ **Normal startup after setup and human attestation.** Run from the intended
4446
+ policy project's directory, load its environment explicitly, then leave this
4447
+ foreground process running:
4448
+
4449
+ ```sh
4450
+ cd /path/to/your/project
4451
+ eval "$(approval env)"
4452
+ approval up
4453
+ ```
4454
+
4455
+ For a source build, replace `approval` in both commands with
4456
+ `node /path/to/approval.md/cli.js`. `--as human:<id>` may name the configured
4457
+ approver explicitly; it does not perform identity setup or policy attestation.
4458
+ `up` reads credentials from its launch environment and does not load
4459
+ `.approval/env`. Existing exported approval variables take precedence over that
4460
+ map, so use a clean shell or unset another instance's variables first. Changing
4461
+ only `--dir` selects the policy; it does not relocate every log, environment or
4462
+ task path. Starting in the intended project keeps the default paths together.
4463
+
4464
+ **Startup messages describe separate parts.** The default task directory is
4465
+ `backlog/tasks/`. If envelopes live elsewhere, pass
4466
+ `--tasks /path/to/existing/task-folder`. The daemon scans regular `.md` files
4467
+ immediately inside that directory, without recursion; an empty default folder
4468
+ does not cover envelopes in nested bundle directories. A missing default folder
4469
+ warns about absent drift coverage while TTL sweeping and queue rendering still
4470
+ run. An explicitly supplied missing directory is an error. `watch-unavailable`
4471
+ reports a failed watcher; the daemon still scans on its interval. Neither
4472
+ warning proves Telegram failed: check the channel's own startup line.
4473
+
4474
+ A `live-draw` doctor failure needs a daemon serving this instance's draw socket.
4475
+ `up` starts that daemon, but serving draws also requires a `supervised-live`
4476
+ policy class and the configured sampling secret resolved in this process's
4477
+ launch environment, with `--no-draw` absent. Starting `up` alone cannot supply a
4478
+ missing secret. Without usable draws, every supervised-live action gates to a
4479
+ human. A policy with no `channels.web.port` and no explicit `--port` serves no
4480
+ web queue; that informational message is legitimate configuration.
4481
+
4482
+ Stop `up` before running Telegram setup or a standalone listener for its bot.
4483
+ One bot must have one polling runtime. After setup changes, reload the instance
4484
+ environment before starting `up` again.
4485
+
4305
4486
  **The ambient runtime: the daemon loop and every configured channel in one
4306
4487
  supervised foreground process.** `approval daemon run --with-channels` is the
4307
4488
  same verb spelled from the other side, and it reaches the same function before
@@ -4324,7 +4505,9 @@ because `git status` does not say what the upstream range changed. So the verb
4324
4505
  does all four, and `approval daemon run` runs the identical preflight from the
4325
4506
  identical module, printing the identical two lines.
4326
4507
 
4327
- It is allowed exactly two writes: a `--ff-only` merge, and `npm run build`. It
4508
+ It is allowed exactly three writes: a `--ff-only` merge, `npm run build`, and
4509
+ clearing an untracked `backlog/tasks/` file the incoming commit already contains
4510
+ out of the merge's way (APRV-300, described below). It
4328
4511
  never resets, never stashes, never checks anything out, and never touches the
4329
4512
  working log. That list is not caution for its own sake: a working `events.jsonl`
4330
4513
  rewound through git underneath a live appender is fork 2 of 2026-08-20, the
@@ -4340,8 +4523,42 @@ running. When it is not, it refuses, and changes nothing:
4340
4523
  | `up-preflight-behind-ahead` | `origin/<branch>..HEAD` is non-empty: this checkout carries commits the remote has never seen. A fast-forward is not the operation for that state, and choosing a side is a decision. | look at them (`git log --oneline origin/main..HEAD`), then push them or `git reset --keep` |
4341
4524
  | `up-preflight-log-diverged` | the upstream range rewrites `.approval/log/events.jsonl` or `.approval/QUEUE.md`, and this working copy has uncommitted changes to one of them. The judgment a human could not make by eye. | `approval log sync` |
4342
4525
  | `up-preflight-dirty-protected` | some other path the upstream range changes is locally modified, so `git merge --ff-only` would refuse rather than overwrite it. | look at the diff, or `approval up --no-preflight` |
4526
+ | `up-preflight-task-file-conflict` | an untracked file under `backlog/tasks/` stopped the fast-forward and it holds lines the incoming copy does not. Which version is wanted is a question, and no verb here will pick. | read the two copies, move yours aside, run `approval up` again |
4343
4527
  | `up-preflight-failed` | a write the preflight attempted did not complete: the fast-forward, or the rebuild. Not a judgment, so it is not in the union above; the message names the step, and for a build it names the exit code `npm run build` came back with. | `npm run build` to see the whole error, or `approval up --no-build` if you mean to run the stale one |
4344
4528
 
4529
+ **An untracked task file no longer stops it (APRV-300).** A lane files
4530
+ `backlog/tasks/aprv-299` on its branch and its pull request merges, while the
4531
+ primary checkout holds the same path untracked from its own `backlog task
4532
+ create`. `git merge --ff-only` will not write over an untracked file, so on
4533
+ 2026-09-07 the preflight refused, and its next-steps text pointed at `git
4534
+ status`, which cannot say whether the local copy holds anything the incoming one
4535
+ does not. That question is answerable, so it is answered. When the merge fails
4536
+ over untracked files and every path git names sits under `backlog/tasks/`, each
4537
+ one is read alongside `git show <target>:<path>` and given one of three
4538
+ verdicts:
4539
+
4540
+ - **byte-identical** — the local copy says nothing the incoming copy does not,
4541
+ so it is removed and the merge is retried once;
4542
+ - **every line also in the incoming copy** — the ordinary shape, a hand-filed
4543
+ stub against a lane copy that added a plan and criteria. Nothing is lost by
4544
+ letting the incoming copy land, but that is a judgment about an operator's
4545
+ file, so the bytes are moved to a sibling of the checkout named
4546
+ `<repo>-preflight-aside-<YYYY-MM-DD>` (outside the repository, so the next
4547
+ fast-forward cannot collide with it again), the destination is printed on the
4548
+ `preflight_warning` line, and the merge is retried once;
4549
+ - **anything else** — `up-preflight-task-file-conflict`, naming your path, the
4550
+ incoming spelling, and how many lines only yours has.
4551
+
4552
+ Every file is judged before any file is touched, the same two-pass shape as
4553
+ `approval log sync`'s payload reconciliation, so a refusal over the last
4554
+ collision cannot have already removed the first. One path outside
4555
+ `backlog/tasks/` and the whole set is declined: the merge keeps its old
4556
+ `up-preflight-failed` refusal and nothing is cleared, because clearing what was
4557
+ understood and then refusing anyway would have moved files for a merge that was
4558
+ never going to run. A path git chose to quote (`core.quotePath`) is declined for
4559
+ the same reason: guessing the spelling of a file about to be moved is the
4560
+ mistake the whole check exists to avoid.
4561
+
4345
4562
  **`git reset --hard` is printed on no path, ever**, and a test asserts it. The
4346
4563
  one reset that appears is `--keep`, which refuses rather than discarding
4347
4564
  uncommitted work, and it is the third step of a runbook whose first step is to
@@ -4498,9 +4715,11 @@ is the verb that hands them the unit.
4498
4715
  **There is no `approval vault get`**, and it is not an oversight. A verb that
4499
4716
  printed a credential would put it in a terminal, a scrollback buffer, a CI log
4500
4717
  and — through the shell that ran it — a history file. A credential's only
4501
- sanctioned journey is from the vault into an adapter, inside the verified-token
4502
- window the adapter contract holds open (SPEC.md §10.4: "the credentials only
4503
- answer to tokens"). Names are visible; values are not.
4718
+ sanctioned journey is from the vault into an adapter, inside the verified
4719
+ execution window the adapter contract holds open. Manual and selected-live
4720
+ paths verify and consume a token; an attested class rule may explicitly
4721
+ authorize an irreversible supervised or autonomous path. Names are visible;
4722
+ values are not.
4504
4723
 
4505
4724
  **What the vault DEFENDS:** credentials at rest, and casual reads by an agent
4506
4725
  that can read files in the working tree — the ciphertext hides the NAMES as well
@@ -4607,26 +4826,47 @@ the check would require this verb to know every adapter a machine might run.
4607
4826
 
4608
4827
  ## adapter
4609
4828
 
4610
- An adapter is the hard boundary of SPEC.md §10.4: it holds the credentials and
4611
- refuses to act without a valid, unexpired, single-use execution token bound to
4612
- the action's `idempotency_key` and its `payload_hash`. An agent that bypasses
4613
- this CLI still cannot send, because the credentials only answer to tokens.
4829
+ An adapter is the hard boundary of SPEC.md §10.4: it holds the credentials while
4830
+ the runtime recomputes the payload hash and applies attested policy. Manual and
4831
+ selected-live paths require a valid, unexpired, single-use execution token bound
4832
+ to the action's `idempotency_key` and `payload_hash`. An explicitly opted-in
4833
+ supervised or autonomous path has no grant and mints no token, so `--token` is
4834
+ optional at the command boundary.
4614
4835
 
4615
- The runtime, not the adapter, owns the sequence: recompute the payload hash,
4616
- read the declared class from the verified log, resolve the credentials the
4617
- adapter says it cannot act without, run the adapter's own pre-token check,
4618
- verify and consume the token, append `execution.started`, call the adapter,
4619
- append the outcome. The adapter implements one method and cannot skip a step,
4620
- because it never holds the sequence.
4836
+ Credential custody does not become implicit on the no-token path. The vault
4837
+ passphrase must already be present in the adapter process environment. The
4838
+ `.approval/env` source-map fallback remains available only inside a real token
4839
+ window: it rejects a null grant and a consumed nonmanual execution carrying no
4840
+ token digest. This lets an operator authorize nonmanual execution without
4841
+ giving an agent a new way to load credentials.
4621
4842
 
4622
- The two steps that sit BEFORE the token spend are there for one reason: a
4843
+ The runtime, not the adapter, owns the sequence: recompute the payload hash,
4844
+ read the declared class from the verified log, check policy eligibility or the
4845
+ manual token without appending a start, resolve the credentials the adapter says
4846
+ it cannot act without, run the adapter's own pre-token check, then recheck policy
4847
+ and, on a manual path, verify and consume the token, append
4848
+ `execution.started`, call the adapter, append the outcome. The adapter implements
4849
+ one method and cannot skip a step, because it never holds the sequence.
4850
+
4851
+ `supervised-live` selection still belongs to `approval request`. On a direct
4852
+ no-token invocation with no earlier approval cycle, the adapter contract runs
4853
+ that existing intake path from the verified declaration before it touches a
4854
+ credential. A selected or unavailable draw records the ordinary pending cycle
4855
+ and stops for its token. The request retains the exact already-hashed payload
4856
+ through the normal payload store, so the selected human sees the bound bytes.
4857
+ An unselected draw appends no approval event and continues, bound to the same
4858
+ attested policy digest through eligibility and start. Once any cycle exists,
4859
+ including a rejected or expired one, the contract never redraws it.
4860
+
4861
+ The two steps that sit before authorization starts the execution are there for one reason: a
4623
4862
  condition that makes the side effect impossible, and that the runtime can
4624
4863
  establish without attempting it, must not cost a human's single-use grant to
4625
4864
  discover. A credential nobody stored refuses `credential-unavailable`; whatever
4626
4865
  the adapter's own check refuses arrives as `adapter-precheck-refused` with the
4627
4866
  adapter's reason in `adapter_code`. Both leave the log exactly as they found it,
4628
4867
  `acted` is `false`, there is no `started_seq` and no `outcome`, and the same
4629
- token executes once the condition is repaired.
4868
+ token executes once the condition is repaired. On an admitted nonmanual path,
4869
+ there is no token to preserve and the same preflight still appends nothing.
4630
4870
 
4631
4871
  The pre-token check is offered only bytes the log binds to the action: the
4632
4872
  grant's `payload_hash` on the manual path, the registered declaration's off it.
@@ -4739,8 +4979,10 @@ The enforcement model it assumes is a split pair of keys. AgentMail keys carry
4739
4979
  `draft_create`, `draft_update`, `draft_read`, `draft_send` and `message_send`
4740
4980
  separately. The agent gets a key WITHOUT the two send permissions, so it can
4741
4981
  compose all day and cannot send; the key WITH them goes in the vault under
4742
- `agentmail.api_key`, readable only inside the verified-token window the contract
4743
- opens. `approval setup adapter agentmail` stores that pair, and
4982
+ `agentmail.api_key`, readable only inside the contract's execution window. On a
4983
+ nonmanual path, opening the encrypted vault requires its passphrase in the
4984
+ adapter process environment; the token-scoped source-map fallback does not run.
4985
+ `approval setup adapter agentmail` stores that pair, and
4744
4986
  `approval payload agentmail-draft` is the composing side's own verb.
4745
4987
 
4746
4988
  Two payload modes, told apart by the keys they carry, and a payload carrying
@@ -4811,6 +5053,82 @@ string as `message_id`, under the one key the adapter contract lifts onto
4811
5053
  as `"provider_ref":{"adapter":"agentmail","id":…}` beside the detail. A send
4812
5054
  whose answer names no id carries neither.
4813
5055
 
5056
+ ## adapter zzz
5057
+
5058
+ Creates a zzz.bot thread or reply for `communicate.zzz.external`. The only
5059
+ credential is `zzz.agent_token`, an invited principal token with write scope.
5060
+ It is read from the vault inside the shared execution window and sent only as an
5061
+ Authorization Bearer header. `--token` is required for manual or selected-live
5062
+ execution and omitted for an explicitly policy-authorized supervised or
5063
+ autonomous execution. On the no-token path, the vault passphrase must already be
5064
+ present in the adapter process environment.
5065
+
5066
+ This verb is available from a source checkout containing APRV-320 until the
5067
+ next approval.md package release. npm `approval-md@0.1.0` predates the adapter;
5068
+ this change does not publish a package.
5069
+
5070
+ The contract implemented here is zzz.bot API v0.1.0: [quickstart](https://zzz.bot/quickstart),
5071
+ [API guide](https://zzz.bot/api), [approval semantics](https://zzz.bot/approval),
5072
+ and [OpenAPI](https://zzz.bot/openapi.json).
5073
+
5074
+ The payload is a strict tagged union:
5075
+
5076
+ ```json
5077
+ {"environment":"production","operation":"create_thread",
5078
+ "room_id":"<room-id-from-GET-api-v1-rooms>","title":"…","body":"…",
5079
+ "metadata":{},"tags":["…"],
5080
+ "references":[{"kind":"external","target":"https://example.com/source",
5081
+ "label":"Source","relationship":"source"}]}
5082
+ {"environment":"preview","operation":"create_reply",
5083
+ "thread_id":"…","body":"…","metadata":{},"tags":["…"],"references":[]}
5084
+ ```
5085
+
5086
+ `environment` is exactly `production` or `preview`; operation is exactly
5087
+ `create_thread` or `create_reply`. Reference `kind` and `relationship` use
5088
+ the alternatives shown above. Unknown keys are refused. Bodies are 1 to 65,536
5089
+ characters, titles 1 to 200, tags at most 10 strings of 1 to 40 characters, and
5090
+ references at most 20. The entire canonical message JSON must fit 65,536 UTF-8
5091
+ bytes, so a maximum-length body can exceed the request limit once its other
5092
+ fields and JSON encoding are included.
5093
+ External reference targets must be HTTP or HTTPS URLs. All optional values that
5094
+ are present remain inside the bound payload and are sent unchanged.
5095
+
5096
+ The message fields are deliberately flat beside the operation tag and target.
5097
+ This keeps the payload a person reviews close to ZZZ's request body. The adapter
5098
+ removes only `environment`, `operation` and the target id when building the
5099
+ POST body; every content field remains byte-for-byte represented in the
5100
+ canonical JSON sent to ZZZ.
5101
+
5102
+ `production` routes to `https://zzz.bot` and `preview` to the fixed preview
5103
+ service. There is no API-base flag. Thread creation posts to
5104
+ `/api/v1/rooms/{room}/threads`; replies post to
5105
+ `/api/v1/threads/{thread}/posts`. Redirects are rejected. The
5106
+ `Idempotency-Key` is deterministic SHA-256 over the RFC 8785 form of the
5107
+ approval action key and payload hash, so the provider's retry identity binds the
5108
+ same action and exact bytes.
5109
+
5110
+ HTTP 201 is accepted only with `{"id":"thr_…"|"pst_…","replayed":false}`,
5111
+ and HTTP 200 only with the same operation-appropriate id and `replayed:true`.
5112
+ The validated service id becomes `provider_ref`. A malformed or inconsistent
5113
+ success, transport error, redirect, or 5xx is `execution.indeterminate` because
5114
+ the POST may have committed. No response text is recorded. Definite refusals
5115
+ map to `zzz-invalid-request` (400), `zzz-unauthorized` (401),
5116
+ `zzz-forbidden` (403), `zzz-not-found` (404),
5117
+ `zzz-idempotency-conflict` (409), `zzz-payload-too-large` (413),
5118
+ `zzz-rejected` (422), or `zzz-rate-limited` (429).
5119
+
5120
+ Public writes require an invited credential with write scope. Private rooms
5121
+ also require current membership carrying write and accepted, unexpired
5122
+ approval.md workflow evidence. zzz.bot intentionally returns 404 when private
5123
+ access is absent, so the adapter does not guess which prerequisite failed.
5124
+ `approval setup adapter zzz` verifies only credential acceptance through one
5125
+ read-only `GET /api/v1/rooms` and posts nothing.
5126
+
5127
+ The local non-guest MCP server publishes this same verb from the registry. MCP
5128
+ invocation remains voluntary. Mechanical enforcement comes from keeping the
5129
+ write credential solely in the vault; an agent that also holds the credential
5130
+ can bypass the adapter.
5131
+
4814
5132
  ## env
4815
5133
 
4816
5134
  This command is the only thing that reads `.approval/env`, and its default output
@@ -4909,7 +5227,7 @@ channel surfaces requests and collects decisions and holds no state, so its setu
4909
5227
  fills the OS keystore and `.approval/env` — the map of where the values that
4910
5228
  unlock the machine live. An adapter executes side effects and holds credentials,
4911
5229
  so its setup fills `.approval/vault.enc`, which holds the values a gated adapter
4912
- SPENDS, read inside the verified-token window and by nothing else. There is no
5230
+ SPENDS, read inside the verified execution window and by nothing else. There is no
4913
5231
  verb that prints one back. (An older build spelled the Telegram one without the
4914
5232
  `channel` noun. That form exits 2 and names this one; there is no alias, because
4915
5233
  two spellings of a distinction the SPEC draws on purpose is how the distinction
@@ -5080,7 +5398,7 @@ unset, nothing is stored and no vault is created.
5080
5398
 
5081
5399
  The values go into the vault, not into the OS keystore and not into
5082
5400
  `.approval/env`: what this verb stores is what a gated adapter spends inside a
5083
- verified-token window.
5401
+ verified execution window.
5084
5402
 
5085
5403
  What it reports: the path, the count, the names written and the names left alone.
5086
5404
  Never a value, on any path, including a failed probe. Exit 1 means the service
@@ -5157,7 +5475,7 @@ agentmail.api_key the key that carries draft_send and message_send
5157
5475
  Store the SENDING key here and give the agent a different one. An AgentMail key
5158
5476
  is a mailbox in one string, so a deployment that hands the agent the sending key
5159
5477
  has an agent that can send without asking anybody, and the gate in front of it is
5160
- decoration. The key in the vault is read only inside the verified-token window
5478
+ decoration. The key in the vault is read only inside the verified execution window
5161
5479
  the adapter contract opens.
5162
5480
 
5163
5481
  The probe sends nothing. It is `GET /v0/inboxes/{inbox_id}`, the same read a
@@ -5179,6 +5497,20 @@ adapter's does. A re-run that replaced only one name is offered the same probe
5179
5497
  over the stored pair, read through `readAgentmailConfig` over the vault: the
5180
5498
  exact path `approval adapter agentmail` takes at send time, printed by nothing.
5181
5499
 
5500
+ ## setup adapter zzz
5501
+
5502
+ The manifest contains one secret, `zzz.agent_token`. It must be an invited
5503
+ zzz.bot principal credential with write scope. Store the write-capable token in
5504
+ the vault and keep it out of the agent environment; otherwise the agent can post
5505
+ without passing through the adapter.
5506
+
5507
+ The optional probe sends nothing. It makes one authenticated
5508
+ `GET /api/v1/rooms` against production and reports success only when zzz.bot
5509
+ accepts the credential. That endpoint does not disclose the principal's write
5510
+ scope, room memberships, or accepted private-room workflow evidence, so setup
5511
+ states those limits instead of claiming the token can publish. The actual
5512
+ approved POST remains the first proof of all write prerequisites.
5513
+
5182
5514
  ## setup channel
5183
5515
 
5184
5516
  A channel is not an adapter, and the two setup verbs fill different stores.
@@ -5189,9 +5521,11 @@ credentials, so `approval setup adapter <name>` fills the vault instead.
5189
5521
 
5190
5522
  ## setup channel telegram
5191
5523
 
5192
- Stop `approval channel telegram listen` first. Two processes long-polling one bot
5193
- is a 409 from the Bot API, and the loser is whichever asked second. This is a
5194
- configuration verb; it is not meant to run beside the listener.
5524
+ Stop any `approval up` process or `approval channel telegram listen` polling
5525
+ this bot first. Setup also uses `getUpdates` to discover the approver chat.
5526
+ Competing polls produce HTTP 409 from the Bot API. This is a configuration verb;
5527
+ after it finishes, reload the instance environment and use `approval up` for
5528
+ normal operation.
5195
5529
 
5196
5530
  The token is never typed into this process on a machine with a keystore: the
5197
5531
  helper's own no-echo prompt collects it, and this runtime reads it back on stdout
@@ -5361,3 +5695,22 @@ queue, so an unbounded guest wait is one stranger stalling every other session.
5361
5695
  The guest instructions string says so, tells the caller to poll `status`, and
5362
5696
  states plainly that a granted request executes nowhere: the demo is the approval
5363
5697
  flow itself.
5698
+
5699
+ ## Constrained Codex preparation
5700
+
5701
+ approval codex prepare is an artifact generator. It writes one fresh review
5702
+ directory and has no activation path. Its requirements, managed config,
5703
+ launchers and launchd files are text for a human or MDM workflow to inspect.
5704
+
5705
+ approval codex setup --check proves only that those artifacts match their
5706
+ closed manifest and hashes. approval codex doctor --strict asks the separate
5707
+ host question: are the package and its ancestors root-owned and immutable, are
5708
+ the three principals distinct, are the roots disjoint and canonical, and are
5709
+ the broker and runner present? POSIX ownership does not establish ACL custody,
5710
+ so this slice executes no manifest-selected binary and reports runtime versions
5711
+ unchecked. Unknown evidence is a refusal.
5712
+
5713
+ The first slice deliberately makes start and serve return codex-not-ready. An
5714
+ npm install, generated config, or passing bundle check does not create a
5715
+ mandatory boundary. The whole approval codex family is operator-only and absent
5716
+ from the ordinary broad MCP catalog.
@@ -0,0 +1,30 @@
1
+ # Constrained Codex preparation
2
+
3
+ approval codex prepare creates a review bundle for a future constrained Codex
4
+ session. It is packaging and diagnostics, not activation.
5
+
6
+ The bundle pins one macOS/Codex version, distinct Codex, broker and runner
7
+ principals, disjoint active-worktree, primary-gate and root-owned installation
8
+ roots, and one exact MCP executable plus argument vector. Its requirements
9
+ template makes native Codex tools read-only, disables command network and the
10
+ browser, computer-use, plugin and web-search surfaces, and allowlists only the
11
+ strict MCP identity.
12
+
13
+ Preparation writes only a new output directory. approval codex setup --check
14
+ verifies the exact file set, SHA-256 index, instance schema and every generated
15
+ template byte. A passing check means the inert bundle is internally consistent.
16
+
17
+ approval codex doctor --strict --manifest PATH inspects host custody. It rejects
18
+ unsupported manifest platforms or version declarations, missing or overlapping principals,
19
+ overlapping roots, symlinks, path drift, non-root-owned or writable trusted
20
+ ancestors, invocation drift, missing executables and a workspace writable by
21
+ the Codex principal. APRV-325.1 also unconditionally reports broker-not-ready
22
+ and runner-not-ready. POSIX ownership and mode cannot prove the absence of
23
+ writable ACLs, so this slice executes no manifest-selected binary and reports
24
+ both installed runtime versions unchecked.
25
+
26
+ The package does not run a postinstall script, call sudo, edit Codex
27
+ configuration, create accounts, load services, access credentials, start a
28
+ model, or alter APPROVAL.md. A human or MDM system must eventually install and
29
+ own the reviewed artifacts. APRV-325.2 and APRV-325.3 must provide the broker,
30
+ runner and end-to-end denial evidence before the session can be called enforced.