approval-md 0.3.0 → 0.4.1

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 (270) hide show
  1. package/README.md +83 -914
  2. package/SPEC.md +101 -13
  3. package/cli.js +134 -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 +72 -0
  8. package/dist/src/channels/contract.js +161 -1
  9. package/dist/src/channels/contract.js.map +1 -1
  10. package/dist/src/channels/relay-server.d.ts +90 -0
  11. package/dist/src/channels/relay-server.js +295 -0
  12. package/dist/src/channels/relay-server.js.map +1 -0
  13. package/dist/src/channels/relay.d.ts +273 -0
  14. package/dist/src/channels/relay.js +564 -0
  15. package/dist/src/channels/relay.js.map +1 -0
  16. package/dist/src/channels/tagging.d.ts +25 -0
  17. package/dist/src/channels/tagging.js +63 -6
  18. package/dist/src/channels/tagging.js.map +1 -1
  19. package/dist/src/channels/telegram-minimal.d.ts +251 -0
  20. package/dist/src/channels/telegram-minimal.js +618 -0
  21. package/dist/src/channels/telegram-minimal.js.map +1 -0
  22. package/dist/src/channels/telegram-webhook.d.ts +273 -0
  23. package/dist/src/channels/telegram-webhook.js +536 -0
  24. package/dist/src/channels/telegram-webhook.js.map +1 -0
  25. package/dist/src/channels/telegram.d.ts +725 -20
  26. package/dist/src/channels/telegram.js +1332 -123
  27. package/dist/src/channels/telegram.js.map +1 -1
  28. package/dist/src/cli/amend.js +358 -5
  29. package/dist/src/cli/amend.js.map +1 -1
  30. package/dist/src/cli/attest.js +43 -6
  31. package/dist/src/cli/attest.js.map +1 -1
  32. package/dist/src/cli/audit-card.d.ts +1 -1
  33. package/dist/src/cli/audit-card.js +40 -1
  34. package/dist/src/cli/audit-card.js.map +1 -1
  35. package/dist/src/cli/audit.d.ts +7 -5
  36. package/dist/src/cli/audit.js +22 -10
  37. package/dist/src/cli/audit.js.map +1 -1
  38. package/dist/src/cli/channel-relay.d.ts +108 -0
  39. package/dist/src/cli/channel-relay.js +334 -0
  40. package/dist/src/cli/channel-relay.js.map +1 -0
  41. package/dist/src/cli/channel-telegram-webhook.d.ts +275 -0
  42. package/dist/src/cli/channel-telegram-webhook.js +716 -0
  43. package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
  44. package/dist/src/cli/channel-telegram.d.ts +347 -28
  45. package/dist/src/cli/channel-telegram.js +648 -56
  46. package/dist/src/cli/channel-telegram.js.map +1 -1
  47. package/dist/src/cli/channel.d.ts +1 -1
  48. package/dist/src/cli/channel.js +5 -1
  49. package/dist/src/cli/channel.js.map +1 -1
  50. package/dist/src/cli/daemon.js +37 -5
  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 +856 -111
  54. package/dist/src/cli/doctor.js.map +1 -1
  55. package/dist/src/cli/execute.d.ts +29 -1
  56. package/dist/src/cli/execute.js +387 -21
  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 +10 -1
  65. package/dist/src/cli/help.js +283 -54
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook.d.ts +485 -6
  68. package/dist/src/cli/hook.js +2370 -70
  69. package/dist/src/cli/hook.js.map +1 -1
  70. package/dist/src/cli/init.js +16 -7
  71. package/dist/src/cli/init.js.map +1 -1
  72. package/dist/src/cli/instructions.js +24 -3
  73. package/dist/src/cli/instructions.js.map +1 -1
  74. package/dist/src/cli/log-anchor.d.ts +55 -0
  75. package/dist/src/cli/log-anchor.js +199 -60
  76. package/dist/src/cli/log-anchor.js.map +1 -1
  77. package/dist/src/cli/log-unlock.d.ts +23 -0
  78. package/dist/src/cli/log-unlock.js +109 -0
  79. package/dist/src/cli/log-unlock.js.map +1 -0
  80. package/dist/src/cli/main.d.ts +0 -12
  81. package/dist/src/cli/main.js +72 -4
  82. package/dist/src/cli/main.js.map +1 -1
  83. package/dist/src/cli/muse.d.ts +2 -0
  84. package/dist/src/cli/muse.js +62 -0
  85. package/dist/src/cli/muse.js.map +1 -0
  86. package/dist/src/cli/payload.js +79 -1
  87. package/dist/src/cli/payload.js.map +1 -1
  88. package/dist/src/cli/scaffold.js +6 -0
  89. package/dist/src/cli/scaffold.js.map +1 -1
  90. package/dist/src/cli/serve.d.ts +53 -0
  91. package/dist/src/cli/serve.js +270 -0
  92. package/dist/src/cli/serve.js.map +1 -0
  93. package/dist/src/cli/up.js +26 -2
  94. package/dist/src/cli/up.js.map +1 -1
  95. package/dist/src/cli/verb-registry.d.ts +2 -0
  96. package/dist/src/cli/verb-registry.js +338 -39
  97. package/dist/src/cli/verb-registry.js.map +1 -1
  98. package/dist/src/cli/wordmark.d.ts +2 -2
  99. package/dist/src/cli/wordmark.js +2 -2
  100. package/dist/src/core/attest-bootstrap.d.ts +95 -0
  101. package/dist/src/core/attest-bootstrap.js +135 -0
  102. package/dist/src/core/attest-bootstrap.js.map +1 -0
  103. package/dist/src/core/attest.d.ts +20 -3
  104. package/dist/src/core/attest.js +25 -0
  105. package/dist/src/core/attest.js.map +1 -1
  106. package/dist/src/core/audit.d.ts +153 -12
  107. package/dist/src/core/audit.js +415 -20
  108. package/dist/src/core/audit.js.map +1 -1
  109. package/dist/src/core/channel-lease.d.ts +322 -0
  110. package/dist/src/core/channel-lease.js +663 -0
  111. package/dist/src/core/channel-lease.js.map +1 -0
  112. package/dist/src/core/command-class.d.ts +51 -0
  113. package/dist/src/core/command-class.js +766 -8
  114. package/dist/src/core/command-class.js.map +1 -1
  115. package/dist/src/core/daemon-host.d.ts +178 -0
  116. package/dist/src/core/daemon-host.js +228 -0
  117. package/dist/src/core/daemon-host.js.map +1 -0
  118. package/dist/src/core/daemon-identity.d.ts +238 -0
  119. package/dist/src/core/daemon-identity.js +252 -0
  120. package/dist/src/core/daemon-identity.js.map +1 -0
  121. package/dist/src/core/gate.d.ts +364 -6
  122. package/dist/src/core/gate.js +802 -27
  123. package/dist/src/core/gate.js.map +1 -1
  124. package/dist/src/core/harness-version.d.ts +128 -4
  125. package/dist/src/core/harness-version.js +179 -12
  126. package/dist/src/core/harness-version.js.map +1 -1
  127. package/dist/src/core/harness-wait.d.ts +82 -3
  128. package/dist/src/core/harness-wait.js +89 -3
  129. package/dist/src/core/harness-wait.js.map +1 -1
  130. package/dist/src/core/log-lock.d.ts +435 -0
  131. package/dist/src/core/log-lock.js +1093 -0
  132. package/dist/src/core/log-lock.js.map +1 -0
  133. package/dist/src/core/log-subscribe.d.ts +21 -0
  134. package/dist/src/core/log-subscribe.js +16 -6
  135. package/dist/src/core/log-subscribe.js.map +1 -1
  136. package/dist/src/core/log-write-layer.d.ts +55 -0
  137. package/dist/src/core/log-write-layer.js +91 -0
  138. package/dist/src/core/log-write-layer.js.map +1 -0
  139. package/dist/src/core/log.d.ts +130 -22
  140. package/dist/src/core/log.js +473 -50
  141. package/dist/src/core/log.js.map +1 -1
  142. package/dist/src/core/payload-census.d.ts +51 -0
  143. package/dist/src/core/payload-census.js +125 -2
  144. package/dist/src/core/payload-census.js.map +1 -1
  145. package/dist/src/core/payload-store.d.ts +31 -0
  146. package/dist/src/core/payload-store.js +113 -8
  147. package/dist/src/core/payload-store.js.map +1 -1
  148. package/dist/src/core/payload.d.ts +18 -3
  149. package/dist/src/core/payload.js +20 -4
  150. package/dist/src/core/payload.js.map +1 -1
  151. package/dist/src/core/policy-diff.d.ts +10 -0
  152. package/dist/src/core/policy-diff.js +10 -2
  153. package/dist/src/core/policy-diff.js.map +1 -1
  154. package/dist/src/core/policy-explain.d.ts +7 -1
  155. package/dist/src/core/policy-explain.js +16 -1
  156. package/dist/src/core/policy-explain.js.map +1 -1
  157. package/dist/src/core/policy-load.d.ts +24 -0
  158. package/dist/src/core/policy-load.js.map +1 -1
  159. package/dist/src/core/policy-match.d.ts +38 -0
  160. package/dist/src/core/policy-match.js +35 -0
  161. package/dist/src/core/policy-match.js.map +1 -1
  162. package/dist/src/core/policy-proposal.d.ts +40 -0
  163. package/dist/src/core/policy-proposal.js +37 -16
  164. package/dist/src/core/policy-proposal.js.map +1 -1
  165. package/dist/src/core/prompt-layout.d.ts +186 -1
  166. package/dist/src/core/prompt-layout.js +345 -3
  167. package/dist/src/core/prompt-layout.js.map +1 -1
  168. package/dist/src/core/read-scope.d.ts +10 -0
  169. package/dist/src/core/read-scope.js +4 -0
  170. package/dist/src/core/read-scope.js.map +1 -1
  171. package/dist/src/core/run-payload.d.ts +160 -0
  172. package/dist/src/core/run-payload.js +304 -0
  173. package/dist/src/core/run-payload.js.map +1 -0
  174. package/dist/src/core/sender-identity.d.ts +59 -11
  175. package/dist/src/core/sender-identity.js +79 -11
  176. package/dist/src/core/sender-identity.js.map +1 -1
  177. package/dist/src/core/state.d.ts +52 -1
  178. package/dist/src/core/state.js +52 -6
  179. package/dist/src/core/state.js.map +1 -1
  180. package/dist/src/core/telegram-config.d.ts +79 -0
  181. package/dist/src/core/telegram-config.js +108 -0
  182. package/dist/src/core/telegram-config.js.map +1 -1
  183. package/dist/src/core/validate.d.ts +20 -6
  184. package/dist/src/core/validate.js +0 -0
  185. package/dist/src/core/validate.js.map +1 -1
  186. package/dist/src/core/verify.d.ts +23 -1
  187. package/dist/src/core/verify.js +20 -2
  188. package/dist/src/core/verify.js.map +1 -1
  189. package/dist/src/daemon/audit.d.ts +80 -5
  190. package/dist/src/daemon/audit.js +126 -6
  191. package/dist/src/daemon/audit.js.map +1 -1
  192. package/dist/src/daemon/daemon.d.ts +174 -2
  193. package/dist/src/daemon/daemon.js +298 -7
  194. package/dist/src/daemon/daemon.js.map +1 -1
  195. package/dist/src/daemon/git-evidence.d.ts +2 -2
  196. package/dist/src/daemon/git-evidence.js +1 -1
  197. package/dist/src/daemon/projection.d.ts +13 -2
  198. package/dist/src/daemon/projection.js +39 -6
  199. package/dist/src/daemon/projection.js.map +1 -1
  200. package/dist/src/mcp/server.d.ts +39 -3
  201. package/dist/src/mcp/server.js +22 -3
  202. package/dist/src/mcp/server.js.map +1 -1
  203. package/dist/src/muse/server.d.ts +16 -0
  204. package/dist/src/muse/server.js +238 -0
  205. package/dist/src/muse/server.js.map +1 -0
  206. package/dist/src/serve/archive.d.ts +166 -0
  207. package/dist/src/serve/archive.js +363 -0
  208. package/dist/src/serve/archive.js.map +1 -0
  209. package/dist/src/serve/arguments.d.ts +93 -0
  210. package/dist/src/serve/arguments.js +228 -0
  211. package/dist/src/serve/arguments.js.map +1 -0
  212. package/dist/src/serve/credentials.d.ts +91 -0
  213. package/dist/src/serve/credentials.js +143 -0
  214. package/dist/src/serve/credentials.js.map +1 -0
  215. package/dist/src/serve/follow.d.ts +86 -0
  216. package/dist/src/serve/follow.js +101 -0
  217. package/dist/src/serve/follow.js.map +1 -0
  218. package/dist/src/serve/hook-thread.d.ts +134 -0
  219. package/dist/src/serve/hook-thread.js +333 -0
  220. package/dist/src/serve/hook-thread.js.map +1 -0
  221. package/dist/src/serve/hook-worker.d.ts +134 -0
  222. package/dist/src/serve/hook-worker.js +173 -0
  223. package/dist/src/serve/hook-worker.js.map +1 -0
  224. package/dist/src/serve/server.d.ts +412 -0
  225. package/dist/src/serve/server.js +1251 -0
  226. package/dist/src/serve/server.js.map +1 -0
  227. package/docs/README-extended.md +768 -0
  228. package/docs/cli-reference.md +2139 -67
  229. package/package.json +4 -2
  230. package/schema/event.schema.json +413 -30
  231. package/schema/fixtures/event/invalid/audit-lock-reclaimed-age-without-created.json +19 -0
  232. package/schema/fixtures/event/invalid/audit-lock-reclaimed-agent-actor.json +14 -0
  233. package/schema/fixtures/event/invalid/audit-lock-reclaimed-lockfile-path.json +14 -0
  234. package/schema/fixtures/event/invalid/audit-lock-reclaimed-operator-cleared-system-actor.json +14 -0
  235. package/schema/fixtures/event/invalid/audit-lock-reclaimed-unknown-reason.json +14 -0
  236. package/schema/fixtures/event/invalid/audit-lock-reclaimed-without-age.json +13 -0
  237. package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +6 -1
  238. package/schema/fixtures/event/invalid/audit-reviewed-bad-payload-hash.json +21 -0
  239. package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +5 -0
  240. package/schema/fixtures/event/invalid/audit-reviewed-no-subject-hash.json +19 -0
  241. package/schema/fixtures/event/invalid/audit-reviewed-no-subject-seq.json +19 -0
  242. package/schema/fixtures/event/invalid/audit-reviewed-no-verdict-source.json +19 -0
  243. package/schema/fixtures/event/invalid/audit-reviewed-no-verdict.json +19 -0
  244. package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +6 -1
  245. package/schema/fixtures/event/invalid/audit-sampled-no-policy-hash.json +14 -0
  246. package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
  247. package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
  248. package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
  249. package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
  250. package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
  251. package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
  252. package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
  253. package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
  254. package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
  255. package/schema/fixtures/event/valid/audit-lock-reclaimed-implausible-created.json +18 -0
  256. package/schema/fixtures/event/valid/audit-lock-reclaimed-legacy.json +14 -0
  257. package/schema/fixtures/event/valid/audit-lock-reclaimed-unlock.json +19 -0
  258. package/schema/fixtures/event/valid/audit-lock-reclaimed.json +19 -0
  259. package/schema/fixtures/event/valid/audit-reviewed-reaction.json +5 -0
  260. package/schema/fixtures/event/valid/audit-reviewed.json +6 -1
  261. package/schema/fixtures/event/valid/audit-sampled.json +2 -1
  262. package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
  263. package/schema/fixtures/event/valid/daemon-identity.json +16 -0
  264. package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
  265. package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
  266. package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
  267. package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
  268. package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
  269. package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
  270. package/schema/policy.schema.json +102 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "approval-md",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Human approval for agent actions. CLI runtime for the approval.md convention (pre-release).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,7 @@
