approval-md 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/README.md +584 -553
  2. package/SPEC.md +42 -13
  3. package/dist/src/adapters/agentmail.d.ts +426 -0
  4. package/dist/src/adapters/agentmail.js +2 -2
  5. package/dist/src/adapters/conformance.d.ts +149 -0
  6. package/dist/src/adapters/contract.d.ts +628 -0
  7. package/dist/src/adapters/contract.js +110 -16
  8. package/dist/src/adapters/contract.js.map +1 -1
  9. package/dist/src/adapters/email.d.ts +324 -0
  10. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  11. package/dist/src/adapters/public.d.ts +11 -0
  12. package/dist/src/adapters/public.js +11 -0
  13. package/dist/src/adapters/public.js.map +1 -0
  14. package/dist/src/adapters/registry.d.ts +59 -0
  15. package/dist/src/adapters/registry.js +2 -1
  16. package/dist/src/adapters/registry.js.map +1 -1
  17. package/dist/src/adapters/smtp.d.ts +213 -0
  18. package/dist/src/adapters/vault-provider.d.ts +114 -0
  19. package/dist/src/adapters/vault-provider.js +3 -3
  20. package/dist/src/adapters/zzz.d.ts +66 -0
  21. package/dist/src/adapters/zzz.js +299 -0
  22. package/dist/src/adapters/zzz.js.map +1 -0
  23. package/dist/src/channels/batch.d.ts +109 -0
  24. package/dist/src/channels/cli.d.ts +193 -0
  25. package/dist/src/channels/conformance.d.ts +92 -0
  26. package/dist/src/channels/contract.d.ts +623 -0
  27. package/dist/src/channels/payload-view.d.ts +35 -0
  28. package/dist/src/channels/render-queue.d.ts +149 -0
  29. package/dist/src/channels/tagging.d.ts +196 -0
  30. package/dist/src/channels/telegram.d.ts +1832 -0
  31. package/dist/src/channels/web.d.ts +341 -0
  32. package/dist/src/cli/adapter.d.ts +90 -0
  33. package/dist/src/cli/adapter.js +25 -15
  34. package/dist/src/cli/adapter.js.map +1 -1
  35. package/dist/src/cli/amend.d.ts +59 -0
  36. package/dist/src/cli/args.d.ts +43 -0
  37. package/dist/src/cli/attest.d.ts +41 -0
  38. package/dist/src/cli/audit-card.d.ts +62 -0
  39. package/dist/src/cli/audit.d.ts +59 -0
  40. package/dist/src/cli/channel-telegram.d.ts +806 -0
  41. package/dist/src/cli/channel-web.d.ts +131 -0
  42. package/dist/src/cli/channel.d.ts +71 -0
  43. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  44. package/dist/src/cli/codex.d.ts +2 -0
  45. package/dist/src/cli/codex.js +172 -0
  46. package/dist/src/cli/codex.js.map +1 -0
  47. package/dist/src/cli/coverage.d.ts +61 -0
  48. package/dist/src/cli/daemon.d.ts +120 -0
  49. package/dist/src/cli/doctor.d.ts +129 -0
  50. package/dist/src/cli/doctor.js +119 -5
  51. package/dist/src/cli/doctor.js.map +1 -1
  52. package/dist/src/cli/env.d.ts +65 -0
  53. package/dist/src/cli/execute.d.ts +202 -0
  54. package/dist/src/cli/exit-codes.d.ts +73 -0
  55. package/dist/src/cli/feedback.d.ts +60 -0
  56. package/dist/src/cli/gate-window.d.ts +40 -0
  57. package/dist/src/cli/gate.d.ts +68 -0
  58. package/dist/src/cli/git-scope.d.ts +190 -0
  59. package/dist/src/cli/gloss-attach.d.ts +85 -0
  60. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  61. package/dist/src/cli/gloss-codex.d.ts +24 -0
  62. package/dist/src/cli/gloss-options.d.ts +42 -0
  63. package/dist/src/cli/gloss.d.ts +265 -0
  64. package/dist/src/cli/help.d.ts +103 -0
  65. package/dist/src/cli/help.js +173 -51
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook-codex.d.ts +78 -0
  68. package/dist/src/cli/hook-codex.js +167 -0
  69. package/dist/src/cli/hook-codex.js.map +1 -0
  70. package/dist/src/cli/hook.d.ts +331 -0
  71. package/dist/src/cli/hook.js +186 -80
  72. package/dist/src/cli/hook.js.map +1 -1
  73. package/dist/src/cli/import.d.ts +35 -0
  74. package/dist/src/cli/init.d.ts +84 -0
  75. package/dist/src/cli/init.js +2 -2
  76. package/dist/src/cli/init.js.map +1 -1
  77. package/dist/src/cli/instructions.d.ts +23 -0
  78. package/dist/src/cli/journal.d.ts +41 -0
  79. package/dist/src/cli/log-advance.d.ts +287 -0
  80. package/dist/src/cli/log-advance.js +102 -11
  81. package/dist/src/cli/log-advance.js.map +1 -1
  82. package/dist/src/cli/log-anchor.d.ts +176 -0
  83. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  84. package/dist/src/cli/log-sync.d.ts +243 -0
  85. package/dist/src/cli/log-verbs.d.ts +16 -0
  86. package/dist/src/cli/log-verbs.js +7 -1
  87. package/dist/src/cli/log-verbs.js.map +1 -1
  88. package/dist/src/cli/long-help.d.ts +70 -0
  89. package/dist/src/cli/main.d.ts +77 -0
  90. package/dist/src/cli/main.js +155 -5
  91. package/dist/src/cli/main.js.map +1 -1
  92. package/dist/src/cli/mcp.d.ts +52 -0
  93. package/dist/src/cli/paths.d.ts +56 -0
  94. package/dist/src/cli/payload.d.ts +58 -0
  95. package/dist/src/cli/policy.d.ts +43 -0
  96. package/dist/src/cli/preflight.d.ts +363 -0
  97. package/dist/src/cli/preflight.js +294 -7
  98. package/dist/src/cli/preflight.js.map +1 -1
  99. package/dist/src/cli/progress.d.ts +78 -0
  100. package/dist/src/cli/prompt.d.ts +209 -0
  101. package/dist/src/cli/quickstart.d.ts +46 -0
  102. package/dist/src/cli/quickstart.js +297 -0
  103. package/dist/src/cli/quickstart.js.map +1 -0
  104. package/dist/src/cli/records.d.ts +34 -0
  105. package/dist/src/cli/render.d.ts +22 -0
  106. package/dist/src/cli/sandbox.d.ts +51 -0
  107. package/dist/src/cli/scaffold.d.ts +79 -0
  108. package/dist/src/cli/setup-adapter.d.ts +137 -0
  109. package/dist/src/cli/setup-adapter.js +38 -4
  110. package/dist/src/cli/setup-adapter.js.map +1 -1
  111. package/dist/src/cli/setup-channel.d.ts +117 -0
  112. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  113. package/dist/src/cli/setup-common.d.ts +275 -0
  114. package/dist/src/cli/setup-flow.d.ts +287 -0
  115. package/dist/src/cli/setup-service.d.ts +96 -0
  116. package/dist/src/cli/setup.d.ts +202 -0
  117. package/dist/src/cli/style.d.ts +320 -0
  118. package/dist/src/cli/token.d.ts +39 -0
  119. package/dist/src/cli/up.d.ts +155 -0
  120. package/dist/src/cli/up.js +4 -2
  121. package/dist/src/cli/up.js.map +1 -1
  122. package/dist/src/cli/usage.d.ts +37 -0
  123. package/dist/src/cli/values.d.ts +40 -0
  124. package/dist/src/cli/vault.d.ts +59 -0
  125. package/dist/src/cli/vault.js +2 -2
  126. package/dist/src/cli/vault.js.map +1 -1
  127. package/dist/src/cli/verb-registry.d.ts +76 -0
  128. package/dist/src/cli/verb-registry.js +176 -8
  129. package/dist/src/cli/verb-registry.js.map +1 -1
  130. package/dist/src/cli/wordmark.d.ts +31 -0
  131. package/dist/src/cli/wordmark.js +2 -2
  132. package/dist/src/codex/doctor.d.ts +13 -0
  133. package/dist/src/codex/doctor.js +41 -0
  134. package/dist/src/codex/doctor.js.map +1 -0
  135. package/dist/src/codex/manifest.d.ts +49 -0
  136. package/dist/src/codex/manifest.js +103 -0
  137. package/dist/src/codex/manifest.js.map +1 -0
  138. package/dist/src/codex/templates.d.ts +41 -0
  139. package/dist/src/codex/templates.js +319 -0
  140. package/dist/src/codex/templates.js.map +1 -0
  141. package/dist/src/codex/trust.d.ts +19 -0
  142. package/dist/src/codex/trust.js +183 -0
  143. package/dist/src/codex/trust.js.map +1 -0
  144. package/dist/src/codex/workspace-plan.d.ts +131 -0
  145. package/dist/src/codex/workspace-plan.js +561 -0
  146. package/dist/src/codex/workspace-plan.js.map +1 -0
  147. package/dist/src/core/actor.d.ts +2 -0
  148. package/dist/src/core/actor.js +5 -0
  149. package/dist/src/core/actor.js.map +1 -0
  150. package/dist/src/core/advance-cycle.d.ts +170 -0
  151. package/dist/src/core/agents-md.d.ts +276 -0
  152. package/dist/src/core/apply-patch.d.ts +49 -0
  153. package/dist/src/core/apply-patch.js +266 -0
  154. package/dist/src/core/apply-patch.js.map +1 -0
  155. package/dist/src/core/attest.d.ts +420 -0
  156. package/dist/src/core/attest.js +13 -1
  157. package/dist/src/core/attest.js.map +1 -1
  158. package/dist/src/core/audit.d.ts +492 -0
  159. package/dist/src/core/budgets.d.ts +238 -0
  160. package/dist/src/core/checkpoint.d.ts +500 -0
  161. package/dist/src/core/child-env.d.ts +88 -0
  162. package/dist/src/core/clock.d.ts +52 -0
  163. package/dist/src/core/command-class.d.ts +543 -0
  164. package/dist/src/core/command-class.js +43 -8
  165. package/dist/src/core/command-class.js.map +1 -1
  166. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  167. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  168. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  169. package/dist/src/core/coverage.d.ts +217 -0
  170. package/dist/src/core/credential-spec.d.ts +72 -0
  171. package/dist/src/core/dark-session.d.ts +331 -0
  172. package/dist/src/core/decision-refusal.d.ts +185 -0
  173. package/dist/src/core/env-file.d.ts +450 -0
  174. package/dist/src/core/execute.d.ts +858 -0
  175. package/dist/src/core/execute.js +44 -6
  176. package/dist/src/core/execute.js.map +1 -1
  177. package/dist/src/core/frontmatter.d.ts +78 -0
  178. package/dist/src/core/gate-window.d.ts +312 -0
  179. package/dist/src/core/gate.d.ts +1364 -0
  180. package/dist/src/core/gate.js +68 -13
  181. package/dist/src/core/gate.js.map +1 -1
  182. package/dist/src/core/git-run.d.ts +73 -0
  183. package/dist/src/core/harness-version.d.ts +157 -0
  184. package/dist/src/core/harness-version.js +2 -1
  185. package/dist/src/core/harness-version.js.map +1 -1
  186. package/dist/src/core/harness-wait.d.ts +55 -0
  187. package/dist/src/core/head-retry.d.ts +107 -0
  188. package/dist/src/core/instance.d.ts +253 -0
  189. package/dist/src/core/intake-limits.d.ts +247 -0
  190. package/dist/src/core/jcs.d.ts +52 -0
  191. package/dist/src/core/journal.d.ts +144 -0
  192. package/dist/src/core/live-draw.d.ts +436 -0
  193. package/dist/src/core/log-reconcile.d.ts +89 -0
  194. package/dist/src/core/log-subscribe.d.ts +36 -0
  195. package/dist/src/core/log-subscribe.js +162 -0
  196. package/dist/src/core/log-subscribe.js.map +1 -0
  197. package/dist/src/core/log.d.ts +278 -0
  198. package/dist/src/core/loop.d.ts +274 -0
  199. package/dist/src/core/loop.js +11 -0
  200. package/dist/src/core/loop.js.map +1 -1
  201. package/dist/src/core/md-fence.d.ts +41 -0
  202. package/dist/src/core/money.d.ts +147 -0
  203. package/dist/src/core/payload-census.d.ts +74 -0
  204. package/dist/src/core/payload-store.d.ts +175 -0
  205. package/dist/src/core/payload.d.ts +71 -0
  206. package/dist/src/core/policy-diff.d.ts +292 -0
  207. package/dist/src/core/policy-diff.js +27 -4
  208. package/dist/src/core/policy-diff.js.map +1 -1
  209. package/dist/src/core/policy-expectations.d.ts +199 -0
  210. package/dist/src/core/policy-explain.d.ts +150 -0
  211. package/dist/src/core/policy-explain.js +31 -3
  212. package/dist/src/core/policy-explain.js.map +1 -1
  213. package/dist/src/core/policy-load.d.ts +527 -0
  214. package/dist/src/core/policy-load.js +15 -3
  215. package/dist/src/core/policy-load.js.map +1 -1
  216. package/dist/src/core/policy-match.d.ts +281 -0
  217. package/dist/src/core/policy-match.js +20 -9
  218. package/dist/src/core/policy-match.js.map +1 -1
  219. package/dist/src/core/policy-proposal.d.ts +265 -0
  220. package/dist/src/core/prompt-layout.d.ts +221 -0
  221. package/dist/src/core/protected-path-guard.d.ts +453 -0
  222. package/dist/src/core/protected-path-guard.js +514 -35
  223. package/dist/src/core/protected-path-guard.js.map +1 -1
  224. package/dist/src/core/registration.d.ts +25 -0
  225. package/dist/src/core/reindex.d.ts +99 -0
  226. package/dist/src/core/sampler.d.ts +313 -0
  227. package/dist/src/core/sandbox.d.ts +290 -0
  228. package/dist/src/core/seal.d.ts +165 -0
  229. package/dist/src/core/state.d.ts +505 -0
  230. package/dist/src/core/task-file.d.ts +185 -0
  231. package/dist/src/core/telegram-config.d.ts +93 -0
  232. package/dist/src/core/token.d.ts +409 -0
  233. package/dist/src/core/token.js +21 -38
  234. package/dist/src/core/token.js.map +1 -1
  235. package/dist/src/core/validate.d.ts +138 -0
  236. package/dist/src/core/values.d.ts +137 -0
  237. package/dist/src/core/vault.d.ts +291 -0
  238. package/dist/src/core/verified-snapshot.d.ts +204 -0
  239. package/dist/src/core/verify.d.ts +336 -0
  240. package/dist/src/core/version.d.ts +8 -0
  241. package/dist/src/core/wysiwys.d.ts +370 -0
  242. package/dist/src/daemon/advance-child.d.ts +39 -0
  243. package/dist/src/daemon/advance.d.ts +466 -0
  244. package/dist/src/daemon/audit.d.ts +87 -0
  245. package/dist/src/daemon/daemon.d.ts +1180 -0
  246. package/dist/src/daemon/dark-session.d.ts +64 -0
  247. package/dist/src/daemon/draw-child.d.ts +36 -0
  248. package/dist/src/daemon/draw.d.ts +154 -0
  249. package/dist/src/daemon/git-evidence.d.ts +173 -0
  250. package/dist/src/daemon/git-evidence.js +1 -1
  251. package/dist/src/daemon/projection.d.ts +180 -0
  252. package/dist/src/daemon/prune.d.ts +207 -0
  253. package/dist/src/mcp/http.d.ts +113 -0
  254. package/dist/src/mcp/server.d.ts +265 -0
  255. package/dist/src/mcp/server.js +9 -1
  256. package/dist/src/mcp/server.js.map +1 -1
  257. package/docs/adapter-api.md +106 -0
  258. package/docs/cli-reference.md +389 -36
  259. package/docs/codex-enforced-session.md +30 -0
  260. package/package.json +12 -2
  261. package/schema/codex-instance.schema.json +82 -0
  262. package/schema/event.schema.json +2 -1
  263. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  264. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  265. package/schema/policy.schema.json +21 -1
  266. package/templates/codex/README.md +9 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * `approval setup` — the interactive configuration family (SPEC.md §5.2, §10.1).
