approval-md 0.3.0 → 0.4.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 (226) hide show
  1. package/README.md +83 -914
  2. package/SPEC.md +98 -13
  3. package/cli.js +66 -7
  4. package/dist/src/adapters/smtp.d.ts +18 -0
  5. package/dist/src/adapters/smtp.js +24 -3
  6. package/dist/src/adapters/smtp.js.map +1 -1
  7. package/dist/src/channels/contract.d.ts +19 -0
  8. package/dist/src/channels/contract.js +105 -1
  9. package/dist/src/channels/contract.js.map +1 -1
  10. package/dist/src/channels/relay-server.d.ts +90 -0
  11. package/dist/src/channels/relay-server.js +295 -0
  12. package/dist/src/channels/relay-server.js.map +1 -0
  13. package/dist/src/channels/relay.d.ts +273 -0
  14. package/dist/src/channels/relay.js +564 -0
  15. package/dist/src/channels/relay.js.map +1 -0
  16. package/dist/src/channels/tagging.js +6 -1
  17. package/dist/src/channels/tagging.js.map +1 -1
  18. package/dist/src/channels/telegram-webhook.d.ts +273 -0
  19. package/dist/src/channels/telegram-webhook.js +536 -0
  20. package/dist/src/channels/telegram-webhook.js.map +1 -0
  21. package/dist/src/channels/telegram.d.ts +271 -1
  22. package/dist/src/channels/telegram.js +403 -45
  23. package/dist/src/channels/telegram.js.map +1 -1
  24. package/dist/src/cli/amend.js +350 -4
  25. package/dist/src/cli/amend.js.map +1 -1
  26. package/dist/src/cli/attest.js +43 -6
  27. package/dist/src/cli/attest.js.map +1 -1
  28. package/dist/src/cli/channel-relay.d.ts +108 -0
  29. package/dist/src/cli/channel-relay.js +334 -0
  30. package/dist/src/cli/channel-relay.js.map +1 -0
  31. package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
  32. package/dist/src/cli/channel-telegram-webhook.js +709 -0
  33. package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
  34. package/dist/src/cli/channel-telegram.d.ts +272 -26
  35. package/dist/src/cli/channel-telegram.js +478 -41
  36. package/dist/src/cli/channel-telegram.js.map +1 -1
  37. package/dist/src/cli/channel.d.ts +1 -1
  38. package/dist/src/cli/channel.js +5 -1
  39. package/dist/src/cli/channel.js.map +1 -1
  40. package/dist/src/cli/daemon.js +37 -5
  41. package/dist/src/cli/daemon.js.map +1 -1
  42. package/dist/src/cli/doctor.d.ts +63 -0
  43. package/dist/src/cli/doctor.js +856 -111
  44. package/dist/src/cli/doctor.js.map +1 -1
  45. package/dist/src/cli/execute.d.ts +17 -1
  46. package/dist/src/cli/execute.js +341 -20
  47. package/dist/src/cli/execute.js.map +1 -1
  48. package/dist/src/cli/exit-codes.d.ts +12 -0
  49. package/dist/src/cli/exit-codes.js +13 -0
  50. package/dist/src/cli/exit-codes.js.map +1 -1
  51. package/dist/src/cli/gate.d.ts +2 -0
  52. package/dist/src/cli/gate.js +233 -2
  53. package/dist/src/cli/gate.js.map +1 -1
  54. package/dist/src/cli/help.d.ts +9 -1
  55. package/dist/src/cli/help.js +246 -38
  56. package/dist/src/cli/help.js.map +1 -1
  57. package/dist/src/cli/hook.d.ts +371 -3
  58. package/dist/src/cli/hook.js +1926 -44
  59. package/dist/src/cli/hook.js.map +1 -1
  60. package/dist/src/cli/init.js +16 -7
  61. package/dist/src/cli/init.js.map +1 -1
  62. package/dist/src/cli/instructions.js +24 -3
  63. package/dist/src/cli/instructions.js.map +1 -1
  64. package/dist/src/cli/log-anchor.d.ts +55 -0
  65. package/dist/src/cli/log-anchor.js +199 -60
  66. package/dist/src/cli/log-anchor.js.map +1 -1
  67. package/dist/src/cli/main.d.ts +0 -12
  68. package/dist/src/cli/main.js +51 -0
  69. package/dist/src/cli/main.js.map +1 -1
  70. package/dist/src/cli/muse.d.ts +2 -0
  71. package/dist/src/cli/muse.js +62 -0
  72. package/dist/src/cli/muse.js.map +1 -0
  73. package/dist/src/cli/payload.js +79 -1
  74. package/dist/src/cli/payload.js.map +1 -1
  75. package/dist/src/cli/scaffold.js +6 -0
  76. package/dist/src/cli/scaffold.js.map +1 -1
  77. package/dist/src/cli/serve.d.ts +53 -0
  78. package/dist/src/cli/serve.js +270 -0
  79. package/dist/src/cli/serve.js.map +1 -0
  80. package/dist/src/cli/up.js +21 -2
  81. package/dist/src/cli/up.js.map +1 -1
  82. package/dist/src/cli/verb-registry.d.ts +2 -0
  83. package/dist/src/cli/verb-registry.js +337 -39
  84. package/dist/src/cli/verb-registry.js.map +1 -1
  85. package/dist/src/cli/wordmark.d.ts +2 -2
  86. package/dist/src/cli/wordmark.js +2 -2
  87. package/dist/src/core/attest-bootstrap.d.ts +95 -0
  88. package/dist/src/core/attest-bootstrap.js +135 -0
  89. package/dist/src/core/attest-bootstrap.js.map +1 -0
  90. package/dist/src/core/attest.d.ts +20 -3
  91. package/dist/src/core/attest.js +25 -0
  92. package/dist/src/core/attest.js.map +1 -1
  93. package/dist/src/core/audit.d.ts +55 -3
  94. package/dist/src/core/audit.js +52 -5
  95. package/dist/src/core/audit.js.map +1 -1
  96. package/dist/src/core/channel-lease.d.ts +322 -0
  97. package/dist/src/core/channel-lease.js +663 -0
  98. package/dist/src/core/channel-lease.js.map +1 -0
  99. package/dist/src/core/command-class.d.ts +51 -0
  100. package/dist/src/core/command-class.js +760 -8
  101. package/dist/src/core/command-class.js.map +1 -1
  102. package/dist/src/core/daemon-host.d.ts +178 -0
  103. package/dist/src/core/daemon-host.js +228 -0
  104. package/dist/src/core/daemon-host.js.map +1 -0
  105. package/dist/src/core/daemon-identity.d.ts +238 -0
  106. package/dist/src/core/daemon-identity.js +252 -0
  107. package/dist/src/core/daemon-identity.js.map +1 -0
  108. package/dist/src/core/gate.d.ts +306 -5
  109. package/dist/src/core/gate.js +772 -24
  110. package/dist/src/core/gate.js.map +1 -1
  111. package/dist/src/core/harness-version.d.ts +128 -4
  112. package/dist/src/core/harness-version.js +179 -12
  113. package/dist/src/core/harness-version.js.map +1 -1
  114. package/dist/src/core/harness-wait.d.ts +82 -3
  115. package/dist/src/core/harness-wait.js +89 -3
  116. package/dist/src/core/harness-wait.js.map +1 -1
  117. package/dist/src/core/log-subscribe.d.ts +21 -0
  118. package/dist/src/core/log-subscribe.js +16 -6
  119. package/dist/src/core/log-subscribe.js.map +1 -1
  120. package/dist/src/core/log-write-layer.d.ts +55 -0
  121. package/dist/src/core/log-write-layer.js +91 -0
  122. package/dist/src/core/log-write-layer.js.map +1 -0
  123. package/dist/src/core/log.d.ts +50 -21
  124. package/dist/src/core/log.js +197 -19
  125. package/dist/src/core/log.js.map +1 -1
  126. package/dist/src/core/payload-census.d.ts +51 -0
  127. package/dist/src/core/payload-census.js +125 -2
  128. package/dist/src/core/payload-census.js.map +1 -1
  129. package/dist/src/core/payload-store.d.ts +31 -0
  130. package/dist/src/core/payload-store.js +113 -8
  131. package/dist/src/core/payload-store.js.map +1 -1
  132. package/dist/src/core/payload.d.ts +18 -3
  133. package/dist/src/core/payload.js +20 -4
  134. package/dist/src/core/payload.js.map +1 -1
  135. package/dist/src/core/policy-diff.d.ts +10 -0
  136. package/dist/src/core/policy-diff.js +10 -2
  137. package/dist/src/core/policy-diff.js.map +1 -1
  138. package/dist/src/core/policy-explain.d.ts +7 -1
  139. package/dist/src/core/policy-explain.js +16 -1
  140. package/dist/src/core/policy-explain.js.map +1 -1
  141. package/dist/src/core/policy-load.d.ts +24 -0
  142. package/dist/src/core/policy-load.js.map +1 -1
  143. package/dist/src/core/policy-match.d.ts +38 -0
  144. package/dist/src/core/policy-match.js +35 -0
  145. package/dist/src/core/policy-match.js.map +1 -1
  146. package/dist/src/core/policy-proposal.d.ts +23 -0
  147. package/dist/src/core/policy-proposal.js +8 -2
  148. package/dist/src/core/policy-proposal.js.map +1 -1
  149. package/dist/src/core/read-scope.d.ts +10 -0
  150. package/dist/src/core/read-scope.js +4 -0
  151. package/dist/src/core/read-scope.js.map +1 -1
  152. package/dist/src/core/run-payload.d.ts +160 -0
  153. package/dist/src/core/run-payload.js +304 -0
  154. package/dist/src/core/run-payload.js.map +1 -0
  155. package/dist/src/core/sender-identity.d.ts +35 -10
  156. package/dist/src/core/sender-identity.js +48 -11
  157. package/dist/src/core/sender-identity.js.map +1 -1
  158. package/dist/src/core/state.d.ts +52 -1
  159. package/dist/src/core/state.js +52 -6
  160. package/dist/src/core/state.js.map +1 -1
  161. package/dist/src/core/telegram-config.d.ts +79 -0
  162. package/dist/src/core/telegram-config.js +108 -0
  163. package/dist/src/core/telegram-config.js.map +1 -1
  164. package/dist/src/core/verify.d.ts +23 -1
  165. package/dist/src/core/verify.js +20 -2
  166. package/dist/src/core/verify.js.map +1 -1
  167. package/dist/src/daemon/audit.d.ts +80 -5
  168. package/dist/src/daemon/audit.js +126 -6
  169. package/dist/src/daemon/audit.js.map +1 -1
  170. package/dist/src/daemon/daemon.d.ts +174 -2
  171. package/dist/src/daemon/daemon.js +298 -7
  172. package/dist/src/daemon/daemon.js.map +1 -1
  173. package/dist/src/daemon/git-evidence.d.ts +2 -2
  174. package/dist/src/daemon/git-evidence.js +1 -1
  175. package/dist/src/daemon/projection.d.ts +13 -2
  176. package/dist/src/daemon/projection.js +39 -6
  177. package/dist/src/daemon/projection.js.map +1 -1
  178. package/dist/src/mcp/server.d.ts +39 -3
  179. package/dist/src/mcp/server.js +22 -3
  180. package/dist/src/mcp/server.js.map +1 -1
  181. package/dist/src/muse/server.d.ts +16 -0
  182. package/dist/src/muse/server.js +238 -0
  183. package/dist/src/muse/server.js.map +1 -0
  184. package/dist/src/serve/archive.d.ts +158 -0
  185. package/dist/src/serve/archive.js +354 -0
  186. package/dist/src/serve/archive.js.map +1 -0
  187. package/dist/src/serve/arguments.d.ts +93 -0
  188. package/dist/src/serve/arguments.js +228 -0
  189. package/dist/src/serve/arguments.js.map +1 -0
  190. package/dist/src/serve/credentials.d.ts +91 -0
  191. package/dist/src/serve/credentials.js +143 -0
  192. package/dist/src/serve/credentials.js.map +1 -0
  193. package/dist/src/serve/follow.d.ts +86 -0
  194. package/dist/src/serve/follow.js +101 -0
  195. package/dist/src/serve/follow.js.map +1 -0
  196. package/dist/src/serve/hook-thread.d.ts +134 -0
  197. package/dist/src/serve/hook-thread.js +333 -0
  198. package/dist/src/serve/hook-thread.js.map +1 -0
  199. package/dist/src/serve/hook-worker.d.ts +134 -0
  200. package/dist/src/serve/hook-worker.js +173 -0
  201. package/dist/src/serve/hook-worker.js.map +1 -0
  202. package/dist/src/serve/server.d.ts +412 -0
  203. package/dist/src/serve/server.js +1251 -0
  204. package/dist/src/serve/server.js.map +1 -0
  205. package/docs/README-extended.md +766 -0
  206. package/docs/cli-reference.md +1633 -43
  207. package/package.json +4 -2
  208. package/schema/event.schema.json +59 -2
  209. package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
  210. package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
  211. package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
  212. package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
  213. package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
  214. package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
  215. package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
  216. package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
  217. package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
  218. package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
  219. package/schema/fixtures/event/valid/daemon-identity.json +15 -0
  220. package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
  221. package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
  222. package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
  223. package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
  224. package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
  225. package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
  226. package/schema/policy.schema.json +50 -6
