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
@@ -37,9 +37,12 @@
37
37
  *
38
38
  * ## Lazy expiry — the named requirement
39
39
  *
40
- * A request expires when `ts > requestTs + defaults.approval_ttl`, **whether or
41
- * not** an `approval.expired` event exists. Nothing may depend on a daemon
42
- * having run: if the expiry sweep is asleep, a late grant must still be refused.
40
+ * A request expires when `ts > requestTs + effective window`, **whether or
41
+ * not** an `approval.expired` event exists. The effective window is
42
+ * `defaults.approval_ttl`, narrowed since APRV-423 by whatever harness ceiling
43
+ * the requesting hook declared on `payload.harness_cap_ms` (a self-reported
44
+ * bound that can only shorten; see `core/harness-wait.ts`). Nothing may depend
45
+ * on a daemon having run: if the expiry sweep is asleep, a late grant must still be refused.
43
46
  * {@link requestState} therefore computes expiry two ways — from the event, and
44
47
  * lazily from the arithmetic — and treats them as equivalent.
45
48
  *
@@ -116,6 +119,7 @@ import { type ClockOptions } from "./clock.js";
116
119
  import { type HarnessProvenance } from "./harness-version.js";
117
120
  import { type AppendError, type AppendOptions, type EventRecord, type LogHead } from "./log.js";
118
121
  import { type UsdInput } from "./money.js";
122
+ import type { PromptRendering } from "./prompt-layout.js";
119
123
  import { type Autonomy, type PolicyLoadResult } from "./policy-load.js";
120
124
  import { type Resolution } from "./policy-match.js";
121
125
  import { type DrawAsker, type DrawRefusalReason, type LiveDrawRecord } from "./live-draw.js";
@@ -131,6 +135,30 @@ import { type ValidationError } from "./validate.js";
131
135
  export { requestState, type Decision, type DeclaredAction, type ExecutionFacts, type RequestDerivation, type RequestState, WITHDRAW_REASONS, isWithdrawReason, type WithdrawReason, } from "./state.js";
132
136
  /** Actor stamped on runtime-originated expiry events (SPEC.md §8 `system:`). */
133
137
  export declare const EXPIRY_ACTOR = "system:gate";
138
+ /**
139
+ * Does an `approvers` list name this actor (APRV-137, amended SPEC.md §5.2)?
140
+ *
141
+ * The spelling a valid policy uses is the bare id (`alice`), which is what
142
+ * `policy.schema.json` admits and what the keys of the top-level `approvers`
143
+ * map are: its `identifier` pattern is lowercase alphanumerics with `_` and
144
+ * `-`, so a `human:` prefix inside a roster is a schema violation and never
145
+ * reaches here. The whole actor string is compared as well, which can only ever
146
+ * match a loader more permissive than the shipped schema; it widens nothing for
147
+ * a valid policy, because `alice` and `human:alice` are the same person under
148
+ * either comparison.
149
+ *
150
+ * Comparison is exact and case-sensitive. An identity that matched under
151
+ * folding would let `human:Alice` and `human:alice` be one approver on one host
152
+ * and two on another, and a roster is a list of people rather than a pattern
153
+ * language. An empty list names nobody and therefore matches nobody;
154
+ * `approvers` carries `minItems: 1`, so a valid policy cannot produce one, and
155
+ * that branch stays a fail-closed backstop rather than a reachable path.
156
+ *
157
+ * Exported for `core/audit.ts` (APRV-483), whose review check is this one
158
+ * comparison applied to a reviewer, so a grant and a review can never disagree
159
+ * about who is on a roster.
160
+ */
161
+ export declare function namesApprover(approvers: readonly string[], actor: string): boolean;
134
162
  /**
135
163
  * The closed set of gate refusal codes. Agents branch on these, so the union is
136
164
  * frozen public API in the same sense the exit codes are: adding a code is a
@@ -497,7 +525,51 @@ export declare const GATE_REFUSAL_CODES: readonly [
497
525
  * without an address would spend a human's decision on an authorization
498
526
  * nothing can ever open. Nothing is appended; the next attempt asks again.
499
527
  */
