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
@@ -2,13 +2,16 @@
2
2
  * How long a harness hook waits, and how long the question it opened outlives
3
3
  * that wait (APRV-287).
4
4
  *
5
- * Two numbers with two readers each, which is the whole reason they live here
6
- * rather than beside either one. `cli/hook.ts` uses them to decide when a
7
- * request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
5
+ * Three numbers with two readers each, which is the whole reason they live
6
+ * here rather than beside any one of them. `cli/hook.ts` uses them to decide
7
+ * when a request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
8
8
  * to decide which pending requests on a listener's first cycle are stale enough
9
9
  * to collapse into one message. Two copies of the same duration would be two
10
10
  * answers to "is anybody still waiting on this", and the channel would collapse
11
11
  * questions the hook was still holding open, or leave a dead queue on a phone.
12
+ * The third, APRV-423's margin, is read by the hook (which refuses a harness
13
+ * ceiling too short to hold a human's answer) and by `core/state.ts` (which
14
+ * judges every request against the window that ceiling leaves).
12
15
  *
13
16
  * Nothing here reads a clock, a policy or the log: these are durations, and
14
17
  * every judgment made with them is made by a caller that already holds the
@@ -53,3 +56,79 @@ export declare const HOOK_RETRY_GRACE_MS: number;
53
56
  * caller states about when it started waiting.
54
57
  */
55
58
  export declare function abandonedAfterMs(waitMs: number, graceMs: number): number;
59
+ /**
60
+ * How far below a harness's own timeout a hook-originated request must lapse
61
+ * (APRV-423).
62
+ *
63
+ * A harness bounds the hook process: Hermes caps a `pre_tool_call` entry at
64
+ * 300 s, Claude Code kills the hook at the `timeout` in its settings file, and
65
+ * every one of them treats the killed process as a NON-BLOCKING error and runs
66
+ * the tool call. So when the harness gives up first the hook is already gone,
67
+ * the request is still pending on somebody's phone, and the tap that arrives
68
+ * afterwards is recorded as a grant on a call nobody holds (APRV-410, observed
69
+ * 2026-09-20). The two records then disagree about the same moment: one says
70
+ * denied, the other says approved.
71
+ *
72
+ * The fix is to make the question end BEFORE the process that asked it does,
73
+ * and this is the distance between the two. It is 60 s because the daemon's
74
+ * TTL sweep runs on a 30 s interval (`daemon/daemon.ts`'s
75
+ * `DEFAULT_INTERVAL_MS`): a request that lapses one millisecond after a sweep
76
+ * waits a whole interval for the next one, so a margin of two intervals leaves
77
+ * the `approval.expired` append a full tick of room in the common case, and it
78
+ * lands inside the cap. Anything smaller would make the ordering this task is
79
+ * about depend on where in the sweep cycle the lapse fell.
80
+ *
81
+ * The common case, and not a guarantee. `Daemon.tick` skips a sweep when the
82
+ * previous tick is still running, and the request record's `ts` is stamped at
83
+ * the write boundary, some intake latency after the harness spawned the hook,
84
+ * so the cap runs from an instant slightly before the one the margin counts
85
+ * from. An overrunning tick plus a slow intake can put the `approval.expired`
86
+ * record after the kill. What keeps a late tap safe regardless is not this
87
+ * margin but the lazy refusal: `core/state.ts` judges the lapse by arithmetic
88
+ * whether or not the record exists, so `decide` refuses the tap `expired` and
89
+ * materialises the record then. The margin buys the ORDERING in the common
90
+ * case, so the two records agree without a human ever seeing the disagreement;
91
+ * the refusal is what makes the grant impossible in every case.
92
+ *
93
+ * It is a floor on the cap as well as a subtraction from it. A cap at or below
94
+ * the margin leaves no window in which a human could answer at all: `cli/hook.ts`
95
+ * refuses such a configuration rather than opening a question that is already
96
+ * dead, and `schema/event.schema.json` refuses a record carrying one at the
97
+ * write boundary (its `minimum` is this constant plus one, pinned equal by
98
+ * `tests/harness-cap-ttl.test.ts`).
99
+ */
100
+ export declare const HARNESS_CAP_MARGIN_MS = 60000;
101
+ /**
102
+ * The deadline a hook-originated request is judged by: the SHORTER of the
103
+ * policy's TTL and the harness's cap minus {@link HARNESS_CAP_MARGIN_MS}
104
+ * (APRV-423).
105
+ *
106
+ * `null` means no deadline, exactly as `defaults.approval_ttl`'s absence means
107
+ * it (SPEC.md §6): nothing lapses, and no duration is invented for a policy
108
+ * that declared none.
109
+ *
110
+ * **The cap may only shorten.** It is a self-reported field (the hook states
111
+ * the ceiling its own harness runs it under), and SPEC.md §11.1 invariant 4
112
+ * says such a field never reduces scrutiny. Recording it raw and taking the
113
+ * minimum HERE is what makes that structural: a cap larger than the policy TTL
114
+ * changes nothing, a cap of an hour under a one-minute TTL changes nothing, and
115
+ * the only direction a requester can move its own deadline is earlier. The same
116
+ * shape as `core/gate-window.ts`'s `expires_at`, which clamps a claimed window
117
+ * against the derived one for the same reason.
118
+ *
119
+ * A cap that does not clear the margin yields `0`, which reads as lapsed from
120
+ * the instant the request is written. That is the fail-closed answer for a
121
+ * harness whose ceiling leaves no room for a human, and it is unreachable
122
+ * through the write path: the hook refuses the configuration before it
123
+ * appends, and the event schema refuses the record for any caller that did
124
+ * not come through the hook.
125
+ */
126
+ export declare function harnessCappedTtlMs(policyTtlMs: number | null, capMs: number | null): number | null;
127
+ /**
128
+ * Does `capMs` leave a window a human could answer in?
129
+ *
130
+ * The one question `cli/hook.ts` asks before it opens a request under a cap.
131
+ * Strictly greater: a cap EQUAL to the margin yields a zero-length window,
132
+ * which is a question that expires as it is asked.
133
+ */
134
+ export declare function harnessCapFitsMargin(capMs: number): boolean;
@@ -2,13 +2,16 @@
2
2
  * How long a harness hook waits, and how long the question it opened outlives
