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
@@ -85,6 +85,21 @@
85
85
  ]
86
86
  ]
87
87
  },
88
+ "read_scope": {
89
+ "type": "object",
90
+ "additionalProperties": false,
91
+ "description": "SPEC.md §5.2 (amended, APRV-347): the directories an agent's reads may stay inside. Reads outside every root classify `read.file.out_of_scope`, both for shell readers (`cat`, `head`, `tail`, `sed -n`, `grep`, `rg`, `ls`, `find`, and kin) and for a harness read tool that names a path (Claude Code's `Read`, `Glob` and `Grep`). ADDITIVE ONLY, exactly as `protected_paths` is: the built-in roots — the GATE ROOT (the directory holding this policy file), the session scratchpad, and the system temp root — are in scope whatever this block says or omits, because a runtime that could be denied its own policy, log and workspace could not run at all, and a policy able to shrink its own read scope would be a policy an agent could narrow until nothing was gated. Omitting the block entirely means the built-in roots alone, which is the common case and the whole of the muse jail: a session whose `APPROVAL.md` sits in `~/dev/muse` has `~/dev/muse` as its gate root and every sibling under `~/dev` is out of scope with no grammar here at all. An unparseable value is refused at load like every other key in this closed schema, and a refused policy means every class is `manual` (SPEC.md §5.2, fail closed).",
92
+ "properties": {
93
+ "roots": {
94
+ "type": "array",
95
+ "uniqueItems": true,
96
+ "description": "Extra directories whose reads stay ordinary `read.shell`. An absolute path is taken as written; a relative path is resolved against the gate root, so a policy stays portable between a checkout and a clone of it. Each entry is compared by PATH SEGMENT after the runtime resolves it, so a symlink cannot smuggle a read out of a root and `/dev/muse-other` does not match a root of `/dev/muse`. No globs and no negation: a pattern language half-implemented here would be a scope the author believed was in force and the runtime did not apply.",
97
+ "items": { "$ref": "#/$defs/readRoot" },
98
+ "examples": [["/Users/carter/dev/shared-fixtures"], ["../vendor"]]
99
+ }
100
+ },
101
+ "examples": [{ "roots": ["/opt/reference-corpus"] }]
102
+ },
88
103
  "approvers": {
89
104
  "type": "object",
90
105
  "additionalProperties": false,
@@ -103,11 +118,60 @@
103
118
  "uniqueItems": true,
104
119
  "description": "SPEC.md §5.1/§10.3: channel names this approver can decide on, e.g. [telegram, cli]. At least one, since an approver reachable nowhere can never grant.",
105
120
  "items": { "type": "string", "minLength": 1 }
121
+ },
122
+ "senders": {
123
+ "type": "object",
124
+ "minProperties": 1,
125
+ "additionalProperties": false,
126
+ "description": "SPEC.md §5.2/§10.3 (amended, APRV-324): the transport account ids this person decides from, per channel. ADDITIVE and OPTIONAL: a policy that declares none behaves exactly as every build before this key existed, recording each decision against the actor the listener process was launched with. Declaring the first mapping for a channel is what turns enforcement on for that channel — from then on a sender that channel authenticates and this block does not name is REFUSED (`sender-unmapped`) rather than attributed to the listener's own identity, because a fallback there would let anyone who can reach the configured chat approve as the operator. Written person to sender rather than sender to person, so each human's identity is in one place and a duplicate id is visible on the page; the runtime inverts it at lookup and REFUSES a policy in which two approvers claim one id (`sender-ambiguous`, a load failure, so every class resolves `manual`). The value is the transport's own attribution and never a name the sender chose: for `telegram` it is the numeric `callback_query.from.id`, as a string, and never `from.username`, which is mutable and reusable and would transfer an identity with a handle. This block is an operator ASSERTION that an account belongs to a person, which is what the `policy.core` attestation ceremony makes accountable; it is not, and cannot be, proof of personhood.",
127
+ "propertyNames": { "$ref": "#/$defs/senderChannel" },
128
+ "properties": {
129
+ "telegram": {
130
+ "type": "string",
131
+ "minLength": 1,
132
+ "pattern": "^(?:[0-9]+|hmac-sha256:[0-9a-f]{64})$",
133
+ "description": "The Telegram account this person decides from, in one of two forms (SPEC.md §5.2, APRV-324 and APRV-370).\n\nRAW: the account id the Bot API reports as `callback_query.from.id`, as a decimal string. Digits only, because a `@handle` here would be a mapping key the transport never sends and `from.username` is mutable and reusable.\n\nKEYED: `hmac-sha256:<64 lowercase hex>`, the HMAC-SHA-256 of that same decimal string under an operator-held key named by `APPROVAL_SENDER_KEY`, minted by `approval setup sender-key`. It exists because a policy and a log that are PUBLISHED disclose the account otherwise, once in the file and then on every decision. A plain unkeyed digest would not fix that: a Telegram id is a short decimal number and the whole space is enumerable on a laptop, so the digest has to be keyed to be a digest of anything.\n\nThe form is per entry: a policy may carry both while a migration is in progress, and they cannot collide, since a decimal id can never look like the prefixed form. A channel mapped in the keyed form with no key in the listener's environment REFUSES every decision on that channel (`sender-key-unavailable`) rather than falling back to a raw comparison, because without the key the runtime can compute no digest and therefore cannot run the ambiguity check the mapping rests on. The key is not an authenticator: nothing about the gate's safety rests on its secrecy, and losing it costs the ability to resolve accounts, never the ability to refuse.",
134
+ "examples": [
135
+ "12345678",
136
+ "hmac-sha256:3f2a9c11b4d7e6085a1c2f9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a"
137
+ ]
138
+ },
139
+ "edgeos": {
140
+ "type": "string",
141
+ "minLength": 1,
142
+ "pattern": "^(?:[A-Za-z0-9][A-Za-z0-9._-]{0,127}|hmac-sha256:[0-9a-f]{64})$",
143
+ "description": "The EdgeOS account this person decides from, in one of two forms (SPEC.md §5.2/§10.3, APRV-455, on the ground APRV-324 laid for `telegram`).\n\nRAW: the stable human id the EdgeOS identity service returns from `/humans/me` for the session the person authenticated in, as a string. The runtime treats it as OPAQUE and compares it byte for byte; the grammar admits what such an id is made of (an ASCII letter or digit, then up to 127 letters, digits, `.`, `_` or `-`), which covers a UUID and a decimal id alike. It holds no `:` and no whitespace, so a raw id can never be mistaken for the keyed form and can never contain the separator the runtime's sender index uses. Never an email address or a display name: those are chosen or changed by the person, and a mapping keyed on one would transfer an identity with an edit.\n\nKEYED: `hmac-sha256:<64 lowercase hex>`, the HMAC-SHA-256 of that same id under the operator-held `APPROVAL_SENDER_KEY`, exactly as for `telegram` and for the same reason: a published policy and a published log otherwise disclose the account.\n\nThe id reaches the runtime through `approval channel relay`, whose authenticated caller (the operator's control plane, holding `APPROVAL_RELAY_SECRET`) reports which EdgeOS session made the gesture. The daemon trusts that report the way it trusts the Telegram Bot API's `from.id`: it is operator trust in a relay, stated as such, and the resident cannot choose the id the relay attributes to them.",
144
+ "examples": [
145
+ "4f1c2a9e-7b3d-4e8a-9c21-5d6e7f8a9b0c",
146
+ "hmac-sha256:3f2a9c11b4d7e6085a1c2f9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a"
147
+ ]
148
+ }
149
+ },
150
+ "examples": [
151
+ { "telegram": "12345678" },
152
+ {
153
+ "telegram": "hmac-sha256:3f2a9c11b4d7e6085a1c2f9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a"
154
+ },
155
+ { "telegram": "12345678", "edgeos": "4f1c2a9e-7b3d-4e8a-9c21-5d6e7f8a9b0c" }
156
+ ]
106
157
  }