18
18
  "schema",
19
19
  "docs/adapter-api.md",
20
20
  "docs/cli-reference.md",
21
+ "docs/README-extended.md",
21
22
  "SPEC.md",
22
23
  "README.md",
23
24
  "LICENSE",
@@ -57,5 +58,6 @@
57
58
  "ajv-formats": "3.0.1",
58
59
  "better-sqlite3": "12.11.1",
59
60
  "yaml": "2.9.0"
60
- }
61
+ },
62
+ "gitHead": "5f9b9c3da2b0b220b344590d6d5319d27edf3b61"
61
63
  }
@@ -54,6 +54,7 @@
54
54
  "audit.decision_refused",
55
55
  "audit.gesture_refused",
56
56
  "audit.question_preempted",
57
+ "audit.lock_reclaimed",
57
58
  "reconciliation.required",
58
59
  "reconciliation.satisfied",
59
60
  "payload.pruned",
@@ -64,7 +65,7 @@
64
65
  "gate.path.signed_off",
65
66
  "log.checkpoint"
66
67
  ],
67
- "description": "SPEC.md §8 'Event types (v0.1)': the closed set of thirty-four event types. Closed by design — an unrecognized type fails closed at the write boundary rather than entering the log unvalidated. `payload.pruned` (APRV-38) is the first addition after the v0.1 draft set of sixteen, `approval.withdrawn` (APRV-106) the second, `execution.indeterminate` with `execution.reconciled` (APRV-120) the third and fourth, `reconciliation.required` / `reconciliation.satisfied` (APRV-127) the fifth and sixth, `policy.proposed` / `policy.declined` (APRV-109) the seventh and eighth, `audit.dark_session` (APRV-192) the ninth, `gate.opened` / `gate.closed` / `gate.bypassed` (APRV-214) the tenth, eleventh and twelfth, `log.checkpoint` (APRV-220) the thirteenth, `audit.decision_refused` (APRV-235) the fourteenth, `gate.organ.attested` (APRV-272) the fifteenth, `gate.path.signed_off` (APRV-338) the sixteenth, `audit.gesture_refused` (APRV-355) the seventeenth, and `audit.question_preempted` (APRV-378) the eighteenth: a reader of a v0.1 log may encounter any of them, and a verifier written against the draft enum must be updated to accept them all."
68
+ "description": "SPEC.md §8 'Event types (v0.1)': the closed set of thirty-five event types. Closed by design — an unrecognized type fails closed at the write boundary rather than entering the log unvalidated. `payload.pruned` (APRV-38) is the first addition after the v0.1 draft set of sixteen, `approval.withdrawn` (APRV-106) the second, `execution.indeterminate` with `execution.reconciled` (APRV-120) the third and fourth, `reconciliation.required` / `reconciliation.satisfied` (APRV-127) the fifth and sixth, `policy.proposed` / `policy.declined` (APRV-109) the seventh and eighth, `audit.dark_session` (APRV-192) the ninth, `gate.opened` / `gate.closed` / `gate.bypassed` (APRV-214) the tenth, eleventh and twelfth, `log.checkpoint` (APRV-220) the thirteenth, `audit.decision_refused` (APRV-235) the fourteenth, `gate.organ.attested` (APRV-272) the fifteenth, `gate.path.signed_off` (APRV-338) the sixteenth, `audit.gesture_refused` (APRV-355) the seventeenth, `audit.question_preempted` (APRV-378) the eighteenth, and `audit.lock_reclaimed` (APRV-479) the nineteenth: a reader of a v0.1 log may encounter any of them, and a verifier written against the draft enum must be updated to accept them all."
68
69
  },