3
3
  * that wait (APRV-287).
4
4
  *
5
- * Two numbers with two readers each, which is the whole reason they live here
6
- * rather than beside either one. `cli/hook.ts` uses them to decide when a
7
- * request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
5
+ * Three numbers with two readers each, which is the whole reason they live
6
+ * here rather than beside any one of them. `cli/hook.ts` uses them to decide
7
+ * when a request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
8
8
  * to decide which pending requests on a listener's first cycle are stale enough
9
9
  * to collapse into one message. Two copies of the same duration would be two
10
10
  * answers to "is anybody still waiting on this", and the channel would collapse
11
11
  * questions the hook was still holding open, or leave a dead queue on a phone.
12
+ * The third, APRV-423's margin, is read by the hook (which refuses a harness
13
+ * ceiling too short to hold a human's answer) and by `core/state.ts` (which
14
+ * judges every request against the window that ceiling leaves).
12
15
  *
13
16
  * Nothing here reads a clock, a policy or the log: these are durations, and
14
17
  * every judgment made with them is made by a caller that already holds the
@@ -55,4 +58,87 @@ export const HOOK_RETRY_GRACE_MS = 5 * 60_000;
55
58
  export function abandonedAfterMs(waitMs, graceMs) {
56
59
  return Math.max(0, waitMs) + Math.max(0, graceMs);
57
60
  }