107
158
  }
108
159
  }
109
160
  }
110
161
  },
162
+ "daemons": {
163
+ "type": "array",
164
+ "uniqueItems": true,
165
+ "description": "SPEC.md §5.2 (amended, APRV-383): the daemon instance ids permitted to append to this log. An ALLOWLIST, and the counterpart of `approvers` one layer down: that key says which humans may decide, this one says which daemon processes may write the records of those decisions down.\n\nIt exists for the hosted case (`design/hosted-daemon-identity.md`): when one party runs the daemon for another, records the daemon appends carry `daemon` (see `event.schema.json`) naming the instance that wrote them, and this list is how the tenant's own attested policy states which of them it expects. A daemon whose id this list does not name is refused AT THE WRITE BOUNDARY with `daemon-not-allowed` and writes nothing.\n\nABSENT means NO RESTRICTION, not an empty list: a policy that never declares the key behaves exactly as every policy written before it existed, and the daemon-written records still carry their id. An empty array is a policy that admits no daemon at all, which is a thing an author may say and the runtime will enforce.\n\nWhat being LISTED buys is nothing (SPEC.md §11.1 invariant 4). The id is self-reported by the writing process, so it may only ever cost a daemon the ability to write: no verdict, autonomy, budget, floor, sampling draw, token or TTL reads this key or the record field, and a hostile host holding the tenant's log handle is not what this defends against. What it catches is a daemon started against the wrong tenant's log, a second daemon nobody meant to leave running, and a rebuilt container whose id changed, each of which used to write records indistinguishable from the intended daemon's.\n\nIt is a top-level key rather than a member of the `daemon` block below, and the distinction is worth the two names: `daemon` (singular) is how THIS daemon reads a log it already may write, every key in it a latency setting whose strictest value is the default, while `daemons` (plural) is an ENFORCEMENT key deciding WHICH daemons may write at all. Read only from an ATTESTED policy, like every other fact a refusal is derived from.",
166
+ "items": {
167
+ "type": "string",
168
+ "minLength": 1,
169
+ "maxLength": 64,
170
+ "pattern": "^[a-z0-9][a-z0-9._-]*$",
171
+ "description": "One daemon instance id, in the same grammar the `daemon` record field carries: lowercase letters, digits, `.`, `-` and `_`, starting with a letter or digit. Either an id the operator declared in that daemon's launch environment through `APPROVAL_DAEMON_ID`, or the derived `daemon-<instance id>` form that `approval status` and `approval doctor` print for the instance they are run against."
172
+ },
173
+ "examples": [["daemon-3f2a9c11"], ["village-goa-1", "village-goa-2"]]
174
+ },
111
175
  "classes": {
112
176
  "type": "object",
113
177
  "additionalProperties": false,
@@ -173,7 +237,7 @@
173
237
  "daemon": {
174
238
  "type": "object",
175
239
  "additionalProperties": false,
176
- "description": "APRV-217 (docs/proposals/incremental-prefix-proof.md): how the long-lived readers of this log prove a cached prefix before they reuse it. Read by `approval daemon run` and `approval up` and by nothing else: one-shot processes, the Claude Code hook included, always prove in full, and `approval log verify` and `approval doctor` never use the cache at all. Every key here is a LATENCY setting whose strictest value is the default, so a policy that omits the block, or the whole block, behaves exactly as the runtime did before it existed.",
240
+ "description": "APRV-217 (docs/proposals/incremental-prefix-proof.md): how the long-lived readers of this log prove a cached prefix before they reuse it. Read by `approval daemon run` and `approval up` and by nothing else: one-shot processes, the Claude Code hook included, always prove in full, and `approval log verify` and `approval doctor` never use the cache at all. Every key here is a LATENCY setting whose strictest value is the default, so a policy that omits the block, or the whole block, behaves exactly as the runtime did before it existed. Not to be confused with the top-level `daemons` list above (APRV-383), which is an enforcement key naming WHICH daemon instances may append to this log; this block is how a daemon that may already write reads.",
177
241
  "properties": {
178
242
  "read_proof": {
179
243
  "type": "string",
@@ -328,7 +392,7 @@
328
392
  "autonomy": {
329
393
  "type": "string",
330
394
  "enum": ["human-only", "manual", "supervised-live", "supervised", "supervised-retro", "autonomous"],
331
- "description": "SPEC.md §5.2 (amended, APRV-127; widened, APRV-185): autonomy level, strictest first — `human-only` (reserved to human hands: a person performs the action outside agent execution, so no agent may request it, be granted it, or run it, and every gate verb refuses it with the code `class-human-only`) > `manual` (human sign-off before every action) > `supervised-live` (a policy-declared fraction of actions blocks on the human gate exactly as `manual` does; the rest proceed) > `supervised-retro` (executes immediately, sampled for retrospective review) > `autonomous` (executes freely). `supervised` is the pre-APRV-127 spelling and is an ALIAS OF `supervised-retro`, so every policy written before the split keeps the meaning its author gave it; the runtime records a load-time note naming the alias rather than silently reinterpreting it. The enum is closed: an unrecognised level cannot be ordered against the others, so it must fail validation rather than be guessed at.",
395
+ "description": "SPEC.md §5.2 (amended, APRV-127; widened, APRV-185): autonomy level, strictest first — `human-only` (reserved to human hands: a person performs the action outside agent execution, so no agent may request it, be granted it, or run it, and every gate verb refuses it with the code `class-human-only`) > `manual` (human sign-off before every action) > `supervised-live` (a policy-declared fraction of actions blocks on the human gate exactly as `manual` does; the rest proceed) > `supervised-retro` (executes immediately, sampled for retrospective review) > `autonomous` (executes freely). `supervised` is the pre-APRV-127 spelling and is a DEPRECATED ALIAS OF `supervised-retro` (APRV-335), so every policy written before the split keeps the meaning its author gave it; the runtime records a load-time note naming the alias rather than silently reinterpreting it, and reports it in its health surfaces. Write `supervised-retro`: the bare spelling is admitted here for compatibility and a future version of this schema removes it. The enum is closed: an unrecognised level cannot be ordered against the others, so it must fail validation rather than be guessed at.",
332
396
  "examples": ["human-only", "manual", "supervised-live", "supervised-retro"]
333
397
  },
334
398
  "liveRate": {
@@ -348,7 +412,7 @@
348
412
  "defaultAutonomy": {
349
413
  "type": "string",
350
414
  "enum": ["human-only", "manual", "supervised", "supervised-retro", "autonomous"],
351
- "description": "SPEC.md §5.2 (amended, APRV-127; widened, APRV-185): the autonomy levels a policy may name as its DEFAULT. Identical to `autonomy` less `supervised-live`, which carries a required `live_rate` that `defaults` has nowhere to declare. `human-only` IS admitted, and the asymmetry with `supervised-live` is the reason: that level is excluded for a key `defaults` cannot hold, and `human-only` carries no key at all, so an author naming it here declares maximal strictness over everything the policy did not name — a statement a policy is entitled to make. It is NOT the fail-closed target. A policy that fails to load still resolves every class to `manual`, because a broken policy must stay recoverable through its own gate, and a file whose every class became `human-only` would put the repair for a typo behind a level that admits no gated repair."
415
+ "description": "SPEC.md §5.2 (amended, APRV-127; widened, APRV-185): the autonomy levels a policy may name as its DEFAULT. Identical to `autonomy` less `supervised-live`, which carries a required `live_rate` that `defaults` has nowhere to declare. The bare `supervised` is admitted here on the same terms it is admitted there, as the DEPRECATED ALIAS of `supervised-retro` (APRV-335): a policy that names it still defaults to retrospective supervision, the runtime notes the spelling at load and in its health surfaces, and a future version of this schema removes it. `human-only` IS admitted, and the asymmetry with `supervised-live` is the reason: that level is excluded for a key `defaults` cannot hold, and `human-only` carries no key at all, so an author naming it here declares maximal strictness over everything the policy did not name — a statement a policy is entitled to make. It is NOT the fail-closed target. A policy that fails to load still resolves every class to `manual`, because a broken policy must stay recoverable through its own gate, and a file whose every class became `human-only` would put the repair for a typo behind a level that admits no gated repair."
352
416
  },
353
417
  "duration": {
354
418
  "type": "string",
@@ -360,6 +424,12 @@
360
424
  "pattern": "^[a-z0-9][a-z0-9_-]*$",
361
425
  "description": "SPEC.md §5.1: lowercase identifier used for approver ids — starts alphanumeric, then alphanumerics, `_` or `-`. Case-insensitive collisions are excluded by construction."
362
426
  },
427
+ "senderChannel": {
428
+ "type": "string",
429
+ "enum": ["telegram", "edgeos"],
430
+ "description": "SPEC.md §10.3 (amended, APRV-324; widened, APRV-455): a channel whose transport attributes a gesture to an account id, and therefore the only kind of channel an `approvers.<id>.senders` entry may name. `telegram` qualifies because the Bot API reports `callback_query.from.id`, which the sender cannot choose. `edgeos` qualifies on the same ground: the gesture arrives through `approval channel relay`, authenticated by a shared secret from the daemon's launch environment, from a control plane that attributes it to the EdgeOS `/humans/me` id of the session the person signed in with, which the person cannot choose either. In both cases the daemon trusts the relay's report of who acted, and that is operator trust (APRV-422: no hosted service has authority over decisions; the relay carries a person's gesture and decides nothing). `web` and `cli` are deliberately absent: an unauthenticated form post names nobody and a local process names whoever runs it, so a sender mapping for either would be a binding the runtime cannot check and an identity key the channel cannot support.",
431
+ "examples": ["telegram", "edgeos"]
432
+ },
363
433
  "classPattern": {
364
434
  "type": "string",
365
435
  "pattern": "^(?:[a-z0-9][a-z0-9_-]*|\\*)(?:\\.(?:[a-z0-9][a-z0-9_-]*|\\*))*$",
@@ -369,7 +439,7 @@
369
439
  "type": "object",
370
440
  "additionalProperties": false,
371
441
  "required": ["autonomy"],
372
- "description": "SPEC.md §5.1: a class rule. The shorthand form is `{ autonomy }` alone; the full form may add rates, `allow_irreversible`, `approvers` and `limits`. Both are this one shape — `autonomy` is always REQUIRED, since a rule without it cannot decide anything, and unknown keys are rejected so a typo'd constraint is never silently ignored.",
442
+ "description": "SPEC.md §5.1: a class rule. The shorthand form is `{ autonomy }` alone; the full form may add rates, `allow_irreversible`, `agent_may_request`, `approvers` and `limits`. Both are this one shape — `autonomy` is always REQUIRED, since a rule without it cannot decide anything, and unknown keys are rejected so a typo'd constraint is never silently ignored.",
373
443
  "allOf": [
374
444
  {
375
445
  "$comment": "SPEC.md §5.2 (amended, APRV-127): `live_rate` belongs to `supervised-live` and to nothing else. Required there, because a live mode with no fraction declares a control without saying how much of it runs, and the runtime would have to invent the number an author did not write. Forbidden everywhere else, because a rate sitting on a `manual`, `supervised-retro` or `autonomous` rule is a fraction its author believes is in force that nothing reads — the same silent no-op this schema's closed shape exists to prevent. APRV-185 adds `human-only` to that everywhere-else, and the `else` branch below already carries it with no new clause: a level whose actions no agent may take has no fraction of them to gate, so a `live_rate` written there is a control its author believes is running over a population of size zero.",
@@ -421,6 +491,22 @@
421
491
  }
422
492
  }
423
493
  }
494
+ },
495
+ {
496
+ "$comment": "SPEC.md §5.2 (amended, APRV-445): `agent_may_request: true` opens the `propose` verb to a class an operator declared, so the level it sits on must be one an agent may ask about. Refused on `human-only` as a load error: a human-only class is inert to agents (SPEC.md §11.1 invariant 9), and a flag saying an agent may file a request for one is a door the gate would only slam. False is admitted everywhere as the explicit spelling of the default.",
497
+ "if": {
498
+ "type": "object",
499
+ "properties": { "agent_may_request": { "const": true } },
500
+ "required": ["agent_may_request"]
501
+ },
502
+ "then": {
503
+ "type": "object",
504
+ "properties": {
505
+ "autonomy": {
506
+ "enum": ["supervised-live", "supervised", "supervised-retro", "autonomous", "manual"]
507
+ }
508
+ }
509
+ }
424
510
  }
425
511
  ],
426
512
  "properties": {
@@ -434,6 +520,10 @@
434
520
  "type": "boolean",
435
521
  "description": "SPEC.md §5.2/§7 (amended, APRV-317): explicit permission for truthful `reversible: false` actions matching this rule to retain its nonmanual autonomy. Omission or false preserves the manual floor. Every equally most-specific matching rule must say true; defaults and action metadata cannot supply it."
436
522
  },
523
+ "agent_may_request": {
524
+ "type": "boolean",
525
+ "description": "SPEC.md §5.2 (amended, APRV-445): whether an agent may open a request in this class with `approval propose`, naming the class itself and carrying its payload inline. Absent and false mean no. It is read two ways and only these two: on a key that names the class EXACTLY, and on a declared family key `<prefix>.*` the class falls under. `propose` additionally requires the class to be an exact key of `classes`, so a wildcard rule alone never makes a class proposable; the exact key's own value wins when it states one, and otherwise the most specific family that states one decides. Adding or changing it is a policy edit like any other and takes a human re-attestation, which voids pending requests (`policy-drift`)."
526
+ },
437
527
  "approvers": {
438
528
  "type": "array",
439
529
  "minItems": 1,
@@ -496,6 +586,12 @@
496
586
  "minLength": 1,
497
587
  "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.?(?:/|$))(?!.*//)[^*?\\[\\]{}\\\\\\s]+$",
498
588
  "description": "A repo-relative path: exact file (`SPEC.md`) or directory prefix with a trailing `/` (`design/`). Leading `/`, `.`/`..` segments, whitespace and the glob characters `* ? [ ] { }` are rejected — each would be a path the matcher cannot honour literally, and silently ignoring one would leave the author believing a file is gated when it is not. Named as a `$def` since APRV-266 so the bare-string entry and the `path` of a routed entry are held to one grammar and cannot drift apart."
589
+ },
590
+ "readRoot": {
591
+ "type": "string",
592
+ "minLength": 1,
593
+ "pattern": "^[^*?\\[\\]{}\\\\\\s$~]+$",
594
+ "description": "A directory whose reads stay in scope (APRV-347). Absolute, or relative to the gate root. The glob characters `* ? [ ] { }`, whitespace, `$` and a leading `~` are rejected for the same reason `protectedPath` rejects them: this runtime compares path segments and expands nothing, so a value it would have to expand is one an author believes is in force and the runtime does not apply. Unlike `protectedPath`, an absolute path and `..` segments are both ALLOWED, because a read root may legitimately sit beside the gate root (`../vendor`) or anywhere on the machine — a root widens the scope and is resolved on disk before it is compared, where `..` has a meaning."
499
595
  }
500
596
  }
501
597
  }
@@ -3,16 +3,16 @@
3
3
  "$id": "https://approval.md/schema/values.schema.json",
4
4
  "$comment": "CC0 1.0. See schema/LICENSE.",
5
5
  "title": "APPROVAL.md values block",
6
- "description": "SPEC.md §5.3: the parsed YAML of the OPTIONAL second fenced block inside APPROVAL.md, marked ```yaml approval-values```. It is the human-to-agent direction of a file that until now carried control in one direction only: the policy block says what an agent may do, and this block says what the operator values and how they read what comes back. It is human-authored GUIDANCE and it is never policy. No enforcement path reads it — not routing, not policy matching, not the sampler, not budgets, not token minting, not execution — which is SPEC.md §11.1 invariant 10 (\"guidance never reaches enforcement\"), pinned by tests/values-inert.test.ts. A values block that fails this schema never makes the policy block unloadable; the two are parsed and judged independently, and the failure of guidance can never widen or narrow a class. Because the block lives inside APPROVAL.md, editing it changes the file's bytes and therefore invalidates the whole-file attestation, so a values edit goes through the same attestation ceremony as a policy edit (SPEC.md §5.3). Absence is a declaration, never a default: a file with no values block means the operator has declared no values here, and an agent-facing surface must render it as absence rather than inventing a neutral middle.\n\nThe key set is closed (additionalProperties: false at every level) for the same reason the policy block is: an unrecognised key is something the author believed was in force and the runtime did not understand. Keys deliberately REJECTED, recorded here so the rejection is reviewable rather than forgotten: (1) anything class-shaped or override-shaped — a `classes:` map, an `autonomy:`, a `defaults:`, an `overrides:` — because that is policy, and admitting a policy-shaped key into the guidance block would make invariant 10 unprovable: a static guard cannot show that guidance never reaches enforcement once guidance is allowed to LOOK like enforcement; (2) `priority` and `weight`, because a number invites arithmetic and there is no arithmetic to do here — nothing ranks, sums or thresholds these entries, and a field that suggests it does would be read as a knob; (3) `hate`, because a fourth standing grade invites an escalation ladder, and the standing lists are deliberately three (love/like/dislike). Intensity is carried where it is actually observed, by the event vocabulary's `reaction` (disliked|indifferent|liked|loved, SPEC.md §5.2), not by an ever-longer list of nouns in a static file.",
6
+ "description": "SPEC.md §5.3: the parsed YAML of the OPTIONAL second fenced block inside APPROVAL.md, marked ```yaml approval-values```. It is the human-to-agent direction of a file that until now carried control in one direction only: the policy block says what an agent may do, and this block says what the operator values and how they read what comes back. It is human-authored GUIDANCE and it is never policy. No enforcement path reads it — not routing, not policy matching, not the sampler, not budgets, not token minting, not execution — which is SPEC.md §11.1 invariant 10 (\"guidance never reaches enforcement\"), pinned by tests/values-inert.test.ts. A values block that fails this schema never makes the policy block unloadable; the two are parsed and judged independently, and the failure of guidance can never widen or narrow a class. Because the block lives inside APPROVAL.md, editing it changes the file's bytes and therefore invalidates the whole-file attestation, so a values edit goes through the same attestation ceremony as a policy edit (SPEC.md §5.3). Absence is a declaration, never a default: a file with no values block means the operator has declared no values here, and an agent-facing surface must render it as absence rather than inventing a neutral middle.\n\nThe key set is closed (additionalProperties: false at every level) for the same reason the policy block is: an unrecognised key is something the author believed was in force and the runtime did not understand. Keys deliberately REJECTED, recorded here so the rejection is reviewable rather than forgotten: (1) anything class-shaped or override-shaped — a `classes:` map, an `autonomy:`, a `defaults:`, an `overrides:` — because that is policy, and admitting a policy-shaped key into the guidance block would make invariant 10 unprovable: a static guard cannot show that guidance never reaches enforcement once guidance is allowed to LOOK like enforcement; (2) `priority` and `weight`, because a number invites arithmetic and there is no arithmetic to do here — nothing ranks, sums or thresholds these entries, and a field that suggests it does would be read as a knob; (3) `hate`, because a fourth standing grade invites an escalation ladder, and the standing lists are deliberately three (love/like/dislike). Intensity is carried where it is actually observed, by the event vocabulary's `reaction` (disliked|indifferent|liked|loved, SPEC.md §5.2), not by an ever-longer list of nouns in a static file; (4) `wants`, which the first revision carried for what the operator asks of the agent as behaviour, folded into `like` by APRV-336. A request about behaviour and a preference about the output are graded by the same person in the same way, and one list is easier to keep true than two whose boundary has to be re-decided on every edit.",
7
7
  "type": "object",
8
8
  "additionalProperties": false,
9
9
  "required": ["version"],
10
10
  "properties": {
11
11
  "version": {
12
- "type": "integer",
13
- "const": 1,
14
- "description": "SPEC.md §5.3: values format version. The only REQUIRED key: a block with no version cannot be interpreted safely under a future format revision. It is an INTEGER here, unlike the policy block's quoted string version (\"0.1\"), and deliberately so. The policy version is quoted because it is a dotted format identifier that YAML would otherwise coerce to the float 0.1, losing the distinction between \"0.10\" and \"0.1\". This block has no dotted versions and never will: it is a small, closed key set that either is revision 1 or is a revision this runtime does not know, so a bare integer is the honest type and leaves nothing for YAML to coerce.",
15
- "examples": [1]
12
+ "type": "string",
13
+ "const": "0.2",
14
+ "description": "SPEC.md §5.3: values format version. The only REQUIRED key: a block with no version cannot be interpreted safely under a future format revision. It is a QUOTED STRING, spelled exactly as the policy block's version is (\"0.1\"), because the two blocks live in one file and are read by one person: two version styles in one document is a detail an author has to remember rather than one they can see, and a block that says `version: 1` beside a block that says `version: \"0.1\"` invites the reader to conclude the values block is four revisions ahead. The quotes are load-bearing. A dotted identifier written bare is a FLOAT to YAML, so `version: 0.2` parses as the number 0.2, which loses the distinction between \"0.20\" and \"0.2\" and is refused by this schema; the loader refuses it by name, with the quoting as the fix. (APRV-336 replaced the integer 1 of the first revision; a block still carrying it is refused by the values loader alone and never by the policy loader.)",
15
+ "examples": ["0.2"]
16
16
  },
17
17
  "love": {
18
18
  "$ref": "#/$defs/valueList",
@@ -26,14 +26,10 @@
26
26
  "$ref": "#/$defs/valueList",
27
27
  "description": "SPEC.md §5.3: what the operator dislikes. The negative standing grade. It is NOT a prohibition: a prohibition belongs in the policy block, where it is enforced; an entry here is a preference an agent should weigh in how it works, and a runtime that refused an action because of it would be enforcing guidance."
28
28
  },
29
- "wants": {
30
- "$ref": "#/$defs/valueList",
31
- "description": "SPEC.md §5.3: what the operator wants FROM the agent, as behaviour rather than as taste. Where love/like/dislike describe the operator, this describes the working relationship they are asking for. Examples: \"honest opinions on the work, including when you think a task is wrong\"; \"a journal entry of about five points per milestone\". Still guidance: nothing here is checked, counted, or enforced, and an agent that does not do these things is not refused by anything."
32
- },
33
- "responds": {
29
+ "communication": {
34
30
  "type": "string",
35
31
  "maxLength": 500,
36
- "description": "SPEC.md §5.3: how the operator reads and answers — the shape of the human end of the loop, so an agent can read silence, terseness or delay correctly. Deliberately a SINGLE SCALAR rather than a list: a list here would grow into a second rules document sitting beside the policy block, and the one thing this block must never become is a place where rules live outside the enforced file. One sentence or two, and no more room than that.",
32
+ "description": "SPEC.md §5.3: how the operator reads and answers — the shape of the human end of the loop, so an agent can read silence, terseness or delay correctly. Deliberately a SINGLE SCALAR rather than a list: a list here would grow into a second rules document sitting beside the policy block, and the one thing this block must never become is a place where rules live outside the enforced file. One sentence or two, and no more room than that. (Named `responds` in the first revision; renamed by APRV-336, which also freed the word from the `approval feedback` verb it collided with.)",
37
33
  "examples": [
38
34
  "Usually within the hour on the phone; a bare 'ok' means yes and is not curtness."
39
35
  ]
@@ -1 +0,0 @@
1
- { "version": "1" }