69
70
  "actor": {
70
71
  "type": "string",
@@ -92,6 +93,13 @@
92
93
  }
93
94
  ]
94
95
  },
96
+ "daemon": {
97
+ "type": "string",
98
+ "minLength": 1,
99
+ "maxLength": 64,
100
+ "pattern": "^[a-z0-9][a-z0-9._-]*$",
101
+ "description": "SPEC.md §8 (amended, APRV-383): WHICH daemon instance appended this record. Present exactly on the records a daemon wrote, so a tenant whose daemon is hosted by another party can open their own log and tell which host process acted on their behalf — before this field every daemon-written record carried the same generic `system:daemon` actor and nothing else. The value is either the id declared in the daemon's launch environment (`APPROVAL_DAEMON_ID`) or `daemon-` followed by the eight hex digits of the instance id `approval doctor` prints as its `keychain-scope` suffix, so it is stable across restarts on one machine and keystore without anything being stored.\n\nOPTIONAL and additive: every record written before the field existed still validates and still verifies, and the ABSENCE of the field is absence — it is never read as a claim that no daemon wrote the record, because until this task no daemon could say.\n\nSELF-REPORTED, and bounded by SPEC.md §11.1 invariant 4 in the only way a self-reported field may be: a policy MAY list the ids permitted to write (`daemons` in `policy.schema.json`), an id the list does not name is REFUSED at the write boundary (`daemon-not-allowed`), and an id the list DOES name gains nothing at all — no verdict, autonomy, budget, floor, sampling draw, token or TTL reads this field or that list. Its SHAPE is constrained here for the reason `payload.harness_version`'s is: the value comes from a launch environment and is being written into an append-only log, so a banner, a path, a newline or a shell fragment cannot arrive through it, and two ids differing only in case would be two ids nobody can tell apart on a phone."
102
+ },
95
103
  "task": {
96
104
  "type": "string",
97
105
  "minLength": 1,
@@ -118,9 +126,10 @@
118
126
  "cursor",
119
127
  "codex",
120
128
  "grok",
121
- "muse"
129
+ "muse",
130
+ "hermes"
122
131
  ],