500
- "token-delivery-unavailable"];
528
+ "token-delivery-unavailable",
529
+ /**
530
+ * `approval propose` named a class the policy has not opened to agents
531
+ * (APRV-445, amended SPEC.md §5.2): the class is not an exact key of
532
+ * `policy.classes`, or neither that key nor a declared `<prefix>.*` family of
533
+ * it sets `agent_may_request: true`. Nothing is registered and nothing is
534
+ * appended. The repair is the operator's, a policy edit and a re-attestation;
535
+ * no retry by the agent changes it.
536
+ */
537
+ "class-not-agent-requestable",
538
+ /**
539
+ * `approval propose` / `approval start` carried a payload larger than
540
+ * {@link PROPOSE_PAYLOAD_MAX_BYTES} (APRV-445). Refused before the bytes are
541
+ * parsed or hashed; nothing is stored and nothing is appended.
542
+ */
543
+ "payload-too-large",
544
+ /**
545
+ * `approval request` (or a task FILE's registration) named a `propose:` task
546
+ * (APRV-445 refutation, S1). A proposal is requested only through `propose`,
547
+ * which re-checks the operator's opening of its class; a plain request on the
548
+ * same task would skip that check, and a file-registered `propose:` id would
549
+ * borrow the exclusions proposals get from the harness hook.
550
+ */
551
+ "task-is-proposal",
552
+ /**
553
+ * `approval start` named a task `propose` did not register (APRV-445, L9).
554
+ * `start` records executions of proposals and of nothing else; every other
555
+ * action executes through `approval run`, an adapter, or the hook.
556
+ */
557
+ "task-not-proposal",
558
+ /**
559
+ * `approval propose --key` does not begin with `<class>:` (APRV-445, S4). The
560
+ * key names the class it was proposed under, so a key cannot be read back
561
+ * later as belonging to a different class than its record says.
562
+ */
563
+ "key-class-mismatch",
564
+ /**
565
+ * `approval propose` found the log moving under it on every one of its
566
+ * bounded attempts (APRV-445 recheck 3): each re-read showed an answer the
567
+ * next append was refused for. Exit 1, nothing more was appended, and the
568
+ * proposal's standing is whatever the log now says; read it with `wait
569
+ * --timeout 0` or call again. Distinct from `already-decided`, which would
570
+ * tell a caller a proposal is answered when it may stand granted or pending.
571
+ */
572
+ "contended"];
501
573
  export type GateRefusalCode = (typeof GATE_REFUSAL_CODES)[number];
502
574
  /** Every gate failure is one of these. Nothing here throws. */