3
+ *
4
+ * APRV-73 gave `.approval/env` a format and `approval env` a reader. This verb
5
+ * is the writer, and it is the only one: it establishes the things an operator
6
+ * must have before any gate operation works — a declared human identity, a
7
+ * vault passphrase, a sampling secret, a live Telegram bot and chat, and an
8
+ * adapter's credentials — by putting each VALUE where that kind of value
9
+ * belongs and each SOURCE in `.approval/env`.
10
+ *
11
+ * ## Who lives where
12
+ *
13
+ * This file holds the three subcommands whose subject is a value this runtime
14
+ * MINTS or a name it records, plus the dispatch:
15
+ *
16
+ * ```
17
+ * setup identity # APPROVAL_HUMAN, in this file
18
+ * setup vault # the vault passphrase, in this file
19
+ * setup sampling # the audit sampling secret, in this file
20
+ * setup checkpoint # cli/setup-checkpoint.ts (the vault + a print)
21
+ * setup channel <name> # cli/setup-channel.ts (keystore + .approval/env)
22
+ * setup adapter <name> # cli/setup-adapter.ts (the vault)
23
+ * setup service # cli/setup-service.ts (a launchd/systemd unit)
24
+ * ```
25
+ *
26
+ * The last two are **two nouns and not one list**, and the split is SPEC.md
27
+ * §4's: a CHANNEL surfaces requests and collects decisions and holds no state,
28
+ * so its setup fills the keystore and `.approval/env`; an ADAPTER executes side
29
+ * effects and holds credentials, so its setup fills the vault.
30
+ * An older build spelled the Telegram one without the `channel` noun, and that
31
+ * form is gone (APRV-79): the dispatch answers it with the new one and exits 2
32
+ * rather than aliasing it, because an alias would leave two spellings of a
33
+ * distinction the SPEC draws on purpose.
34
+ *
35
+ * Everything the three files share — the dependency bag, the keystore seam, the
36
+ * front matter, the human-only gate, the service names, the plaintext-literal
37
+ * offer — is `cli/setup-common.ts`, which imports from none of them. The
38
+ * conversation `setup channel|adapter` both run is `cli/setup-flow.ts`.
39
+ *
40
+ * ## The order these run in
41
+ *
42
+ * Nothing enforced it and nothing said it, which APRV-76 noticed the hard way.
43
+ * It is:
44
+ *
45
+ * ```
46
+ * approval init # the directory and the .gitignore
47
+ * (write APPROVAL.md) # the policy NAMES every variable below
48
+ * approval setup identity # APPROVAL_HUMAN
49
+ * approval setup vault # the passphrase, into the keystore
50
+ * eval "$(approval env)" # the ONLY thing that puts them in a shell
51
+ * approval setup adapter <name> # the adapter's credentials, into the vault
52
+ * ```
53
+ *
54
+ * The policy comes before every `setup`, because each of them reads variable
55
+ * NAMES out of it. The `eval` comes before `setup adapter`, because that
56
+ * subcommand needs the passphrase's VALUE in the environment and will not read
57
+ * `.approval/env` to get it (§11.1 invariant 7). `setup sampling` and
58
+ * `setup channel telegram` slot in anywhere after the policy.
59
+ *
60
+ * ## What this verb is not allowed to do
61
+ *
62
+ * **It never appends to the log, never attests, and never edits `APPROVAL.md`.**
63
+ * Configuration is not an authorized action, and the log is the record of
64
+ * authorized actions; a "telegram configured" event would be a line in the one
65
+ * file this project promises never to rewrite, saying something the log has no
66
+ * business knowing. `tests/cli-setup.test.ts` byte-compares `events.jsonl`
67
+ * across a complete run of every subcommand to keep that true by assertion.
68
+ * When a policy line is needed (the sampling secret's name), this verb PRINTS
69
+ * the `approval policy amend` invocation and stops: an amendment is a human
70
+ * ceremony with an attestation at the end of it, and a setup wizard that
71
+ * silently edited an attested policy would be forging the sign-off.
72
+ *
73
+ * It writes exactly three things: lines in `.approval/env`, through a writer
74
+ * that preserves every other line and comment (`core/env-file.ts`), items in
75
+ * the OS keystore, and entries in the vault (`setup adapter` only).
76
+ *
77
+ * ## Interactive or nothing
78
+ *
79
+ * Every subcommand refuses when stdin is not a terminal, or when `--json` was
80
+ * given, and exits 2 printing THE EXACT NON-INTERACTIVE ALTERNATIVE — the
81
+ * `security add-generic-password` line to run, or the `.approval/env` line to
82
+ * add, or the `export` to put in a shell profile. This is not a wizard being
83
+ * precious about its terminal. A `setup` that could be driven from a pipe would
84
+ * be a way for a CI job or an agent to write `APPROVAL_HUMAN` and a keystore
85
+ * item, which is precisely the boundary §11 draws: identity is config-declared,
86
+ * so establishing it must be an act of the human at the machine. The refusal
87
+ * text is the documented scripted path, so nobody has to reverse-engineer one.
88
+ *
89
+ * `setup identity` is EXEMPT from the human-only `--as` gate that every other
90
+ * subcommand carries, and the exemption is not a hole: identity is what that
91
+ * gate reads. A verb that demanded `APPROVAL_HUMAN` before it would let you set
92
+ * `APPROVAL_HUMAN` could only ever be run by someone who did not need it. The
93
+ * control on this path is the terminal itself.
94
+ *
95
+ * ## Answers, and answering again (APRV-90)
96
+ *
97
+ * Every typed question in this family runs through `prompt.ts`'s `askUntil`: a
98
+ * wrong answer is one line saying what was wrong and the same question again,
99
+ * never an exit code with a help page under it. `setup identity` also
100
+ * NORMALISES what it is given — `carter` is recorded as `human:carter`, and
101
+ * `human:carter` is taken as it stands ({@link identityFromAnswer}). The prompt
102
+ * still prints the `human:` prefix, because the prefix is what distinguishes
103
+ * the actor kinds the human-only verbs refuse, but nobody has to retype a
104
+ * prefix the question already showed them. `agent:` and `system:` are refused
105
+ * with the sentence that names why, as a reason to answer again.
106
+ *
107
+ * ## Where a secret goes, and how it gets there
108
+ *
109
+ * Three service names, one per secret, documented so an operator can find them
110
+ * with `security find-generic-password` or `secret-tool lookup` by hand:
111
+ * `approval-tg-token`, `approval-vault-passphrase`, `approval-sampling-secret`.
112
+ *
113
+ * - **macOS** (`darwin` and `security` on PATH) → `keychain:<service>`;
114
+ * - **Linux with `secret-tool`** → `secret-service:<service>` (the same string
115
+ * is the label, so the two platforms name one secret one way);
116
+ * - **neither** → the operator is OFFERED a plaintext literal in
117
+ * `.approval/env`, and must type `yes` in full to take it, having been shown
118
+ * the same warning `approval env --check` will print at them forever after.
119
+ * §5.2 permits literals for a stated reason, and refusing here would only
120
+ * move the value into a shell profile where nothing can see it to report it.
121
+ *
122
+ * **A value the operator already holds is never handled by this process.** The
123
+ * Telegram token on macOS is collected by `security`'s OWN no-echo prompt: we
124
+ * spawn `security add-generic-password … -w` WITH NO VALUE and with inherited
125
+ * stdio, Apple's prompt reads it from the terminal, and the token reaches this
126
+ * runtime only afterwards, on the stdout of a `find-generic-password -w` read
127
+ * that puts nothing in an argv either. Off macOS it comes through
128
+ * `Prompter.readSecret`, which at least keeps it off the screen.
129
+ *
130
+ * **A value we generate ourselves is a different question**, and it is the one
131
+ * place this family makes a trade rather than following a rule. The vault
132
+ * passphrase and the sampling secret are `randomBytes(32)`, minted in this
133
+ * process, so they are already in this process and there is nobody to prompt.
134
+ * They reach the keystore by STDIN first: `security add-generic-password -w`
135
+ * with the value written to its stdin twice (the prompt asks for confirmation),
136
+ * and `secret-tool store`, which documents stdin as its input. Only if the
137
+ * stdin form FAILS does the fallback put the value in an argv (`-w <value>`),
138
+ * and then the outcome says so out loud. That residual exposure is a value
139
+ * minted one millisecond earlier, never used, visible in `ps` to the same user
140
+ * who is running the command and to root — which is the boundary §11 already
141
+ * declares undefended. It is accepted for generated values and for nothing
142
+ * else: no path in this family ever puts an operator's own token in an argv.
143
+ *
144
+ * **And there is one standing exception to the rule above, which
145
+ * `setup adapter <name>` takes.** A credential bound for the VAULT must pass
146
+ * through this process, because the vault is not a helper with a prompt: it is
147
+ * a file this runtime encrypts, so `setCredential` needs the bytes. There is
148
+ * nothing to delegate the typing to and no third party to hold the value. The
149
+ * secret is read with `Prompter.readSecret` (no echo), handed straight to the
150
+ * cipher, and never printed, logged, or placed in an argv — which is exactly
151
+ * what `approval vault set` already does when a human pastes a credential onto
152
+ * its stdin. The rule is "never handle a value someone else can hold for you";
153
+ * for the vault nobody can, so it is stated here rather than left to look like
154
+ * an oversight.
155
+ *
156
+ * ## Seams
157
+ *
158
+ * The prompter, the keystore, and `fetch` are injected. The alternative is a
159
+ * test suite that needs a terminal, writes to the developer's real Keychain,
160
+ * and talks to the real Bot API — and the third of those would put a real bot
161
+ * token in a test run. `tests/cli-setup.test.ts` drives all three through fakes
162
+ * and the mock Bot API on loopback, and the spawned-CLI cases never get past
163
+ * the terminal check, so nothing under `npm test` can reach a keystore at all.
164
+ */
165
+ import { type SetupDeps } from "./setup-common.js";
166
+ import type { Streams } from "./main.js";
167
+ /**
168
+ * One typed answer to `human identity (human:<id>):`, as an identity (APRV-90).
169
+ *
170
+ * **A bare id is accepted and normalised.** The prompt prints the `human:`
171
+ * prefix because the prefix is load-bearing — actors are `human:`, `agent:` or
172
+ * `system:`, and the human-only verbs refuse the other two — so an operator who
173
+ * has never read SPEC.md §11 learns the shape from the question itself. Making
174
+ * them retype a prefix the prompt just printed adds a failure path and teaches
175
+ * nothing further, so `carter` becomes `human:carter` and `human:carter` is
176
+ * taken as it stands.
177
+ *
178
+ * An answer with a colon in it is taken as a FULL actor and validated as one,
179
+ * which is what makes `agent:claude` a refusal rather than `human:agent:claude`.
180
+ * That refusal is a reason handed back to {@link askUntil}, so the operator is
181
+ * asked again rather than dropped out of the verb.
182
+ */
183
+ export declare function identityFromAnswer(answer: string): {
184
+ ok: true;
185
+ value: string;
186
+ } | {
187
+ ok: false;
188
+ reason: string;
189
+ };
190
+ /**
191
+ * `approval setup identity` — declare who the human is.
192
+ *
193
+ * EXEMPT from the human-only gate, and the module doc says why: this is the
194
+ * verb that creates the thing the gate reads.
195
+ */
196
+ export declare function commandSetupIdentity(argv: string[], streams: Streams, cwd: string, deps?: SetupDeps): number;
197
+ /** `approval setup vault` — mint and store the vault passphrase. HUMAN-ONLY. */
198
+ export declare function commandSetupVault(argv: string[], streams: Streams, cwd: string, deps?: SetupDeps): number;
199
+ /** `approval setup sampling` — mint and store the audit sampling secret. HUMAN-ONLY. */
200
+ export declare function commandSetupSampling(argv: string[], streams: Streams, cwd: string, deps?: SetupDeps): number;
201
+ /** `approval setup <identity|vault|sampling|channel <name>|adapter <name>>`. */
202
+ export declare function commandSetup(argv: string[], streams: Streams, cwd: string, deps?: SetupDeps): number | Promise<number>;
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Terminal presentation: colour, glyphs, headings, tables (APRV-91, APRV-93).
3
+ *
4
+ * The observation behind this file was a real `examples/email-demo.md` run and
5
+ * a `policy amend --dry-run` the operator called dense: two 64-hex hashes on one
6
+ * line, the same absolute path three times, every line the same visual weight,
7
+ * no colour anywhere. Nothing was WRONG; nothing was scannable either.
8
+ *
9
+ * The design rules, from APRV-91's brief:
10
+ *
11
+ * 1. ROLES, NOT COLOURS. Callers ask for `ok` or `key`, never for green or
12
+ * bold, so a theme is one table and a colour-blind reader loses nothing.
13
+ * 2. COLOUR IS REDUNDANT, ALWAYS. Every coloured thing is also carried by a
14
+ * glyph or a word, so the plain-text degradation is lossless. That is not
15
+ * politeness: piped output IS the tested output, and a pipe gets no colour.
16
+ * 3. NEVER COLOUR A COPYABLE VALUE. Hashes, tokens, paths and commands are
17
+ * printed raw so a triple-click yields clean bytes. Colour the LABEL.
18
+ * 4. DECIDED ONCE. {@link style} memoizes the process-wide answer; tests build
19
+ * their own with {@link makeStyle} rather than mutating the environment.
20
+ *
21
+ * NO NEW DEPENDENCY, deliberately: a colour library is thousands of lines to
22
+ * buy the eleven escape sequences below, and this repo justifies every package.
23
+ *
24
+ * `--json` IS AN ABSOLUTE VETO here, above even FORCE_COLOR. The brief lists
25
+ * `--json` alongside the other conditions, but the machine-readable shapes are
26
+ * frozen public API; one escape byte in a JSON stream is a parse error, not a
27
+ * cosmetic regression. So a caller that passes `json: true` cannot get colour by
28
+ * any combination of environment variables.
29
+ */
30
+ /** The presentation roles. A theme is this list mapped to SGR parameters. */
31
+ export type Role = "brand" | "ok" | "warn" | "fail" | "key" | "value" | "muted" | "rule" | "secret";
32
+ /** The glyph vocabulary, and its ASCII degradation. */
33
+ declare const GLYPHS: {
34
+ readonly ok: readonly ["✓", "[ok]"];
35
+ readonly fail: readonly ["✗", "[x]"];
36
+ readonly skip: readonly ["–", "[-]"];
37
+ readonly point: readonly ["▸", ">"];
38
+ readonly bar: readonly ["│", "|"];
39
+ readonly rule: readonly ["─", "-"];
40
+ };
41
+ export type Glyph = keyof typeof GLYPHS;
42
+ /** How many characters of a 64-hex digest a human is shown. */
43
+ export declare const SHORT_HASH_LENGTH = 12;
44
+ export interface StyleInput {
45
+ /**
46
+ * Whether stdout is a terminal. Omitted means "ask this process".
47
+ *
48
+ * Tests pass an explicit `true` to render coloured output into a captured
49
+ * string, which is the only way to assert BOTH modes without a pty.
50
+ */
51
+ tty?: boolean;
52
+ /** Environment to read. Omitted means `process.env`. */
53
+ env?: Record<string, string | undefined>;
54
+ /** Whether this invocation is answering in JSON. An absolute veto on colour. */
55
+ json?: boolean;
56
+ /** The `--no-color` flag. An explicit off, above FORCE_COLOR. */
57
+ noColor?: boolean;
58
+ }
59
+ export interface Style {
60
+ /** Whether escape sequences are emitted at all. */
61
+ readonly enabled: boolean;
62
+ /** Whether glyphs degrade to their ASCII spellings. */
63
+ readonly ascii: boolean;
64
+ /** Wrap `text` in `role`. A no-op when colour is off, or when text is empty. */
65
+ paint(role: Role, text: string): string;
66
+ brand(text: string): string;
67
+ ok(text: string): string;
68
+ warn(text: string): string;
69
+ fail(text: string): string;
70
+ key(text: string): string;
71
+ /** Identity by contract: a value is never dressed. Here so call sites can say so. */
72
+ value(text: string): string;
73
+ muted(text: string): string;
74
+ secret(text: string): string;
75
+ /** One glyph, already coloured by its natural role where it has one. */
76
+ glyph(name: Glyph): string;
77
+ /** The bare glyph, uncoloured. */
78
+ rawGlyph(name: Glyph): string;
79
+ /** A section heading: the label in `key`, nothing else. */
80
+ heading(text: string): string;
81
+ /** A horizontal rule of `width` characters, in `rule`. */
82
+ rule(width?: number): string;
83
+ /** Aligned two-column rows. Column one in `key` unless the row says otherwise. */
84
+ table(rows: readonly TableRow[], options?: TableOptions): string;
85
+ }
86
+ /**
87
+ * One row of a two-column table.
88
+ *
89
+ * `left` is the label column and `right` the detail. `glyph` puts a coloured
90
+ * status mark ahead of the label (doctor's column), and `plainLeft` opts a row
91
+ * out of `key` styling for a left cell that is itself a copyable value.
92
+ */
93
+ export interface TableRow {
94
+ left: string;
95
+ right?: string;
96
+ glyph?: Glyph;
97
+ /** Role for the glyph and, when set, nothing else. Defaults by glyph name. */
98
+ role?: Role;
99
+ plainLeft?: boolean;
100
+ /** Extra lines printed under the row, indented to the detail column. */
101
+ under?: readonly string[];
102
+ }
103
+ export interface TableOptions {
104
+ /** Spaces before every row. Default 0. */
105
+ indent?: number;
106
+ /** Spaces between the columns. Default 2. */
107
+ gap?: number;
108
+ }
109
+ /**
110
+ * One cell. A bare string is an UNDRESSED cell, which is the common case and
111
+ * the safe default: rule 3 above says a value is never painted, so a caller has
112
+ * to ask for a role before anything is.
113
+ */
114
+ export interface Cell {
115
+ text: string;
116
+ role?: Role;
117
+ }
118
+ export type GridCell = string | Cell;
119
+ /** A row, with the lines that hang beneath it when it has any. */
120
+ export interface GridRow {
121
+ cells: readonly GridCell[];
122
+ under?: readonly string[];
123
+ }
124
+ export type GridInput = readonly GridCell[] | GridRow;
125
+ export interface GridOptions {
126
+ /** A header row, rendered in `key` unless a cell asks for its own role. */
127
+ header?: readonly GridCell[];
128
+ /** Per-column alignment; missing entries are `left`. */
129
+ align?: readonly ("left" | "right")[];
130
+ /** Spaces before every row. Default 0. */
131
+ indent?: number;
132
+ /** Spaces between columns: one number for all, or one per boundary. */
133
+ gap?: number;
134
+ gaps?: readonly number[];
135
+ /** Spaces (after `indent`) before an `under` line. Default 2. */
136
+ underHang?: number;
137
+ }
138
+ /**
139
+ * The one aligned-columns renderer in this CLI (APRV-102).
140
+ *
141
+ * Three hand-rolled versions of this arithmetic existed — `style.table`,
142
+ * `execute.ts`'s queue and `hook.ts`'s classify — and they had already drifted
143
+ * on the question that matters: WIDTH IS MEASURED ON THE UNDRESSED TEXT.
144
+ * Escape sequences occupy no terminal columns, so padding computed after
145
+ * painting is wrong by exactly the length of the escapes and the table lines up
146
+ * only in a pipe. Here the cell text is padded and painted separately, so the
147
+ * coloured render is the plain one with escapes inserted and nothing else.
148
+ *
149
+ * Every line is `trimEnd`ed: trailing spaces are invisible in review and very
150
+ * visible in the diff of a pinned transcript.
151
+ *
152
+ * Returned with no trailing newline; the caller owns the stream.
153
+ */
154
+ export declare function table(st: Style, rows: readonly GridInput[], options?: GridOptions): string;
155
+ /**
156
+ * The one shape a refusal is printed in (APRV-91 #8/#13).
157
+ *
158
+ * ✗ payload-mismatch message.json does not hash to the registered hash
159
+ * fix: approval payload hash message.json
160
+ *
161
+ * Glyph and machine-readable code in `fail`, message plain, and an optional
162
+ * repair on a second line. NEVER followed by a help page: the operator did not
163
+ * mistype anything, so the flags are not the answer and printing them buries
164
+ * the one line that is.
165
+ *
166
+ * The `fix:` LABEL is dressed and the command after it is not, which departs
167
+ * from the brief's "the command in `key`" on purpose. A fix line exists to be
168
+ * copied and run, and rule 3 (never dress a copyable value) is the rule that
169
+ * makes the whole palette safe to trust. The label alone carries the emphasis.
170
+ *
171
+ * Returned as text with no trailing newline, so callers keep control of the
172
+ * stream and of whether a blank line follows.
173
+ */
174
+ export declare function refusal(style: Style, code: string, message: string, fix?: string): string;
175
+ /**
176
+ * The one shape a legitimate skip is printed in (APRV-153).
177
+ *
178
+ * – web-port this policy declares no channels.web.port, so no queue page is served
179
+ * to enable: approval policy amend
180
+ *
181
+ * The counterpart of {@link refusal} for a part that is not wrong. The fact it
182
+ * reports is doctor's fact (an optional part, absent by configuration), so it
183
+ * borrows doctor's vocabulary: the skip glyph, the check name in `warn`, the
184
+ * detail plain. `approval up` used to print these through {@link refusal}, and
185
+ * the red ✗ taught an operator that a healthy startup had failed; this shape
186
+ * exists to prevent that mis-reading.
187
+ *
188
+ * The second line is labelled `to enable:` rather than `fix:` because nothing
189
+ * is broken and there is nothing to repair; it is the path to turning the part
190
+ * on. Like a fix line it exists to be copied and run, so the label is dressed
191
+ * and the command after it is not (rule 3).
192
+ *
193
+ * Returned as text with no trailing newline, like {@link refusal}.
194
+ */
195
+ export declare function skipNotice(style: Style, check: string, detail: string, enable?: string): string;
196
+ /**
197
+ * The notice under a printed execution token, on a surface that is not Telegram.
198
+ *
199
+ * Three facts and an instruction, in the order a reader needs them: it works
200
+ * once, nothing anywhere can give it back, so the copy has to happen now.
201
+ */
202
+ export declare const TOKEN_NOTICE = "single-use \u00B7 stored nowhere \u00B7 copy it now";
203
+ /** The same, for the Telegram listener, where the extra clause is load-bearing. */
204
+ export declare const TOKEN_NOTICE_TELEGRAM = "single-use \u00B7 stored nowhere \u00B7 not sent to Telegram \u00B7 copy it now";
205
+ /**
206
+ * The execution token, in a rule-boxed panel (APRV-91's brief, APRV-102).
207
+ *
208
+ * ─────────────────────────────────────────────────────────────
209
+ * execution token task-042:chaser
210
+ * 729a25b06567ccc0aed356f3423e39bf12b6252056b7890acde455603010fb11
211
+ * single-use · stored nowhere · copy it now
212
+ * ─────────────────────────────────────────────────────────────
213
+ *
214
+ * Trust surfaces look different from chatter: this is the one value in the whole
215
+ * CLI that exists for exactly as long as the terminal keeps it, so it gets a box
216
+ * and whitespace rather than a prefix on a line of prose.
217
+ *
218
+ * THE TOKEN LINE IS UNCOLOURED AND ALONE. Rule 3 in the header is not a
219
+ * preference here: a triple-click on the token must yield the token, and an
220
+ * escape sequence in the middle of it yields something that cannot be spent.
221
+ * The label carries the emphasis, the notice wears `secret` (bold yellow, never
222
+ * red: red is failure and this is a success), and the rules wear `rule`.
223
+ *
224
+ * One helper rather than one per surface, because `grant`, the Telegram listener
225
+ * and the CLI channel each print this and three copies is three chances for the
226
+ * one that matters to lose its warning.
227
+ */
228
+ export declare function tokenPanel(st: Style, actionKey: string, token: string, notice?: string): string;
229
+ /** Build a style. Tests use this; the CLI uses {@link style}. */
230
+ export declare function makeStyle(input?: StyleInput): Style;
231
+ /**
232
+ * The process-wide style, decided once.
233
+ *
234
+ * `json` and `noColor` are known only after a command line is parsed, so the
235
+ * first caller passes them and later callers get that same answer. A verb that
236
+ * answers in JSON therefore has to ask FIRST, which every JSON branch does by
237
+ * construction: it asks for the style before it prints.
238
+ */
239
+ export declare function style(input?: StyleInput): Style;
240
+ /** Forget the memoized answer. For tests, and for nothing else. */
241
+ export declare function resetStyle(): void;
242
+ /**
243
+ * The first 12 characters of a digest, for human output only.
244
+ *
245
+ * 12 hex characters is 48 bits: ample to tell two hashes in one report apart,
246
+ * which is the entire job here, and short enough that two of them fit on a line
247
+ * with their labels. The FULL value stays in `--json`, which is what anything
248
+ * comparing hashes should be reading. Anything that is not a 64-hex digest is
249
+ * returned untouched, so this is safe to apply to a field that may be null or
250
+ * already short.
251
+ */
252
+ export declare function shortHash(hash: string): string;
253
+ /**
254
+ * `path` relative to `cwd` when it is inside it, absolute otherwise.
255
+ *
256
+ * The dense-output complaint was largely this: the same 70-character absolute
257
+ * path three times in one report. A path INSIDE the working directory is
258
+ * printed relative because that is how the operator would type it. A path
259
+ * outside stays absolute, because `../../../etc/approval/APPROVAL.md` is worse
260
+ * than the truth in every way.
261
+ */
262
+ export declare function relPath(path: string, cwd: string): string;
263
+ /** One numbered step of a {@link runbook}: a command, and at most a comment. */
264
+ export interface RunbookStep {
265
+ /** The runnable command, printed raw so a triple-click yields clean bytes. */
266
+ command: string;
267
+ /** A short trailing comment. Rendered after the command as `# …`. */
268
+ note?: string;
269
+ }
270
+ /** The body of a {@link runbook}, in the order a reader needs it. */
271
+ export interface RunbookInput {
272
+ /**
273
+ * Verbatim output from whatever refused (git, gh), indented under the
274
+ * headline. It goes here rather than inline in the headline because a
275
+ * remote's four-line rejection swallowed into a sentence is the exact
276
+ * failure APRV-129 exists to undo.
277
+ */
278
+ quote?: readonly string[];
279
+ /** YOUR STATE: three or four short lines, one fact each, no commands. */
280
+ state: readonly string[];
281
+ /** NEXT STEPS: numbered, ONE runnable command per line. */
282
+ steps: readonly RunbookStep[];
283
+ /** Closing pointers: one line each, rationale compressed, no commands. */
284
+ footer?: readonly string[];
285
+ }
286
+ /**
287
+ * A refusal the operator has to READ AND ACT ON, rendered as a runbook
288
+ * (APRV-129).
289
+ *
290
+ * ✗ push-rejected the remote refused the push
291
+ * ! [remote rejected] main -> main (protected branch hook declined)
292
+ *
293
+ * YOUR STATE
294
+ * attestation appended at seq 2
295
+ * committed LOCALLY on main, NOT on origin
296
+ *
297
+ * NEXT STEPS
298
+ * 1. git branch policy-amend-2
299
+ * 2. git push -u origin policy-amend-2
300
+ *
301
+ * why a merge commit: … see docs/cli-reference.md
302
+ *
303
+ * The shape comes from the first live `push-rejected`, which said all of this
304
+ * correctly as one paragraph and was read as the single word REJECTED. The
305
+ * register already existed in this file (the token panel, doctor's
306
+ * line-per-check), and this is that register applied to recovery.
307
+ *
308
+ * ONE RUNNABLE COMMAND PER LINE is the load-bearing rule. A step is a command
309
+ * and at most a trailing comment, so the reader can copy down the list without
310
+ * parsing prose for the part that is typed. Rationale is not deleted: it is
311
+ * compressed into `footer` as one line plus a documentation pointer.
312
+ *
313
+ * Structure survives with styling stripped: the headings are their own words,
314
+ * the numbers are literal digits, and the indentation is spaces. NO_COLOR and
315
+ * ASCII mode lose the colour and the glyph spelling, nothing else.
316
+ *
317
+ * Returned as text with no trailing newline, like {@link refusal}.
318
+ */
319
+ export declare function runbook(st: Style, code: string, headline: string, input: RunbookInput): string;
320
+ export {};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The token verbs: `approval token` (status) and `approval consume` (plumbing).
3
+ *
4
+ * As everywhere else in this CLI, **no logic lives here.** Minting, verification,
5
+ * the death conditions, and the append are all `core/token.ts`; this file
6
+ * resolves paths and identity, chooses an exit code, and formats output.
7
+ *
8
+ * ## `approval token` reports status; it does not print the token
9
+ *
10
+ * SPEC.md §10.1 lists `approval token <action-key> # print single-use execution
11
+ * token if granted`. Under the settled hash-only design (2026-08-06) the log
12
+ * carries `sha256(token)` and the raw token is returned by the grant call and
13
+ * kept nowhere else — so there is nothing for this verb to fetch. It would have
14
+ * to *store* the secret to print it, which is precisely the property the design
15
+ * exists to avoid.
16
+ *
17
+ * The honest reading, and the one implemented here: the token is printed **by
18
+ * `approval grant`**, once; `approval token` answers "is a live, unspent token
19
+ * outstanding for this action, and what is its digest?". That interpretation is
20
+ * flagged in the task notes for human review — it is a reading of the spec, not
21
+ * a silent amendment of it, and §10.4's normative sentence (adapters MUST
22
+ * require a valid, unexpired, single-use token bound to the idempotency key) is
23
+ * unaffected either way.
24
+ *
25
+ * ## `approval consume` is internal
26
+ *
27
+ * It is the seam APRV-18's `approval run` will wrap: verify, append
28
+ * `execution.started`, hand control to the command. It ships now so the token
29
+ * boundary is testable end to end and so an adapter integration can be driven by
30
+ * hand, and its help text says INTERNAL in the first line.
31
+ *
32
+ * Exit codes are the frozen table, mapped exactly as the gate verbs map them: a
33
+ * refusal ("that token will not execute") is 1, because the command was
34
+ * well-formed and the answer is no; only filesystem facts are 4 and only a
35
+ * crashed write is 3.
36
+ */
37
+ import type { Streams } from "./main.js";
38
+ export declare function commandToken(argv: string[], streams: Streams, cwd: string): number;
39
+ export declare function commandConsume(argv: string[], streams: Streams, cwd: string): number;