123
- "description": "SPEC.md §10.1 (amended, APRV-227): which harness hook wrote this record. A closed set, extended only by a task that adds the case, because it is the discriminator a reader scopes `harness_version` by: one log holds the records of every harness that ever wrote to it, and a version with no binary named beside it is a string nobody can compare against anything. OPTIONAL and additive — every record written before the field existed still validates and still verifies, and the two fields travel together or not at all. APRV-358 added `grok`, which APRV-243 shipped an adapter for and never added here: a Grok session's manual-class registration failed validation at the write boundary, so the request never reached a human. Closed does not mean hand-maintained any more. This list is pinned set-equal to `HARNESS_KINDS` in `src/core/harness-version.ts` by `tests/harness-enum.test.ts`, along with every other place a harness name is enumerated, so the next adapter cannot land with the enum left behind."
132
+ "description": "SPEC.md §10.1 (amended, APRV-227): which harness hook wrote this record. A closed set, extended only by a task that adds the case, because it is the discriminator a reader scopes `harness_version` by: one log holds the records of every harness that ever wrote to it, and a version with no binary named beside it is a string nobody can compare against anything. OPTIONAL and additive — every record written before the field existed still validates and still verifies, and the two fields travel together or not at all. APRV-358 added `grok`, which APRV-243 shipped an adapter for and never added here: a Grok session's manual-class registration failed validation at the write boundary, so the request never reached a human. Closed does not mean hand-maintained any more. This list is pinned set-equal to `HARNESS_KINDS` in `src/core/harness-version.ts` by `tests/harness-enum.test.ts`, along with every other place a harness name is enumerated, so the next adapter cannot land with the enum left behind. APRV-398 added `hermes`, the Hermes Agent adapter, through that pinning rather than after it."
124
133
  },
125
134
  "harness_version": {
126
135
  "type": "string",
@@ -232,6 +241,156 @@
232
241
  { "type": "number", "minimum": 0 }
233
242
  ],
234
243
  "description": "The read form: the decimal string, or the pre-APRV-121 JSON number. The log is append-only, so a verifier must accept every shape the write boundary ever admitted. Used only in historical validation mode."