503
575
  export interface GateRefusal {
@@ -520,7 +592,12 @@ export interface GateRefusal {
520
592
  limits?: IntakeVerdict[];
521
593
  /** Schema errors, when `code` is `envelope-invalid`. */
522
594
  errors?: ValidationError[];
523
- /** The underlying append error, when `code` is `append-failed`. */
595
+ /**
596
+ * The underlying append error, when `code` is `append-failed`; and, on a
597
+ * `budget-exceeded` from {@link startHarnessExecution}, the error that kept
598
+ * its `budget.exceeded` record out (APRV-478). Never present beside a
599
+ * `record`.
600
+ */
524
601
  append?: AppendError;
525
602
  /**
526
603
  * The two policy hashes actually compared, when `code` is `policy-drift`
@@ -639,6 +716,22 @@ type ReadOutcome = {
639
716
  * underneath it.
640
717
  */
641
718
  export declare function readGateRecords(logPath: string, schemaDir?: string): ReadOutcome;
719
+ /**
720
+ * The policy file the gate will hash for attestation.
721
+ *
722
+ * `file` wins; otherwise discovery walks `POLICY_FILENAMES` in `dir` exactly as
723
+ * `loadPolicy` does, so the attested file and the enforced file are the same
724
+ * file. When neither exists the first candidate is returned anyway, so
725
+ * `checkAttestation` reports `unreadable` and the gate refuses — a missing
726
+ * policy is never a pass.
727
+ */
728
+ /**
729
+ * The policy file a gate operation reads: `policy.file`, or the first policy
730
+ * filename found in `policy.dir` (default: the working directory). Exported for
731
+ * `core/audit.ts` (APRV-483), whose roster check must read the same file a
732
+ * grant would and hold it to the same attestation.
733
+ */
734
+ export declare function policyPathOf(options: GateOptions): string;
642
735
  /**
643
736
  * The policy as a gate operation would load it: one read through the seam of
644
737
  * {@link GateOptions.policy}, parsed, failing closed (APRV-324).
@@ -731,7 +824,24 @@ export declare function register(logPath: string, source: RegisterSource, actor:
731
824
  */
732
825
  export interface RegisterOptions extends GateOptions {
733
826
  harness?: HarnessProvenance;
827
+ /** Set by `propose` alone: the `propose:` task namespace is its (APRV-445). */
828
+ proposal?: true;
734
829
  }
830
+ /**
831
+ * The `task.registered` record for `task`, or the `not-registered` refusal.
832
+ *
833
+ * The one raise site for SPEC.md §11.2's `not-registered` ("no task.registered
834
+ * record exists for the task id"). `request` reaches it through
835
+ * {@link registeredAction}; `approval wait` calls it directly (APRV-428), so a
836
+ * wait on a task the log has never seen is refused in the same words rather
837
+ * than answered as a vacuous grant. The latest registration wins, as it always
838
+ * has for `registeredAction`; the register path refuses a second one, so a log
839
+ * carrying two was written by something else.
840
+ */
841
+ export declare function taskRegistration(records: EventRecord[], task: string): {
842
+ ok: true;
843
+ record: EventRecord;
844
+ } | GateRefusal;
735
845
  /**
736
846
  * The declared action for `(task, actionKey)`, as registered in the log.
737
847
  *
@@ -797,6 +907,25 @@ export interface RequestInput {
797
907
  * policy's `defaults.approval_ttl` remains the only deadline with authority.
798
908
  */
799
909
  wait_until?: string;
910
+ /**
911
+ * The ceiling the requesting harness imposes on the hook process that is
912
+ * asking, in milliseconds (APRV-423).
913
+ *
914
+ * Recorded as `payload.harness_cap_ms` and read back by `core/state.ts`,
915
+ * which judges the request against the SHORTER of this minus a fixed margin
916
+ * and the policy's own `defaults.approval_ttl`. It is the answer to APRV-410:
917
+ * a harness that kills the hook while the question is still on a phone turns
918
+ * the tap that follows into a grant on a call nobody holds, so the question
919
+ * is made to end first and both records say the same thing.
920
+ *
921
+ * Claimed, and structurally unable to reduce scrutiny (SPEC.md §11.1
922
+ * invariant 4): the runtime takes a MINIMUM, so an overstated cap changes
923
+ * nothing and an understated one only shortens the claimant's own window.
924
+ * The write boundary constrains it to a positive integer on a request that
925
+ * also declares `execution: "harness"` — a cap on a token-minting request
926
+ * would be a question with a shorter life than the authorization it mints.
927
+ */
928
+ harnessCapMs?: number;
800
929
  /**
801
930
  * The caller has established that loop safety floors this action to `manual`
802
931
  * for this invocation (APRV-145, amended SPEC.md §10.2).
@@ -842,6 +971,15 @@ export interface RequestInput {
842
971
  * spend a human's decision on an authorization that can never execute.
843
972
  */
844
973
  delivery?: "self";
974
+ /**
975
+ * This request was opened by `approval propose`, whose caller named the class
976
+ * itself (APRV-445). When set, intake re-checks `agentRequestability` against
977
+ * the policy bytes THIS attempt read, inside the retried read-check-append
978
+ * cycle, so a re-attestation between `propose`'s own check and the append
979
+ * cannot admit a class the policy no longer opens to agents (SPEC.md §11.1
980
+ * invariant 5). It only ever adds a refusal.
981
+ */
982
+ agentProposal?: true;
845
983
  }
846
984
  /**
847
985
  * Why a `supervised-live` action was, or was not, sent to the human gate
@@ -1032,6 +1170,17 @@ export interface DecideOptions extends GateOptions {
1032
1170
  * silently mixed in with policy-attested ones.
1033
1171
  */
1034
1172
  senderSource?: "policy";
1173
+ /**
1174
+ * Which prompt layout the approver decided on (APRV-489), recorded as
1175
+ * `payload.rendering` on `approval.granted` / `approval.rejected` only.
1176
+ *
1177
+ * Set by a decision surface that drew a non-default layout, or that was asked
1178
+ * for one and drew the technical card instead (then `fallback` names why).
1179
+ * Absent under a technical policy, so those records are byte-identical to
1180
+ * every earlier build's. A record of the screen and never an input: no check
1181
+ * here or anywhere reads it back.
1182
+ */
1183
+ rendering?: PromptRendering;
1035
1184
  }