61
+ /**
62
+ * How far below a harness's own timeout a hook-originated request must lapse
63
+ * (APRV-423).
64
+ *
65
+ * A harness bounds the hook process: Hermes caps a `pre_tool_call` entry at
66
+ * 300 s, Claude Code kills the hook at the `timeout` in its settings file, and
67
+ * every one of them treats the killed process as a NON-BLOCKING error and runs
68
+ * the tool call. So when the harness gives up first the hook is already gone,
69
+ * the request is still pending on somebody's phone, and the tap that arrives
70
+ * afterwards is recorded as a grant on a call nobody holds (APRV-410, observed
71
+ * 2026-09-20). The two records then disagree about the same moment: one says
72
+ * denied, the other says approved.
73
+ *
74
+ * The fix is to make the question end BEFORE the process that asked it does,
75
+ * and this is the distance between the two. It is 60 s because the daemon's
76
+ * TTL sweep runs on a 30 s interval (`daemon/daemon.ts`'s
77
+ * `DEFAULT_INTERVAL_MS`): a request that lapses one millisecond after a sweep
78
+ * waits a whole interval for the next one, so a margin of two intervals leaves
79
+ * the `approval.expired` append a full tick of room in the common case, and it
80
+ * lands inside the cap. Anything smaller would make the ordering this task is
81
+ * about depend on where in the sweep cycle the lapse fell.
82
+ *
83
+ * The common case, and not a guarantee. `Daemon.tick` skips a sweep when the
84
+ * previous tick is still running, and the request record's `ts` is stamped at
85
+ * the write boundary, some intake latency after the harness spawned the hook,
86
+ * so the cap runs from an instant slightly before the one the margin counts
87
+ * from. An overrunning tick plus a slow intake can put the `approval.expired`
88
+ * record after the kill. What keeps a late tap safe regardless is not this
89
+ * margin but the lazy refusal: `core/state.ts` judges the lapse by arithmetic
90
+ * whether or not the record exists, so `decide` refuses the tap `expired` and
91
+ * materialises the record then. The margin buys the ORDERING in the common
92
+ * case, so the two records agree without a human ever seeing the disagreement;
93
+ * the refusal is what makes the grant impossible in every case.
94
+ *
95
+ * It is a floor on the cap as well as a subtraction from it. A cap at or below
96
+ * the margin leaves no window in which a human could answer at all: `cli/hook.ts`
97
+ * refuses such a configuration rather than opening a question that is already
98
+ * dead, and `schema/event.schema.json` refuses a record carrying one at the
99
+ * write boundary (its `minimum` is this constant plus one, pinned equal by
100
+ * `tests/harness-cap-ttl.test.ts`).
101
+ */
102
+ export const HARNESS_CAP_MARGIN_MS = 60_000;
103
+ /**
104
+ * The deadline a hook-originated request is judged by: the SHORTER of the
105
+ * policy's TTL and the harness's cap minus {@link HARNESS_CAP_MARGIN_MS}
106
+ * (APRV-423).
107
+ *
108
+ * `null` means no deadline, exactly as `defaults.approval_ttl`'s absence means
109
+ * it (SPEC.md §6): nothing lapses, and no duration is invented for a policy
110
+ * that declared none.
111
+ *
112
+ * **The cap may only shorten.** It is a self-reported field (the hook states
113
+ * the ceiling its own harness runs it under), and SPEC.md §11.1 invariant 4
114
+ * says such a field never reduces scrutiny. Recording it raw and taking the
115
+ * minimum HERE is what makes that structural: a cap larger than the policy TTL
116
+ * changes nothing, a cap of an hour under a one-minute TTL changes nothing, and
117
+ * the only direction a requester can move its own deadline is earlier. The same
118
+ * shape as `core/gate-window.ts`'s `expires_at`, which clamps a claimed window
119
+ * against the derived one for the same reason.
120
+ *
121
+ * A cap that does not clear the margin yields `0`, which reads as lapsed from
122
+ * the instant the request is written. That is the fail-closed answer for a
123
+ * harness whose ceiling leaves no room for a human, and it is unreachable
124
+ * through the write path: the hook refuses the configuration before it
125
+ * appends, and the event schema refuses the record for any caller that did
126
+ * not come through the hook.
127
+ */
128
+ export function harnessCappedTtlMs(policyTtlMs, capMs) {
129
+ if (capMs === null)
130
+ return policyTtlMs;
131
+ const fromCap = Math.max(0, capMs - HARNESS_CAP_MARGIN_MS);
132
+ return policyTtlMs === null ? fromCap : Math.min(policyTtlMs, fromCap);
133
+ }
134
+ /**
135
+ * Does `capMs` leave a window a human could answer in?
136
+ *
137
+ * The one question `cli/hook.ts` asks before it opens a request under a cap.
138
+ * Strictly greater: a cap EQUAL to the margin yields a zero-length window,
139
+ * which is a question that expires as it is asked.
140
+ */
141
+ export function harnessCapFitsMargin(capMs) {
142
+ return capMs > HARNESS_CAP_MARGIN_MS;
143
+ }
58
144
  //# sourceMappingURL=harness-wait.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"harness-wait.js","sourceRoot":"","sources":["../../../src/core/harness-wait.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC,iDAAiD;AACjD,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,MAAM,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,OAAe;IAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC"}