244
+ },
245
+ "audit_reviewed_record": {
246
+ "$comment": "SPEC.md §5.2/§8 (amended, APRV-481): the write-boundary form of an `audit.reviewed` record. A review must name the sample it answers (`subject_seq`), the execution that sample named (`sampled_subject_hash`), and what it concluded (`verdict`), because the follower and the research models read reviews as approvals and a review that does not say which action it judged, or what it concluded, is one nobody can count. `validate(..., { mode: \"historical\" })` replaces this definition with `audit_reviewed_record_historical`, so a review written before APRV-481 still validates and verifies unchanged.",
247
+ "type": "object",
248
+ "properties": {
249
+ "actor": {
250
+ "type": "string",
251
+ "pattern": "^human:"
252
+ },
253
+ "payload": {
254
+ "type": "object",
255
+ "properties": {
256
+ "subject_seq": {
257
+ "type": "integer",
258
+ "minimum": 1,
259
+ "description": "SPEC.md §5.2/§8 (amended, APRV-481): the `seq` of the `audit.sampled` record this review answers. Not the execution's seq: the sample is what was offered for review, and the execution it names is joined through `sampled_subject_hash`. REQUIRED on new records."
260
+ },
261
+ "sampled_subject_hash": {
262
+ "type": "string",
263
+ "pattern": "^[a-f0-9]{64}$",
264
+ "description": "SPEC.md §5.2/§8 (amended, APRV-481): the `hash` of the `execution.started` record the sample named (the sample's own `subject_hash`), copied by the runtime from the verified log and never supplied by a caller. The join key from a review to the execution it judged. REQUIRED on new records."
265
+ },
266
+ "verdict": {
267
+ "type": "string",
268
+ "enum": [
269
+ "ok",
270
+ "denied"
271
+ ],
272
+ "description": "SPEC.md §5.2 (amended, APRV-127): what the reviewer concluded. `ok`: the action should have happened — the spelling the v0.1 fixtures already used, kept rather than renamed, because a second word for a verdict already in logs is the drift this vocabulary exists to avoid. `denied`: it should not have, and the runtime appends a `reconciliation.required` naming the obligation the denial creates. REQUIRED on every record written since APRV-481, because a review that does not say what it concluded cannot count as anybody's approval; a review written before then may carry none and reads as the observation it was (the historical form of this definition), which is why the ABSENCE of this field on an old record is never read as a denial."
273
+ },
274
+ "verdict_source": {
275
+ "type": "string",
276
+ "const": "explicit",
277
+ "description": "SPEC.md §5.2 (amended, PR #614 refutation F5): how the verdict was given. `explicit` is the only value: since APRV-482 a verdict is said by the reviewer (`--ok`/`--deny`, a card's OK or second Deny) and never defaulted. A review written before then may carry `verdict: \"ok\"` that the runtime filled in for a grade-only tap or a bare terminal review, and nothing else in the record tells the two apart, so this field is the discriminator a follower reads: present means explicit, absent (on an old record) means the verdict may have been defaulted. REQUIRED on new records."
278
+ },
279
+ "payload_hash": {
280
+ "type": "string",
281
+ "pattern": "^[a-f0-9]{64}$",
282
+ "description": "SPEC.md §5.2/§10.3 (amended, APRV-481): the SHA-256/JCS binding of the bytes the sampled execution ran, present ONLY when the surface that collected this review rendered those bytes whole in front of the reviewer (a review card's `bytes` view). Absent when the reviewer saw a hash, saw nothing, or reviewed at a terminal, so the field never claims more than the reviewer read. The runtime checks it equals the execution's recorded binding before appending."
283
+ },
284
+ "reaction": {
285
+ "type": "string",
286
+ "enum": [
287
+ "disliked",
288
+ "indifferent",
289
+ "liked",
290
+ "loved"
291
+ ],
292
+ "description": "SPEC.md §5.2 (amended, APRV-237): what the human thought of the action, graded. `verdict` remains the ENFORCEMENT field and is the only one any enforcement path reads; `reaction` is guidance travelling the human-to-agent direction and no routing, matching, sampling, budgeting or token decision consults it (SPEC.md §11.1 invariant 10). Optional and additive: a review written before the field existed still validates and still verifies, and the ABSENCE of the field is absence — it is never read as `indifferent`, which is a thing a person had to actually say. The four words are the whole vocabulary; a fifth is refused here rather than left to accumulate synonyms across surfaces."
293
+ }
294
+ },
295
+ "required": [
296
+ "subject_seq",
297
+ "sampled_subject_hash",
298
+ "verdict",
299
+ "verdict_source"
300
+ ]
301
+ }
302
+ },
303
+ "required": [
304
+ "actor",
305
+ "payload"
306
+ ]
307
+ },
308
+ "audit_reviewed_record_historical": {
309
+ "$comment": "The read form of `audit_reviewed_record` (APRV-481): every constraint a review has carried since APRV-56 and APRV-237, without the three fields APRV-481 made required or the `verdict_source` PR #614's F5 fix made required. The log is append-only, so a verifier must accept every review the write boundary ever admitted. Used only in historical validation.",
310
+ "type": "object",
311
+ "properties": {
312
+ "actor": {
313
+ "type": "string",
314
+ "pattern": "^human:"
315
+ },
316
+ "payload": {
317
+ "type": "object",
318
+ "properties": {
319
+ "subject_seq": {
320
+ "type": "integer",
321
+ "minimum": 1
322
+ },
323
+ "sampled_subject_hash": {
324
+ "type": "string",
325
+ "pattern": "^[a-f0-9]{64}$"
326
+ },
327
+ "verdict": {
328
+ "type": "string",
329
+ "enum": [
330
+ "ok",
331
+ "denied"
332
+ ]
333
+ },
334
+ "verdict_source": {
335
+ "type": "string",
336
+ "const": "explicit"
337
+ },
338
+ "payload_hash": {
339
+ "type": "string",
340
+ "pattern": "^[a-f0-9]{64}$"
341
+ },
342
+ "reaction": {
343
+ "type": "string",
344
+ "enum": [
345
+ "disliked",
346
+ "indifferent",
347
+ "liked",
348
+ "loved"
349
+ ]
350
+ }
351
+ }
352
+ }
353
+ },
354
+ "required": [
355
+ "actor"
356
+ ]
357
+ },
358
+ "audit_sampled_record": {
359
+ "$comment": "SPEC.md §5.2/§8 (amended, PR #614 refutation F2, recheck NF-3): the write-boundary form of an `audit.sampled` record. A sample pins the attested policy its execution ran under (`policy_sha256`: the latest attestation before the sampled `execution.started`), because a retrospective review is held to the approvers roster of THAT policy: read from whatever policy is in force at review or sampling time, a re-attestation after the run could re-roster the review or leave the sample with no roster at all. `validate(..., { mode: \"historical\" })` replaces this definition with `audit_sampled_record_historical`, so a sample written before the field existed still validates and verifies, and reads as not pinned.",
360
+ "type": "object",
361
+ "properties": {
362
+ "payload": {
363
+ "type": "object",
364
+ "properties": {
365
+ "policy_sha256": {
366
+ "type": "string",
367
+ "pattern": "^[a-f0-9]{64}$",
368
+ "description": "SPEC.md §5.2/§8 (amended, PR #614 refutation F2): the SHA-256 of the policy bytes the latest attestation named when this sample was taken, copied by the runtime from the verified log. A review of this sample reads its roster only from bytes that hash to this value. REQUIRED on new records."
369
+ }
370
+ },
371
+ "required": [
372
+ "policy_sha256"
373
+ ]
374
+ }
375
+ },
376
+ "required": [
377
+ "payload"
378
+ ]
379
+ },
380
+ "audit_sampled_record_historical": {
381
+ "$comment": "The read form of `audit_sampled_record` (PR #614 refutation F2): the same field shape without the requirement, so a sample written before samples pinned their policy still verifies. Used only in historical validation.",
382
+ "type": "object",
383
+ "properties": {
384
+ "payload": {
385
+ "type": "object",
386
+ "properties": {
387
+ "policy_sha256": {
388
+ "type": "string",
389
+ "pattern": "^[a-f0-9]{64}$"
390
+ }
391
+ }
392
+ }
393
+ }
235
394
  }
236
395
  },
237
396
  "allOf": [
@@ -314,39 +473,24 @@
314
473
  ]
315
474
  },