1036
1185
  export type DecideResult = {
1037
1186
  ok: true;
@@ -1090,6 +1239,15 @@ export type DecideResult = {
1090
1239
  */
1091
1240
  export declare function decide(logPath: string, actionKey: string, decision: Decision, actor: string, options?: DecideOptions): DecideResult;
1092
1241
  export interface WithdrawOptions extends GateOptions {
1242
+ /**
1243
+ * Withdraw only a pending request that is VOID at the moment of the append
1244
+ * (APRV-445 recheck, SF1): pinned to a policy hash that is no longer the
1245
+ * attested one. Judged inside the retried read-check-append cycle, so a
1246
+ * caller acting on a stale reading cannot retract a question a concurrent
1247
+ * caller has just re-asked under the policy in force. A live, non-void
1248
+ * request refuses `duplicate-request`.
1249
+ */
1250
+ onlyIfVoid?: true;
1093
1251
  /** Why the requester is retracting. Defaults to `cancelled`. */
1094
1252
  reason?: WithdrawReason;
1095
1253
  /** The requester's free-text elaboration, recorded in the payload. */
@@ -1172,7 +1330,19 @@ export interface HarnessCarry {
1172
1330
  /** `seq` of the `approval.granted`, on `granted` only. */
1173
1331
  decisionSeq: number | null;
1174
1332
  }
1175
- export declare function findHarnessCarry(records: EventRecord[], payloadHash: string, cls: string, ts: string, ttlMs: number | null): HarnessCarry | null;
1333
+ export declare function findHarnessCarry(records: EventRecord[], payloadHash: string, cls: string, ts: string, ttlMs: number | null,
1334
+ /**
1335
+ * How long a question of this caller's outlives the wait that opened it
1336
+ * (APRV-410, `core/harness-wait.ts`'s `abandonedAfterMs`), or `null` for a
1337
+ * caller that declares no such window.
1338
+ *
1339
+ * Only a GRANTED candidate is bounded by it. A pending one is the question
1340
+ * this invocation is about to adopt, and the withdrawal of an abandoned
1341
+ * pending question is the requester's own business, not this function's:
1342
+ * `cli/hook.ts` sweeps those through `withdraw`, which is requester-only by
1343
+ * design (APRV-106 rule 1).
1344
+ */
1345
+ abandonAfterMs?: number | null): HarnessCarry | null;
1176
1346
  /**
1177
1347
  * `GateOptions` plus the one thing a harness spend states about itself
1178
1348
  * (APRV-146).
@@ -1447,3 +1617,191 @@ export type ExpireResult = {
1447
1617
  * as `rejected`.
1448
1618
  */
1449
1619
  export declare function expire(logPath: string, actionKey: string, options?: GateOptions): ExpireResult;
1620
+ /**
1621
+ * The largest payload `approval propose` and `approval start` accept, in UTF-8
1622
+ * bytes of the JSON text as the caller sent it: 256 KiB.
1623
+ *
1624
+ * Well under `approval serve`'s 1 MiB body cap, so the refusal a caller meets is
1625
+ * this named one rather than a transport's. A proposal is a sentence or a
1626
+ * paragraph a human will read on a phone; a payload this size is not one.
1627
+ */
1628
+ export declare const PROPOSE_PAYLOAD_MAX_BYTES: number;
1629
+ /** The longest `--key` `propose` accepts, in UTF-8 bytes (APRV-445, L6). */
1630
+ export declare const PROPOSE_KEY_MAX_BYTES = 1024;
1631
+ /** The longest `--summary` `propose` accepts, in UTF-8 bytes (APRV-445, L6). */
1632
+ export declare const PROPOSE_SUMMARY_MAX_BYTES = 4096;
1633
+ /** `origin.app` on every envelope `propose` registers. */
1634
+ export declare const PROPOSE_ORIGIN_APP = "approval-propose";
1635
+ /** The task-id prefix `propose` derives under. */
1636
+ export declare const PROPOSE_TASK_PREFIX = "propose:";
1637
+ /**
1638
+ * Was this task registered by `propose` (APRV-445)?
1639
+ *
1640
+ * A proposal's request carries `execution: "harness"` like a hook's, so the two
1641
+ * hook-side readers of that marker (the carry and the abandoned-question sweep)
1642
+ * ask this first: a proposal waits days for a human, is spent only by `start`,
1643
+ * and is withdrawn only by its requester's own call.
1644
+ */
1645
+ export declare function isProposalTask(task: unknown): boolean;
1646
+ /**
1647
+ * The task id `propose` registers under, from who is asking, the class and the
1648
+ * key: `propose:` and the first 32 hex digits of the SHA-256 over the RFC 8785
1649
+ * canonical JSON of `[actor, class, key]`.
1650
+ *
1651
+ * Deterministic, so a retry lands on the task its first attempt registered and
1652
+ * is recognised as a retry. The ACTOR is the tenant half of the derivation: one
1653
+ * `approval serve` process serves one store as one fixed `agent:<id>`, so within
1654
+ * a store the actor is who the tenant's agent is, and two actors proposing the
1655
+ * same key derive two different tasks, the second of which `register` refuses
1656
+ * because the key is already declared under the first (`task-already-registered`).
1657
+ * The class is in it so that the same key proposed under a second class is a
1658
+ * different task rather than a silent re-declaration.
1659
+ */
1660
+ export declare function proposedTaskId(actor: string, cls: string, actionKey: string): string;
1661
+ /** What an agent proposes. The payload is a parsed JSON object. */
1662
+ export interface ProposeInput {
1663
+ cls: string;
1664
+ actionKey: string;
1665
+ summary: string;
1666
+ payload: unknown;
1667
+ }
1668
+ /**
1669
+ * How a proposal stands after the call.
1670
+ *
1671
+ * `decision` is what the caller branches on: `requested` means a human is (or
1672
+ * was) asked and `state` says where that stands; `autonomous` and `supervised`
1673
+ * mean the policy itself authorizes the action, nothing was put in front of a
1674
+ * human, and the caller proceeds and records the execution with `approval start`.
1675
+ *
1676
+ * `idempotent` is true exactly when this call appended nothing: a retry of a
1677
+ * proposal whose registration (and, on the manual path, whose request) already
1678
+ * stands under the same class, key and payload hash.
1679
+ */
1680
+ export type ProposeResult = {
1681
+ ok: true;
1682
+ task: string;
1683
+ actionKey: string;
1684
+ cls: string;
1685
+ payloadHash: string;
1686
+ decision: "requested" | "autonomous" | "supervised";
1687
+ /**
1688
+ * `null` off the manual path, where there is no request to be in a state;
1689
+ * `executed` once `start` recorded the execution (APRV-445, L7).
1690
+ */
1691
+ state: RequestState | "executed" | null;
1692
+ seq: number | null;
1693
+ idempotent: boolean;
1694
+ } | GateRefusal;
1695
+ /**
1696
+ * `approval propose` (APRV-445, amended SPEC.md §5.2): register and request one
1697
+ * action in one call, for a requester that holds the payload inline and no file
1698
+ * on the gate's machine.
1699
+ *
1700
+ * ## Why this verb may name its class when `request` may not
1701
+ *
1702
+ * `approval request` reads the class from the log's `task.registered` record so
1703
+ * that an agent cannot call a `financial.spend` a `read.web`. Here the agent is
1704
+ * also the registrant, so that rule alone would prove nothing, and the bound is
1705
+ * the operator's instead: the class must be an EXACT key of `policy.classes`, and
1706
+ * that key or a declared `<prefix>.*` family of it must say
1707
+ * `agent_may_request: true`. A class reached only through a wildcard is refused,
1708
+ * so the set of classes an agent can propose in is a set of lines the operator
1709
+ * wrote and attested by name. What the agent chooses among them is still its own
1710
+ * statement (SPEC.md §11.1 invariant 4 is why the choice is bounded at all), and
1711
+ * an operator opening two classes of different autonomy to agents is trusting
1712
+ * the agent to pick the right one; the record names the class it picked.
1713
+ *
1714
+ * ## Order
1715
+ *
1716
+ * Attestation, then the agent-requestability gate and the class's own autonomy
1717
+ * (`human-only`, an unruled `harness.launch.*`), all BEFORE anything is
1718
+ * registered, so a refused class leaves no registration behind. Then the
1719
+ * registration, then {@link request} with `agentProposal: true`, which repeats
1720
+ * the gate against the bytes its own attempt reads. `execution: "harness"` on the
1721
+ * request: nothing ever presents a token for a proposal, the requester proceeds
1722
+ * on the grant itself and records that with `approval start`, so a minted token
1723
+ * would be a live credential with no spender.
1724
+ *
1725
+ * ## Idempotency
1726
+ *
1727
+ * The task id is {@link proposedTaskId}. A second call with the same actor,
1728
+ * class, key and payload hash finds its own registration and appends nothing
1729
+ * while the request is live or decided, answering with the request's state and
1730
+ * `idempotent: true`. A withdrawn or expired request (a re-attestation voids
1731
+ * pending requests as `policy-drift`) is asked again under the same registration.
1732
+ * The same key with DIFFERENT bytes refuses: `duplicate-request` while a request
1733
+ * is live, `payload-mismatch` otherwise, because a key is bound to the bytes it
1734
+ * was first registered with and new bytes need a new key.
1735
+ */
1736
+ export declare function propose(logPath: string, input: ProposeInput, actor: string, options?: GateOptions, attempt?: number, appendedEarlier?: boolean): ProposeResult;
1737
+ export type StartProposedResult = {
1738
+ ok: true;
1739
+ task: string;
1740
+ actionKey: string;
1741
+ cls: string;
1742
+ /** `grant` when a human's harness grant was spent; `policy` when the policy authorized it. */
1743
+ authorization: "grant" | "policy";
1744
+ record: EventRecord;
1745
+ } | GateRefusal;
1746
+ /**
1747
+ * `approval start <task> --action <key> --payload-json <json>` (APRV-445): record
1748
+ * the `execution.started` for an action its requester is about to carry out
1749
+ * itself, outside any harness hook.
1750
+ *
1751
+ * Until this verb an agent holding only the serve agent credential could ask and
1752
+ * wait, and could not say that it then acted: `approval run` spawns argv on the
1753
+ * gate's machine, and the harness hook writes starts only for tool calls it
1754
+ * classified. A proposal the policy authorizes outright (`autonomous`,
1755
+ * `supervised`) therefore left no record at all, and one a human granted left a
1756
+ * grant nobody spent, so a second publication of the same key was indistinguishable
1757
+ * from the first.
1758
+ *
1759
+ * It adds no authority. It writes through the two existing spenders and through
1760
+ * nothing else: {@link consumeHarnessGrant} when the key carries a request (it
1761
+ * refuses unless that request is granted, unexpired, harness-executed and
1762
+ * unspent, under the policy it was asked under), and {@link startHarnessExecution}
1763
+ * when it carries none (it refuses a `manual` or `human-only` class, an escalated
1764
+ * loop and a spent key, and charges the budgets). Both pin the single-use rule:
1765
+ * a second start of one key is `already-executed`.
1766
+ *
1767
+ * Two checks of its own, both before either spender runs. REQUESTER-ONLY: the
1768
+ * actor must be the one that registered the task (`not-requester` otherwise), as
1769
+ * `withdraw` is, so an agent cannot spend or exhaust a key someone else declared.
1770
+ * And the bytes: the caller presents the payload it is about to act on, and its
1771
+ * hash must equal the registered `payload_hash` (`payload-mismatch` otherwise),
1772
+ * so the thing done is the thing that was approved, which the grant path checks
1773
+ * again against the grant's own binding.
1774
+ */
1775
+ export declare function startProposed(logPath: string, task: string, actionKey: string, payload: unknown, actor: string, options?: GateOptions): StartProposedResult;
1776
+ /**
1777
+ * A request's state as a waiter must read it (APRV-445 refutation, B1).
1778
+ *
1779
+ * `requestState` answers what the log RECORDED. A waiter needs what the record
1780
+ * still AUTHORIZES, and the two part in three ways the gate already enforces
1781
+ * at the spend and the decision but no reader was told about:
1782
+ *
1783
+ * - a GRANT whose request has lapsed its window is `expired`: there is no
1784
+ * separate grant TTL (`grantLapsed`), so a tap at hour 71 of a 72 h window
1785
+ * authorizes nothing at hour 73;
1786
+ * - a PENDING request pinned to a policy hash that is no longer the attested
1787
+ * one is `void` for every task, because the decision refuses `policy-drift`;
1788
+ * a GRANT is `void` on the same condition only where its spend enforces the
1789
+ * same refusal: a harness grant (`execution: "harness"`), which includes
1790
+ * every proposal. A token grant spent through `approval run` stays `granted`;
1791
+ * - a request the runtime withdrew for `policy-drift` (APRV-235) is `void` too,
1792
+ * for the same reason, rather than an ordinary requester `withdrawn`;
1793
+ * - a key whose execution started is `executed`.
1794
+ *
1795
+ * `void` and `expired` both mean "ask again": the question as asked can no
1796
+ * longer be answered usefully. Everything else is `requestState` verbatim. An
1797
+ * unattested policy (`attestedSha256` null) voids nothing, because that state is
1798
+ * the human's to repair and may end in the same bytes.
1799
+ */
1800
+ export type RequestStanding = RequestState | "void" | "executed";
1801
+ export declare function requestStanding(records: EventRecord[], actionKey: string, ts: string, ttlMs: number | null, attestedSha256: string | null): RequestStanding;
1802
+ /**
1803
+ * The SHA-256 the live policy bytes are attested under, or `null` when they are
1804
+ * not attested (unread, unattested, or changed since). One read of the file
1805
+ * through the same seam every gate operation uses.
1806
+ */
1807
+ export declare function attestedPolicySha256(records: EventRecord[], options?: GateOptions): string | null;