1
+ {"version":3,"file":"harness-wait.js","sourceRoot":"","sources":["../../../src/core/harness-wait.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC,iDAAiD;AACjD,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,MAAM,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,OAAe;IAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAA0B,EAC1B,KAAoB;IAEpB,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC,CAAC;IAC3D,OAAO,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AACzE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,KAAK,GAAG,qBAAqB,CAAC;AACvC,CAAC"}
@@ -47,7 +47,7 @@
47
47
  * secret and is not derived from one — it is a hash of a directory path, which
48
48
  * `.approval/env` already carries in the open.
49
49
  */
50
- import { type ResolvedVariable } from "./env-file.js";
50
+ import { type ResolvedVariable, type SourceRunner } from "./env-file.js";
51
51
  import type { PolicyLoadResult } from "./policy-load.js";
52
52
  /**
53
53
  * The unscoped item names this project used before APRV-178.
@@ -59,6 +59,12 @@ import type { PolicyLoadResult } from "./policy-load.js";
59
59
  export declare const LEGACY_SERVICE_TELEGRAM_TOKEN = "approval-tg-token";
60
60
  export declare const LEGACY_SERVICE_VAULT_PASSPHRASE = "approval-vault-passphrase";
61
61
  export declare const LEGACY_SERVICE_SAMPLING_SECRET = "approval-sampling-secret";
62
+ /**
63
+ * The sender key (APRV-370). Born scoped, so it has no legacy instance to be a
64
+ * fallback for; it sits in this list because the naming rule is the list's, and
65
+ * a name minted outside it is a name `approval env --check` reports as unknown.
66
+ */
67
+ export declare const SERVICE_SENDER_KEY = "approval-sender-key";
62
68
  /** Every unscoped name, for the "is this the legacy one?" test. */
63
69
  export declare const LEGACY_SERVICES: readonly string[];
64
70
  /** How many hex digits of the digest a scoped name carries. */
@@ -196,8 +202,16 @@ export declare function parseEnvProvenance(raw: string | undefined): EnvProvenan
196
202
  * half of the incident that survived fixing the file: the operator's rc
197
203
  * exported the production token, so every fresh terminal kept using the
198
204
  * production bot.
205
+ * - `stale-export` — the value in this shell is NOT what this instance's file
206
+ * resolves to today (APRV-390). The name-only rules above cannot see this at
207
+ * all, and it is the case that actually cost an evening: a token re-stored in
208
+ * the keychain does not reach a shell that exported the old one, because
209
+ * `approval env` never overrides a variable that is already set. The daemon
210
+ * then answered 401 while the keychain item behind the same line passed
211
+ * `getMe` by hand. Only {@link valueFindings} produces it, because only that
212
+ * function is allowed to read a value.
199
213
  */
200
- export type InstanceFindingKind = "foreign-instance" | "legacy-shared" | "ambient-bleed";
214
+ export type InstanceFindingKind = "foreign-instance" | "legacy-shared" | "ambient-bleed" | "stale-export";
201
215
  export interface InstanceFinding {
202
216
  kind: InstanceFindingKind;
203
217
  /** The environment variable the finding is about. */
@@ -249,5 +263,48 @@ export interface FindingsContext {
249
263
  * env --check` and `approval up` to say different things about one shell.
250
264
  */
251
265
  export declare function ownEnvExports(logPath: string, context?: FindingsContext): ReadonlySet<string>;
266
+ /**
267
+ * The same question, answered by COMPARING VALUES (APRV-390).
268
+ *
269
+ * ## Why a second function, and why only start-up may call it
270
+ *
271
+ * Everything above answers from names, so that a diagnostic never blocks on a
272
+ * keystore-unlock dialog ({@link NON_RESOLVING_RUNNER}). That constraint is
273
+ * right for `approval doctor` and wrong for the thing `approval up` is about to
274
+ * do, which is USE the value. Two cases observed on 2026-09-19 are invisible to
275
+ * every name-only rule:
276
+ *
277
+ * 1. **A stale export.** `approval env` never overrides a variable already set
278
+ * in the shell — that is invariant 7 working as designed — so a token
279
+ * re-stored in the keychain never reaches a terminal that exported the old
280
+ * one. The names agree, the file is this instance's, the provenance claim is
281
+ * this instance's own, and the value is wrong. The daemon got 401 from a
282
+ * token that passed `getMe` when read from the keychain by hand.
283
+ * 2. **A false positive on the correct ritual.** `APPROVAL_HUMAN` was reported
284
+ * cross-instance when the export came from this instance's own `eval
285
+ * "$(approval env)"` seconds earlier, because the provenance claim did not
286
+ * match. A finding about a value that IS the file's value is noise, and
287
+ * noise in a refusal is how a refusal gets overridden by habit.
288
+ *
289
+ * Comparing answers both: a value equal to what the file resolves to is
290
+ * correct however it got there, and a value that differs is wrong however
291
+ * honest its provenance looks.
292
+ *
293
+ * ## What it does with the values
294
+ *
295
+ * It compares them and drops them. Nothing is printed, returned, logged or put
296
+ * in a message on any path — every finding this produces carries a variable
297
+ * name, a file path and a line number, exactly like the name-only ones. The
298
+ * only new capability is answering "same or different".
299
+ *
300
+ * ## Where it fails soft, and in which direction
301
+ *
302
+ * A file entry that cannot be resolved (a locked keychain, a helper that is not
303
+ * installed, a `secret-tool` with no D-Bus) yields no comparison, and the
304
+ * name-only finding for that variable stands unchanged. That is the safe
305
+ * direction: an unverifiable value keeps whatever scrutiny it already had, and
306
+ * a caller that cannot resolve anything gets exactly the pre-APRV-390 report.
307
+ */
308
+ export declare function valueFindings(logPath: string, load: PolicyLoadResult, runner: SourceRunner, ambientEnv?: NodeJS.ProcessEnv): InstanceFinding[];
252
309
  /** The name-only rules, over an already-resolved variable set. */
253
310
  export declare function findingsFor(logPath: string, variables: readonly ResolvedVariable[], context?: FindingsContext): InstanceFinding[];
@@ -60,11 +60,18 @@ import { NON_RESOLVING_RUNNER, envFilePathFor, resolveEnvironment, } from "./env
60
60
  export const LEGACY_SERVICE_TELEGRAM_TOKEN = "approval-tg-token";
61
61
  export const LEGACY_SERVICE_VAULT_PASSPHRASE = "approval-vault-passphrase";
62
62
  export const LEGACY_SERVICE_SAMPLING_SECRET = "approval-sampling-secret";
63
+ /**
64
+ * The sender key (APRV-370). Born scoped, so it has no legacy instance to be a
65
+ * fallback for; it sits in this list because the naming rule is the list's, and
66
+ * a name minted outside it is a name `approval env --check` reports as unknown.
67
+ */
68
+ export const SERVICE_SENDER_KEY = "approval-sender-key";
63
69
  /** Every unscoped name, for the "is this the legacy one?" test. */
64
70
  export const LEGACY_SERVICES = [
65
71
  LEGACY_SERVICE_TELEGRAM_TOKEN,
66
72
  LEGACY_SERVICE_VAULT_PASSPHRASE,
67
73
  LEGACY_SERVICE_SAMPLING_SECRET,
74
+ SERVICE_SENDER_KEY,
68
75
  ];
69
76
  /** How many hex digits of the digest a scoped name carries. */
70
77
  export const INSTANCE_ID_LENGTH = 8;
@@ -265,6 +272,112 @@ export function ownEnvExports(logPath, context = {}) {
265
272
  const view = provenanceView(logPath, context);
266
273
  return view.claimsThisFile ? view.names : new Set();
267
274
  }
275
+ /**
276
+ * The same question, answered by COMPARING VALUES (APRV-390).
277
+ *
278
+ * ## Why a second function, and why only start-up may call it
279
+ *
280
+ * Everything above answers from names, so that a diagnostic never blocks on a
281
+ * keystore-unlock dialog ({@link NON_RESOLVING_RUNNER}). That constraint is
282
+ * right for `approval doctor` and wrong for the thing `approval up` is about to
283
+ * do, which is USE the value. Two cases observed on 2026-09-19 are invisible to
284
+ * every name-only rule:
285
+ *
286
+ * 1. **A stale export.** `approval env` never overrides a variable already set
287
+ * in the shell — that is invariant 7 working as designed — so a token
288
+ * re-stored in the keychain never reaches a terminal that exported the old
289
+ * one. The names agree, the file is this instance's, the provenance claim is
290
+ * this instance's own, and the value is wrong. The daemon got 401 from a
291
+ * token that passed `getMe` when read from the keychain by hand.
292
+ * 2. **A false positive on the correct ritual.** `APPROVAL_HUMAN` was reported
293
+ * cross-instance when the export came from this instance's own `eval
294
+ * "$(approval env)"` seconds earlier, because the provenance claim did not
295
+ * match. A finding about a value that IS the file's value is noise, and
296
+ * noise in a refusal is how a refusal gets overridden by habit.
297
+ *
298
+ * Comparing answers both: a value equal to what the file resolves to is
299
+ * correct however it got there, and a value that differs is wrong however
300
+ * honest its provenance looks.
301
+ *
302
+ * ## What it does with the values
303
+ *
304
+ * It compares them and drops them. Nothing is printed, returned, logged or put
305
+ * in a message on any path — every finding this produces carries a variable
306
+ * name, a file path and a line number, exactly like the name-only ones. The
307
+ * only new capability is answering "same or different".
308
+ *
309
+ * ## Where it fails soft, and in which direction
310
+ *
311
+ * A file entry that cannot be resolved (a locked keychain, a helper that is not
312
+ * installed, a `secret-tool` with no D-Bus) yields no comparison, and the
313
+ * name-only finding for that variable stands unchanged. That is the safe
314
+ * direction: an unverifiable value keeps whatever scrutiny it already had, and
315
+ * a caller that cannot resolve anything gets exactly the pre-APRV-390 report.
316
+ */
317
+ export function valueFindings(logPath, load, runner, ambientEnv = process.env) {
318
+ const envPath = envFilePathFor(logPath);
319
+ const named = instanceFindings(logPath, load, ambientEnv);
320
+ // EVERY variable whose value came from the shell and whose file has a line
321
+ // for it — not only the ones the name-only rule complained about.
322
+ //
323
+ // This is the correction that matters, and the reason the set is not taken
324
+ // from `named`. The provenance claim (APRV-278) vouches for an export by
325
+ // naming this instance, this file's DIGEST and this variable, and all three
326
+ // stay true when the file is untouched and the KEYSTORE ITEM behind one of
327
+ // its lines is re-stored. The claim is honest and the value is stale, so the
328
+ // vouched variables are exactly the ones this check must still look at.
329
+ const nameOnly = resolveEnvironment(load, envPath, NON_RESOLVING_RUNNER, ambientEnv);
330
+ if (!nameOnly.ok)
331
+ return named;
332
+ const ambientNames = new Set(nameOnly.variables
333
+ .filter((variable) => variable.status === "set-in-environment" && variable.fileSource !== undefined)
334
+ .map((variable) => variable.name));
335
+ if (ambientNames.size === 0)
336
+ return named;
337
+ // Resolve the FILE, by hiding exactly those names from the resolution: the
338
+ // ambient environment wins otherwise, and the answer would be the value we
339
+ // are trying to check. Nothing here is exported — this process's own
340
+ // environment is untouched, so invariant 7 is as true after this call as
341
+ // before it. The file cannot SET anything; it can only fail to match.
342
+ const hidden = { ...ambientEnv };
343
+ for (const name of ambientNames)
344
+ delete hidden[name];
345
+ const fromFile = resolveEnvironment(load, envPath, runner, hidden);
346
+ if (!fromFile.ok)
347
+ return named;
348
+ const fileValues = new Map();
349
+ for (const variable of fromFile.variables) {
350
+ if (variable.value !== undefined && variable.status !== "set-in-environment") {
351
+ fileValues.set(variable.name, variable.value);
352
+ }
353
+ }
354
+ // Everything the name-only rule found that is NOT about an ambient value: a
355
+ // foreign item name and a legacy shared one are facts about names, and no
356
+ // comparison of values can make either of them go away.
357
+ const findings = named.filter((finding) => finding.kind !== "ambient-bleed");
358
+ for (const name of ambientNames) {
359
+ const fileValue = fileValues.get(name);
360
+ const ambientValue = ambientEnv[name];
361
+ // Unresolvable (a locked keychain, a missing helper, a missing item): no
362
+ // comparison was made, so whatever the name-only rule said still stands.
363
+ if (fileValue === undefined) {
364
+ const bleed = named.find((finding) => finding.kind === "ambient-bleed" && finding.variable === name);
365
+ if (bleed !== undefined)
366
+ findings.push(bleed);
367
+ continue;
368
+ }
369
+ // The shell holds exactly what the file configures. Correct, however it got
370
+ // there, and whatever the provenance claim did or did not say.
371
+ if (ambientValue === fileValue)
372
+ continue;
373
+ findings.push({
374
+ kind: "stale-export",
375
+ variable: name,
376
+ detail: `${name} is exported in this shell with a value that is NOT what ${envPath} resolves to today, and an export always wins over the file, so this process would use the stale one`,
377
+ });
378
+ }
379
+ return findings;
380
+ }
268
381
  /** The name-only rules, over an already-resolved variable set. */
269
382
  export function findingsFor(logPath, variables, context = {}) {
270
383
  const home = instanceHomeFor(logPath);
@@ -1 +1 @@
1
- {"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../src/core/instance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../src/core/instance.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,27 @@ export interface LogSubscriptionOptions {
25
25
  signal?: AbortSignal;
26
26
  /** Bounded fallback when filesystem notifications are missing. */
27
27
  pollIntervalMs?: number;
28
+ /**
29
+ * Drain the currently verified snapshot and RETURN, rather than waiting for
30
+ * appends (APRV-421).
31
+ *
32
+ * The streaming form is what `approval log follow` wants: a foreground
33
+ * process that stays. A PAGED reader wants the opposite and has nowhere to
34
+ * put a wait. `approval serve`'s `log/follow` answers one request with the
35
+ * records after a cursor and the cursor they end at, and the caller comes
36
+ * back with that cursor when it wants more. Expressing "we are caught up" as
37
+ * a race between the iterator and a timer is a timing heuristic; expressing
38
+ * it here is one flag, because this module already knows exactly when the
39
+ * verified snapshot is exhausted.
40
+ *
41
+ * It changes nothing about what is emitted or what is refused. The
42
+ * genesis-to-head verification, the cursor binding, the retained hash and
43
+ * every terminal failure are the ones below; this decides only whether the
44
+ * generator waits after the drain or ends. No watcher is established either,
45
+ * which matters for the caller this exists for: a server that left one file
46
+ * watcher behind per request would leak handles at the rate it is polled.
47
+ */
48
+ once?: boolean;
28
49
  }
29
50
  /**
30
51
  * Yield verified records after an exclusive cursor, then wait for appends.
@@ -86,12 +86,16 @@ export async function* subscribeVerifiedLog(logPath, options = {}) {
86
86
  options.signal?.addEventListener("abort", abort, { once: false });
87
87
  // Watch the directory so creation of an initially absent log is visible.
88
88
  // Failure to establish a watch is harmless: bounded polling remains active.
89
- try {
90
- watcher = watch(dirname(logPath), { persistent: false }, hint);
91
- watcher.on("error", hint);
92
- }
93
- catch {
94
- watcher = null;
89
+ // A `once` subscription never reaches the wait below, so it establishes no
90
+ // watch at all rather than opening and closing one per call.
91
+ if (options.once !== true) {
92
+ try {
93
+ watcher = watch(dirname(logPath), { persistent: false }, hint);
94
+ watcher.on("error", hint);
95
+ }
96
+ catch {
97
+ watcher = null;
98
+ }
95
99
  }
96
100
  try {
97
101
  while (!options.signal?.aborted) {
@@ -131,6 +135,12 @@ export async function* subscribeVerifiedLog(logPath, options = {}) {
131
135
  }
132
136
  if (options.signal?.aborted)
133
137
  return;
138
+ // The verified snapshot is exhausted and this caller asked for exactly
139
+ // that much (APRV-421). Everything above ran unchanged: the chain was
140
+ // verified from genesis, the cursor was bound, and a mismatch already
141
+ // threw. What is skipped is only the waiting.
142
+ if (options.once === true)
143
+ return;
134
144
  // An append between watcher setup/read/drain and this point changes the
135
145
  // version and causes an immediate verification instead of a lost wakeup.
136
146
  if (wakeVersion !== versionBeforeRead)
@@ -1 +1 @@
1
- {"version":3,"file":"log-subscribe.js","sourceRoot":"","sources":["../../../src/core/log-subscribe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"log-subscribe.js","sourceRoot":"","sources":["../../../src/core/log-subscribe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The filesystem calls that put durable bytes on disk: `core/log.ts`'s append
3
+ * (APRV-440) and `core/payload-store.ts`'s atomic write (APRV-457). They are
4
+ * kept in their own module so that `core/log.ts`'s public surface stays what
5
+ * `tests/log.test.ts` pins: no mutation, no reorder, no truncate, and no way to
6
+ * change how its bytes reach the disk.
7
+ *
8
+ * Why a seam at all. The durability claim is "fsync after the write and before
9
+ * ok", and a page cache that was flushed reads back exactly like one that was
10
+ * not until the machine dies. Only a layer that records the ORDER of the calls
11
+ * can prove the claim, so `tests/log-fsync.test.ts` and
12
+ * `tests/payload-fsync.test.ts` install one that records every call and then
13
+ * delegates to the real one, which keeps each log they build a real log built
14
+ * through the real append path.
15
+ *
16
+ * One seam for both writers, deliberately. A log record is only as durable as
17
+ * the payload it binds: a record that survives a crash while its payload comes
18
+ * back NUL-filled verifies and points at nothing (APRV-457). Proving the two
19
+ * through one layer keeps one model of "what reached the disk" for both.
20
+ *
21
+ * Nothing in `src/` calls {@link setAppendWriteLayerForTests}. It changes how
22
+ * the bytes reach the disk and nothing about which bytes, so every check before
23
+ * the write (lock, tail, compare-and-append, schema, payload hashing) runs as in
24
+ * production.
25
+ */
26
+ export interface AppendWriteLayer {
27
+ /** `open(2)` with numeric flags; throws like `fs.openSync`. */
28
+ open(path: string, flags: number, mode: number): number;
29
+ /** One `write(2)` of the whole buffer; returns the bytes written. */
30
+ write(fd: number, data: Buffer): number;
31
+ /** `fsync(2)` of a file or directory descriptor; throws on failure. */
32
+ fsync(fd: number): void;
33
+ close(fd: number): void;
34
+ /**
35
+ * `rename(2)`; throws like `fs.renameSync`. Used by the payload store's
36
+ * temp-then-rename write (APRV-457); the log append never renames.
37
+ */
38
+ rename(from: string, to: string): void;
39
+ }
40
+ /** The layer the next append (or payload store write) uses. */
41
+ export declare function appendWriteLayer(): AppendWriteLayer;
42
+ /**
43
+ * Replace the append's write layer, or restore Node's with `null`. For tests
44
+ * and the opt-in benchmarks only.
45
+ */
46
+ export declare function setAppendWriteLayerForTests(layer: AppendWriteLayer | null): void;
47
+ /**
48
+ * fsync a directory through the current layer, so a name created or renamed
49
+ * into it survives a crash. Skipped on Windows (a directory cannot be opened
50
+ * for this there, and its filesystems journal names themselves) and tolerated
51
+ * where the filesystem reports directory fsync as unsupported; every other
52
+ * failure throws for the caller to report. The read-only handle is closed even
53
+ * when the fsync throws.
54
+ */
55
+ export declare function fsyncDirectory(dir: string): void;