316
475
  "then": {
476
+ "$ref": "#/$defs/audit_reviewed_record"
477
+ }
478
+ },
479
+ {
480
+ "$comment": "SPEC.md §5.2/§8 (amended, PR #614 refutation F2): a sample names the attested policy it was taken under, so its review can be held to that policy's roster.",
481
+ "if": {
317
482
  "type": "object",
318
483
  "properties": {
319
- "actor": {
320
- "type": "string",
321
- "pattern": "^human:"
322
- },
323
- "payload": {
324
- "type": "object",
325
- "properties": {
326
- "verdict": {
327
- "type": "string",
328
- "enum": [
329
- "ok",
330
- "denied"
331
- ],
332
- "description": "SPEC.md §5.2 (amended, APRV-127): what the reviewer concluded. `ok`: the action should have happened — the spelling the v0.1 fixtures already used, kept rather than renamed, because a second word for a verdict already in logs is the drift this vocabulary exists to avoid. `denied`: it should not have, and the runtime appends a `reconciliation.required` naming the obligation the denial creates. Optional and additive — a review written before the split carries none and reads as the observation it was, which is why the ABSENCE of this field is never read as a denial."
333
- },
334
- "reaction": {
335
- "type": "string",
336
- "enum": [
337
- "disliked",
338
- "indifferent",
339
- "liked",
340
- "loved"
341
- ],
342
- "description": "SPEC.md §5.2 (amended, APRV-237): what the human thought of the action, graded. `verdict` remains the ENFORCEMENT field and is the only one any enforcement path reads; `reaction` is guidance travelling the human-to-agent direction and no routing, matching, sampling, budgeting or token decision consults it (SPEC.md §11.1 invariant 10). Optional and additive: a review written before the field existed still validates and still verifies, and the ABSENCE of the field is absence — it is never read as `indifferent`, which is a thing a person had to actually say. The four words are the whole vocabulary; a fifth is refused here rather than left to accumulate synonyms across surfaces."
343
- }
344
- }
484
+ "event": {
485
+ "const": "audit.sampled"
345
486
  }
346
487
  },
347
488
  "required": [
348
- "actor"
489
+ "event"
349
490
  ]
491
+ },
492
+ "then": {
493
+ "$ref": "#/$defs/audit_sampled_record"
350
494
  }
351
495
  },
352
496
  {
@@ -833,6 +977,36 @@
833
977
  }
834
978
  }
835
979
  },
980
+ {
981
+ "$comment": "SPEC.md §8 (amended, APRV-447, pending sign-off): a harness `execution.started` carries `policy_sha256`, the SHA-256 of the attested policy the write boundary re-checked the action's class against when it recorded the start (on the hook route the verdict was drawn from the hook's own earlier read, so a re-attest landing between the two leaves the stamp naming the policy in force at the append). Before it, a policy-authorized start (autonomous, supervised, an unselected supervised-live draw) named no policy at all, and a reader had to reconstruct the rules in force from the latest `policy.updated` before the record's seq; a granted start could reach its policy through `grant_seq` and carries the stamp too, written by the same code. The runtime assigns the value at the write boundary from its own attestation check, exactly as it assigns `policy_sha256` on `approval.requested` and `ts` on every gate-typed event: no caller parameter carries it, and a caller-supplied value of the same name is ignored. No verdict, budget, draw or refusal reads it back, so §11.1 invariant 4 is untouched. It is covered by the record hash like every other payload field. OPTIONAL and additive: every record written before the field existed still validates and still verifies, an `execution.started` written by any other path records none, and only the SHAPE is constrained here, a 64-character lowercase hex digest. Whether the hash names an attestation the log actually carries is not expressible in a schema that sees one record. The field name is a stable contract: downstream followers of the log use it to join a start to the rules it ran under.",
982
+ "if": {
983
+ "type": "object",
984
+ "properties": {
985
+ "event": {
986
+ "const": "execution.started"
987
+ }
988
+ },
989
+ "required": [
990
+ "event"
991
+ ]
992
+ },
993
+ "then": {
994
+ "type": "object",
995
+ "properties": {
996
+ "payload": {
997
+ "type": "object",
998
+ "properties": {
999
+ "policy_sha256": {
1000
+ "type": "string",
1001
+ "pattern": "^[a-f0-9]{64}$",
1002
+ "description": "SPEC.md §8 (amended, APRV-447): SHA-256 (lowercase hex) of the attested policy file's exact bytes the write boundary re-checked this start's class against, stamped by the runtime at the write boundary. Never a caller's parameter and never an input to a verdict.",
1003
+ "examples": ["5f1c0de9a3b7e2d4c6f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e"]
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+ }
1009
+ },
836
1010
  {
837
1011
  "$comment": "SPEC.md §10.3 (amended, APRV-38): a decision collected as part of a batch gesture carries the batch's channel delivery id as a first-class payload field. Optional — a unit decision has no batch — and constrained only when present, so the field can never arrive as an empty string or a number that audit would have to guess at.",
838
1012
  "if": {
@@ -1126,6 +1300,25 @@
1126
1300
  "type": "string",
1127
1301
  "pattern": "^[a-f0-9]{64}$",
1128
1302
  "description": "SPEC.md §9/§10.3 (APRV-119): SHA-256 (lowercase hex) over the UTF-8 text of the canonical rendering of this request's payload, as produced by the versioned deterministic renderer named inside that text."
1303
+ },
1304
+ "harness_cap_ms": {
1305
+ "type": "integer",
1306
+ "minimum": 60001,
1307
+ "maximum": 86400000,
1308
+ "description": "SPEC.md §6 (APRV-423): the ceiling, in milliseconds, that the harness running the requesting hook imposes on that hook's own process — Hermes caps a `pre_tool_call` entry at 300 s, Claude Code kills the hook at the `timeout` in its settings file. A BOUNDED INPUT and never a deadline of its own: the runtime judges the request against the SHORTER of this minus a fixed margin and the policy's `defaults.approval_ttl`, so a stated cap can only move the lapse EARLIER (§11.1 invariant 4). The minimum is HARNESS_CAP_MARGIN_MS + 1 (src/core/harness-wait.ts, 60 000 ms; tests/harness-cap-ttl.test.ts pins the two equal): a cap at or below the margin leaves a zero-length window, a question that lapses as it is asked, and the hook refuses such a configuration before it appends, so a record carrying one could only come from a caller that skipped the hook and is refused here. Above 24 h is a number no harness contract states."
1309
+ }
1310
+ },
1311
+ "dependentSchemas": {
1312
+ "harness_cap_ms": {
1313
+ "$comment": "APRV-423. A record carrying a harness ceiling MUST also declare `execution: \"harness\"`. The ceiling shortens the window in which this request may be decided, and a request that mints an execution token would otherwise hold an authorization with a longer shelf life than the question that produced it. A schema sees one record, which is exactly enough to see both fields, so the pairing is refused at the write boundary rather than left to a caller's discipline.",
1314
+ "properties": {
1315
+ "execution": {
1316
+ "const": "harness"
1317
+ }
1318
+ },
1319
+ "required": [
1320
+ "execution"
1321
+ ]
1129
1322
  }
1130
1323
  }
1131
1324
  }
@@ -1988,6 +2181,196 @@
1988
2181
  }
1989
2182
  }
1990
2183
  },
