approval-md 0.2.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 (378) hide show
  1. package/README.md +85 -877
  2. package/SPEC.md +150 -19
  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 +53 -1
  8. package/dist/src/channels/contract.js +304 -7
  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 +394 -12
  22. package/dist/src/channels/telegram.js +621 -68
  23. package/dist/src/channels/telegram.js.map +1 -1
  24. package/dist/src/channels/web.d.ts +9 -0
  25. package/dist/src/channels/web.js +17 -0
  26. package/dist/src/channels/web.js.map +1 -1
  27. package/dist/src/cli/amend.js +564 -34
  28. package/dist/src/cli/amend.js.map +1 -1
  29. package/dist/src/cli/attest.d.ts +9 -0
  30. package/dist/src/cli/attest.js +177 -13
  31. package/dist/src/cli/attest.js.map +1 -1
  32. package/dist/src/cli/channel-relay.d.ts +108 -0
  33. package/dist/src/cli/channel-relay.js +334 -0
  34. package/dist/src/cli/channel-relay.js.map +1 -0
  35. package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
  36. package/dist/src/cli/channel-telegram-webhook.js +709 -0
  37. package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
  38. package/dist/src/cli/channel-telegram.d.ts +347 -28
  39. package/dist/src/cli/channel-telegram.js +763 -28
  40. package/dist/src/cli/channel-telegram.js.map +1 -1
  41. package/dist/src/cli/channel.d.ts +10 -1
  42. package/dist/src/cli/channel.js +14 -1
  43. package/dist/src/cli/channel.js.map +1 -1
  44. package/dist/src/cli/codex-bridge.d.ts +819 -0
  45. package/dist/src/cli/codex-bridge.js +1607 -0
  46. package/dist/src/cli/codex-bridge.js.map +1 -0
  47. package/dist/src/cli/codex.d.ts +1 -1
  48. package/dist/src/cli/codex.js +304 -7
  49. package/dist/src/cli/codex.js.map +1 -1
  50. package/dist/src/cli/daemon.js +41 -6
  51. package/dist/src/cli/daemon.js.map +1 -1
  52. package/dist/src/cli/doctor.d.ts +63 -0
  53. package/dist/src/cli/doctor.js +1322 -122
  54. package/dist/src/cli/doctor.js.map +1 -1
  55. package/dist/src/cli/execute.d.ts +17 -1
  56. package/dist/src/cli/execute.js +366 -22
  57. package/dist/src/cli/execute.js.map +1 -1
  58. package/dist/src/cli/exit-codes.d.ts +12 -0
  59. package/dist/src/cli/exit-codes.js +13 -0
  60. package/dist/src/cli/exit-codes.js.map +1 -1
  61. package/dist/src/cli/gate.d.ts +2 -0
  62. package/dist/src/cli/gate.js +233 -2
  63. package/dist/src/cli/gate.js.map +1 -1
  64. package/dist/src/cli/help.d.ts +14 -2
  65. package/dist/src/cli/help.js +395 -82
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook-codex.d.ts +49 -1
  68. package/dist/src/cli/hook-codex.js +60 -1
  69. package/dist/src/cli/hook-codex.js.map +1 -1
  70. package/dist/src/cli/hook.d.ts +828 -4
  71. package/dist/src/cli/hook.js +2972 -142
  72. package/dist/src/cli/hook.js.map +1 -1
  73. package/dist/src/cli/import.js +1 -1
  74. package/dist/src/cli/import.js.map +1 -1
  75. package/dist/src/cli/init.js +16 -7
  76. package/dist/src/cli/init.js.map +1 -1
  77. package/dist/src/cli/instructions.js +24 -3
  78. package/dist/src/cli/instructions.js.map +1 -1
  79. package/dist/src/cli/log-anchor.d.ts +55 -0
  80. package/dist/src/cli/log-anchor.js +199 -60
  81. package/dist/src/cli/log-anchor.js.map +1 -1
  82. package/dist/src/cli/main.d.ts +0 -12
  83. package/dist/src/cli/main.js +56 -3
  84. package/dist/src/cli/main.js.map +1 -1
  85. package/dist/src/cli/muse.d.ts +2 -0
  86. package/dist/src/cli/muse.js +62 -0
  87. package/dist/src/cli/muse.js.map +1 -0
  88. package/dist/src/cli/payload.js +79 -1
  89. package/dist/src/cli/payload.js.map +1 -1
  90. package/dist/src/cli/policy-apply.d.ts +195 -0
  91. package/dist/src/cli/policy-apply.js +573 -0
  92. package/dist/src/cli/policy-apply.js.map +1 -0
  93. package/dist/src/cli/policy.js +14 -1
  94. package/dist/src/cli/policy.js.map +1 -1
  95. package/dist/src/cli/preflight.d.ts +151 -13
  96. package/dist/src/cli/preflight.js +398 -41
  97. package/dist/src/cli/preflight.js.map +1 -1
  98. package/dist/src/cli/sandbox.js +17 -1
  99. package/dist/src/cli/sandbox.js.map +1 -1
  100. package/dist/src/cli/scaffold.d.ts +1 -1
  101. package/dist/src/cli/scaffold.js +7 -1
  102. package/dist/src/cli/scaffold.js.map +1 -1
  103. package/dist/src/cli/serve.d.ts +53 -0
  104. package/dist/src/cli/serve.js +270 -0
  105. package/dist/src/cli/serve.js.map +1 -0
  106. package/dist/src/cli/setup-channel.d.ts +9 -0
  107. package/dist/src/cli/setup-channel.js +28 -1
  108. package/dist/src/cli/setup-channel.js.map +1 -1
  109. package/dist/src/cli/setup-common.d.ts +3 -1
  110. package/dist/src/cli/setup-common.js +3 -2
  111. package/dist/src/cli/setup-common.js.map +1 -1
  112. package/dist/src/cli/setup.d.ts +2 -0
  113. package/dist/src/cli/setup.js +94 -2
  114. package/dist/src/cli/setup.js.map +1 -1
  115. package/dist/src/cli/up.js +134 -51
  116. package/dist/src/cli/up.js.map +1 -1
  117. package/dist/src/cli/values.js +3 -4
  118. package/dist/src/cli/values.js.map +1 -1
  119. package/dist/src/cli/verb-registry.d.ts +2 -0
  120. package/dist/src/cli/verb-registry.js +510 -47
  121. package/dist/src/cli/verb-registry.js.map +1 -1
  122. package/dist/src/cli/wordmark.d.ts +2 -2
  123. package/dist/src/cli/wordmark.js +2 -2
  124. package/dist/src/codex/broker.d.ts +229 -0
  125. package/dist/src/codex/broker.js +548 -0
  126. package/dist/src/codex/broker.js.map +1 -0
  127. package/dist/src/codex/runner.d.ts +178 -0
  128. package/dist/src/codex/runner.js +231 -0
  129. package/dist/src/codex/runner.js.map +1 -0
  130. package/dist/src/codex/serve.d.ts +56 -0
  131. package/dist/src/codex/serve.js +98 -0
  132. package/dist/src/codex/serve.js.map +1 -0
  133. package/dist/src/codex/workspace-commit.d.ts +219 -0
  134. package/dist/src/codex/workspace-commit.js +549 -0
  135. package/dist/src/codex/workspace-commit.js.map +1 -0
  136. package/dist/src/core/advance-cycle.d.ts +51 -0
  137. package/dist/src/core/advance-cycle.js +66 -2
  138. package/dist/src/core/advance-cycle.js.map +1 -1
  139. package/dist/src/core/agents-md.d.ts +20 -18
  140. package/dist/src/core/agents-md.js +33 -31
  141. package/dist/src/core/agents-md.js.map +1 -1
  142. package/dist/src/core/attest-bootstrap.d.ts +95 -0
  143. package/dist/src/core/attest-bootstrap.js +135 -0
  144. package/dist/src/core/attest-bootstrap.js.map +1 -0
  145. package/dist/src/core/attest.d.ts +234 -2
  146. package/dist/src/core/attest.js +342 -7
  147. package/dist/src/core/attest.js.map +1 -1
  148. package/dist/src/core/audit.d.ts +73 -3
  149. package/dist/src/core/audit.js +65 -5
  150. package/dist/src/core/audit.js.map +1 -1
  151. package/dist/src/core/channel-lease.d.ts +322 -0
  152. package/dist/src/core/channel-lease.js +663 -0
  153. package/dist/src/core/channel-lease.js.map +1 -0
  154. package/dist/src/core/channel-owner.d.ts +213 -0
  155. package/dist/src/core/channel-owner.js +358 -0
  156. package/dist/src/core/channel-owner.js.map +1 -0
  157. package/dist/src/core/command-class.d.ts +205 -0
  158. package/dist/src/core/command-class.js +1433 -28
  159. package/dist/src/core/command-class.js.map +1 -1
  160. package/dist/src/core/commit-guard.d.ts +272 -0
  161. package/dist/src/core/commit-guard.js +424 -0
  162. package/dist/src/core/commit-guard.js.map +1 -0
  163. package/dist/src/core/daemon-actor.d.ts +45 -0
  164. package/dist/src/core/daemon-actor.js +54 -0
  165. package/dist/src/core/daemon-actor.js.map +1 -0
  166. package/dist/src/core/daemon-host.d.ts +178 -0
  167. package/dist/src/core/daemon-host.js +228 -0
  168. package/dist/src/core/daemon-host.js.map +1 -0
  169. package/dist/src/core/daemon-identity.d.ts +238 -0
  170. package/dist/src/core/daemon-identity.js +252 -0
  171. package/dist/src/core/daemon-identity.js.map +1 -0
  172. package/dist/src/core/dark-session.d.ts +109 -8
  173. package/dist/src/core/dark-session.js +266 -82
  174. package/dist/src/core/dark-session.js.map +1 -1
  175. package/dist/src/core/decision-refusal.d.ts +23 -2
  176. package/dist/src/core/decision-refusal.js +24 -2
  177. package/dist/src/core/decision-refusal.js.map +1 -1
  178. package/dist/src/core/env-file.d.ts +5 -0
  179. package/dist/src/core/env-file.js +60 -1
  180. package/dist/src/core/env-file.js.map +1 -1
  181. package/dist/src/core/execute.d.ts +15 -2
  182. package/dist/src/core/execute.js +15 -2
  183. package/dist/src/core/execute.js.map +1 -1
  184. package/dist/src/core/gate.d.ts +392 -6
  185. package/dist/src/core/gate.js +852 -24
  186. package/dist/src/core/gate.js.map +1 -1
  187. package/dist/src/core/gesture-refusal.d.ts +166 -0
  188. package/dist/src/core/gesture-refusal.js +188 -0
  189. package/dist/src/core/gesture-refusal.js.map +1 -0
  190. package/dist/src/core/harness-version.d.ts +128 -4
  191. package/dist/src/core/harness-version.js +181 -12
  192. package/dist/src/core/harness-version.js.map +1 -1
  193. package/dist/src/core/harness-wait.d.ts +82 -3
  194. package/dist/src/core/harness-wait.js +89 -3
  195. package/dist/src/core/harness-wait.js.map +1 -1
  196. package/dist/src/core/instance.d.ts +59 -2
  197. package/dist/src/core/instance.js +113 -0
  198. package/dist/src/core/instance.js.map +1 -1
  199. package/dist/src/core/log-subscribe.d.ts +21 -0
  200. package/dist/src/core/log-subscribe.js +16 -6
  201. package/dist/src/core/log-subscribe.js.map +1 -1
  202. package/dist/src/core/log-write-layer.d.ts +55 -0
  203. package/dist/src/core/log-write-layer.js +91 -0
  204. package/dist/src/core/log-write-layer.js.map +1 -0
  205. package/dist/src/core/log.d.ts +89 -22
  206. package/dist/src/core/log.js +197 -19
  207. package/dist/src/core/log.js.map +1 -1
  208. package/dist/src/core/payload-census.d.ts +51 -0
  209. package/dist/src/core/payload-census.js +125 -2
  210. package/dist/src/core/payload-census.js.map +1 -1
  211. package/dist/src/core/payload-store.d.ts +31 -0
  212. package/dist/src/core/payload-store.js +113 -8
  213. package/dist/src/core/payload-store.js.map +1 -1
  214. package/dist/src/core/payload.d.ts +18 -3
  215. package/dist/src/core/payload.js +20 -4
  216. package/dist/src/core/payload.js.map +1 -1
  217. package/dist/src/core/policy-diff.d.ts +10 -0
  218. package/dist/src/core/policy-diff.js +10 -2
  219. package/dist/src/core/policy-diff.js.map +1 -1
  220. package/dist/src/core/policy-explain.d.ts +17 -1
  221. package/dist/src/core/policy-explain.js +48 -1
  222. package/dist/src/core/policy-explain.js.map +1 -1
  223. package/dist/src/core/policy-load.d.ts +65 -1
  224. package/dist/src/core/policy-load.js +21 -3
  225. package/dist/src/core/policy-load.js.map +1 -1
  226. package/dist/src/core/policy-match.d.ts +81 -0
  227. package/dist/src/core/policy-match.js +87 -0
  228. package/dist/src/core/policy-match.js.map +1 -1
  229. package/dist/src/core/policy-proposal.d.ts +75 -0
  230. package/dist/src/core/policy-proposal.js +110 -4
  231. package/dist/src/core/policy-proposal.js.map +1 -1
  232. package/dist/src/core/protected-path-guard.d.ts +117 -4
  233. package/dist/src/core/protected-path-guard.js +362 -48
  234. package/dist/src/core/protected-path-guard.js.map +1 -1
  235. package/dist/src/core/question-preempted.d.ts +141 -0
  236. package/dist/src/core/question-preempted.js +152 -0
  237. package/dist/src/core/question-preempted.js.map +1 -0
  238. package/dist/src/core/read-scope.d.ts +182 -0
  239. package/dist/src/core/read-scope.js +256 -0
  240. package/dist/src/core/read-scope.js.map +1 -0
  241. package/dist/src/core/run-payload.d.ts +160 -0
  242. package/dist/src/core/run-payload.js +304 -0
  243. package/dist/src/core/run-payload.js.map +1 -0
  244. package/dist/src/core/sandbox.d.ts +81 -0
  245. package/dist/src/core/sandbox.js +190 -1
  246. package/dist/src/core/sandbox.js.map +1 -1
  247. package/dist/src/core/sender-identity.d.ts +501 -0
  248. package/dist/src/core/sender-identity.js +609 -0
  249. package/dist/src/core/sender-identity.js.map +1 -0
  250. package/dist/src/core/shlex.d.ts +102 -0
  251. package/dist/src/core/shlex.js +159 -0
  252. package/dist/src/core/shlex.js.map +1 -0
  253. package/dist/src/core/state.d.ts +52 -1
  254. package/dist/src/core/state.js +52 -6
  255. package/dist/src/core/state.js.map +1 -1
  256. package/dist/src/core/telegram-config.d.ts +79 -0
  257. package/dist/src/core/telegram-config.js +108 -0
  258. package/dist/src/core/telegram-config.js.map +1 -1
  259. package/dist/src/core/values.d.ts +18 -8
  260. package/dist/src/core/values.js +36 -1
  261. package/dist/src/core/values.js.map +1 -1
  262. package/dist/src/core/verify.d.ts +23 -1
  263. package/dist/src/core/verify.js +20 -2
  264. package/dist/src/core/verify.js.map +1 -1
  265. package/dist/src/daemon/advance.d.ts +10 -0
  266. package/dist/src/daemon/advance.js +25 -4
  267. package/dist/src/daemon/advance.js.map +1 -1
  268. package/dist/src/daemon/audit.d.ts +80 -5
  269. package/dist/src/daemon/audit.js +126 -6
  270. package/dist/src/daemon/audit.js.map +1 -1
  271. package/dist/src/daemon/daemon.d.ts +174 -2
  272. package/dist/src/daemon/daemon.js +307 -7
  273. package/dist/src/daemon/daemon.js.map +1 -1
  274. package/dist/src/daemon/git-evidence.d.ts +2 -2
  275. package/dist/src/daemon/git-evidence.js +1 -1
  276. package/dist/src/daemon/projection.d.ts +13 -2
  277. package/dist/src/daemon/projection.js +39 -6
  278. package/dist/src/daemon/projection.js.map +1 -1
  279. package/dist/src/mcp/server.d.ts +39 -3
  280. package/dist/src/mcp/server.js +30 -3
  281. package/dist/src/mcp/server.js.map +1 -1
  282. package/dist/src/muse/server.d.ts +16 -0
  283. package/dist/src/muse/server.js +238 -0
  284. package/dist/src/muse/server.js.map +1 -0
  285. package/dist/src/serve/archive.d.ts +158 -0
  286. package/dist/src/serve/archive.js +354 -0
  287. package/dist/src/serve/archive.js.map +1 -0
  288. package/dist/src/serve/arguments.d.ts +93 -0
  289. package/dist/src/serve/arguments.js +228 -0
  290. package/dist/src/serve/arguments.js.map +1 -0
  291. package/dist/src/serve/credentials.d.ts +91 -0
  292. package/dist/src/serve/credentials.js +143 -0
  293. package/dist/src/serve/credentials.js.map +1 -0
  294. package/dist/src/serve/follow.d.ts +86 -0
  295. package/dist/src/serve/follow.js +101 -0
  296. package/dist/src/serve/follow.js.map +1 -0
  297. package/dist/src/serve/hook-thread.d.ts +134 -0
  298. package/dist/src/serve/hook-thread.js +333 -0
  299. package/dist/src/serve/hook-thread.js.map +1 -0
  300. package/dist/src/serve/hook-worker.d.ts +134 -0
  301. package/dist/src/serve/hook-worker.js +173 -0
  302. package/dist/src/serve/hook-worker.js.map +1 -0
  303. package/dist/src/serve/server.d.ts +412 -0
  304. package/dist/src/serve/server.js +1251 -0
  305. package/dist/src/serve/server.js.map +1 -0
  306. package/docs/README-extended.md +766 -0
  307. package/docs/cli-reference.md +2563 -73
  308. package/docs/codex-enforced-session.md +75 -2
  309. package/docs/codex-workspace-broker.md +118 -0
  310. package/package.json +6 -2
  311. package/schema/event.schema.json +595 -9
  312. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
  313. package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
  314. package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
  315. package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
  316. package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
  317. package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
  318. package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
  319. package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
  320. package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
  321. package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
  322. package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
  323. package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
  324. package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
  325. package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
  326. package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
  327. package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
  328. package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
  329. package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
  330. package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
  331. package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
  332. package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
  333. package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
  334. package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
  335. package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
  336. package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
  337. package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
  338. package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
  339. package/schema/fixtures/event/valid/daemon-identity.json +15 -0
  340. package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
  341. package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
  342. package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
  343. package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
  344. package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
  345. package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
  346. package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
  347. package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
  348. package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
  349. package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
  350. package/schema/fixtures/policy/valid/canonical.json +1 -1
  351. package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
  352. package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
  353. package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
  354. package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
  355. package/schema/fixtures/policy-md/valid/canonical.md +1 -1
  356. package/schema/fixtures/policy-md/valid/with-values.md +5 -7
  357. package/schema/fixtures/values/invalid/class-shaped.json +1 -1
  358. package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
  359. package/schema/fixtures/values/invalid/non-string-item.json +1 -1
  360. package/schema/fixtures/values/invalid/over-cap.json +1 -1
  361. package/schema/fixtures/values/invalid/unknown-key.json +1 -1
  362. package/schema/fixtures/values/invalid/version-float.json +1 -0
  363. package/schema/fixtures/values/invalid/version-integer.json +1 -0
  364. package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
  365. package/schema/fixtures/values/valid/empty-lists.json +2 -3
  366. package/schema/fixtures/values/valid/full.json +5 -7
  367. package/schema/fixtures/values/valid/minimal.json +1 -1
  368. package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
  369. package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
  370. package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
  371. package/schema/fixtures/values-md/invalid/version-1.md +69 -0
  372. package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
  373. package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
  374. package/schema/fixtures/values-md/valid/absent.md +1 -1
  375. package/schema/fixtures/values-md/valid/with-values.md +5 -7
  376. package/schema/policy.schema.json +100 -4
  377. package/schema/values.schema.json +7 -11
  378. package/schema/fixtures/values/invalid/version-string.json +0 -1
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Read scope: which directories an agent may read from (APRV-347).
3
+ *
4
+ * ## The hole this closes
5
+ *
6
+ * Writes and deletes have been path-scoped for a while. `files.delete.scratch`
7
+ * versus `files.delete.out_of_scope` is decided by comparing a resolved target
8
+ * against roots the caller supplied (`ClassifierContext.scratchRoots`), and the
9
+ * file tools carry their target into the payload a grant binds. Reads had none
10
+ * of that: every shell reader classified `read.shell` with no path bound, and
11
+ * `Read`, `Glob` and `Grep` were answered `allow` before classification ever
12
+ * ran. So a policy could say a great deal about what an agent may WRITE and
13
+ * nothing at all about what it may SEE, and an agent working in one directory
14
+ * could read every sibling of it.
15
+ *
16
+ * This module is the pure half of the read-side mirror. It holds the class
17
+ * name, the roots arithmetic, and the one genuinely fiddly question — which
18
+ * words of a read command are paths — and it touches no disk, reads no
19
+ * environment and resolves nothing. The impure half (relative paths resolved
20
+ * against a working directory, symlinks followed, the escape that only the
21
+ * filesystem can see) lives in `src/cli/hook.ts`, exactly where the delete
22
+ * rule's second pass lives, and it can only ever TIGHTEN this file's answer.
23
+ *
24
+ * ## Fail closed, in three places
25
+ *
26
+ * SPEC.md §11.1: ambiguity resolves to the stricter path. Here that is
27
+ *
28
+ * 1. a target this file cannot read as a path (a `$VAR`, a glob, a `~`) is out
29
+ * of scope, because what it expands to is not in the text;
30
+ * 2. a read command naming NO target reads the working directory, so it is
31
+ * checked against the working directory rather than waved through;
32
+ * 3. an empty root list means nothing is in scope — but a caller that passes no
33
+ * roots at all gets today's answer instead (see {@link ClassifierContext}),
34
+ * because a caller that forgot the field must not have every read it makes
35
+ * turned into a decision.
36
+ *
37
+ * ## What a root is
38
+ *
39
+ * The gate root (the directory holding the policy file the runtime resolved),
40
+ * the session scratchpad, and the system temp root. A policy may WIDEN that
41
+ * with `read_scope.roots`; it may not narrow it below the gate root, because a
42
+ * runtime that cannot read its own policy, log and workspace cannot run at all.
43
+ */
44
+ /**
45
+ * The class a read outside every root takes.
46
+ *
47
+ * A sibling of `read.shell` rather than a replacement for it: a read INSIDE the
48
+ * roots is the same ordinary, autonomous act it has always been, and a policy
49
+ * that says nothing about this class gets `defaults.autonomy` for it, which is
50
+ * the fail-closed direction for a name nobody has declared.
51
+ */
52
+ export declare const READ_OUT_OF_SCOPE_CLASS = "read.file.out_of_scope";
53
+ /**
54
+ * The `read_scope` block of a policy (SPEC.md §5, amended APRV-347).
55
+ *
56
+ * Additive and optional, with the same discipline `protected_paths` has: the
57
+ * built-in roots stand whatever this says, so a policy can widen the scope and
58
+ * never shrink it. A relative entry is resolved against the gate root, so a
59
+ * policy stays portable between a checkout and a clone of it.
60
+ */
61
+ export interface ReadScope {
62
+ roots?: string[];
63
+ }
64
+ /**
65
+ * Is `candidate` AT or under `root`, by path segment?
66
+ *
67
+ * At-or-under rather than the delete rule's strictly-under: `ls <gate root>` is
68
+ * a read of the workspace an agent is working in, and a rule that made the root
69
+ * itself out of scope would classify the most ordinary command in the session.
70
+ *
71
+ * Segment matching, never string prefixes: `/dev/muse-other` must not match a
72
+ * root of `/dev/muse`, and `startsWith` says it does.
73
+ */
74
+ export declare function isAtOrUnderReadRoot(candidate: string, root: string): boolean;
75
+ /** Is this path inside ANY of these roots? */
76
+ export declare function isInReadScope(candidate: string, roots: readonly string[]): boolean;
77
+ /**
78
+ * A value whose expansion the classifier cannot see, and therefore may not
79
+ * vouch for. The same test the delete rule applies, and for the same reason:
80
+ * `cat $SOMEWHERE` reads whatever that variable holds.
81
+ */
82
+ export declare function isUnreadableTarget(word: string): boolean;
83
+ /**
84
+ * The effective read roots: the built-ins, plus whatever the policy added.
85
+ *
86
+ * Pure, and every input is the caller's. `gateRoot` is the directory holding
87
+ * the policy file the runtime resolved; `systemRoots` are the scratchpad and
88
+ * temp roots the caller already resolved (`resolveScratchRoots` in the hook);
89
+ * `declared` is `read_scope.roots` verbatim.
90
+ *
91
+ * A declared entry that is relative is joined onto the gate root. A declared
92
+ * entry the caller cannot vouch for — empty, or one this file can see is not a
93
+ * path at all — is DROPPED rather than accepted, because a root is an
94
+ * authorization and a malformed one must not become `/`.
95
+ *
96
+ * The result is de-duplicated and otherwise in the order given, so the first
97
+ * root a path matches is the most specific one a reader would expect.
98
+ */
99
+ export declare function effectiveReadRoots(options: {
100
+ gateRoot: string;
101
+ declared?: readonly string[] | undefined;
102
+ systemRoots?: readonly string[] | undefined;
103
+ }): string[];
104
+ /**
105
+ * How a reader's positionals map to paths.
106
+ *
107
+ * - `all`: every positional is a file or directory (`cat a b`, `ls src`,
108
+ * `diff a b`, `cut -d: -f1 /etc/passwd` — `cut`'s delimiter and field list
109
+ * are flags, so none of its positionals is a pattern).
110
+ * - `after-pattern`: the FIRST positional is a pattern or a script and the rest
111
+ * are paths (`grep needle src`, `sed -n 1,5p file`, `jq .x file.json`) —
112
+ * UNLESS the pattern arrived through a flag (`-e`, `-f`), in which case every
113
+ * positional is a path and the shape collapses to `all`.
114
+ * - `walk`: `find`'s shape — positionals up to the first primary are paths.
115
+ *
116
+ * Binaries absent from this table are absent on purpose, and each omission is a
117
+ * decision not to widen anything:
118
+ *
119
+ * - `echo`, `printf`, `tr`, `test`, `type`, `which`, `pwd`, `true`, `false`,
120
+ * `cd`: their positionals are not files, or name a file without reading its
121
+ * contents. A rule that treated `echo /etc/passwd` as a read of that file
122
+ * would route text through a human.
123
+ * - `basename`, `dirname`, `readlink`, `realpath`: they manipulate or resolve a
124
+ * path and never open it. What leaks is the existence of a name, which is not
125
+ * what this class is about.
126
+ * - `less` and `more` are NOT added to the classifier's reader list by this
127
+ * task. Adding them would take them from `unclassified` (a deny) to
128
+ * `read.shell` (this repository's policy: autonomous), which is a widening,
129
+ * and a task that exists to narrow reads has no business doing that in
130
+ * passing. They are named in the follow-up in `docs/sandboxed-exec.md`.
131
+ *
132
+ * APRV-397 adds the packaging readers, so the reads the new classifier rows
133
+ * answer are scoped by the same arithmetic every other reader's are: `tar`,
134
+ * `gunzip` and `base64` are `all` (their operands are files, and a flag value
135
+ * that lands among the positionals resolves against the working directory,
136
+ * which costs nothing: see the note below), and `openssl` is `after-pattern`,
137
+ * because its first positional is the digest subcommand and the files follow it. Only
138
+ * the READ forms of those binaries ever reach this table: `tar -x`, a plain
139
+ * `gunzip` and `base64 -o` take write classes in the classifier, and this module
140
+ * scopes `read.shell` alone.
141
+ */
142
+ export type ReadTargetShape = "all" | "after-pattern" | "walk";
143
+ /** Which readers take paths, and where. Keyed by the binary's basename. */
144
+ export declare const READ_TARGET_SHAPES: Readonly<Record<string, ReadTargetShape>>;
145
+ /**
146
+ * The paths a read command will open, or `null` when this binary is not one
147
+ * whose reads this module scopes.
148
+ *
149
+ * An empty array is a real answer and is NOT the same as `null`: it means this
150
+ * reader opens the working directory (`ls`, `find`, `grep needle` with no file
151
+ * operand), and the caller checks the working directory in its place. `null`
152
+ * means "not a scoped reader", and the caller leaves the segment alone.
153
+ *
154
+ * Treating a non-path positional as a path costs nothing: a bare word resolves
155
+ * against the working directory, which is inside a root in every session this
156
+ * module is meant for. Treating a path as a non-path costs the whole property.
157
+ */
158
+ export declare function readTargetsOf(bin: string, positionals: readonly string[], args?: readonly string[]): string[] | null;
159
+ /**
160
+ * The verdict the PURE half can reach for one target.
161
+ *
162
+ * `out-of-scope` and `in-scope` are final. `needs-disk` is the honest answer
163
+ * for a relative path: its meaning depends on a working directory this file
164
+ * does not have, and the caller with the disk decides it. A caller that cannot
165
+ * do the second pass must treat `needs-disk` as out of scope — which is what
166
+ * `hook classify` and `hook <harness>` both do, through the same function.
167
+ */
168
+ export type ReadTargetVerdict = "in-scope" | "out-of-scope" | "needs-disk";
169
+ /**
170
+ * Read one target against the roots, as far as text alone can settle it.
171
+ *
172
+ * Absolute and inside a root: in scope. Absolute and outside every root: out of
173
+ * scope, decided here, no disk needed. Unreadable (a variable, a glob, a `~`):
174
+ * out of scope, because what it names is not in the text. Anything relative, or
175
+ * carrying a `..`, is `needs-disk`.
176
+ */
177
+ export declare function readTargetVerdict(target: string, roots: readonly string[]): ReadTargetVerdict;
178
+ /**
179
+ * The roots, rendered for a human: `approval policy check`'s line and the
180
+ * hook's verdict note say the same sentence.
181
+ */
182
+ export declare function renderReadRoots(roots: readonly string[]): string;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Read scope: which directories an agent may read from (APRV-347).
3
+ *
4
+ * ## The hole this closes
5
+ *
6
+ * Writes and deletes have been path-scoped for a while. `files.delete.scratch`
7
+ * versus `files.delete.out_of_scope` is decided by comparing a resolved target
8
+ * against roots the caller supplied (`ClassifierContext.scratchRoots`), and the
9
+ * file tools carry their target into the payload a grant binds. Reads had none
10
+ * of that: every shell reader classified `read.shell` with no path bound, and
11
+ * `Read`, `Glob` and `Grep` were answered `allow` before classification ever
12
+ * ran. So a policy could say a great deal about what an agent may WRITE and
13
+ * nothing at all about what it may SEE, and an agent working in one directory
14
+ * could read every sibling of it.
15
+ *
16
+ * This module is the pure half of the read-side mirror. It holds the class
17
+ * name, the roots arithmetic, and the one genuinely fiddly question — which
18
+ * words of a read command are paths — and it touches no disk, reads no
19
+ * environment and resolves nothing. The impure half (relative paths resolved
20
+ * against a working directory, symlinks followed, the escape that only the
21
+ * filesystem can see) lives in `src/cli/hook.ts`, exactly where the delete
22
+ * rule's second pass lives, and it can only ever TIGHTEN this file's answer.
23
+ *
24
+ * ## Fail closed, in three places
25
+ *
26
+ * SPEC.md §11.1: ambiguity resolves to the stricter path. Here that is
27
+ *
28
+ * 1. a target this file cannot read as a path (a `$VAR`, a glob, a `~`) is out
29
+ * of scope, because what it expands to is not in the text;
30
+ * 2. a read command naming NO target reads the working directory, so it is
31
+ * checked against the working directory rather than waved through;
32
+ * 3. an empty root list means nothing is in scope — but a caller that passes no
33
+ * roots at all gets today's answer instead (see {@link ClassifierContext}),
34
+ * because a caller that forgot the field must not have every read it makes
35
+ * turned into a decision.
36
+ *
37
+ * ## What a root is
38
+ *
39
+ * The gate root (the directory holding the policy file the runtime resolved),
40
+ * the session scratchpad, and the system temp root. A policy may WIDEN that
41
+ * with `read_scope.roots`; it may not narrow it below the gate root, because a
42
+ * runtime that cannot read its own policy, log and workspace cannot run at all.
43
+ */
44
+ /**
45
+ * The class a read outside every root takes.
46
+ *
47
+ * A sibling of `read.shell` rather than a replacement for it: a read INSIDE the
48
+ * roots is the same ordinary, autonomous act it has always been, and a policy
49
+ * that says nothing about this class gets `defaults.autonomy` for it, which is
50
+ * the fail-closed direction for a name nobody has declared.
51
+ */
52
+ export const READ_OUT_OF_SCOPE_CLASS = "read.file.out_of_scope";
53
+ /** Non-empty path segments, `.` dropped. Identical to the classifier's own. */
54
+ function segmentsOf(candidate) {
55
+ return candidate
56
+ .split(/[/\\]+/u)
57
+ .filter((segment) => segment.length > 0 && segment !== ".");
58
+ }
59
+ /**
60
+ * Is `candidate` AT or under `root`, by path segment?
61
+ *
62
+ * At-or-under rather than the delete rule's strictly-under: `ls <gate root>` is
63
+ * a read of the workspace an agent is working in, and a rule that made the root
64
+ * itself out of scope would classify the most ordinary command in the session.
65
+ *
66
+ * Segment matching, never string prefixes: `/dev/muse-other` must not match a
67
+ * root of `/dev/muse`, and `startsWith` says it does.
68
+ */
69
+ export function isAtOrUnderReadRoot(candidate, root) {
70
+ const want = segmentsOf(root);
71
+ const have = segmentsOf(candidate);
72
+ if (want.length === 0)
73
+ return false;
74
+ if (have.length < want.length)
75
+ return false;
76
+ return want.every((segment, index) => segment === have[index]);
77
+ }
78
+ /** Is this path inside ANY of these roots? */
79
+ export function isInReadScope(candidate, roots) {
80
+ return roots.some((root) => isAtOrUnderReadRoot(candidate, root));
81
+ }
82
+ /**
83
+ * A value whose expansion the classifier cannot see, and therefore may not
84
+ * vouch for. The same test the delete rule applies, and for the same reason:
85
+ * `cat $SOMEWHERE` reads whatever that variable holds.
86
+ */
87
+ export function isUnreadableTarget(word) {
88
+ return (word.includes("$") ||
89
+ word.includes("*") ||
90
+ word.includes("?") ||
91
+ word.includes("[") ||
92
+ word.startsWith("~"));
93
+ }
94
+ /**
95
+ * The effective read roots: the built-ins, plus whatever the policy added.
96
+ *
97
+ * Pure, and every input is the caller's. `gateRoot` is the directory holding
98
+ * the policy file the runtime resolved; `systemRoots` are the scratchpad and
99
+ * temp roots the caller already resolved (`resolveScratchRoots` in the hook);
100
+ * `declared` is `read_scope.roots` verbatim.
101
+ *
102
+ * A declared entry that is relative is joined onto the gate root. A declared
103
+ * entry the caller cannot vouch for — empty, or one this file can see is not a
104
+ * path at all — is DROPPED rather than accepted, because a root is an
105
+ * authorization and a malformed one must not become `/`.
106
+ *
107
+ * The result is de-duplicated and otherwise in the order given, so the first
108
+ * root a path matches is the most specific one a reader would expect.
109
+ */
110
+ export function effectiveReadRoots(options) {
111
+ const roots = [];
112
+ const add = (candidate) => {
113
+ if (candidate.length === 0)
114
+ return;
115
+ if (!roots.includes(candidate))
116
+ roots.push(candidate);
117
+ };
118
+ add(options.gateRoot);
119
+ for (const root of options.systemRoots ?? [])
120
+ add(root);
121
+ for (const entry of options.declared ?? []) {
122
+ if (typeof entry !== "string" || entry.length === 0)
123
+ continue;
124
+ if (isUnreadableTarget(entry))
125
+ continue;
126
+ add(entry.startsWith("/") ? entry : `${options.gateRoot}/${entry}`);
127
+ }
128
+ return roots;
129
+ }
130
+ /** Which readers take paths, and where. Keyed by the binary's basename. */
131
+ export const READ_TARGET_SHAPES = {
132
+ base64: "all",
133
+ cat: "all",
134
+ cksum: "all",
135
+ cut: "all",
136
+ diff: "all",
137
+ du: "all",
138
+ file: "all",
139
+ find: "walk",
140
+ grep: "after-pattern",
141
+ gunzip: "all",
142
+ head: "all",
143
+ jq: "after-pattern",
144
+ ls: "all",
145
+ md5sum: "all",
146
+ openssl: "after-pattern",
147
+ rg: "after-pattern",
148
+ sed: "after-pattern",
149
+ sha256sum: "all",
150
+ shasum: "all",
151
+ sort: "all",
152
+ stat: "all",
153
+ tail: "all",
154
+ tar: "all",
155
+ tree: "all",
156
+ uniq: "all",
157
+ wc: "all",
158
+ };
159
+ /**
160
+ * `find` primaries: the first word starting with `-` ends the path list.
161
+ *
162
+ * `find` is the one reader whose arguments are a little language, and its shape
163
+ * is `find [paths…] [expression]`. This one reads the RAW argument list rather
164
+ * than the flag-filtered positionals, because the filter is what tells the
165
+ * paths from the expression: in `find . -name '*.ts'` the pattern `*.ts` is a
166
+ * positional too, and a rule fed the filtered list would read it as a path,
167
+ * find it unreadable, and call an ordinary walk of the workspace out of scope.
168
+ */
169
+ function walkTargets(args) {
170
+ const targets = [];
171
+ for (const word of args) {
172
+ if (word.startsWith("-"))
173
+ break;
174
+ targets.push(word);
175
+ }
176
+ return targets;
177
+ }
178
+ /**
179
+ * Flags that carry the pattern or the script, so every positional is a path.
180
+ *
181
+ * `grep -e needle src`, `sed -f script.sed file`, `rg --regexp needle dir`: the
182
+ * first positional is the FILE, and a rule that skipped it would leave the one
183
+ * target that matters unchecked. Under-detection is the failure mode that
184
+ * matters here — an unchecked read is a read outside the jail — so the shape
185
+ * widens to `all` whenever one of these appears.
186
+ */
187
+ const PATTERN_BEARING_FLAGS = [
188
+ "-e",
189
+ "-f",
190
+ "--regexp",
191
+ "--expression",
192
+ "--file",
193
+ ];
194
+ /** Did the pattern (or script) arrive through a flag rather than a positional? */
195
+ function patternCameFromFlag(args) {
196
+ return args.some((arg) => PATTERN_BEARING_FLAGS.includes(arg) ||
197
+ PATTERN_BEARING_FLAGS.some((flag) => flag.startsWith("--") && arg.startsWith(`${flag}=`)));
198
+ }
199
+ /**
200
+ * The paths a read command will open, or `null` when this binary is not one
201
+ * whose reads this module scopes.
202
+ *
203
+ * An empty array is a real answer and is NOT the same as `null`: it means this
204
+ * reader opens the working directory (`ls`, `find`, `grep needle` with no file
205
+ * operand), and the caller checks the working directory in its place. `null`
206
+ * means "not a scoped reader", and the caller leaves the segment alone.
207
+ *
208
+ * Treating a non-path positional as a path costs nothing: a bare word resolves
209
+ * against the working directory, which is inside a root in every session this
210
+ * module is meant for. Treating a path as a non-path costs the whole property.
211
+ */
212
+ export function readTargetsOf(bin, positionals, args = []) {
213
+ const shape = READ_TARGET_SHAPES[basenameOf(bin)];
214
+ if (shape === undefined)
215
+ return null;
216
+ switch (shape) {
217
+ case "all":
218
+ return [...positionals];
219
+ case "after-pattern":
220
+ return patternCameFromFlag(args) ? [...positionals] : positionals.slice(1);
221
+ case "walk":
222
+ return walkTargets(args.length === 0 ? positionals : args);
223
+ }
224
+ }
225
+ /** The last segment of a command word, so `/usr/bin/cat` reads as `cat`. */
226
+ function basenameOf(bin) {
227
+ const segments = segmentsOf(bin);
228
+ return segments[segments.length - 1] ?? bin;
229
+ }
230
+ /**
231
+ * Read one target against the roots, as far as text alone can settle it.
232
+ *
233
+ * Absolute and inside a root: in scope. Absolute and outside every root: out of
234
+ * scope, decided here, no disk needed. Unreadable (a variable, a glob, a `~`):
235
+ * out of scope, because what it names is not in the text. Anything relative, or
236
+ * carrying a `..`, is `needs-disk`.
237
+ */
238
+ export function readTargetVerdict(target, roots) {
239
+ if (target.length === 0)
240
+ return "needs-disk";
241
+ if (isUnreadableTarget(target))
242
+ return "out-of-scope";
243
+ if (!target.startsWith("/"))
244
+ return "needs-disk";
245
+ if (segmentsOf(target).includes(".."))
246
+ return "needs-disk";
247
+ return isInReadScope(target, roots) ? "in-scope" : "out-of-scope";
248
+ }
249
+ /**
250
+ * The roots, rendered for a human: `approval policy check`'s line and the
251
+ * hook's verdict note say the same sentence.
252
+ */
253
+ export function renderReadRoots(roots) {
254
+ return roots.length === 0 ? "(none)" : roots.join(", ");
255
+ }
256
+ //# sourceMappingURL=read-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-scope.js","sourceRoot":"","sources":["../../../src/core/read-scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * The `approval run` payload: the argv, the cwd, and the bytes of the script
3
+ * the argv names (SPEC.md §6.2, §10.4; APRV-401).
4
+ *
5
+ * ## The hazard this closes
6
+ *
7
+ * §6.2 defined this payload as "the argv array and cwd", and `approval run`
8
+ * recomputes it from the argv and cwd it is about to spawn. For a command that
9
+ * IS its own bytes (`npm update @types/node`) that is the whole action. For a
10
+ * command that names a script it is not: the payload binds a NAME, and the
11
+ * bytes behind that name are whatever the path holds at execution time. The
12
+ * requester controls the file between the request and the grant, so a human
13
+ * approved a path and the runtime executed a file. Found on a live grant during
14
+ * APRV-398, exploited by nobody, filed as APRV-401.
15
+ *
16
+ * The payload hash is supposed to BE the binding (§10.4: "A grant therefore
17
+ * approves specific bytes"), so the repair is to put the bytes inside it. A
18
+ * script edited between the declaration and the execution hashes differently,
19
+ * and the difference refuses `payload-mismatch` before the child is spawned and
20
+ * before anything is appended, exactly as a changed argv already did.
21
+ *
22
+ * ## The shape, as the operator ruled it (APRV-401 notes, 2026-09-21)
23
+ *
24
+ * > the script digest is PART OF THE BINDING, never advisory. Shape: when argv
25
+ * > names an interpreter (bash, sh, zsh, node, python3 and kin) followed by a
26
+ * > path that resolves to a regular file at request time, the runtime reads and
27
+ * > hashes the file into the bound payload, the card shows the digest, byte
28
+ * > count and path, and approval run re-reads the file and refuses
29
+ * > payload-mismatch if the bytes changed. No interpretation of the script and
30
+ * > no second pass for files it sources; the limit is documented. Out of scope:
31
+ * > piped installers (already opaque), sourced files, interpreters the
32
+ * > classifier does not name.
33
+ *
34
+ * So this is an INTERPRETER-KEYED rule and not "every argv word that happens to
35
+ * name a file". One script per invocation: the first word after the interpreter
36
+ * that resolves to a readable regular file, which is the interpreter's operand
37
+ * whether or not a subcommand comes first (`deno run job.ts`).
38
+ * {@link SCRIPT_INTERPRETERS} is the list, and every member of it is a name
39
+ * `core/command-class.ts` already knows: the six shells of its unwrappable set,
40
+ * `node` from its own branch, and `python`, `python3`, `perl`, `ruby` and
41
+ * `deno` from its inline-source table. A launcher neither file names binds
42
+ * nothing, which is the documented limit rather than an oversight.
43
+ *
44
+ * The one thing here that the ruling does not spell out is the DEGENERATE case:
45
+ * `./install.sh` with no interpreter word at all, where the kernel reads the
46
+ * shebang and the script is `argv[0]` itself. It is bound, because it is the
47
+ * same fact with the interpreter implied rather than typed, and leaving it out
48
+ * would be a hole in the rule shaped exactly like the rule.
49
+ *
50
+ * ## What is NOT bound, stated plainly
51
+ *
52
+ * - **An inline program.** `bash -c '…'`, `node -e '…'`, `python3 -c '…'`: the
53
+ * program is a word of the argv, so it was always bound, and there is no file
54
+ * to read. The inline flags per interpreter are in {@link SCRIPT_INTERPRETERS}.
55
+ * - **Nothing resolved through PATH.** The interpreter itself is not hashed. Its
56
+ * bytes are the host's rather than the requester's, and resolving a program
57
+ * name through `PATH` would make the payload a function of the environment the
58
+ * process happens to hold, which §11 says is never read implicitly.
59
+ * - **Nothing transitive.** A bound script that sources another file, curls a
60
+ * second one, or execs a third binds only its own bytes. The digest is a
61
+ * binding, not a sandbox.
62
+ * - **A piped installer.** `curl … | sh` has no file operand and is opaque to
63
+ * the classifier for the same reason it is unbindable here.
64
+ * - **The instant between hashing and spawning.** The digest is taken, then
65
+ * `execution.started` is appended, then the child is spawned. A file rewritten
66
+ * inside that window runs unbound. Closing it needs the executor to hold the
67
+ * bytes rather than the path, which is the adapter contract of §10.4.
68
+ *
69
+ * ## Absence is not a claim
70
+ *
71
+ * `script` is omitted when nothing is bound, and that omission is deliberate: an
72
+ * invocation naming no readable script hashes to exactly the value it hashed
73
+ * before this module existed, so every record already in the log, and every
74
+ * declaration already written into a task file, verifies unchanged. What the
75
+ * absence does NOT say is "this runtime checked and there was nothing" — it is
76
+ * the ordinary shape of a payload with no script to bind, and a reader wanting
77
+ * to know whether a digest was possible reads the argv, which is right there.
78
+ *
79
+ * Every skip is safe for one reason, and it is the same reason in each case: the
80
+ * value is recomputed at execution by this same rule. A file that was
81
+ * unreadable at declaration time and readable at execution time gains an entry
82
+ * and refuses; one that existed and was deleted loses one and refuses. The rule
83
+ * does not have to decide correctly what a command will do, because both ends
84
+ * apply it to the same tree and any disagreement is a refusal.
85
+ *
86
+ * Reads the filesystem, by design and by necessity: at most one file per call,
87
+ * and only the one the argv names. Writes nothing, appends nothing, reads no
88
+ * log, no policy, no environment and no clock.
89
+ */
90
+ /** The payload key carrying the bound script. Omitted when there is none. */
91
+ export declare const BOUND_SCRIPT_KEY = "script";
92
+ /**
93
+ * How much of a file is read at once while digesting it.
94
+ *
95
+ * Chunked rather than `readFileSync`, because a script may be of any size and
96
+ * the executor must not be the thing that runs out of memory: a refusal to
97
+ * compute the binding is a refusal to execute.
98
+ */
99
+ export declare const DIGEST_CHUNK_BYTES: number;
100
+ /**
101
+ * The interpreters whose script operand is bound, and the flags that mean
102
+ * "the program is inline, there is no file".
103
+ *
104
+ * Every name here is one `core/command-class.ts` already knows, which is the
105
+ * ruling's "interpreters the classifier does not name" read as a positive list:
106
+ * the six shells of its unwrappable set, `node` from its own classification
107
+ * branch, and `python`, `python3`, `perl`, `ruby`, `deno` from its
108
+ * inline-source table. The list is duplicated rather than imported because
109
+ * those tables are private to that module and a public export of them would be
110
+ * a second consumer of a decision that belongs to classification; the comment
111
+ * is the seam, and a name added there without being added here binds nothing,
112
+ * which fails toward the old behaviour rather than toward a wrong digest.
113
+ *
114
+ * The shell inline flag is `-c` with any run of `l` and `i` before it, which is
115
+ * `INLINE_SCRIPT_FLAG` in that module, spelled here as the same regular
116
+ * expression for the same reason.
117
+ */
118
+ export declare const SCRIPT_INTERPRETERS: ReadonlyMap<string, readonly (string | RegExp)[]>;
119
+ /** One script the argv names, bound by its bytes. */
120
+ export interface BoundScript {
121
+ /** Which argv word named it. */
122
+ argv_index: number;
123
+ /** The path as resolved against the payload's `cwd`: absolute, always. */
124
+ path: string;
125
+ /** The file's size in bytes, which the approver's card shows beside the digest. */
126
+ bytes: number;
127
+ /** SHA-256 (lowercase hex) of the file's bytes. */
128
+ sha256: string;
129
+ }
130
+ /** The `approval run` payload value, canonicalized and hashed by the caller. */
131
+ export interface RunPayload {
132
+ argv: string[];
133
+ cwd: string;
134
+ /** Present only when the argv named a readable script. */
135
+ script?: BoundScript;
136
+ }
137
+ /**
138
+ * The script this argv runs, bound by its bytes, or `null` (APRV-401).
139
+ *
140
+ * Two shapes, and only two: an interpreter this module names followed by a path
141
+ * operand, and a path at `argv[0]` that the kernel will read a shebang from.
142
+ * Anything else binds nothing and the payload is what it always was.
143
+ */
144
+ export declare function boundScript(argv: readonly string[], cwd: string): BoundScript | null;
145
+ /**
146
+ * The value `approval run`'s `payload_hash` is taken over (SPEC.md §6.2).
147
+ *
148
+ * `{argv, cwd}` when the argv names no readable script, byte for byte what it
149
+ * has always been; `{argv, cwd, script}` when it does. The caller canonicalizes
150
+ * and hashes it — this function decides WHAT is bound and never how it is
151
+ * digested into an approval.
152
+ */
153
+ export declare function runPayloadValue(argv: readonly string[], cwd: string): RunPayload;
154
+ /**
155
+ * One line naming the bound script, for a refusal message or a terminal.
156
+ *
157
+ * Not a rendering an approver signs: the canonical renderer owns that, and this
158
+ * is the executor explaining to an agent WHICH file moved.
159
+ */
160
+ export declare function describeBoundScript(script: BoundScript | null): string;