@@ -0,0 +1,766 @@
1
+ # approval.md: the extended guide
2
+
3
+ [Start with the short README](../README.md) · [Specification](../SPEC.md) · [CLI reference](cli-reference.md) · [Website](https://approval.md/)
4
+
5
+ approval.md puts a policy between an agent and the actions you want to oversee. Some actions proceed, some are sampled, some wait for you, and some remain yours to perform. The policy lives in `APPROVAL.md`; the record lives in a hash-chained event log.
6
+
7
+ This guide covers the details behind that loop: setup, integration choices, credential custody, supervision, evidence, and operation. It describes the reference implementation, not a promise that every tool in every harness is intercepted.
8
+
9
+ **Version scope.** Reviewed against `main` on September 20, 2026. The package version is `0.3.0`, and a [v0.3.0 release](https://github.com/approval-md/approval.md/releases/tag/v0.3.0) exists. `main` also contains unreleased work, so check the [changelog](../CHANGELOG.md)'s Unreleased section and the relevant integration runbook before assuming a source feature is in an installed package. The policy format's `version: "0.1"` and values format's `version: "0.2"` are separate from the package version.
10
+
11
+ ## Contents
12
+
13
+ - [How the pieces fit](#how-the-pieces-fit)
14
+ - [Installation and setup](#installation-and-setup)
15
+ - [Define what needs approval](#define-what-needs-approval)
16
+ - [Values and feedback](#values-and-feedback)
17
+ - [Channels and identity](#channels-and-identity)
18
+ - [From a request to an execution](#from-a-request-to-an-execution)
19
+ - [Credentials and adapters](#credentials-and-adapters)
20
+ - [Harnesses and MCP](#harnesses-and-mcp)
21
+ - [The log, evidence, and privacy](#the-log-evidence-and-privacy)
22
+ - [Operating and troubleshooting](#operating-and-troubleshooting)
23
+ - [Security and evidence limits](#security-and-evidence-limits)
24
+ - [How this compares](#how-this-compares)
25
+ - [Developing and maintaining the project](#developing-and-maintaining-the-project)
26
+
27
+ ## How the pieces fit
28
+
29
+ **Files are the interface. The log is the truth. The database is a cache.**
30
+
31
+ `APPROVAL.md` contains human-readable prose and a fenced policy block. A human attests the file's exact bytes. The runtime resolves action classes against that policy and reads verified log state before making gate decisions.
32
+
33
+ Agents reach that runtime through the CLI, an MCP server, a harness hook, or an application using the adapter API. Telegram, the terminal, and the local web queue carry requests and decisions; they do not own approval state.
34
+
35
+ There are two execution paths worth keeping separate:
36
+
37
+ | Path | Who performs the action? | Where the control sits |
38
+ | --- | --- | --- |
39
+ | Runtime command or adapter | `approval run` or an adapter | Policy checks, the execution record, and a single-use token where a grant is required. An adapter can additionally hold the only usable credential. |
40
+ | Harness hook or approval bridge | The agent's harness | The integration returns a permission decision for a covered call. The harness must actually honor it; outcomes depend on the available post-execution reports. |
41
+
42
+ An MCP connection is a way to call the runtime, not a third kind of containment. It does not automatically put unrelated shell, browser, or file tools behind the gate.
43
+
44
+ The main local artifacts are:
45
+
46
+ ```text
47
+ APPROVAL.md policy and optional values
48
+ .approval/log/events.jsonl authoritative event chain
49
+ .approval/QUEUE.md rendered queue, derived from the log
50
+ .approval/*.sqlite rebuildable indexes
51
+ .approval/payloads/ stored bytes behind payload bindings
52
+ .approval/vault.enc encrypted adapter credentials
53
+ .approval/env local environment source map
54
+ .approval/keys/ private keys for sealed token delivery
55
+ .approval-journal/ the separate, ungated agent journal
56
+ ```
57
+
58
+ Task declarations can live under an `approval:` key in Markdown frontmatter. The [Backlog.md example](../examples/backlog-md-project/README.md) shows the envelope on an ordinary task file; adopting a particular board is not the security boundary.
59
+
60
+ ## Installation and setup
61
+
62
+ ### Install the package
63
+
64
+ Use Node.js 20 or newer:
65
+
66
+ ```sh
67
+ npm install -g approval-md
68
+ ```
69
+
70
+ Run setup from the project whose policy and log you intend to use. A source checkout of approval.md and a project using approval.md are different directories; do not accidentally configure the former when you meant the latter.
71
+
72
+ ### Guided setup
73
+
74
+ ```sh
75
+ approval quickstart
76
+ ```
77
+
78
+ This is a human-at-a-terminal walkthrough, not an unattended installer. It asks for an identity, a terminal or Telegram channel, and the action families that should always ask: communications, spending, file deletion, public posting, and pushes to main. It displays the resulting policy and asks you to type `understood` before attesting it.
79
+
80
+ The generated policy uses **`autonomous` for other classified, reversible actions**. It is a convenience policy, not a default-deny policy. Change `defaults.autonomy` to `manual` and re-attest when that better fits your project. Safety floors and unclassified-command refusals are separate checks and still apply. Retrospective sampling is not enabled by this walkthrough.
81
+
82
+ An interrupted ceremony does not leave an attested policy behind. It also does not install harness hooks or start an agent.
83
+
84
+ ### Manual setup
85
+
86
+ ```sh
87
+ approval init
88
+ approval setup identity
89
+ ```
90
+
91
+ `init` creates missing policy and runtime files, merges its ignore entries into `.gitignore`, and does not overwrite an existing policy. It creates an empty log directory, not an attestation or a fabricated first event.
92
+
93
+ **Read and edit the scaffold.** It is an example policy: it names `alice`, includes example budgets, and declares retrospective sampling without provisioning its secret. Replace the approver, rules, and channel configuration with your choices. The [short README's example](../README.md#define-what-needs-approval) is a smaller, terminal-first starting point.
94
+
95
+ After reviewing the file, attest it with the identity named in your policy:
96
+
97
+ ```sh
98
+ approval policy attest --as human:you
99
+ ```
100
+
101
+ Replace `you` with your actual approver ID. The first attestation creates the event log. Attestation records which bytes were accepted; it is not a claim that the policy is sensible or that every integration has been installed.
102
+
103
+ ### Activate and start
104
+
105
+ The setup commands record local configuration sources in `.approval/env`. Activate them explicitly in the human-controlled terminal that will run the daemon:
106
+
107
+ ```sh
108
+ approval env --check
109
+ eval "$(approval env)"
110
+ approval doctor
111
+ approval up
112
+ ```
113
+
114
+ `approval env --check` reports configuration sources without printing secret values. Use the absolute-directory activation command printed by quickstart when the shell is elsewhere.
115
+
116
+ `approval up` is a foreground process running the daemon and configured channel listeners. It handles expiry, task-envelope drift checks, queue regeneration, and configured retrospective sampling. A missing optional credential or an absent task folder is not proof that those features are active; read startup diagnostics and `approval doctor`.
117
+
118
+ For terminal decisions, open a separate human-controlled terminal in the same project, activate its intended environment, and run `approval channel cli`. For Telegram, configure the bot before expecting a phone prompt.
119
+
120
+ Starting the daemon does not intercept an agent's tools. Install the chosen [harness integration](#harnesses-and-mcp), then test a harmless call that your policy requires to pause. Confirm the request arrives and the expected record lands before relying on the setup.
121
+
122
+ ### Install from source
123
+
124
+ Use a checkout for unreleased work or contributions:
125
+
126
+ ```sh
127
+ git clone https://github.com/approval-md/approval.md.git
128
+ cd approval.md
129
+ npm ci
130
+ npm run build
131
+ npm link
132
+ ```
133
+
134
+ The linked CLI executes the compiled build. Rebuild after source changes; `approval doctor` checks for a stale build. Source-checkout startup can also perform its documented fetch, safe fast-forward, and rebuild preflight. Do not treat `approval up` as a read-only command; see the [startup reference](cli-reference.md#up).
135
+
136
+ ## Define what needs approval
137
+
138
+ The policy is exactly one `yaml approval-policy` fenced block inside `APPROVAL.md`. Surrounding prose helps humans and agents understand your intent, but is not executable policy. Unknown policy keys fail validation rather than silently becoming unenforced promises.
139
+
140
+ ### Five levels, not six different behaviors
141
+
142
+ | Autonomy | Meaning | Additional setup |
143
+ | --- | --- | --- |
144
+ | `human-only` | Reserved for a human to perform outside agent execution. The agent cannot request, receive, or spend permission for it. | No approval can turn this into an agent action. |
145
+ | `manual` | Requires a human decision before each covered action proceeds. | A reachable approval channel. |
146
+ | `supervised-live` | A declared fraction follows the manual path; other actions can proceed under policy. | A class-level `live_rate` and the configured sampling machinery. |
147
+ | `supervised-retro` | Can proceed immediately, with a configured sample reviewed afterwards. | Retrospective rate and an operator-held sampling secret. |
148
+ | `autonomous` | Does not ask for human approval. | Attestation and the other applicable runtime checks still matter. |
149
+
150
+ The schema also accepts the deprecated spelling `supervised`, an alias of `supervised-retro`. It is not a sixth behavior. `supervised-live` cannot be the default autonomy because its required rate belongs on a class rule.
151
+
152
+ Retrospective review is not an approval that happened earlier, and a denial afterwards cannot undo an email or payment.
153
+
154
+ ### Classes and matching
155
+
156
+ Classes describe actions: `communicate.email.external`, `financial.spend`, `files.write.workspace`, `vcs.push.main`, `vcs.ref.delete`, or `account.credential`. Rules match most-specific-first. A `*` matches a segment; trailing `.*` covers deeper descendants. Equally specific rules resolve to the strictest applicable autonomy.
157
+
158
+ Unmatched classes use `defaults.autonomy`. An unparseable policy resolves conservatively to all-manual; an unattested or changed policy cannot simply authorize work through an old attestation.
159
+
160
+ A class name is **not** an integration. Adding `calendar.write.own` does not install a calendar adapter. Harness hooks classify the command and path information they receive; directly registered tasks declare their own envelopes. Neither is automatic discovery of every action a model, plugin, browser, or external service could perform.
161
+
162
+ ```sh
163
+ approval hook classify -- git push origin main
164
+ approval policy check vcs.push.main --reversible false
165
+ ```
166
+
167
+ The first command inspects classification without executing the command. The policy check explains resolution; it is diagnostic, not authorization to execute. `approval instructions --schemas` describes approval.md's own CLI verbs, not the complete capability set of a connected harness.
168
+
169
+ ### Sampling
170
+
171
+ These are fragments to merge into an existing policy, not complete replacement files:
172
+
173
+ ```yaml
174
+ classes:
175
+ files.write.workspace:
176
+ autonomy: supervised-live
177
+ live_rate: 0.10
178
+ retro_rate: 0.20
179
+ vcs.commit.branch:
180
+ autonomy: supervised-retro
181
+ retro_rate: 0.10
182
+
183
+ audit:
184
+ sampling_secret_env: APPROVAL_SAMPLING_SECRET
185
+ supervised_sample_rate: 0.10
186
+ ```
187
+
188
+ `live_rate` controls pre-execution selection; `retro_rate` overrides the global retrospective rate for that class. Naming an environment variable does not create a secret. Provision it outside agent-owned configuration and check sampling with `approval doctor`. Live selection uses the runtime's operator-held sampling mechanism; a percentage in YAML alone is not a functioning control.
189
+
190
+ ### Irreversible actions
191
+
192
+ An action declared `reversible: false` normally raises nonmanual autonomy to `manual`. `human-only` remains denied to agents.
193
+
194
+ An operator can deliberately retain nonmanual behavior with `allow_irreversible: true` on an eligible class rule. Every equally most-specific matching rule must opt in, and the changed policy must be attested. This exception cannot be placed in `defaults` or used to override `human-only`.
195
+
196
+ Treat it as a deliberate delegation, not a way to repair an inconvenient prompt. A declaration of reversibility is also not independent proof that a real-world effect can be undone.
197
+
198
+ ### Budgets and request limits
199
+
200
+ Class limits and applicable budget scopes are conjunctive: satisfying one does not waive another. Consumption is computed from the log over rolling windows, rather than trusting a mutable counter.
201
+
202
+ ```yaml
203
+ classes:
204
+ financial.spend:
205
+ autonomy: manual
206
+ limits:
207
+ per_action_usd: 25
208
+ daily_usd: 100
209
+ max_pending: 3
210
+ requests_per_hour: 10
211
+
212
+ budgets:
213
+ global:
214
+ daily_usd: 100
215
+ daily_actions: 200
216
+ max_pending: 20
217
+ ```
218
+
219
+ These controls bound the actions and cost information the runtime actually sees. They are not a replacement for provider billing limits or an independent accounting of every charge an agent could cause elsewhere.
220
+
221
+ ### Reads and protected files
222
+
223
+ `read_scope.roots` adds directories to the built-in read scope: the policy's own project root, the session scratchpad, and the system temporary directory. Relative additions resolve against the policy root, not an arbitrary working directory supplied by a caller. Scope checks resolve paths and symlinks.
224
+
225
+ An out-of-scope read classifies as `read.file.out_of_scope`. Give that class an explicit rule; a broad autonomous `read.*` rule is not a read restriction by itself.
226
+
227
+ ```yaml
228
+ read_scope:
229
+ roots: [../shared-reference]
230
+
231
+ classes:
232
+ read.*: { autonomy: autonomous }
233
+ read.file.out_of_scope: { autonomy: manual }
234
+ ```
235
+
236
+ This applies on the read surfaces the integration covers. A native `Read` tool omitted from a hook matcher is not controlled merely because the classifier supports it.
237
+
238
+ The built-in protected set includes the policy, log-related state, agent instructions, harness configuration, and other governing surfaces, with distinct classifications. `protected_paths` can add protection or route a path family to an eligible `policy.edit` subclass; it cannot narrow built-in protection.
239
+
240
+ ```yaml
241
+ protected_paths:
242
+ - path: SPEC.md
243
+ class: policy.edit.spec
244
+ - path: design/
245
+ class: policy.edit.design
246
+
247
+ classes:
248
+ policy.edit: { autonomy: manual }
249
+ policy.edit.spec: { autonomy: manual }
250
+ policy.edit.design: { autonomy: manual }
251
+ ```
252
+
253
+ These are literal files or directory prefixes, not globs. Core gate configuration is not just an ordinary document: its human-only rules cannot be weakened by adding a path entry.
254
+
255
+ ### Change and attest the policy
256
+
257
+ `approval policy attest` records the SHA-256 of the whole `APPROVAL.md` file. Editing prose or values also changes those bytes, even though neither is executable policy. A mismatch requires a new attestation.
258
+
259
+ For policy changes, use the `approval policy amend` workflow. It presents semantic changes to resolutions, approvers, defaults, and limits. `--require-load` refuses to attest a policy that does not load; `--dry-run` reports without applying. Read `approval policy amend --help` for the change-input and commit options rather than guessing their syntax.
260
+
261
+ **Edited `APPROVAL.md` in a checkout that has fallen behind.** This is the ordinary state after a policy edit, and `approval log sync` or `approval up` will refuse to fast-forward over the uncommitted file. Do not stash, reset, or check the file out to get past that. Run `approval policy amend --pr --require-load --as human:<id>` instead: it fetches `origin/main` itself, bases the amendment commit on the remote rather than on your checkout, attests the edited bytes, commits the policy and the log together, pushes a `policy-amend-<seq>` branch, opens the pull request, and arms its merge. Rehearse with `--dry-run` first. Once that lands, the sync and the daemon start go through.
262
+
263
+ Two related attestations are different evidence: `approval policy attest --organ <path>` records reviewed gate-configuration bytes, while `--path <path>` records whole-file sign-off for eligible protected documents. Neither is a substitute for attesting the operative policy. The [hook installation guide](claude-code-hook.md#installing-it) explains the CI evidence required for gate configuration.
264
+
265
+ ### Why this verb exists: seq 2
266
+
267
+ Read this repository's own log. At **seq 2** a policy amendment was attested at
268
+ 11:56:07. It was **superseded** seven minutes later, at seq 3, because the edit
269
+ broke a pinned assertion and nobody found out until the test suite ran against
270
+ it. The operator attested bytes whose consequences had never been shown to
271
+ them. (This account originally said eleven minutes. The log says seven, and
272
+ the log won.)
273
+
274
+ That is the failure the load advisory is for. Had `approval policy amend`
275
+ existed that morning, the load failure would have been on screen while the
276
+ human was deciding, and `--require-load` would have refused to attest at all.
277
+
278
+ ### Policy reference
279
+
280
+ The closed [policy schema](../schema/policy.schema.json) and [SPEC section 5](../SPEC.md#5-the-approvalmd-policy-file) are the complete field reference. The main groups are:
281
+
282
+ | Group | What it controls |
283
+ | --- | --- |
284
+ | `defaults` | Fallback autonomy, channel, approval lifetime, expiry behavior, and token delivery. |
285
+ | `classes` | Per-class autonomy, approvers, rates, limits, and explicit irreversible-action delegation. |
286
+ | `approvers` | Eligible channels and optional transport-sender mappings. |
287
+ | `budgets` | Global and named-scope spend, action, and pending-request ceilings. |
288
+ | `read_scope` / `protected_paths` | Additional read roots and protected file families. |
289
+ | `audit` | Sampling, timestamp-skew reporting, and checkpoint keys and cadence. |
290
+ | `daemon` | Verification behavior for long-lived readers. |
291
+ | `daemons` | The daemon instance ids allowed to append, for a daemon one party hosts for another; absent means no restriction. |
292
+ | `channels` | Telegram and web configuration, plus supported prompt-layout choices. |
293
+ | `vault` | The name of the environment variable holding the vault passphrase. |
294
+ | `payload_retention` | Optional pruning policy for eligible stored payloads; absence means retain them. |
295
+
296
+ Keys ending in `_env` name environment variables, not secret values. Prompt customization cannot hide the fields required for a decision or remove the canonical payload from the authority path.
297
+
298
+ #### Every key
299
+
300
+ Every key that can appear in the policy block, and after it the four of the
301
+ optional values block. The schema is closed at every level: an unrecognised key
302
+ fails validation, which fails the policy closed to all-manual, because a key the
303
+ runtime did not understand is a rule its author believed was in force. A values
304
+ key the schema refuses fails the values reader alone and never the policy. Full
305
+ semantics: SPEC.md section 5.
306
+
307
+ | key | what it says |
308
+ | --- | --- |
309
+ | `version` | Policy format version, quoted (`"0.1"`). The only required key (§5.1). |
310
+ | `defaults.autonomy` | Autonomy for an action matching no class rule. Five of the six levels are admitted: `supervised-live` is not, since it needs a `live_rate` that `defaults` has nowhere to hold. `human-only` is, and reserves every unnamed class to human hands. No default of its own, and `manual` is the fail-closed choice (§5.2, APRV-185). |
311
+ | `defaults.channel` | Channel name requests surface on by default; expected to name a key of `channels`, which is a runtime cross-check rather than a schema one. No default (§5.1, §10.3). |
312
+ | `defaults.approval_ttl` | How long a pending request stays actionable. Duration string, `24h`. No default; the scaffolded policy writes one (§5.1). |
313
+ | `defaults.token_delivery` | How a minted token reaches the process that will spend it. `manual` (the default, and what an absent key means): printed once on the granting surface and carried by a human. `sealed`: sealed to a per-request X25519 key so `approval wait` can hand it back, which addresses the token and never authorizes it (§10.4, APRV-105). |
314
+ | `defaults.on_expiry` | What happens when the TTL lapses. `reject` is the only value, and absent means `reject` (§5.1). |
315
+ | `payload_retention` | How long payload bytes are kept after their action is terminal. Absent means nothing is ever pruned (§5.2). |
316
+ | `protected_paths` | Repo-relative files and directory prefixes whose edit is classified `policy.edit`. A bare string is the whole entry. Additive only, no globs, and absent means the built-in protected set alone (§5.2, APRV-107). |
317
+ | `protected_paths[].path` | The path half of the object form: the same grammar as the bare string, an exact file (`SPEC.md`) or a directory prefix (`design/`) (§5.2, APRV-266). |
318
+ | `protected_paths[].class` | The class half: one lowercase segment under `policy.edit`. Four reserved names, `policy.edit.spec`, `policy.edit.harness`, `policy.edit.ci` and `policy.edit.design`, plus any word an author mints beside them. Nothing outside `policy.edit` may be named, and a route below the `policy.edit` line is refused `protected-route-floor`. No default: an entry that wants a sub-class states it (§5.2, APRV-266). |
319
+ | `read_scope` | Directories an agent's reads may stay inside. A read resolving outside every root is `read.file.out_of_scope`. Additive only: the gate root (this policy's own directory), the session scratchpad and the system temp root are in scope whatever this says, and absent means those alone (§5.2, APRV-347). |
320
+ | `read_scope.roots` | The extra directories. Absolute, or relative to the gate root. No globs and no negation; each is resolved on disk before it is compared, so a symlink cannot smuggle a read out of one. Absent means no widening (§5.2, APRV-347). |
321
+ | `approvers.<name>.channels` | The channels one approver can decide on. At least one: an approver reachable nowhere can never grant. No default (§5.1). |
322
+ | `approvers.<name>.senders` | The transport account ids this person decides from, per channel: `senders.telegram: "12345678"`, the numeric `callback_query.from.id` and never a `@handle`, which is mutable and would transfer an identity. Optional and additive; absent everywhere means every decision is recorded against the identity the deciding process was launched with, exactly as before the key existed. Declaring the first entry for a channel turns enforcement on for that channel: a sender it authenticates and this block does not name is refused `sender-unmapped` and nothing is recorded. Two approvers claiming one id refuses the whole policy (`sender-ambiguous`), so every class resolves `manual`. Only channels whose transport authenticates a sender may appear, which today is `telegram` alone (§5.2, §10.3, APRV-324). |
323
+ | `classes.<pattern>.autonomy` | Required on every class rule, so it has no default. Six levels, strictest first: `human-only`, `manual`, `supervised-live`, `supervised-retro`, `autonomous`, and `supervised`, which is the pre-split spelling and the DEPRECATED alias of `supervised-retro`: it still parses, `approval doctor`'s `autonomy-alias` row names every rule that writes it, and a future schema version removes it (§5.2, APRV-127, APRV-185, APRV-335). |
324
+ | `classes.<pattern>.live_rate` | The fraction of a `supervised-live` class that blocks on the gate, in (0, 1]. Required there and refused everywhere else, so it has no default: a live mode with no fraction declares a control without saying how much of it runs. Selection is HMAC-SHA-256 over the payload hash under the operator's secret (§5.2, APRV-127). |
325
+ | `classes.<pattern>.retro_rate` | This class's retrospective sampling rate, in (0, 1], overriding `audit.supervised_sample_rate` for it alone. Optional on `supervised`, `supervised-retro` and `supervised-live`, refused on the rest. Absent means the global rate (§5.2, APRV-183). |
326
+ | `classes.<pattern>.allow_irreversible` | Explicit operator permission for a truthful `reversible: false` action to retain this rule's `autonomous` or supervised behavior. Optional boolean; absent or `false` preserves the manual floor. `true` is refused on `manual` and `human-only`, cannot appear in `defaults`, and takes effect only when every equally most-specific matching rule says `true` (§5.2, §7, APRV-317). |
327
+ | `classes.<pattern>.agent_may_request` | Whether an agent may open a request in this class with `approval propose`, naming the class itself and carrying the payload inline. Optional boolean; absent or `false` means no. Read only on a key that names the class EXACTLY and on a declared `<prefix>.*` family of it; the exact key's own value wins, else the most specific family that states one. `propose` additionally requires the class to be an exact key, so a wildcard match alone never opens it. `true` is refused on `human-only` (§5.2, APRV-445). |
328
+ | `classes.<pattern>.approvers` | Approver ids permitted to decide this class. Absent restricts nobody, since the list is a narrowing and a narrowing nobody wrote narrows nothing; a named list refuses everyone else with `actor-not-approver` (§5.1). |
329
+ | `classes.<pattern>.limits` | Per-class ceilings, every value a positive number: `per_action_usd`, `daily_usd`, and the request-volume counts `max_pending` and `requests_per_hour`. Absent means this class carries no ceiling of its own (§5.1, §5.2). |
330
+ | `budgets.global.daily_usd` | Repo-wide spend ceiling per rolling day, computed from the log. Absent means no spend ceiling (§5.1). |
331
+ | `budgets.global.daily_actions` | Repo-wide count of side-effecting actions per rolling day. Absent means no count ceiling (§5.1). |
332
+ | `budgets.global.max_pending` | Simultaneously pending requests across the scope; excess is refused `queue-full`. Absent means no ceiling (§5.2). |
333
+ | `budgets.<scope>` | Any other named scope, same three keys. Budgets are conjunctive with class limits (§5.2). |
334
+ | `audit.supervised_sample_rate` | The FALLBACK fraction of supervised actions escalated for retrospective review, in [0, 1], for classes declaring no `retro_rate`. Absent means no fallback rate is configured (§5.2, APRV-183). |
335
+ | `audit.sampling_secret_env` | Name of the variable holding the operator's HMAC sampling secret. Unnamed means sampling is off and says so (§5.2, §11). |
336
+ | `audit.skew_tolerance` | How far a gate-typed event's timestamp may step back before verification reports an anomaly. Report-only; default 2 seconds (§8). |
337
+ | `audit.checkpoint_keys` | Public halves of the Ed25519 keys permitted to sign a `log.checkpoint`, base64 DER SPKI. The private halves live in the vault and never in this file. A list, so a retired key stays listed: a checkpoint signed by a key the list does not carry is refused. Absent, empty or unreadable means verification skips the checkpoint check with a reason and never reports it as a pass (§9, APRV-220). |
338
+ | `daemons` | The daemon instance ids permitted to append to this log, for the case where one party runs the daemon for another. Optional and additive; ABSENT means no restriction, exactly as before the key existed, and an empty list admits no daemon at all. Every record a daemon appends carries its id (`daemon` on the record), which is `APPROVAL_DAEMON_ID` from that process's launch environment or the derived `daemon-<instance id>` that `approval status` and `approval doctor` print. An id this list does not name is refused at the write boundary (`daemon-not-allowed`) with nothing written; an id it DOES name gains nothing else, since the id is self-reported and no verdict, budget, floor, draw or token reads it. Read from the attested policy only (§5.2, APRV-383, `design/hosted-daemon-identity.md`). |
339
+ | `audit.checkpoint_every` | How long the log may go without a human-signed checkpoint before verification says one is due, and before the listener puts one `CHECKPOINT DUE` prompt on the approver's channel (`approval setup checkpoint` mints the key). Report-only at every layer: a due checkpoint is a warning and never a refusal. Absent means the cadence is off and nothing is ever reported as due (§9, APRV-220, APRV-257). |
340
+ | `daemon.read_proof` | Which prefix proof a long-lived reader runs before reusing a cached prefix: `full` (the default, re-hash the whole prefix on every read) or `incremental` (hash only the appended bytes, re-proving in full on a cadence). One-shot processes, the Claude Code hook and `approval log verify` prove in full regardless (§5.2, APRV-217). |
341
+ | `daemon.full_reproof_every` | Reads one full re-proof may cover under `incremental`, the anchoring read included. Default 50 (§5.2). |
342
+ | `daemon.full_reproof_after` | Wall clock one full re-proof may cover under `incremental`. Duration string, default `60s` (§5.2). |
343
+ | `vault.passphrase_env` | Name of the variable holding the vault passphrase. Absent means `APPROVAL_VAULT_PASSPHRASE` (§5.2, §10.4). |
344
+ | `channels.telegram.token_env` | Name of the variable holding the bot token. Default `APPROVAL_TG_TOKEN` (§5.1). |
345
+ | `channels.telegram.chat_id_env` | Name of the variable holding the approver chat id. Default `APPROVAL_TG_CHAT` (§5.1). |
346
+ | `channels.telegram.delivery` | `paced` (the default) shows one summary line and the oldest pending request, then the next one after a decision, `/skip` or `/next`; `burst` sends every pending request the listener has not sent yet. Neither mode changes what is pending: that is re-derived from the verified log on every cycle (§10.3, APRV-216). |
347
+ | `channels.web.port` | TCP port for the local approval UI, bound on loopback only. No default in the schema; the scaffolded policy names `4680`, and 0 is excluded because the policy must name a port a human can navigate to (§5.1). |
348
+ | `channels.<name>.prompt.rows` | Order only, for `telegram`, `web` and `cli`: the rows named here render in this order ahead of the rest, which keep their default relative order behind them. Never a whitelist, so a field added later cannot be lost to a list written before it existed. Absent means the layout the channel ships (§5.2, §10.3, APRV-218). |
349
+ | `channels.<name>.prompt.always` | Rows this channel renders only when abnormal, or not at all, render on every prompt instead. The anomaly mark stays a statement about the value, so a forced-on row shouts only when the value is in fact the reason to look. Absent means the channel's own visibility rules (§5.2, §10.3, APRV-218). |
350
+ | `channels.<name>.prompt.hide` | Rows this channel never renders. Refused for the rows required for a decision (`action_key`, `class`, `command_breakdown`, `protected_path`, `policy_diff`, `policy_load`) with `prompt-row-required`, and refused for a row `always` also names. Absent means nothing is hidden, and the canonical payload block is out of reach either way (§5.2, §10.3, APRV-218). |
351
+ | `channels.<other>` | An unknown channel name is accepted as an object, so a third-party transport does not fail the whole policy closed (§10.3). A `prompt` block written under such a name is still validated: a layout is checked wherever it appears. |
352
+
353
+ And the values block (SPEC.md §5.3), which is guidance and reaches no
354
+ enforcement path:
355
+
356
+ | key | what it says |
357
+ | --- | --- |
358
+ | `version` | Values format version, quoted (`"0.2"`), spelled as the policy block's `"0.1"` is. The only required key here too. The quotes are load-bearing: a bare `0.2` is a float to YAML, and the reader refuses it by name (§5.3, APRV-336). |
359
+ | `love` | What you love in the work. The strongest of the three standing grades. Absent means you named none (§5.3). |
360
+ | `like` | What you like, which since APRV-336 is also where what you ask of an agent as behaviour lives: the former `wants` list folded in here (§5.3). |
361
+ | `dislike` | What you dislike. NOT a prohibition, which belongs in the policy block where it is enforced (§5.3). |
362
+ | `communication` | One string, at most 500 characters, on how you read and answer, so an agent can read silence or terseness correctly. Called `responds` before APRV-336 (§5.3). |
363
+
364
+ Every key ending in `_env` carries a variable's *name* and never its value:
365
+ agents may read `APPROVAL.md`, so a secret it carried would be a secret they
366
+ hold. Where those values live is recorded in `.approval/env`, which a single
367
+ verb reads, `approval env`, whose output is an export block a human evaluates.
368
+
369
+ ## Values and feedback
370
+
371
+ Approval is also a judgment about the work, not only permission to execute. An optional second block carries your voice:
372
+
373
+ ````markdown
374
+ ```yaml approval-values
375
+ version: "0.2"
376
+ love:
377
+ - honest thoughts on the work
378
+ - small diffs with one idea
379
+ like:
380
+ - success reported first
381
+ - say when you are stuck
382
+ dislike:
383
+ - confident documentation that is stale
384
+ communication: I read the journal after a session and react on the samples.
385
+ ```
386
+ ````
387
+
388
+ ```sh
389
+ approval values
390
+ approval feedback
391
+ ```
392
+
393
+ Values and feedback are human-authored guidance. Reviews and grants can carry graded reactions; the strongest reactions require a note. `approval journal write` is the agent's separate, ungated outlet.
394
+
395
+ None of these changes an autonomy verdict, budget, token, or sampling draw. A malformed values block does not invalidate the policy parser. `approval import agents-md` can draft policy and values from supported sections of an instruction file; its output still needs human review and attestation.
396
+
397
+ ## Channels and identity
398
+
399
+ ### Telegram
400
+
401
+ ```sh
402
+ approval setup channel telegram
403
+ ```
404
+
405
+ This provisions the local channel configuration. The operative policy must also name Telegram as the desired channel, allow the approver to use it, and declare the relevant channel settings. Review and re-attest policy changes; setup is not permission to change policy silently.
406
+
407
+ The setup uses the supported OS keystore and records configuration sources in `.approval/env`. The policy contains variable names, not the bot token. Reload the intended environment before starting `approval up`.
408
+
409
+ Requests distinguish **computed** facts from **claimed** agent-authored descriptions and include the payload to be approved. Agent text is rendered as data. An optional model-generated gloss is labeled unverified and does not change the verdict. Decisions update and disarm the request card.
410
+
411
+ Use a distinct bot for each independently polling instance. Two listeners sharing a bot conflict; the runtime includes a local ownership check, but it cannot discover every competing process on another machine. See the [Telegram walkthrough](../examples/telegram-demo.md).
412
+
413
+ ### Terminal and local web
414
+
415
+ `approval channel cli` presents the queue in a human-controlled terminal. `approval channel web` serves the local queue on loopback; the policy declares its port.
416
+
417
+ The web channel has no independent login proving who clicked. Its boundary is access to the local machine and socket. There is no anti-CSRF token; the same-origin check is a speed bump, not protection against a local process that can POST directly. Do not expose this queue publicly as an authenticated approval service.
418
+
419
+ ### What the recorded identity means
420
+
421
+ Local `human:<id>` identities are config-declared. Someone able to set that configuration and write to the gate's state is inside the trust boundary. An attestation does not prove that a particular physical person was present.
422
+
423
+ Telegram can additionally map transport-authenticated sender IDs to approvers through the attested policy. Once a channel has mappings, unmapped senders are refused rather than attributed to the listener's identity. Use numeric account IDs, not mutable handles. Keyed `hmac-sha256:` mappings avoid publishing raw account IDs; `approval setup sender-key` provisions the operator-held key.
424
+
425
+ Sender mapping attributes a transport account. It is not proof of personhood, and it does not remove the trusted-host assumption. See the [channel reference](cli-reference.md#channel).
426
+
427
+ ## From a request to an execution
428
+
429
+ For a directly registered action, the normal sequence is:
430
+
431
+ ```text
432
+ Declare the task and payload
433
+ ↓
434
+ Register → request → human decision, when required
435
+ ↓
436
+ Wait → execute through the appropriate runtime boundary
437
+ ↓
438
+ Record the outcome → review and reconcile
439
+ ```
440
+
441
+ The [Telegram demo](../examples/telegram-demo.md) and [Backlog.md example](../examples/backlog-md-project/README.md) contain complete declarations and runnable command sequences. An illustrative sequence is not a substitute for a valid envelope and payload.
442
+
443
+ The runtime binds the declared payload, checks policy and relevant limits, and records the request. A human grant on the command/adapter path mints a single-use token. Execution verifies the binding and consumes the grant before reaching the effect.
444
+
445
+ `approval run <action> --token "$TOKEN" -- <command>` records the execution start before spawning the child and records its outcome afterwards. It returns the child's exit status. Reusing a spent token is refused. A failed or ambiguous provider operation is not permission to replay a consumed grant blindly; inspect what happened before requesting another action.
446
+
447
+ **Hook grants are different.** The runtime returns a permission verdict to the harness rather than a token for it to execute arbitrarily. Grant consumption is recorded on the harness path. Register the post-execution counterparts to obtain outcomes, and distinguish an observed result from a start with no corresponding result.
448
+
449
+ ### Token delivery
450
+
451
+ With the default `manual` delivery, the raw token is shown once at the surface that records the grant. A Telegram decision prints it in the runtime's terminal, not in the chat. The local web channel displays it once in the granting response.
452
+
453
+ With `defaults.token_delivery: sealed`, the request carries a recipient public key and the token is sealed for the requesting process. The log may contain ciphertext; the private key stays local. Sealing changes who can retrieve an authorized token, not who may authorize the action.
454
+
455
+ The raw token is not an ordinary log field. Keep tokens, private keys, vault passphrases, and provider credentials out of transcripts, task files, commits, and example payloads.
456
+
457
+ ### No answer is not approval
458
+
459
+ `defaults.approval_ttl` bounds the pending request's lifetime. Expiry does not become approval. The waiter's timeout and the request's lifetime are separate: a caller stopping its wait is not automatically a rejection.
460
+
461
+ `approval withdraw` retracts a pending request; `approval wait --withdraw-on-timeout` makes that behavior explicit for a waiter. Harness retries have their own carryover rules, described in the corresponding integration guide. Inspect an existing request before creating duplicates.
462
+
463
+ ## Credentials and adapters
464
+
465
+ The strongest control is often a missing capability: the agent can prepare the action, but cannot perform it without a credential held elsewhere.
466
+
467
+ ```sh
468
+ approval setup vault
469
+ approval setup adapter email
470
+ ```
471
+
472
+ The encrypted vault is an adapter credential store. `.approval/env` is a separate map of how the operator's process obtains its configuration. Keeping an encrypted file is not sufficient if the agent also has its passphrase or an alternative sending key.
473
+
474
+ `approval vault set` accepts secret material through stdin or a named environment variable, not a `--value` command-line argument. There is no `approval vault get`. The shared adapter contract verifies the payload and policy, records the start, opens the credential window, executes, redacts the result, and records the outcome.
475
+
476
+ Manual and selected-live actions need a grant token. Policy-authorized nonmanual actions do not mint a pretend human grant; their credential access follows the documented non-token path.
477
+
478
+ ### SMTP email
479
+
480
+ `approval adapter email` sends the bound email payload through SMTP with STARTTLS using vaulted settings. Setup probes the connection without sending a message. The adapter derives a `Message-ID` from the action binding so the email and log can be correlated.
481
+
482
+ A successful adapter call is not a guarantee that a recipient read the message, nor a universal exactly-once delivery guarantee. See the [SMTP walkthrough](../examples/email-demo.md) for the full setup and execution sequence.
483
+
484
+ ### AgentMail drafts
485
+
486
+ `approval adapter agentmail` uses a two-key arrangement: the agent's key can create, update, and read drafts; the vaulted key holds sending authority such as `draft_send` and `message_send`.
487
+
488
+ `approval payload agentmail-draft` snapshots the draft for approval. Before sending, the adapter re-fetches it and refuses `agentmail-draft-drifted` when the checked fields no longer match. This check happens before token consumption.
489
+
490
+ The provider permissions are part of the boundary. An agent that also holds a sending key can go around this arrangement. Follow the [AgentMail walkthrough](../examples/agentmail-demo.md), including the negative tests.
491
+
492
+ ### First-class zzz.bot messages
493
+
494
+ `approval adapter zzz` creates threads or replies through the versioned zzz.bot API. The tagged payload binds the selected environment, operation, destination, content, metadata, and references. The adapter uses fixed supported origins, rejects redirects, and derives an idempotency key from the action and payload binding.
495
+
496
+ Public writes need an invited credential with write scope. Private-room writes additionally depend on room membership and accepted, unexpired workflow evidence. The setup probe checks an authenticated room-list read; it does not prove every write prerequisite.
497
+
498
+ This adapter is included in the published 0.2/0.3 package line, not just an unreleased experiment. Its repository tests use a mock HTTP service; do not mistake that for evidence of a live post in your environment. See [`adapter zzz`](cli-reference.md#adapter-zzz).
499
+
500
+ ### Build an adapter
501
+
502
+ Import the supported ESM API from `approval-md/adapters`, not from internal `dist/src` paths. `executeThroughAdapter` supplies the shared execution contract; `runAdapterConformance` tests ordering, binding, single use, credential scope, redaction, and failures.
503
+
504
+ The [adapter API guide](adapter-api.md) includes a typed example and the vault credential provider. Passing conformance checks the adapter under its fixtures, not the provider's permissions or custody on your host. Automatic third-party registration into the CLI is separate work; an embedding application can use the public contract today.
505
+
506
+ ## Harnesses and MCP
507
+
508
+ The following reflects the versions and evidence described in the linked repository runbooks. A fixture test, a source reading, and a live probe are different kinds of evidence. Recheck behavior after harness upgrades.
509
+
510
+ | Surface | Integration | Coverage and maturity |
511
+ | --- | --- | --- |
512
+ | Claude Code | `approval hook claude-code` | Pre-execution gating and post-execution reporting for supported, matched tools. Install both halves; review the documented timeout and carryover limits. |
513
+ | Cursor | `approval hook cursor` | Native Cursor dialect. `failClosed: true` is required to cover hook failures. Additional read-tool support must be deliberately wired and checked. |
514
+ | Claude Agent SDK | Python `HookMatcher` shim | Calls the Claude Code hook and supplies the tool-use ID. Repository fixtures exercise the CLI envelope; they are not a substitute for testing the complete SDK host. |
515
+ | Grok Build | `approval hook grok` | Dedicated dialect. Documented crash, timeout, and malformed-output paths fail open; the compatibility behavior still needs the documented live probe. |
516
+ | Muse Code | `approval hook muse` | Formats and failure behavior have live-probe evidence for the documented build. The harness fails open on hook failures, including invalid mixed-dialect output. |
517
+ | Hermes Agent | `approval hook hermes` | Live-probe evidence on `main` at `118984d7`: `fail_closed: true` blocks a hook crash, a hook timeout and unparseable output, which makes this the only harness besides Claude Code whose hook is enforcement rather than a backstop. Below that build the key is ignored silently, so the version floor and `approval doctor`'s row matter. Requires explicit hook registration, `hooks_auto_accept` where there is no TTY, and an absolute working directory on every gated call. |
518
+ | Generic MCP | `approval mcp serve` | Agent-facing runtime tools. Does not automatically intercept native tools or give an agent human decision authority. |
519
+ | Sandboxed harness (HTTP) | `approval serve` | The same agent-facing verbs over HTTP, plus `hook/<harness>` for every adapter above, a paged `log/follow`, and a store export. For a harness with no local log and no local policy. Two bearer credentials from the launch environment; the process terminates no TLS. |
520
+
521
+ Runbooks: [Claude Code](claude-code-hook.md), [Cursor](cursor-hook.md), [Agent SDK](agent-sdk-hook.md), [Grok](grok-hook.md), [Muse](muse-hook.md), [Hermes](hermes-hook.md).
522
+
523
+ Do not copy a hook command between harnesses on the assumption that similarly named events speak the same protocol. A deny encoded for one harness can be ignored by another.
524
+
525
+ ### A harness that has no local log: `approval serve`
526
+
527
+ Every row above assumes the harness and the gate share a filesystem. When they do not, because the harness runs in a sandbox and the daemon runs elsewhere, `approval serve` is the door. It publishes the same agent-facing verbs `approval mcp serve` publishes, name for name and schema for schema, and adds three endpoints that transport withheld: `POST /hook/<harness>` takes the envelope the stdin form would read and answers the same verdict bytes with the same exit-code semantics, `GET /log/follow` pages the verified log by an exclusive `(seq, hash)` cursor, and `GET /export` returns the store as an archive.
528
+
529
+ Two bearer credentials come from the launch environment, and authorization is per verb. `APPROVAL_SERVE_AGENT_TOKEN` opens the hook and a five-verb allowlist of what a harness under oversight needs in order to ask and to be answered: `instructions`, `hook_classify`, `request`, `wait` and `withdraw`. `APPROVAL_SERVE_TENANT_TOKEN` opens everything else, including the verbs that return records or gate state (`log_tail`, `log_export`, `log_verify`, `gate_status`, `queue`, `status`), the ones that answer what the policy does (`policy_check`, `policy_test`), the ones that touch the host (`run`, `sandbox`, the adapters, `log_advance`, `register`, whose positional is a host path), and both payload builders, since one takes a file on the daemon's machine and the other spends the host's own AgentMail key on ids the caller chose. The agent credential never reads the log it is judged by, a verb added to the registry is tenant-scoped until somebody widens the allowlist, and no caller of any scope names a path: the store flags are pinned from the launch configuration, a positional beginning with a dash is refused because the verb would read it as a flag, the agent may not use a path-typed flag at all, and the tenant's are confined to the store. Which flags are path-typed comes from the registry, so a new one is confined on the day it appears. A verb call and a hook call answer with one body carrying `exit_code`, `stdout` and `stderr`, so a refusal can never be read as a permission. The export carries `APPROVAL.md`, the log, the payload bytes behind every `payload_hash` and the projections, follows no link of either kind (a symlink or a second hard name for one inode refuses the whole export), and carries nothing under `.approval/keys`, `.approval/env`, `.approval/daemon` or the vault. The server binds loopback unless `--listen` names another interface in full and `--allow-non-loopback` is passed as well; it terminates no TLS, so put a proxy you control in front of it. See [the reference](cli-reference.md#serve).
530
+
531
+ ### Wire the calls you intend to cover
532
+
533
+ The classifier can support a tool that the installation example's matcher does not include. Check read, edit, shell, notebook, and outcome events against your actual harness configuration. Claude Code read support includes `Read`, `Glob`, and `Grep`, but a matcher listing only shell and edit tools does not enable it.
534
+
535
+ Keep the integration's wait below the harness's hook timeout. Protect the configuration, use the correct primary policy/log directory across worktrees, and record required organ attestations. Test both an allowed action and a denied action; a healthy-looking process is not proof that the hook fires.
536
+
537
+ Opaque code and indirect effects remain important limits. Classification of a script invocation does not, by itself, bind every byte that script might later read or execute. Where stronger control is needed, use bounded adapters, credential separation, and the applicable sandbox rather than assuming shell-text classification proves arbitrary program behavior.
538
+
539
+ ### Codex is several integration paths
540
+
541
+ **MCP.** `approval mcp serve` gives Codex the same agent-facing tools as other clients. Its ordinary shell and file tools remain separate unless another control covers them.
542
+
543
+ **Native hook.** `approval hook codex` is experimental. In the documented versions, native Bash events omit the effective per-call working directory, so the adapter refuses them rather than approve ambiguous execution context. Bounded direct-patch support does not establish general shell enforcement. See the [native-hook runbook](codex-hook.md).
544
+
545
+ **App-server bridge.** `approval codex bridge` starts a child `codex app-server` over stdio and answers the approval requests that server emits through approval.md's decision path. It can bind command and working-directory information unavailable to the native hook. File-change requests need the appropriate bound content or correlated notification. The preflight stops when server-side auto-review answers first. This is not evidence that every possible native action generates a request. See the [bridge evidence and contract](codex-app-server-bridge.md).
546
+
547
+ **Workspace broker and confined process.** `approval codex apply` and `approval codex serve` provide bounded, gate-authorized canonical workspace writes. `approval codex start` provides the documented macOS confined-process path: disposable writable workspace, canonical read-only workspace, restricted reads and environment, and denied outbound network including loopback. Descendants are inside that confinement; an independently launched desktop process is not.
548
+
549
+ `approval codex prepare` creates an inert review bundle. It is not activation, account provisioning, or proof of installed custody. Linux confinement is not implemented in this build; the confined-session route refuses rather than silently falling back. Read the [constrained-session guide](codex-enforced-session.md), [workspace broker](codex-workspace-broker.md), and [activation runbook](codex-activation.md) together.
550
+
551
+ ### MCP authority and remote clients
552
+
553
+ The MCP server derives its tools from the CLI registry and uses the same command implementations. Human-only verbs such as granting approvals, attesting policy, and setting vault credentials are not published. The server's agent identity is fixed at startup; a tool call cannot select a human identity.
554
+
555
+ Guest mode narrows the available tools further. It supports declaration, requests, and inspection without granting remote guests general command execution or adapter credential use. A network deployment still needs appropriate transport access control and custody; `--guest` is not an account system.
556
+
557
+ See the [MCP walkthrough](../examples/mcp-demo.md) and [guest connector runbook](../examples/grok-bot-connector/runbook.md). MCP tasks/elicitation mapping and exhaustive discovery of a client's native capabilities should not be inferred from the existence of an MCP server.
558
+
559
+ ## The log, evidence, and privacy
560
+
561
+ ```sh
562
+ approval log tail
563
+ approval log verify
564
+ approval status
565
+ ```
566
+
567
+ Each event chains to its predecessor. Gate decisions use verified records, and decision-sensitive writes compare the expected head before appending. The queue and SQLite indexes are derived views; edit the authoritative policy through its ceremony and rebuild views rather than treating a cache as authority.
568
+
569
+ Keep these facts distinct: an agent proposed something; policy permitted it; a human granted it; execution started; an outcome was reported; an independent system witnessed an effect. They are related records, not synonyms.
570
+
571
+ ### Which policy authorized a start
572
+
573
+ Every `execution.started` a harness path writes (the hook route, including `approval serve`'s, and `approval start` for a proposal) carries `payload.policy_sha256`: the SHA-256 of the attested policy bytes the write boundary re-checked the action's class against when it recorded the start. It is the kind of value `approval.requested` and `approval.granted` carry, and it equals the hash of the `policy.updated` attestation `approval status` named at the time of the start. On a granted start it equals the grant's own pin wherever the grant carries one, because a spend under a different attested policy is refused `policy-drift`. A policy-authorized start (autonomous, supervised, or an unselected supervised-live draw) previously named no policy, so a reader had to find the latest attestation before the record's seq; a granted start reached its policy through `grant_seq` and now carries the stamp as well. On the hook route the verdict is drawn from the hook's own earlier read of the policy, so a re-attest landing between that read and the append leaves the stamp naming the policy in force at the append.
574
+
575
+ The runtime writes the field at the write boundary. No caller parameter carries it; a same-named value in a request, an option, or the action's own payload is ignored, and an unattested or edited policy produces no start at all. Nothing that decides reads it back: verdicts, budgets, draws, and the single-use rule are computed exactly as before. Records written before the field existed (APRV-447) simply lack it and still validate. Downstream consumers may use it as a join key, so the field name is stable.
576
+
577
+ ### Checkpoints and external witnesses
578
+
579
+ A chain can reveal inconsistent edits, reordering, and other integrity failures. It cannot, on its own, reveal a valid suffix removed without an external anchor, or defeat an attacker able to replace the entire chain and its trusted context.
580
+
581
+ The runtime supports Git evidence and signed checkpoints. `approval setup checkpoint` provisions the checkpoint key; the policy lists accepted public keys and can request a checkpoint cadence. A due checkpoint is reported, not silently treated as a fresh signature or an authorization failure.
582
+
583
+ `approval coverage` compares available outside witnesses, including Git and supported provider observations, with gate evidence. Missing evidence is a finding, not automatic proof of malicious behavior. Equally, a log containing approvals is not proof that no unobserved effects occurred.
584
+
585
+ See [checkpoints](checkpoints.md), [Git evidence](git-evidence.md), and the [coverage reference](cli-reference.md#coverage).
586
+
587
+ ### Follow the log
588
+
589
+ Downstream consumers can resume a verified stream with a sequence and its hash:
590
+
591
+ ```sh
592
+ approval log follow --from <seq> --cursor-hash <hash> --json
593
+ ```
594
+
595
+ The sequence is exclusive. Persist the processed sequence and hash after applying an idempotent effect: delivery across reconnects is at least once. A cursor without its expected hash is a weaker bootstrap, not the same continuity check. See [`log follow`](cli-reference.md#log-follow).
596
+
597
+ ### Decide what to publish
598
+
599
+ **`approval init` does not ignore `.approval/log/`, and since APRV-445 it does ignore `.approval/payloads/`.** The log is trackable by default; the payload bytes are not, because they can include real message text, recipients, paths, a person's own words, and other information unsuitable for a public repository. A directory scaffolded before APRV-445 still tracks them until a re-run of `approval init` appends the ignore line (it says so), and the log itself still carries every summary in cleartext.
600
+
601
+ Review staged content before committing. Add local ignore rules when payloads or logs must stay private, or keep the evidence in a suitably private repository. Omitting payload bytes trades away the ability to reconstruct them from a public hash; it does not remove existing copies from history.
602
+
603
+ Vault files, environment maps, and sealed-delivery private keys are ignored by the scaffold. That is a convenience, not a guarantee that no one can accidentally stage a secret. `payload_retention` controls eligible local pruning; it does not erase Git history, channel messages, backups, or provider records.
604
+
605
+ ## Operating and troubleshooting
606
+
607
+ `approval status` answers from runtime state. `approval doctor` checks this machine's configuration and health: build, identity, attestation, log, channels, sampling, credentials, hook wiring, and available evidence checks. It reports findings and suggested fixes; it does not repair them. A skipped optional check is not a passed security test.
608
+
609
+ In a directory `approval init` has just scaffolded, most of doctor's rows report `not applicable` rather than passing, and each names the absence it skipped on: `telegram`, `envelope-integrity`, `vault`, `environment`, `log-drift`, `harness-hook-outcomes`, `harness-hook-wiring`, `log-advance-cadence`, `dark-sessions`, `verified-snapshot`, `read-proof`, `main-behind-origin`, `attested-policy-on-main`, `harness-version-unverified`, `live-draw`, `checkpoint`, `gate-organs`, `sealed-keys`, `codex-hook-wiring`, and `sender-mapping`. A dash there reports a configuration you have not made yet. The one row expected to fail on the scaffolded policy is `audit-sampling`, because the scaffold names a sampling rate and does not provision the secret it needs.
610
+
611
+ | Symptom | Check first |
612
+ | --- | --- |
613
+ | Policy changed and work stops | Review the current bytes and re-attest; do not remove the mismatch check. |
614
+ | No approval arrives | Correct project/log, channel configuration, exported sources, listener health, and whether the tool actually triggered the hook. |
615
+ | Telegram polling conflicts | Another process or instance using the same bot, including on another host. |
616
+ | Sampling is disabled | A rate, the named secret, and the relevant daemon machinery are all required. The example scaffold does not provision the secret. |
617
+ | Hook denies an unclassified command | Inspect `approval hook classify`; use a supported representation or bounded adapter rather than hiding the command in an opaque wrapper. |
618
+ | An outcome is missing | Correct post-execution event wiring and whether the harness supplied an interpretable outcome. |
619
+ | Worktrees disagree about state | Primary policy/log selection, the documented log synchronization workflow, and stale compiled code. |
620
+ | A token is consumed or an effect is uncertain | Inspect the execution and provider evidence before requesting a new grant. |
621
+
622
+ The daemon can regenerate projections and perform its configured maintenance. It cannot safely invent an answer to a genuinely divergent hash chain. Do not resolve a log conflict by concatenating branches or deleting whichever records block startup. Follow the [dogfood operations guide](dogfood-cutover.md) and the relevant CLI reference.
623
+
624
+ `approval setup service` generates the supported launchd or systemd user-service configuration for inspection. A persistent service still needs the intended environment and instance, and should not compete with an already-running foreground listener.
625
+
626
+ ### Exit codes
627
+
628
+ The CLI's frozen table is:
629
+
630
+ | Code | Meaning |
631
+ | --- | --- |
632
+ | 0 | success |
633
+ | 1 | integrity failure (corrupt log) |
634
+ | 2 | usage error |
635
+ | 3 | torn tail |
636
+ | 4 | I/O error |
637
+ | 5 | no valid execution token (approval run only) |
638
+ | 6 | timeout (approval wait only) |
639
+ | 7 | void: a re-attest voided a pending request or a harness grant; ask again (approval wait only) |
640
+
641
+ **Context matters.** Ordinary well-formed gate refusals also use exit 1; inspect the machine-readable `error.code` rather than interpreting every 1 as corruption. `approval run` returns the executed child's status. Harness hooks have their own protocol semantics: exit 0 can carry a deny verdict, and exit 2 can mean a blocking or visible hook report rather than CLI misuse. See the command's `--help` and its integration guide.
642
+
643
+ ## Security and evidence limits
644
+
645
+ The intended setting is oversight of broadly cooperative agents, with stronger enforcement at boundaries that actually withhold authority. The [SPEC threat model](../SPEC.md) and integration runbooks carry the details.
646
+
647
+ **Policy integrity is not human authentication.** Attestation binds bytes. Local identities are declared by configuration; transport sender mappings identify an account. Neither proves a person's presence to a remote service that distrusts the host.
648
+
649
+ **A vault is useful only with custody.** An agent with another sending key, the vault passphrase, or control of the trusted runtime can bypass assumptions that an adapter alone cannot repair. An allow/deny response is not credential isolation.
650
+
651
+ **A hook is only as effective as its coverage and host.** Missing matchers, an unregistered hook, unsupported tools, scripts with unbound contents, and a harness that fails open all matter. MCP tool availability is not interception. Initial prompts and context may reach a model before any tool hook can intervene.
652
+
653
+ **Hashes do not make history immutable.** Preserve independently trusted anchors or checkpoints when the threat includes rewriting local history. A valid chain establishes internal consistency under its assumptions, not the absence of all off-log actions.
654
+
655
+ **Sandbox claims are route- and platform-specific.** The runtime has command-sandbox support and a separate macOS Codex confinement path. That is different from sandboxing every agent process. The ordinary command route's unsupported-platform behavior is not the confined-session route's fail-closed guarantee.
656
+
657
+ **Human judgment remains human judgment.** Showing the exact payload does not establish that approving it is wise, that its contents are true, or that a claimed cost is complete. Display-only model glosses, values, and feedback cannot confer authority. A deliberate human gate-open window is a recorded bypass, not an ordinary grant.
658
+
659
+ The [hardened-authorization proposal](proposals/hardened-authorization.md) explores a stronger trust tier. It is a proposal, not a property to infer from current tokens, sender mappings, or checkpoint signatures.
660
+
661
+ ## How this compares
662
+
663
+ Three kinds of thing already exist in this space, and each solves a different
664
+ part of the problem. A hosted daemon and reviewer layer is operated by
665
+ Bountify.ai; it is optional, and nothing in the format depends on it
666
+ ([GOVERNANCE.md](../GOVERNANCE.md)).
667
+
668
+ **Harness-native permission prompts** (Claude Code permission rules and hooks,
669
+ Cursor auto-run, Codex CLI approval modes) enforce inside the one harness they
670
+ ship with. That enforcement is real: a Claude Code PreToolUse deny holds even
671
+ under its bypass mode, and Codex backs its gate with an OS-level sandbox, which
672
+ this project does not attempt. What they lack is a durable record and
673
+ portability. None writes an append-only log of what was asked, who decided and
674
+ what ran; the decision reaches a human only as a terminal prompt; and the
675
+ mechanism does not travel to another harness. approval.md's Claude Code hook is
676
+ built on that PreToolUse mechanism and adds the two missing pieces: the
677
+ decision comes from an attested policy file, and it lands in a verifiable log.
678
+
679
+ **AGENTS.md permissions prose** states the policy in English and trusts the
680
+ agent to obey. Nothing parses it, nothing blocks a call against it, and no
681
+ record exists when it is violated. approval.md is the enforcement layer that
682
+ convention is missing, and treats it as an input: the permissions section of
683
+ this repository's own CLAUDE.md is the first import fixture.
684
+
685
+ **Framework interrupts** (LangGraph `interrupt()`, CrewAI human input, AutoGen
686
+ `UserProxyAgent`, the OpenAI Agents SDK's `needsApproval`, Temporal signal
687
+ approvals) give a developer a pause-and-resume primitive and leave policy,
688
+ audit format, the human channel and the credential boundary to them. They also
689
+ require adopting the framework. Temporal's event history is a real append-only
690
+ execution record with crash recovery this project does not claim, though it
691
+ lives in Temporal's storage rather than as policy-attested files in your repo.
692
+
693
+ **Hosted approval platforms** (HumanLayer, gotoHuman, Permit.io's access
694
+ requests) are the closest relatives: multi-channel human routing, review UIs,
695
+ and in Permit.io's case a real authorization engine richer than autonomy
696
+ classes. Their model is a third-party service in the decision path, with the
697
+ audit trail in the platform's backend, and the agent's own process still
698
+ choosing to honor the returned verdict. They bring hosted infrastructure,
699
+ escalation and team routing, and compliance certifications.
700
+
701
+ The difference is the combination: policy as a hash-attested markdown file in
702
+ your repo; an append-only, hash-chained log you verify locally with one
703
+ command; and an execution boundary where the credential is inert until a
704
+ single-use token is minted at the moment a human decides. Every framework
705
+ primitive and hosted API above relies on the agent's process honoring a
706
+ returned decision. Here the thing the agent needs, the credential, answers only
707
+ to the thing it cannot make, the token. The tradeoffs: you run the daemon and
708
+ listener yourself, there is no OS-level sandbox, no compliance certification,
709
+ and the reference phone channel is one app, Telegram.
710
+
711
+ ## Developing and maintaining the project
712
+
713
+ ### Repository map
714
+
715
+ | Area | Purpose |
716
+ | --- | --- |
717
+ | [`SPEC.md`](../SPEC.md), [`schema/`](../schema/) | Format, semantics, and machine-readable contracts. |
718
+ | [`src/core/`](../src/core/) | Policy, gate state, payloads, tokens, log verification, budgets, and related controls. |
719
+ | [`src/cli/`](../src/cli/) | Commands, setup, diagnostics, harness hooks, and the Codex bridge. |
720
+ | [`src/adapters/`](../src/adapters/) | Shared execution contract, credential providers, and built-in effect adapters. |
721
+ | [`src/channels/`](../src/channels/), [`src/daemon/`](../src/daemon/), [`src/mcp/`](../src/mcp/) | Presentations and transports around the runtime. |
722
+ | [`src/codex/`](../src/codex/), [`templates/codex/`](../templates/codex/) | Constrained-workspace preparation and broker/session support. |
723
+ | [`tests/`](../tests/), [`conformance/`](../conformance/), [`scripts/`](../scripts/) | Regression tests, language-neutral vectors, probes, and development tooling. |
724
+ | [`docs/`](./), [`examples/`](../examples/), [`design/`](../design/) | Integration contracts, walkthroughs, operations, and design records. |
725
+ | [`backlog/`](../backlog/) | Implementation work and remaining acceptance criteria. A task's existence is not a shipped feature. |
726
+ | [`index.html`](../index.html), [`features/`](../features/), [`llms.txt`](../llms.txt), [`llms-full.txt`](../llms-full.txt) | The website and its machine-readable companions. |
727
+ | [`metrics/`](../metrics/), [`brand/`](../brand/) | Agent-hour badge data and visual assets. Badge activity is not an adoption or security certification. |
728
+ | [`hosted/`](../hosted/README.md), [`judgy/`](../judgy/README.md) | Hosting scaffolding and reviewer/demo pages; separate from the core runtime contract. |
729
+
730
+ The hosted directory contains a policy builder and tenant launcher, not a complete account system or managed multi-tenant control plane. Judgy's pages render supplied data and retain simulated/replay/not-run provenance labels; a visualization is not a new authorization path. Neither should be advertised as a finished service merely because its directory exists.
731
+
732
+ ### Running the checks
733
+
734
+ ```sh
735
+ npm ci
736
+ npm run build
737
+ npm test
738
+ npm run lint
739
+ npm run typecheck
740
+ npm run conformance
741
+ ```
742
+
743
+ For the workflow while editing and before pushing:
744
+
745
+ ```sh
746
+ npm run check:changed
747
+ npm run ci:local
748
+ ```
749
+
750
+ The repository classifies changes into documentation, records, and full-check tiers; the shared scripts decide rather than a contributor asserting a lighter tier. The protected-path evidence guard is a separate concern from passing ordinary tests. A local run does not reproduce every CI platform or installed harness.
751
+
752
+ The [conformance suite](../conformance/README.md) checks language-neutral vectors and their manifest. Negative controls matter: a verifier accepting broken evidence has not passed just because it also accepts valid evidence. Live provider and harness probes remain separate from fixture success.
753
+
754
+ ### Keep documentation from growing stale
755
+
756
+ The short README owns the introduction, a usable starting path, and integration signposts. This guide owns the product tour and operating boundaries. Exact flags belong in the [CLI reference](cli-reference.md); policy field completeness belongs with the schema and specification. Avoid duplicating volatile diagnostic row counts, release-state paragraphs, and provider claims across all three.
757
+
758
+ [`tests/docs-guard.test.ts`](../tests/docs-guard.test.ts) holds this guide to the frozen sources: the exit-code table, the refusal names the AgentMail section quotes, every autonomy level the schema admits, every policy key the schema defines (the dictionary above), the doctor rows a fresh directory skips, the seq 2 citation, and the web channel's CSRF stance. It also checks that the root README still links here. When a claim moves between documents, move its assertion in the same change. Do not delete the behavioral coverage or hide obsolete text in comments to pass it.
759
+
760
+ This guide ships in the npm package through the explicit `files` list in `package.json`, beside `README.md` and the CLI reference. The website's feature index and `llms-full.txt` link into this guide's sections; the short README keeps several older anchors as navigation compatibility points, and their detailed material lives here.
761
+
762
+ ### Contributing, license, and governance
763
+
764
+ Read [CONTRIBUTING.md](../CONTRIBUTING.md), including the DCO sign-off, and the repository's [agent instructions](../AGENTS.md) before changing code or governing documents. Proposed designs and pending sign-offs must remain distinguishable from adopted behavior.
765
+
766
+ The code is [Apache 2.0](../LICENSE); the specification and schemas are [CC0 1.0](../schema/LICENSE). [GOVERNANCE.md](../GOVERNANCE.md) explains stewardship, use of the name, and the relationship to optional hosted offerings. The file format and reference runtime do not require a proprietary hosted service.