2184
+ {
2185
+ "$comment": "SPEC.md §8 (proposed, APRV-479, pending sign-off): `<log>.lock` was taken over from a holder that is gone, and this is the first record under the lock that was taken. A `system:log` actor is this runtime's log writer, which proved the holder gone after waiting out its whole lock timeout: its process has exited in the writer's own pid namespace and boot (Linux: `kill(pid, 0)` answers ESRCH with `/proc/<pid>` absent or a zombie, or the pid names a process that started later; elsewhere the same host and boot and `kill(pid, 0)` answering ESRCH), or the lockfile names no holder and is older than the reclaim age. A `human:` actor is a person's `approval log unlock`, for a lock no writer can judge (another container or boot): the reason is then that person's assertion, not a proof. `core/log-lock.ts` states the rules.\n\nNo existing type carries it. The audit records before it are about a gesture on a request, a question answered elsewhere, or a sweep over activity; this is about the log's own lock and nothing else.\n\nAUDIT TIER, on the strict terms `audit.decision_refused` set: it grants nothing, mints no token, settles no request, charges no budget, is not sampled, and NO ENFORCEMENT PATH READS IT. Its `ts` is the write boundary's, like every audit record's.\n\nThe payload names the lockfile by its base name, the reason from a closed set, how long the lock had been held, and, when the lockfile carried a holder record, the holder's pid, kind and start of hold, strictly parsed: nothing else is taken from the file. A start of hold that is not plausible (before the log's last record by more than 24 hours, or after the taker's clock by more than 5 minutes) is recorded as null, with no age. The holder's host is deliberately not recorded: a log is often committed, and a machine name is not the log's business.",
2186
+ "if": {
2187
+ "type": "object",
2188
+ "properties": {
2189
+ "event": {
2190
+ "const": "audit.lock_reclaimed"
2191
+ }
2192
+ },
2193
+ "required": [
2194
+ "event"
2195
+ ]
2196
+ },
2197
+ "then": {
2198
+ "type": "object",
2199
+ "required": [
2200
+ "actor",
2201
+ "payload"
2202
+ ],
2203
+ "allOf": [
2204
+ {
2205
+ "$comment": "`age_ms` is required, and is absent exactly when `holder.created` is null: an implausible `created` is not recorded, and neither is the age derived from it (APRV-479 R3-7).",
2206
+ "if": {
2207
+ "type": "object",
2208
+ "properties": {
2209
+ "payload": {
2210
+ "type": "object",
2211
+ "properties": {
2212
+ "holder": {
2213
+ "type": "object",
2214
+ "properties": {
2215
+ "created": {
2216
+ "type": "null"
2217
+ }
2218
+ },
2219
+ "required": [
2220
+ "created"
2221
+ ]
2222
+ }
2223
+ },
2224
+ "required": [
2225
+ "holder"
2226
+ ]
2227
+ }
2228
+ },
2229
+ "required": [
2230
+ "payload"
2231
+ ]
2232
+ },
2233
+ "then": {
2234
+ "type": "object",
2235
+ "properties": {
2236
+ "payload": {
2237
+ "type": "object",
2238
+ "not": {
2239
+ "properties": {
2240
+ "age_ms": {}
2241
+ },
2242
+ "required": [
2243
+ "age_ms"
2244
+ ]
2245
+ }
2246
+ }
2247
+ }
2248
+ },
2249
+ "else": {
2250
+ "type": "object",
2251
+ "properties": {
2252
+ "payload": {
2253
+ "type": "object",
2254
+ "properties": {
2255
+ "age_ms": {}
2256
+ },
2257
+ "required": [
2258
+ "age_ms"
2259
+ ]
2260
+ }
2261
+ }
2262
+ }
2263
+ },
2264
+ {
2265
+ "$comment": "A person's assertion is never recorded as the writer's proof, nor the reverse: `operator-cleared` if and only if the actor is `human:`.",
2266
+ "if": {
2267
+ "type": "object",
2268
+ "properties": {
2269
+ "payload": {
2270
+ "type": "object",
2271
+ "properties": {
2272
+ "reason": {
2273
+ "const": "operator-cleared"
2274
+ }
2275
+ },
2276
+ "required": [
2277
+ "reason"
2278
+ ]
2279
+ }
2280
+ },
2281
+ "required": [
2282
+ "payload"
2283
+ ]
2284
+ },
2285
+ "then": {
2286
+ "type": "object",
2287
+ "properties": {
2288
+ "actor": {
2289
+ "type": "string",
2290
+ "pattern": "^human:"
2291
+ }
2292
+ }
2293
+ },
2294
+ "else": {
2295
+ "type": "object",
2296
+ "properties": {
2297
+ "actor": {
2298
+ "type": "string",
2299
+ "pattern": "^system:"
2300
+ }
2301
+ }
2302
+ }
2303
+ }
2304
+ ],
2305
+ "properties": {
2306
+ "actor": {
2307
+ "type": "string",
2308
+ "pattern": "^(system|human):",
2309
+ "description": "`system:log`: the runtime's log writer, which proved the holder gone. `human:<id>`: the person who ran `approval log unlock`, and only with reason `operator-cleared`. An `agent:` actor is refused: no agent takes over the log's lock."
2310
+ },
2311
+ "payload": {
2312
+ "type": "object",
2313
+ "additionalProperties": false,
2314
+ "required": [
2315
+ "lockfile",
2316
+ "reason"
2317
+ ],
2318
+ "properties": {
2319
+ "lockfile": {
2320
+ "type": "string",
2321
+ "minLength": 1,
2322
+ "pattern": "^[^/\\\\]+$",
2323
+ "description": "The lockfile's base name (`events.jsonl.lock`), never a path."
2324
+ },
2325
+ "reason": {
2326
+ "enum": [
2327
+ "holder-dead",
2328
+ "legacy-aged",
2329
+ "operator-cleared"
2330
+ ],
2331
+ "description": "Why the holder counts as gone. CLOSED, additive-only. `holder-dead`: the writer proved it: no process has the holder's pid in the writer's pid namespace and boot, or the pid names a later process. `legacy-aged`: the writer found the lockfile naming no holder and older than the reclaim age. `operator-cleared`: a person ran `approval log unlock` and asserted the holder gone, which is not a proof; it is the only reason a `human:` actor may carry, and a `system:` actor may never carry it."
2332
+ },
2333
+ "age_ms": {
2334
+ "type": "integer",
2335
+ "minimum": 0,
2336
+ "description": "How long the lock had been held when it was taken over, in milliseconds, by the taker's clock: from the holder record's `created`, or from the lockfile's mtime when it had none. Required, except that it is absent exactly when `holder.created` is null (its `created` was implausible, so no age derived from it is recorded)."
2337
+ },
2338
+ "holder": {
2339
+ "type": "object",
2340
+ "additionalProperties": false,
2341
+ "required": [
2342
+ "pid",
2343
+ "op",
2344
+ "created"
2345
+ ],
2346
+ "description": "The holder record the lockfile carried, strictly parsed. Absent exactly when it carried none.",
2347
+ "properties": {
2348
+ "pid": {
2349
+ "type": "integer",
2350
+ "minimum": 1
2351
+ },
2352
+ "op": {
2353
+ "enum": [
2354
+ "append",
2355
+ "hold"
2356
+ ],
2357
+ "description": "`append`: one record. `hold`: a whole operation under the lock (`log sync`, `log advance`, an export, an unlock)."
2358
+ },
2359
+ "created": {
2360
+ "type": [
2361
+ "string",
2362
+ "null"
2363
+ ],
2364
+ "format": "date-time",
2365
+ "description": "When the holder took the lock, by its own clock. `null` when the value the lockfile carried was not plausible: earlier than the log's last record's `ts` minus 24 hours, or later than the taker's clock plus 5 minutes (a value the file chose, recorded as nothing)."
2366
+ }
2367
+ }
2368
+ }
2369
+ }
2370
+ }
2371
+ }
2372
+ }
2373
+ },
1991
2374
  {
1992
2375
  "$comment": "SPEC.md §5.2 (amended, APRV-214), 'The open window': a person opening the harness gate on themselves so the gate can be debugged from inside. The actor MUST be `^human:`, in the schema as well as in `core/gate-window.ts`, for the reason attestation refuses an agent: this is the one act that suspends the policy, and an agent able to author it could authorize its own next command. The window's state lives here and in no file, so what a reader derives is derived from a verified chain and nothing else. Expiry is `ts` plus `duration`, and a record claiming an `expires_at` beyond that reads as the shorter of the two: the field is a convenience for a human reading the log, never the authority.",
1993
2376
  "if": {
@@ -0,0 +1,19 @@
1
+ {
2
+ "seq": 217,
3
+ "ts": "2026-10-05T07:41:12.408Z",
4
+ "event": "audit.lock_reclaimed",
5
+ "actor": "system:log",
6
+ "payload": {
7
+ "lockfile": "events.jsonl.lock",
8
+ "reason": "holder-dead",
9
+ "age_ms": 63958419351790,
10
+ "holder": {
11
+ "pid": 48213,
12
+ "op": "append",
13
+ "created": null
14
+ }
15
+ },
16
+ "alg": "sha256/jcs",
17
+ "prev": "7f625051c176f2f260a1f7e25c9a933717ebb49917a8788ac25ae7a4614ebb30",
18
+ "hash": "2e9b7c1a4d6f08213b5c7e9a0d2f416385a7c9e1b3d5f70921a3c5e7092b4d6f"
19
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "seq": 214,
3
+ "ts": "2026-10-05T07:41:12.408Z",
4
+ "event": "audit.lock_reclaimed",
5
+ "actor": "agent:claude-code",
6
+ "payload": {
7
+ "lockfile": "events.jsonl.lock",
8
+ "reason": "holder-dead",
9
+ "age_ms": 734211
10
+ },
11
+ "alg": "sha256/jcs",
12
+ "prev": "7f625051c176f2f260a1f7e25c9a933717ebb49917a8788ac25ae7a4614ebb30",
13
+ "hash": "2e9b7c1a4d6f08213b5c7e9a0d2f416385a7c9e1b3d5f70921a3c5e7092b4d6f"
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "seq": 214,
3
+ "ts": "2026-10-05T07:41:12.408Z",
4
+ "event": "audit.lock_reclaimed",
5
+ "actor": "system:log",
6
+ "payload": {
7
+ "lockfile": "/home/tenant/.approval/log/events.jsonl.lock",
8
+ "reason": "holder-dead",
9
+ "age_ms": 734211
10
+ },
11
+ "alg": "sha256/jcs",
12
+ "prev": "7f625051c176f2f260a1f7e25c9a933717ebb49917a8788ac25ae7a4614ebb30",
13
+ "hash": "2e9b7c1a4d6f08213b5c7e9a0d2f416385a7c9e1b3d5f70921a3c5e7092b4d6f"
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "seq": 216,
3
+ "ts": "2026-10-05T11:02:40.115Z",
4
+ "event": "audit.lock_reclaimed",
5
+ "actor": "system:log",
6
+ "payload": {
7
+ "lockfile": "events.jsonl.lock",
8
+ "reason": "operator-cleared",
9
+ "age_ms": 4021877
10
+ },
11
+ "alg": "sha256/jcs",
12
+ "prev": "7f625051c176f2f260a1f7e25c9a933717ebb49917a8788ac25ae7a4614ebb30",
13
+ "hash": "2e9b7c1a4d6f08213b5c7e9a0d2f416385a7c9e1b3d5f70921a3c5e7092b4d6f"
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "seq": 214,
3
+ "ts": "2026-10-05T07:41:12.408Z",
4
+ "event": "audit.lock_reclaimed",
5
+ "actor": "system:log",
6
+ "payload": {
7
+ "lockfile": "events.jsonl.lock",
8
+ "reason": "holder-looked-old",
9
+ "age_ms": 734211
10
+ },
11
+ "alg": "sha256/jcs",
12
+ "prev": "7f625051c176f2f260a1f7e25c9a933717ebb49917a8788ac25ae7a4614ebb30",
13
+ "hash": "2e9b7c1a4d6f08213b5c7e9a0d2f416385a7c9e1b3d5f70921a3c5e7092b4d6f"
14
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "seq": 215,
3
+ "ts": "2026-10-05T07:41:12.408Z",
4
+ "event": "audit.lock_reclaimed",
5
+ "actor": "system:log",
6
+ "payload": {
7
+ "lockfile": "events.jsonl.lock",
8
+ "reason": "holder-dead"
9
+ },
10
+ "alg": "sha256/jcs",
11
+ "prev": "7f625051c176f2f260a1f7e25c9a933717ebb49917a8788ac25ae7a4614ebb30",
12
+ "hash": "2e9b7c1a4d6f08213b5c7e9a0d2f416385a7c9e1b3d5f70921a3c5e7092b4d6f"
13
+ }
@@ -7,7 +7,12 @@
7
7
  "actor": "agent:chaser",
8
8
  "channel": "cli",
9
9
  "payload": {
10
- "verdict": "ok"
10
+ "subject_seq": 15,
11
+ "subject_event": "audit.sampled",
12
+ "reviewed": true,
13
+ "sampled_subject_hash": "6c1d3f0b9a8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a392817060504",
14
+ "verdict": "ok",
15
+ "verdict_source": "explicit"
11
16
  },
12
17
  "alg": "sha256/jcs",
13
18
  "prev": "7f625051c176f2f260a1f7e25c9a933717ebb49917a8788ac25ae7a4614ebb30",