approval-md 0.1.0 → 0.2.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 (266) hide show
  1. package/README.md +584 -553
  2. package/SPEC.md +42 -13
  3. package/dist/src/adapters/agentmail.d.ts +426 -0
  4. package/dist/src/adapters/agentmail.js +2 -2
  5. package/dist/src/adapters/conformance.d.ts +149 -0
  6. package/dist/src/adapters/contract.d.ts +628 -0
  7. package/dist/src/adapters/contract.js +110 -16
  8. package/dist/src/adapters/contract.js.map +1 -1
  9. package/dist/src/adapters/email.d.ts +324 -0
  10. package/dist/src/adapters/env-passphrase.d.ts +93 -0
  11. package/dist/src/adapters/public.d.ts +11 -0
  12. package/dist/src/adapters/public.js +11 -0
  13. package/dist/src/adapters/public.js.map +1 -0
  14. package/dist/src/adapters/registry.d.ts +59 -0
  15. package/dist/src/adapters/registry.js +2 -1
  16. package/dist/src/adapters/registry.js.map +1 -1
  17. package/dist/src/adapters/smtp.d.ts +213 -0
  18. package/dist/src/adapters/vault-provider.d.ts +114 -0
  19. package/dist/src/adapters/vault-provider.js +3 -3
  20. package/dist/src/adapters/zzz.d.ts +66 -0
  21. package/dist/src/adapters/zzz.js +299 -0
  22. package/dist/src/adapters/zzz.js.map +1 -0
  23. package/dist/src/channels/batch.d.ts +109 -0
  24. package/dist/src/channels/cli.d.ts +193 -0
  25. package/dist/src/channels/conformance.d.ts +92 -0
  26. package/dist/src/channels/contract.d.ts +623 -0
  27. package/dist/src/channels/payload-view.d.ts +35 -0
  28. package/dist/src/channels/render-queue.d.ts +149 -0
  29. package/dist/src/channels/tagging.d.ts +196 -0
  30. package/dist/src/channels/telegram.d.ts +1832 -0
  31. package/dist/src/channels/web.d.ts +341 -0
  32. package/dist/src/cli/adapter.d.ts +90 -0
  33. package/dist/src/cli/adapter.js +25 -15
  34. package/dist/src/cli/adapter.js.map +1 -1
  35. package/dist/src/cli/amend.d.ts +59 -0
  36. package/dist/src/cli/args.d.ts +43 -0
  37. package/dist/src/cli/attest.d.ts +41 -0
  38. package/dist/src/cli/audit-card.d.ts +62 -0
  39. package/dist/src/cli/audit.d.ts +59 -0
  40. package/dist/src/cli/channel-telegram.d.ts +806 -0
  41. package/dist/src/cli/channel-web.d.ts +131 -0
  42. package/dist/src/cli/channel.d.ts +71 -0
  43. package/dist/src/cli/checkpoint-tap.d.ts +169 -0
  44. package/dist/src/cli/codex.d.ts +2 -0
  45. package/dist/src/cli/codex.js +172 -0
  46. package/dist/src/cli/codex.js.map +1 -0
  47. package/dist/src/cli/coverage.d.ts +61 -0
  48. package/dist/src/cli/daemon.d.ts +120 -0
  49. package/dist/src/cli/doctor.d.ts +129 -0
  50. package/dist/src/cli/doctor.js +119 -5
  51. package/dist/src/cli/doctor.js.map +1 -1
  52. package/dist/src/cli/env.d.ts +65 -0
  53. package/dist/src/cli/execute.d.ts +202 -0
  54. package/dist/src/cli/exit-codes.d.ts +73 -0
  55. package/dist/src/cli/feedback.d.ts +60 -0
  56. package/dist/src/cli/gate-window.d.ts +40 -0
  57. package/dist/src/cli/gate.d.ts +68 -0
  58. package/dist/src/cli/git-scope.d.ts +190 -0
  59. package/dist/src/cli/gloss-attach.d.ts +85 -0
  60. package/dist/src/cli/gloss-codex-child.d.ts +9 -0
  61. package/dist/src/cli/gloss-codex.d.ts +24 -0
  62. package/dist/src/cli/gloss-options.d.ts +42 -0
  63. package/dist/src/cli/gloss.d.ts +265 -0
  64. package/dist/src/cli/help.d.ts +103 -0
  65. package/dist/src/cli/help.js +173 -51
  66. package/dist/src/cli/help.js.map +1 -1
  67. package/dist/src/cli/hook-codex.d.ts +78 -0
  68. package/dist/src/cli/hook-codex.js +167 -0
  69. package/dist/src/cli/hook-codex.js.map +1 -0
  70. package/dist/src/cli/hook.d.ts +331 -0
  71. package/dist/src/cli/hook.js +186 -80
  72. package/dist/src/cli/hook.js.map +1 -1
  73. package/dist/src/cli/import.d.ts +35 -0
  74. package/dist/src/cli/init.d.ts +84 -0
  75. package/dist/src/cli/init.js +2 -2
  76. package/dist/src/cli/init.js.map +1 -1
  77. package/dist/src/cli/instructions.d.ts +23 -0
  78. package/dist/src/cli/journal.d.ts +41 -0
  79. package/dist/src/cli/log-advance.d.ts +287 -0
  80. package/dist/src/cli/log-advance.js +102 -11
  81. package/dist/src/cli/log-advance.js.map +1 -1
  82. package/dist/src/cli/log-anchor.d.ts +176 -0
  83. package/dist/src/cli/log-checkpoint.d.ts +22 -0
  84. package/dist/src/cli/log-sync.d.ts +243 -0
  85. package/dist/src/cli/log-verbs.d.ts +16 -0
  86. package/dist/src/cli/log-verbs.js +7 -1
  87. package/dist/src/cli/log-verbs.js.map +1 -1
  88. package/dist/src/cli/long-help.d.ts +70 -0
  89. package/dist/src/cli/main.d.ts +77 -0
  90. package/dist/src/cli/main.js +155 -5
  91. package/dist/src/cli/main.js.map +1 -1
  92. package/dist/src/cli/mcp.d.ts +52 -0
  93. package/dist/src/cli/paths.d.ts +56 -0
  94. package/dist/src/cli/payload.d.ts +58 -0
  95. package/dist/src/cli/policy.d.ts +43 -0
  96. package/dist/src/cli/preflight.d.ts +363 -0
  97. package/dist/src/cli/preflight.js +294 -7
  98. package/dist/src/cli/preflight.js.map +1 -1
  99. package/dist/src/cli/progress.d.ts +78 -0
  100. package/dist/src/cli/prompt.d.ts +209 -0
  101. package/dist/src/cli/quickstart.d.ts +46 -0
  102. package/dist/src/cli/quickstart.js +297 -0
  103. package/dist/src/cli/quickstart.js.map +1 -0
  104. package/dist/src/cli/records.d.ts +34 -0
  105. package/dist/src/cli/render.d.ts +22 -0
  106. package/dist/src/cli/sandbox.d.ts +51 -0
  107. package/dist/src/cli/scaffold.d.ts +79 -0
  108. package/dist/src/cli/setup-adapter.d.ts +137 -0
  109. package/dist/src/cli/setup-adapter.js +38 -4
  110. package/dist/src/cli/setup-adapter.js.map +1 -1
  111. package/dist/src/cli/setup-channel.d.ts +117 -0
  112. package/dist/src/cli/setup-checkpoint.d.ts +57 -0
  113. package/dist/src/cli/setup-common.d.ts +275 -0
  114. package/dist/src/cli/setup-flow.d.ts +287 -0
  115. package/dist/src/cli/setup-service.d.ts +96 -0
  116. package/dist/src/cli/setup.d.ts +202 -0
  117. package/dist/src/cli/style.d.ts +320 -0
  118. package/dist/src/cli/token.d.ts +39 -0
  119. package/dist/src/cli/up.d.ts +155 -0
  120. package/dist/src/cli/up.js +4 -2
  121. package/dist/src/cli/up.js.map +1 -1
  122. package/dist/src/cli/usage.d.ts +37 -0
  123. package/dist/src/cli/values.d.ts +40 -0
  124. package/dist/src/cli/vault.d.ts +59 -0
  125. package/dist/src/cli/vault.js +2 -2
  126. package/dist/src/cli/vault.js.map +1 -1
  127. package/dist/src/cli/verb-registry.d.ts +76 -0
  128. package/dist/src/cli/verb-registry.js +176 -8
  129. package/dist/src/cli/verb-registry.js.map +1 -1
  130. package/dist/src/cli/wordmark.d.ts +31 -0
  131. package/dist/src/cli/wordmark.js +2 -2
  132. package/dist/src/codex/doctor.d.ts +13 -0
  133. package/dist/src/codex/doctor.js +41 -0
  134. package/dist/src/codex/doctor.js.map +1 -0
  135. package/dist/src/codex/manifest.d.ts +49 -0
  136. package/dist/src/codex/manifest.js +103 -0
  137. package/dist/src/codex/manifest.js.map +1 -0
  138. package/dist/src/codex/templates.d.ts +41 -0
  139. package/dist/src/codex/templates.js +319 -0
  140. package/dist/src/codex/templates.js.map +1 -0
  141. package/dist/src/codex/trust.d.ts +19 -0
  142. package/dist/src/codex/trust.js +183 -0
  143. package/dist/src/codex/trust.js.map +1 -0
  144. package/dist/src/codex/workspace-plan.d.ts +131 -0
  145. package/dist/src/codex/workspace-plan.js +561 -0
  146. package/dist/src/codex/workspace-plan.js.map +1 -0
  147. package/dist/src/core/actor.d.ts +2 -0
  148. package/dist/src/core/actor.js +5 -0
  149. package/dist/src/core/actor.js.map +1 -0
  150. package/dist/src/core/advance-cycle.d.ts +170 -0
  151. package/dist/src/core/agents-md.d.ts +276 -0
  152. package/dist/src/core/apply-patch.d.ts +49 -0
  153. package/dist/src/core/apply-patch.js +266 -0
  154. package/dist/src/core/apply-patch.js.map +1 -0
  155. package/dist/src/core/attest.d.ts +420 -0
  156. package/dist/src/core/attest.js +13 -1
  157. package/dist/src/core/attest.js.map +1 -1
  158. package/dist/src/core/audit.d.ts +492 -0
  159. package/dist/src/core/budgets.d.ts +238 -0
  160. package/dist/src/core/checkpoint.d.ts +500 -0
  161. package/dist/src/core/child-env.d.ts +88 -0
  162. package/dist/src/core/clock.d.ts +52 -0
  163. package/dist/src/core/command-class.d.ts +543 -0
  164. package/dist/src/core/command-class.js +43 -8
  165. package/dist/src/core/command-class.js.map +1 -1
  166. package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
  167. package/dist/src/core/coverage-sources/gh.d.ts +48 -0
  168. package/dist/src/core/coverage-sources/git.d.ts +101 -0
  169. package/dist/src/core/coverage.d.ts +217 -0
  170. package/dist/src/core/credential-spec.d.ts +72 -0
  171. package/dist/src/core/dark-session.d.ts +331 -0
  172. package/dist/src/core/decision-refusal.d.ts +185 -0
  173. package/dist/src/core/env-file.d.ts +450 -0
  174. package/dist/src/core/execute.d.ts +858 -0
  175. package/dist/src/core/execute.js +44 -6
  176. package/dist/src/core/execute.js.map +1 -1
  177. package/dist/src/core/frontmatter.d.ts +78 -0
  178. package/dist/src/core/gate-window.d.ts +312 -0
  179. package/dist/src/core/gate.d.ts +1364 -0
  180. package/dist/src/core/gate.js +68 -13
  181. package/dist/src/core/gate.js.map +1 -1
  182. package/dist/src/core/git-run.d.ts +73 -0
  183. package/dist/src/core/harness-version.d.ts +157 -0
  184. package/dist/src/core/harness-version.js +2 -1
  185. package/dist/src/core/harness-version.js.map +1 -1
  186. package/dist/src/core/harness-wait.d.ts +55 -0
  187. package/dist/src/core/head-retry.d.ts +107 -0
  188. package/dist/src/core/instance.d.ts +253 -0
  189. package/dist/src/core/intake-limits.d.ts +247 -0
  190. package/dist/src/core/jcs.d.ts +52 -0
  191. package/dist/src/core/journal.d.ts +144 -0
  192. package/dist/src/core/live-draw.d.ts +436 -0
  193. package/dist/src/core/log-reconcile.d.ts +89 -0
  194. package/dist/src/core/log-subscribe.d.ts +36 -0
  195. package/dist/src/core/log-subscribe.js +162 -0
  196. package/dist/src/core/log-subscribe.js.map +1 -0
  197. package/dist/src/core/log.d.ts +278 -0
  198. package/dist/src/core/loop.d.ts +274 -0
  199. package/dist/src/core/loop.js +11 -0
  200. package/dist/src/core/loop.js.map +1 -1
  201. package/dist/src/core/md-fence.d.ts +41 -0
  202. package/dist/src/core/money.d.ts +147 -0
  203. package/dist/src/core/payload-census.d.ts +74 -0
  204. package/dist/src/core/payload-store.d.ts +175 -0
  205. package/dist/src/core/payload.d.ts +71 -0
  206. package/dist/src/core/policy-diff.d.ts +292 -0
  207. package/dist/src/core/policy-diff.js +27 -4
  208. package/dist/src/core/policy-diff.js.map +1 -1
  209. package/dist/src/core/policy-expectations.d.ts +199 -0
  210. package/dist/src/core/policy-explain.d.ts +150 -0
  211. package/dist/src/core/policy-explain.js +31 -3
  212. package/dist/src/core/policy-explain.js.map +1 -1
  213. package/dist/src/core/policy-load.d.ts +527 -0
  214. package/dist/src/core/policy-load.js +15 -3
  215. package/dist/src/core/policy-load.js.map +1 -1
  216. package/dist/src/core/policy-match.d.ts +281 -0
  217. package/dist/src/core/policy-match.js +20 -9
  218. package/dist/src/core/policy-match.js.map +1 -1
  219. package/dist/src/core/policy-proposal.d.ts +265 -0
  220. package/dist/src/core/prompt-layout.d.ts +221 -0
  221. package/dist/src/core/protected-path-guard.d.ts +453 -0
  222. package/dist/src/core/protected-path-guard.js +514 -35
  223. package/dist/src/core/protected-path-guard.js.map +1 -1
  224. package/dist/src/core/registration.d.ts +25 -0
  225. package/dist/src/core/reindex.d.ts +99 -0
  226. package/dist/src/core/sampler.d.ts +313 -0
  227. package/dist/src/core/sandbox.d.ts +290 -0
  228. package/dist/src/core/seal.d.ts +165 -0
  229. package/dist/src/core/state.d.ts +505 -0
  230. package/dist/src/core/task-file.d.ts +185 -0
  231. package/dist/src/core/telegram-config.d.ts +93 -0
  232. package/dist/src/core/token.d.ts +409 -0
  233. package/dist/src/core/token.js +21 -38
  234. package/dist/src/core/token.js.map +1 -1
  235. package/dist/src/core/validate.d.ts +138 -0
  236. package/dist/src/core/values.d.ts +137 -0
  237. package/dist/src/core/vault.d.ts +291 -0
  238. package/dist/src/core/verified-snapshot.d.ts +204 -0
  239. package/dist/src/core/verify.d.ts +336 -0
  240. package/dist/src/core/version.d.ts +8 -0
  241. package/dist/src/core/wysiwys.d.ts +370 -0
  242. package/dist/src/daemon/advance-child.d.ts +39 -0
  243. package/dist/src/daemon/advance.d.ts +466 -0
  244. package/dist/src/daemon/audit.d.ts +87 -0
  245. package/dist/src/daemon/daemon.d.ts +1180 -0
  246. package/dist/src/daemon/dark-session.d.ts +64 -0
  247. package/dist/src/daemon/draw-child.d.ts +36 -0
  248. package/dist/src/daemon/draw.d.ts +154 -0
  249. package/dist/src/daemon/git-evidence.d.ts +173 -0
  250. package/dist/src/daemon/git-evidence.js +1 -1
  251. package/dist/src/daemon/projection.d.ts +180 -0
  252. package/dist/src/daemon/prune.d.ts +207 -0
  253. package/dist/src/mcp/http.d.ts +113 -0
  254. package/dist/src/mcp/server.d.ts +265 -0
  255. package/dist/src/mcp/server.js +9 -1
  256. package/dist/src/mcp/server.js.map +1 -1
  257. package/docs/adapter-api.md +106 -0
  258. package/docs/cli-reference.md +389 -36
  259. package/docs/codex-enforced-session.md +30 -0
  260. package/package.json +12 -2
  261. package/schema/codex-instance.schema.json +82 -0
  262. package/schema/event.schema.json +2 -1
  263. package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
  264. package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
  265. package/schema/policy.schema.json +21 -1
  266. package/templates/codex/README.md +9 -0
@@ -0,0 +1,858 @@
1
+ /**
2
+ * Execution: the events that say a side effect actually happened (SPEC.md §8,
3
+ * §10.1, and the human-settled execution points of 2026-08-06).
4
+ *
5
+ * `core/gate.ts` decides whether an action *may* run and appends no
6
+ * `execution.*` event. `core/token.ts` spends a manual action's token. This
7
+ * module is the single door between those two facts and the world: it is where
8
+ * `execution.started` is appended before a command is spawned, and where
9
+ * `execution.completed` / `execution.failed` are appended after it exits.
10
+ *
11
+ * ## The five properties this module exists to hold
12
+ *
13
+ * 1. **Nothing starts without authorization.** On the manual path a valid token
14
+ * is REQUIRED; absent it, {@link startExecution} refuses `token-required` and
15
+ * appends nothing at all. On the supervised/autonomous paths no token exists
16
+ * (amended SPEC.md §6.3 gives them no grant), so authorization is proven
17
+ * differently and in this order: the action must be declared in a
18
+ * `task.registered` record, the policy must be attested, loop safety must not
19
+ * have escalated the task, no execution may already have started for the key,
20
+ * the executor's recomputed `payload_hash` must equal the one the declaration
21
+ * bound to (APRV-140), and the budget must pass. Only then is
22
+ * `execution.started` appended.
23
+ * 2. **`started` precedes the side effect.** The CLI's `approval run` appends
24
+ * the start event *before* it spawns the child, never after. A log that
25
+ * records an execution only once it succeeded is a log that cannot tell you
26
+ * about the one that did not.
27
+ * 3. **A crash therefore leaves a dangling execution, and that is correct.**
28
+ * Between `started` and its outcome the log honestly says "this began and we
29
+ * do not know how it ended". {@link danglingExecutions} surfaces that state
30
+ * distinctly — `approval status` reports it, `approval queue` does not,
31
+ * because a dangling execution is not a pending decision. It is one of five
32
+ * custody states {@link executionCustody} distinguishes (APRV-120), and the
33
+ * other four matter for the same reason: a harness execution is terminal by
34
+ * design rather than debris, and an attempt whose outcome is unknown is
35
+ * neither a failure nor a thing to retry.
36
+ * 4. **Nothing auto-repairs.** No function here closes a dangling execution as a
37
+ * side effect of anything else. A second `approval run` for the same key does
38
+ * not "recover" the first; it refuses (`token-consumed` on the manual path,
39
+ * `already-executed` off it). Recovery is a human calling
40
+ * {@link resolveExecution} with the outcome they actually observed and a
41
+ * mandatory note saying how they know — the same append path, no fabricated
42
+ * exit code, `attested_by_human: true` so no reader mistakes it for a
43
+ * machine's report. ({@link finishExecution} is the mechanical sibling, used
44
+ * by `approval run`, which watched the child exit.) An automatic
45
+ * reconciliation would have to *guess* whether the email went out, and a
46
+ * guess written into an append-only log is indistinguishable from a fact.
47
+ * The same rule governs an INDETERMINATE outcome, more strictly:
48
+ * {@link reconcileExecution} is human-only, appends beside the record rather
49
+ * than over it, and nothing anywhere converts one into completed or failed.
50
+ * 5. **The budgets contract is honored at the documented charge point.**
51
+ * `core/budgets.ts` charges the manual path at `approval.granted` and the
52
+ * supervised/autonomous paths at `execution.started`. This module is that
53
+ * second charge point: it evaluates budgets at the start timestamp, appends
54
+ * `budget.exceeded` and refuses when they fail, and records
55
+ * `payload.class` + `payload.est_cost_usd` on every start event it writes.
56
+ * The manual path is charged at grant and is deliberately NOT charged again
57
+ * here — `consumeToken` writes that start event, and the evaluator already
58
+ * ignores a start whose window holds a matching grant.
59
+ *
60
+ * ## Loop safety (SPEC.md §10.2), from this side
61
+ *
62
+ * Three consecutive `execution.failed` events for one task escalate it to
63
+ * manual. `core/loop.ts` computes that; this module enforces it on the
64
+ * execution side: an escalated task's supervised/autonomous action refuses with
65
+ * `loop-escalated`, which is not a ban but a redirection — request the action,
66
+ * have a human grant it, and run it with the token. `core/gate.ts` enforces the
67
+ * matching half at intake so the redirection is visible one step earlier.
68
+ *
69
+ * ## Time (amended SPEC.md §8, A2)
70
+ *
71
+ * `execution.*` events are gate-typed, so their timestamps are assigned by the
72
+ * runtime at the write boundary: no public function here takes a `ts`, each
73
+ * reads {@link ExecuteOptions.clock} once, and the party whose budget window
74
+ * and TTL are being judged does not author the clock. Replay is preserved by
75
+ * injection — a test hands in a fixed clock, production hands in nothing.
76
+ */
77
+ import { type AttestationRefusalDetail } from "./attest.js";
78
+ import { type BudgetVerdict } from "./budgets.js";
79
+ import { type ClockOptions } from "./clock.js";
80
+ import { type AppendError, type AppendOptions, type EventRecord } from "./log.js";
81
+ import { type Autonomy } from "./policy-load.js";
82
+ import type { SandboxState } from "./sandbox.js";
83
+ export { isLoopEscalated, loopEscalation, LOOP_ESCALATION_THRESHOLD, type TaskLoopState, } from "./loop.js";
84
+ /**
85
+ * The closed set of execution refusal codes. Frozen public API in the same sense
86
+ * the gate's and the token module's are: an agent branches on these to decide
87
+ * whether to fix itself, ask a human, or stop.
88
+ *
89
+ * The five token codes are re-exposed verbatim rather than collapsed into one:
90
+ * `approval run` on the manual path is a token spend, and "you presented no
91
+ * token" (`token-required`), "you presented the wrong one" (`token-mismatch`),
92
+ * and "it was already spent" (`token-consumed`) call for three different
93
+ * responses.
94
+ */
95
+ export declare const EXECUTE_REFUSAL_CODES: readonly [
96
+ /** Execution preflight requires a person or agent principal, never runtime identity. */
97
+ "actor-invalid",
98
+ /** No `task.registered` record declares this action key (SPEC.md §7). */
99
+ "action-not-registered",
100
+ /**
101
+ * The action's class resolves to `human-only` (APRV-185, amended SPEC.md
102
+ * §5.2): the policy reserves it to human hands, and a person performs it
103
+ * outside agent execution entirely.
104
+ *
105
+ * Refused on BOTH paths, before either is chosen, which is what separates it
106
+ * from `token-required`. That code is a redirection — get a token and come
107
+ * back — and this one is not: there is no token to get and no grant that
108
+ * could mint one, because `core/gate.ts` refuses the request that would open
109
+ * one under the same code. Nothing is appended on either path, and no retry
110
+ * of any shape changes the answer.
111
+ *
112
+ * Surfaced verbatim from `core/token.ts` as well, for a manual-path spend of
113
+ * a token whose class a policy amendment raised after the grant, so an
114
+ * executor meets one spelling of one fact.
115
+ */
116
+ "class-human-only",
117
+ /** The class resolves manual and no token was presented. Nothing appended. */
118
+ "token-required",
119
+ /** Loop safety escalated the task to manual (SPEC.md §10.2). */
120
+ "loop-escalated",
121
+ /** Policy is unattested or its bytes changed (`core/attest.ts`). */
122
+ "policy-not-attested",
123
+ /** The attested policy changed after supervised-live intake decided to proceed. */
124
+ "policy-drift",
125
+ /** An `execution.started` already exists for this key (idempotency). */
126
+ "already-executed",
127
+ /** Budgets refused the start; a `budget.exceeded` event WAS appended. */
128
+ "budget-exceeded",
129
+ /** `finishExecution` found no unfinished `execution.started`. */
130
+ "not-started",
131
+ /** `finishExecution` found the started execution already closed. */
132
+ "already-finished",
133
+ /** No grant governs this manual action key. */
134
+ "not-granted",
135
+ /** A grant exists, but the presented token is not its preimage. */
136
+ "token-mismatch",
137
+ /** The token was already spent. */
138
+ "token-consumed",
139
+ /** The parent request's TTL lapsed. */
140
+ "token-expired",
141
+ /** A human withdrew the grant. */
142
+ "token-revoked",
143
+ /**
144
+ * The grant was harness-executed and minted no token (APRV-106). Surfaced
145
+ * verbatim from `core/token.ts` so the executor's vocabulary stays that
146
+ * module's vocabulary: an agent that reads this has not lost a token, it is
147
+ * holding a grant that authorized a process which runs the command itself.
148
+ */
149
+ "harness-executed",
150
+ /**
151
+ * The payload presented does not hash to the bytes the grant approved
152
+ * (amended SPEC.md §10, A1). Nothing was appended and the token is still live.
153
+ */
154
+ "payload-mismatch",
155
+ /**
156
+ * `resolveExecution` was called without the mandatory human observation, or
157
+ * by an actor that is not a `human:`. Recorded here rather than reusing
158
+ * `not-started` because the log is unchanged for a different reason: the
159
+ * caller, not the state.
160
+ */
161
+ "actor-not-human",
162
+ /**
163
+ * The key's latest `execution.started` is a DELEGATED record (APRV-117,
164
+ * APRV-120): it carries `payload.execution: "harness"`, so the harness ran the
165
+ * command and this runtime never observed an exit status. The record is
166
+ * complete as written and terminal by design, and no outcome may be placed
167
+ * over it.
168
+ *
169
+ * Distinct from the two refusals it sits between, and the distinctions are the
170
+ * point. `not-started` says nothing began; `already-finished` says something
171
+ * began and an outcome already exists. This one says the thing that began is
172
+ * not this runtime's to close: a `completed` or `failed` written here would
173
+ * report an exit code nobody watched, and an `execution.completed` would
174
+ * additionally clear the task's loop-escalation streak (SPEC.md §10.2) on the
175
+ * strength of it. {@link executionCustody} reports these as `delegated` and
176
+ * {@link danglingExecutions} deliberately leaves them out, so this code is the
177
+ * enforcement half of a custody state the projections already draw.
178
+ */
179
+ "execution-delegated",
180
+ /**
181
+ * The key's execution ended in an unknown outcome (APRV-120) and has not been
182
+ * reconciled. INDETERMINATE IS A CUSTODY STATE: the token stays spent, the
183
+ * idempotency key stays burned, and a re-run is refused here rather than
184
+ * anywhere else, because "we do not know whether this happened" is a
185
+ * different fact from "this already happened" and calls for a different
186
+ * repair — a person establishing which it was, with `execution reconcile`.
187
+ */
188
+ "execution-indeterminate",
189
+ /**
190
+ * `reconcileExecution` found no unreconciled `execution.indeterminate` for
191
+ * the key. There is nothing whose outcome is in doubt, so there is nothing to
192
+ * resolve; a dangling execution is closed with `execution resolve` instead.
193
+ */
194
+ "not-indeterminate",
195
+ /**
196
+ * The indeterminate outcome already carries a resolution. A second one would
197
+ * be a second answer to a question a person already answered, and the first
198
+ * record is never rewritten.
199
+ */
200
+ "already-reconciled",
201
+ /**
202
+ * `execution resolve --dangling` was asked to attest with no terminal to
203
+ * attest at, and without `--yes` (APRV-264). The bulk form writes
204
+ * `attested_by_human: true` on every record it appends, and a confirmation a
205
+ * pipe could answer is not an attestation. Distinct from `actor-not-human`,
206
+ * which is about WHO is attesting: this one is about whether anybody was
207
+ * actually asked.
208
+ */
209
+ "dangling-stdin-not-tty",
210
+ /**
211
+ * The bulk confirmation was declined, or withdrawn at the prompt (APRV-264).
212
+ * Nothing was appended and the dangling executions stand exactly as they
213
+ * were. Its own code because "the operator said no" and "the operator could
214
+ * not be asked" are different facts about the same prompt.
215
+ */
216
+ "dangling-declined",
217
+ /** The log could not be read, or holds a line that is not a record. */
218
+ "log-unreadable",
219
+ /** The log's final line is unterminated (a crashed write). */
220
+ "log-torn-tail",
221
+ /** The chain does not verify; nothing may execute on an untrustworthy log. */
222
+ "log-corrupt",
223
+ /**
224
+ * The append itself failed; `append` carries the underlying error. Its `code`
225
+ * is `head-moved` when a record landed between this module's read and its
226
+ * append, so the idempotency and budget checks that authorized the write were
227
+ * made against an older log. Nothing was written. Since APRV-236 this code
228
+ * reaches a caller of {@link startExecution} only after the bounded
229
+ * read-check-append retry is spent (`core/head-retry.ts`), and its message
230
+ * says how many attempts were made; a single lost race is re-derived rather
231
+ * than reported.
232
+ */
233
+ "append-failed"];
234
+ export type ExecuteRefusalCode = (typeof EXECUTE_REFUSAL_CODES)[number];
235
+ /** Every execution failure is one of these. Nothing here throws. */
236
+ export interface ExecuteRefusal {
237
+ ok: false;
238
+ code: ExecuteRefusalCode;
239
+ message: string;
240
+ /** Attestation discriminator, when `code` is `policy-not-attested`. */
241
+ detail?: AttestationRefusalDetail;
242
+ /** The failing verdicts, when `code` is `budget-exceeded`. */
243
+ verdicts?: BudgetVerdict[];
244
+ /** The seq of the record that produced the refusal, when there is one. */
245
+ seq?: number;
246
+ /** An event appended alongside the refusal: only ever `budget.exceeded`. */
247
+ record?: EventRecord;
248
+ /** The underlying append error, when `code` is `append-failed`. */
249
+ append?: AppendError;
250
+ }
251
+ /**
252
+ * Options shared by the execution verbs.
253
+ *
254
+ * No `ts`: `execution.*` events are gate-typed, so amended SPEC.md §8 (A2)
255
+ * assigns their timestamps at the write boundary from {@link ClockOptions}.
256
+ */
257
+ export interface ExecuteOptions extends ClockOptions {
258
+ /**
259
+ * The raw single-use token printed by `approval grant`. REQUIRED for an
260
+ * action whose class resolves to `manual`; meaningless off that path, where no
261
+ * token was ever minted.
262
+ */
263
+ token?: string;
264
+ /**
265
+ * The hash of the payload about to be executed (amended SPEC.md §10, A1),
266
+ * forwarded to `core/token.ts` on the manual path and checked against the
267
+ * registered declaration off it (APRV-140). `approval run` computes it with
268
+ * `runPayloadHash(argv, cwd)`; an adapter with a different payload computes
269
+ * its own. REQUIRED on every path: under A1 every manual grant binds to
270
+ * bytes, and under APRV-140 so does every declaration that executes.
271
+ *
272
+ * It is never read from the log. A value read from the log would prove
273
+ * nothing: the point is that the executor states, independently, what it
274
+ * holds, and the runtime compares.
275
+ */
276
+ presentedPayloadHash?: string;
277
+ /** Where to find `APPROVAL.md`. Same semantics as `loadPolicy`. */
278
+ policy?: {
279
+ dir?: string;
280
+ file?: string;
281
+ };
282
+ /** Schema directory, passed to the append's write-boundary validation. */
283
+ schemaDir?: string;
284
+ /** Lock tuning for the append path. */
285
+ append?: AppendOptions;
286
+ /**
287
+ * How many credential-bearing variables the executor withheld from the child
288
+ * it is about to spawn (APRV-205), recorded on `execution.started` as
289
+ * `env_stripped`.
290
+ *
291
+ * A COUNT, never a name and never a value: a name is half of a credential,
292
+ * and SPEC.md §11.1's raw-secrets invariant is not satisfied by leaking the
293
+ * other half slowly. It is informational — nothing in the gate reads it back
294
+ * and no decision turns on it, which is what keeps it clear of §11.1's
295
+ * "self-reported fields never reduce scrutiny". No CLI flag sets it: it is
296
+ * computed by `core/child-env.ts` at the spawn site, in the same process that
297
+ * spawns.
298
+ */
299
+ envStripped?: number;
300
+ /**
301
+ * The room the child ran in (APRV-193), recorded on `execution.started` as
302
+ * `sandbox`.
303
+ *
304
+ * Computed at the spawn site by `core/sandbox.ts` from what the MACHINE can
305
+ * do and what the caller was holding, never from a claim about either: the
306
+ * three interesting values are the runtime's own findings, and the fourth
307
+ * (`opted-out`) is the operator's `--no-sandbox`, which is recorded precisely
308
+ * because an opt-out nobody can see afterwards is an opt-out that costs
309
+ * nothing to take. Informational in the same sense `envStripped` is: nothing
310
+ * in the gate reads it back and no decision turns on it, which is what keeps
311
+ * it clear of §11.1's "self-reported fields never reduce scrutiny" — the
312
+ * field records a decision already made rather than making one.
313
+ */
314
+ sandbox?: SandboxState;
315
+ /**
316
+ * Where per-request private keys live (APRV-105). Defaults to `.approval/keys/`
317
+ * beside the log. Read when no `token` is passed and a grant carries a
318
+ * `token_sealed`; unlinked once the token is spent.
319
+ */
320
+ keyStoreDir?: string;
321
+ /**
322
+ * How many times {@link startExecution} re-derives its verdict after a
323
+ * `head-moved` refusal, at most `core/head-retry.ts`'s `HEAD_MOVED_ATTEMPTS`
324
+ * (APRV-236).
325
+ *
326
+ * Only ever lowers the bound, on the same terms as `core/gate.ts`'s option of
327
+ * the same name: `1` is the unretried writer, which is what a test pins the
328
+ * pre-APRV-236 shape with, and a larger number, a zero or a fraction falls
329
+ * back to the runtime's own value. The ceiling is not a caller's to raise.
330
+ */
331
+ retryOnHeadMoved?: number;
332
+ /** Internal binding from an unrecorded supervised-live intake verdict. */
333
+ expectedPolicySha256?: string;
334
+ }
335
+ /** What a `task.registered` record declared about one action key. */
336
+ export interface Declaration {
337
+ task: string;
338
+ class: string;
339
+ /**
340
+ * The declared cost as a canonical decimal USD string (APRV-121), `"0"` when
341
+ * the declaration named none. A `task.registered` record written before that
342
+ * change carries a JSON number and normalizes to the same string here.
343
+ */
344
+ est_cost_usd: string;
345
+ reversible: boolean | null;
346
+ summary: string | null;
347
+ /**
348
+ * The content binding the registration declared (amended SPEC.md §6.2,
349
+ * APRV-140), or `null` when it declared none. Off the manual path this is the
350
+ * ONLY thing an execution can be checked against: there is no grant, so the
351
+ * declaration is the whole of what was authorized.
352
+ */
353
+ payload_hash: string | null;
354
+ }
355
+ /**
356
+ * Find the declaration for `actionKey` across every `task.registered` record.
357
+ *
358
+ * The log — not the task file, which may have been edited since — is the
359
+ * authority, exactly as it is for `approval request`. The search is by action
360
+ * key alone because an execution names a key, not a task: SPEC.md §7 makes the
361
+ * `idempotency_key` the identity of a side effect, and an undeclared key is the
362
+ * one thing that must never execute.
363
+ *
364
+ * A key must be declared by exactly one task. If a log somehow carries the same
365
+ * key under two tasks, this returns the last, but callers on an enforcement path
366
+ * MUST first fail closed via {@link declaringTasks}: the collision is refused at
367
+ * registration (`core/gate.ts`, APRV-138), so a log that still holds one is
368
+ * untrustworthy and nothing may execute from the guess.
369
+ */
370
+ export declare function findDeclaration(records: EventRecord[], actionKey: string): Declaration | null;
371
+ /**
372
+ * Every declaration question the log answers, keyed once (APRV-211).
373
+ *
374
+ * The three per-key helpers above each scan the whole log, which is the right
375
+ * shape for the gate (one key, one decision, no bookkeeping to get wrong) and
376
+ * the wrong shape for a caller asking about thousands of keys: `core/audit.ts`'s
377
+ * candidate selection asked all three per `execution.started` and spent three
378
+ * full scans per candidate, which is quadratic in the log and measured at 3.3 s
379
+ * on a ten-thousand-record log.
380
+ *
381
+ * This is a **per-call derivation and nothing more**. It caches nothing across
382
+ * calls, holds no state, reads no file and no clock, and is built from the
383
+ * records the caller already verified. The per-key helpers remain the gate's
384
+ * API: an enforcement path deciding ONE action asks them, because a decision
385
+ * that depends on an index built somewhere else is a decision that depends on
386
+ * that index being fresh.
387
+ */
388
+ export interface DeclarationIndex {
389
+ /** Per key, the distinct tasks declaring it, exactly as {@link declaringTasks}. */
390
+ declaringTasks: Map<string, string[]>;
391
+ /** Per key, the LAST declaration in log order, exactly as {@link findDeclaration}. */
392
+ declarations: Map<string, Declaration>;
393
+ /** Keys the log holds an `approval.requested` for, as {@link hasApprovalCycle}. */
394
+ requested: Set<string>;
395
+ }
396
+ export declare function indexDeclarations(records: readonly EventRecord[]): DeclarationIndex;
397
+ /**
398
+ * The distinct tasks that declare `actionKey`. More than one is a cross-task
399
+ * collision (APRV-138): the registration boundary refuses these, so a log that
400
+ * still holds one cannot be trusted to say which declaration governs. Every
401
+ * enforcement caller of {@link findDeclaration} guards on this and fails closed
402
+ * rather than executing the last-registered (possibly weaker) declaration.
403
+ */
404
+ export declare function declaringTasks(records: EventRecord[], actionKey: string): string[];
405
+ /**
406
+ * Has a human ever been asked about this action key (amended SPEC.md §6.3,
407
+ * APRV-127)?
408
+ *
409
+ * True as soon as the log holds one `approval.requested` for the key, and it
410
+ * stays true: a rejected, expired or withdrawn cycle is still a cycle, and an
411
+ * action that could shed its gate by being refused would be an action that
412
+ * profits from a "no".
413
+ *
414
+ * Pure, and derived from the log alone — never from a payload field a requester
415
+ * wrote about itself. Two callers: {@link startExecution}, which requires the
416
+ * token of any action that went through the gate, and `core/audit.ts`, which
417
+ * leaves such actions out of the retrospective pool because a human already
418
+ * looked.
419
+ */
420
+ export declare function hasApprovalCycle(records: readonly EventRecord[], actionKey: string): boolean;
421
+ export type StartResult = {
422
+ ok: true;
423
+ /** The appended `execution.started` record. */
424
+ record: EventRecord;
425
+ /** The autonomy that admitted it — `manual` means a token was spent. */
426
+ autonomy: Autonomy;
427
+ task: string;
428
+ class: string;
429
+ /** Canonical decimal USD string (APRV-121). */
430
+ est_cost_usd: string;
431
+ /** The digest of the spent token, on the manual path only. */
432
+ tokenSha256?: string;
433
+ } | ExecuteRefusal;
434
+ /** Internal adapter preflight result. The token is never rendered or logged. */
435
+ export type ExecutionEligibilityResult = {
436
+ ok: true;
437
+ mode: "token" | "policy";
438
+ autonomy: Autonomy;
439
+ /** Explicit or locally delivered token, present only on the token path. */
440
+ token?: string;
441
+ } | ExecuteRefusal;
442
+ /**
443
+ * Check whether an adapter execution may proceed to credential resolution.
444
+ *
445
+ * This is a preflight, never an execution permit. A successful result appends
446
+ * nothing, consumes nothing and leaves delivered-token key material in place.
447
+ * {@link startExecution} must still run after adapter precheck and repeats every
448
+ * check against a fresh verified read before compare-and-append. A budget
449
+ * refusal retains `startExecution`'s required `budget.exceeded` audit record.
450
+ */
451
+ export declare function checkExecutionEligibility(logPath: string, actionKey: string, options: ExecuteOptions, actor: string): ExecutionEligibilityResult;
452
+ /**
453
+ * Begin an execution: the single entry point for appending `execution.started`.
454
+ *
455
+ * Check order, and why it is this order:
456
+ *
457
+ * 1. **The log reads**, so a torn or unreadable log stops everything before a
458
+ * policy question is asked.
459
+ * 2. **The declaration.** An action key no `task.registered` record declares is
460
+ * `action-not-registered` — SPEC.md §7's "an action's class MUST be declared
461
+ * before an execution token can be requested for it", enforced at the last
462
+ * possible moment as well as the first.
463
+ * 3. **Policy resolution**, including SPEC.md §7's irreversibility floor (the
464
+ * declared `reversible: false` forces `manual`, which forces a token). A
465
+ * failed policy load resolves everything to `manual` — `policy-match.ts`'s
466
+ * contract, not softened here — so an unparseable policy makes every action
467
+ * require a human's token.
468
+ * 4. **Manual path: the token, or nothing.** No token → `token-required`, and
469
+ * the log is untouched. With one, `consumeToken` verifies it and appends the
470
+ * start event; a class that resolves manual but was never granted refuses
471
+ * `not-granted` from that layer. Attestation is not re-checked here: the
472
+ * grant that minted the token could only have happened under an attested
473
+ * policy, and re-checking would refuse an execution a human already
474
+ * authorized because a file changed afterwards.
475
+ * 5. **Non-manual path**, in order: attestation → loop escalation → idempotency
476
+ * → content binding → budgets → append. Attestation first because an
477
+ * unverified policy cannot answer the autonomy question it was just asked to
478
+ * answer; the binding (APRV-140) after the free checks and before the
479
+ * charging one; budgets last because a budget refusal *writes*
480
+ * (`budget.exceeded`), and the cheaper refusals must leave the log
481
+ * untouched.
482
+ *
483
+ * `actor` is not pre-validated: the event schema is the authority on actor
484
+ * shape, and a malformed one is refused at the write boundary as
485
+ * `append-failed`, with the schema's own error attached. One rule about actors,
486
+ * enforced in one place.
487
+ */
488
+ export declare function startExecution(logPath: string, actionKey: string, options: ExecuteOptions, actor: string): StartResult;
489
+ /**
490
+ * What a failure says about itself, beyond its exit status (APRV-211).
491
+ *
492
+ * Machine-readable first: `code` is the executor's own closed refusal code (for
493
+ * the daemon's advance, one of `cli/log-advance.ts`'s
494
+ * `LOG_ADVANCE_REFUSAL_CODES`), so a status surface can branch on it rather
495
+ * than parse prose. The message is the sentence a human reads.
496
+ *
497
+ * It is a REPORT and never an authorization: nothing in the gate reads either
498
+ * field back, no decision anywhere turns on them, and SPEC.md §11.1's rule that
499
+ * self-reported fields never reduce scrutiny is untouched — the only thing they
500
+ * can do is make a failure explicable. The message is written by this runtime's
501
+ * own code and must stay that way: a raw child stderr forwarded here could
502
+ * carry a credential into a permanent log.
503
+ */
504
+ export interface FailureReason {
505
+ code: string;
506
+ message: string;
507
+ }
508
+ /**
509
+ * The provider's own identifier for a completed effect (APRV-251, SPEC.md §8).
510
+ *
511
+ * Two strings and no more, because the schema admits two and no more: the
512
+ * adapter that executed the action, and the id the provider's record files the
513
+ * effect under. See {@link FinishOptions.providerRef} for who may write one and
514
+ * what nobody may do with it.
515
+ */
516
+ export interface ProviderRef {
517
+ adapter: string;
518
+ id: string;
519
+ }
520
+ /**
521
+ * The bounds SPEC.md §8 and `schema/event.schema.json` place on a reference.
522
+ *
523
+ * Printable ASCII with no spaces, and short. An identifier is short; the bound
524
+ * is what keeps the field from becoming somewhere to put a message. Stated here
525
+ * as well as in the schema so that the write path can DECLINE to record a
526
+ * reference that would not validate, rather than hand the schema a record it
527
+ * will reject and leave a completed side effect with no outcome in the log.
528
+ */
529
+ export declare const PROVIDER_REF_ADAPTER_MAX = 64;
530
+ export declare const PROVIDER_REF_ID_MAX = 256;
531
+ /** Does `value` fit what the schema will accept for a reference member? */
532
+ export declare function providerRefMemberOk(value: string, max: number): boolean;
533
+ /** Is `ref` recordable, in full? A half-recordable reference is not recorded. */
534
+ export declare function providerRefRecordable(ref: ProviderRef): boolean;
535
+ /** {@link ExecuteOptions} plus the reason a non-zero exit carries (APRV-211). */
536
+ export interface FinishOptions extends ExecuteOptions {
537
+ reason?: FailureReason;
538
+ /**
539
+ * The same shape for a SUCCESS that is worth explaining (APRV-234).
540
+ *
541
+ * `reason` covers the failure case and deliberately says nothing when the
542
+ * exit was zero, because a completion usually has nothing to explain.
543
+ * Sometimes it does: the daemon's advance rebuilds the day's records branch
544
+ * on the trunk when the trunk has moved under it, and a status surface
545
+ * reading this log a day later cannot tell that from an exit status. So the
546
+ * executor may state it, in the same closed `{code, message}` shape, recorded
547
+ * on `execution.completed` only and only when the caller supplies it.
548
+ *
549
+ * A REPORT and never an authorization, exactly as {@link FailureReason} is:
550
+ * nothing in the gate reads it back, no decision anywhere turns on it, and
551
+ * SPEC.md §11.1's rule that self-reported fields never reduce scrutiny is
552
+ * untouched. Like the reason, it is written by this runtime's own code — a
553
+ * raw child stderr forwarded here could carry a credential into a permanent
554
+ * log.
555
+ */
556
+ note?: FailureReason;
557
+ /**
558
+ * The identifier the provider's own record files this effect under
559
+ * (APRV-251), recorded on `execution.completed` and on nothing else.
560
+ *
561
+ * `approval coverage` reads a witness this project does not write (git, `gh`,
562
+ * an adapter's provider) and asks whether the log ever saw each effect. With
563
+ * no reference the strongest answer available is a record of a matching class
564
+ * inside the effect's window, so a gated send covers an ungated one of the
565
+ * same class beside it. A reference names the exact effect, which is what
566
+ * turns that answer into one about this action and no other.
567
+ *
568
+ * Written by the adapter contract and by nothing else in this runtime: the
569
+ * `adapter` half is the registered adapter's name, which the contract already
570
+ * holds, and the `id` half is lifted from what the adapter returned, after
571
+ * the redaction sweep that scans everything else it returned. The contract
572
+ * omits a reference whose bytes that sweep touched, because a redacted
573
+ * identifier matches nothing and would read like one that does.
574
+ *
575
+ * A REPORT and never an authorization, exactly as {@link FailureReason} and
576
+ * `note` are: nothing in the gate reads it back, no decision anywhere turns
577
+ * on it, and SPEC.md §11.1's rule that self-reported fields never reduce
578
+ * scrutiny is untouched. Recorded only when the caller states one, so an
579
+ * execution whose adapter names no reference records none.
580
+ */
581
+ providerRef?: ProviderRef;
582
+ /**
583
+ * A test seam and nothing else (APRV-261): called once, between the read that
584
+ * authorizes this outcome and the append that records it.
585
+ *
586
+ * It exists because the interleaving APRV-233 is about — a record landing in
587
+ * exactly that window, so the compare-and-append meets a head that is no
588
+ * longer the one the checks were made against — cannot be produced by timing
589
+ * without producing a coin flip instead. The old harness held the append
590
+ * lockfile, slept a flat 300 ms hoping a child process had got past its read,
591
+ * released the lock and raced. A seam constructs the window rather than
592
+ * waiting for it, which is the difference between a test that proves the
593
+ * retry and a test that usually does.
594
+ *
595
+ * WHAT IT CANNOT DO, which is why it is safe to have on this path. It takes
596
+ * nothing and returns nothing, so no check can be relaxed, no field can be
597
+ * supplied and no verdict can be reported by it. The refusals above it have
598
+ * already run; the append below it still states `open.head`, the head THAT
599
+ * read observed, so anything the seam does to the log is caught by the
600
+ * compare-and-append exactly as an external writer's record would be. The
601
+ * strictest thing a seam can do is move the head and make the attempt fail
602
+ * closed, which is precisely what the test asks it for. Nothing in the
603
+ * runtime sets it: `grep -rn afterRead src/` finds this declaration, the one
604
+ * call below it, and the one line in `daemon/advance.ts` that forwards a
605
+ * caller's.
606
+ */
607
+ afterRead?: () => void;
608
+ }
609
+ export type FinishResult = {
610
+ ok: true;
611
+ record: EventRecord;
612
+ event: "execution.completed" | "execution.failed";
613
+ exitCode: number;
614
+ task: string;
615
+ } | ExecuteRefusal;
616
+ /**
617
+ * Close an execution with the outcome that actually happened.
618
+ *
619
+ * Exit `0` appends `execution.completed`; anything else appends
620
+ * `execution.failed`. Both carry `payload.exit_code` — the number, unmapped and
621
+ * uninterpreted, so a reader can tell exit 1 from exit 127 from a signal death
622
+ * (which `approval run` records as `128 + signal`, the shell convention).
623
+ * Neither event consumes budget: the commitment was charged at authorization
624
+ * time and charging it again would double-count (`core/budgets.ts`).
625
+ *
626
+ * Refuses `not-started` when the key has no `execution.started`,
627
+ * `already-finished` when the most recent start already has an outcome after
628
+ * it, and `execution-delegated` when that start was the harness's rather than
629
+ * this runtime's (APRV-146). All three leave the log untouched.
630
+ *
631
+ * **This is the human recovery path for a dangling execution**, and it is
632
+ * deliberately the only one. Nothing in this codebase closes a dangling
633
+ * execution automatically: an operator who knows the email went out records
634
+ * `0`, an operator who knows it did not records the failure, and either way the
635
+ * log holds an observation rather than a runtime's guess.
636
+ */
637
+ export declare function finishExecution(logPath: string, actionKey: string, exitCode: number, actor: string, options?: FinishOptions): FinishResult;
638
+ /** What a human observed about a dangling execution. */
639
+ export type ResolveOutcome = "completed" | "failed";
640
+ export type ResolveResult = {
641
+ ok: true;
642
+ record: EventRecord;
643
+ event: "execution.completed" | "execution.failed";
644
+ outcome: ResolveOutcome;
645
+ task: string;
646
+ } | ExecuteRefusal;
647
+ /**
648
+ * Close a dangling execution with what a human actually observed.
649
+ *
650
+ * {@link finishExecution} is the mechanical path: `approval run` knows the
651
+ * child's exit code because it waited for it. This is the path for the case
652
+ * that code cannot cover — the runtime died between `execution.started` and its
653
+ * outcome, so the log honestly says "this began and we do not know how it
654
+ * ended", and only a person who went and looked can say more.
655
+ *
656
+ * Five properties, all deliberate:
657
+ *
658
+ * 1. **The note is mandatory and non-empty.** The whole value of this event is
659
+ * the observation behind it; an unexplained human-attested outcome is
660
+ * indistinguishable from a guess, and a guess written into an append-only
661
+ * log is indistinguishable from a fact. The CLI refuses an empty note as a
662
+ * usage error before reaching here, and this refuses it again.
663
+ * 2. **Human-only.** An agent closing its own dangling execution is the agent
664
+ * reporting on itself, which is the one thing the log exists not to accept.
665
+ * 3. **`exit_code: null`.** Not `0`, not `127`: nobody ran anything and there
666
+ * is no code to report. A fabricated exit code would read exactly like an
667
+ * observed one, and `payload.attested_by_human: true` marks the difference
668
+ * for every reader and every projection.
669
+ * 4. **A harness execution is out of reach** (APRV-146). A delegated start is
670
+ * refused `execution-delegated` here as it is in {@link finishExecution}: the
671
+ * record is terminal by design, and a person attesting an outcome for a
672
+ * command this runtime never watched would be attesting to the one thing the
673
+ * log already says nobody observed.
674
+ * 5. **No attestation requirement.** Resolve records a fact a human observed;
675
+ * it exercises no policy authority — it authorizes nothing, spends no
676
+ * budget, mints no token — so it does not require an attested policy. A
677
+ * dangling execution left unclosable because a policy file was edited would
678
+ * be a repair blocked by an unrelated fact.
679
+ */
680
+ export declare function resolveExecution(logPath: string, actionKey: string, outcome: ResolveOutcome, note: string, actor: string, options?: ExecuteOptions): ResolveResult;
681
+ export type IndeterminateResult = {
682
+ ok: true;
683
+ record: EventRecord;
684
+ reason: IndeterminateReason;
685
+ task: string;
686
+ } | ExecuteRefusal;
687
+ /**
688
+ * Close an execution as INDETERMINATE: the side effect was attempted and
689
+ * nobody knows whether it committed (APRV-120).
690
+ *
691
+ * `execution.failed` used to carry this case, and conflating the two is what
692
+ * made a retry look safe. An adapter that times out mid-send reads, in a log
693
+ * that only knows `failed`, exactly like one that never opened a socket; a
694
+ * caller reading the second reasonably tries again, and against the first that
695
+ * is a double send. So the runtime writes down which it is, and the difference
696
+ * is positional rather than a judgment: the adapter contract records
697
+ * `execution.failed` for everything that goes wrong BEFORE `act` is entered
698
+ * (provably not committed) and this for anything after (provably nothing).
699
+ *
700
+ * Three properties, all deliberate:
701
+ *
702
+ * 1. **The consumption is burned.** The token was spent at
703
+ * `execution.started` and stays spent; the idempotency key stays used; the
704
+ * budget stays charged. Refunding an attempt whose outcome is unknown would
705
+ * be the runtime deciding the effect did not happen, which is the one thing
706
+ * nobody here knows.
707
+ * 2. **No exception text.** `reason` is a closed code and nothing else is
708
+ * recorded. An error message is where a credential rides into the log with
709
+ * a plausible excuse, and §11.1's third invariant does not have an
710
+ * exception for diagnostics. The caller still receives the message, redacted,
711
+ * from the adapter contract.
712
+ * 3. **Nothing auto-resolves.** No function here, and nothing in the daemon,
713
+ * ever converts this into completed or failed. Only
714
+ * {@link reconcileExecution} does, on a person's evidence.
715
+ */
716
+ export declare function indeterminateExecution(logPath: string, actionKey: string, reason: IndeterminateReason, actor: string, options?: ExecuteOptions): IndeterminateResult;
717
+ export type ReconcileResult = {
718
+ ok: true;
719
+ record: EventRecord;
720
+ resolution: ReconcileResolution;
721
+ task: string;
722
+ /** The `execution.indeterminate` record this resolves. */
723
+ indeterminateSeq: number;
724
+ } | ExecuteRefusal;
725
+ /**
726
+ * Record what a person established about an indeterminate execution.
727
+ *
728
+ * The counterpart of {@link resolveExecution}, and deliberately a separate verb
729
+ * with separate refusals: `resolve` closes an execution nobody watched finish,
730
+ * and this resolves one whose effect may or may not have landed. The questions
731
+ * are different ("what did the runtime do?" against "did the far side commit?"),
732
+ * the evidence is different (this repo's log against the relying party's), and
733
+ * an operator who reached for the wrong one should be told so rather than
734
+ * quietly write the wrong record.
735
+ *
736
+ * Four properties:
737
+ *
738
+ * 1. **The original is never rewritten.** This appends a record that NAMES the
739
+ * indeterminate one by seq. The observation "we did not know" survives its
740
+ * own resolution, which is the whole reason the log is append-only, and an
741
+ * auditor can see both the doubt and its answer.
742
+ * 2. **Human-only, and never the daemon.** An agent reconciling its own unknown
743
+ * outcome is the executing party reporting on itself; a daemon doing it on a
744
+ * schedule is a guess with a cron entry. The mandatory note is the evidence,
745
+ * in the reconciler's own words.
746
+ * 3. **The two resolutions are distinct in the log.** `executed` and
747
+ * `not-executed` are separate closed values, not two readings of one
748
+ * sentence, because everything downstream of the record turns on which.
749
+ * 4. **The key stays burned either way.** Resolving `not-executed` re-opens the
750
+ * possibility of the EFFECT, not of this action: an `idempotency_key` is the
751
+ * global identity of one side effect (§6.2) and a used one is used. The
752
+ * repair is to declare a fresh action and request it, which is a new
753
+ * question with a new answer, and the reconciliation is what makes asking it
754
+ * honest.
755
+ */
756
+ export declare function reconcileExecution(logPath: string, actionKey: string, resolution: ReconcileResolution, note: string, actor: string, options?: ExecuteOptions): ReconcileResult;
757
+ /**
758
+ * What the log knows about one started execution (APRV-120).
759
+ *
760
+ * The word is custody rather than status because the question is not "did it
761
+ * work" but "who is holding this, and what may still be done with it". Five
762
+ * states, and the two that are easy to confuse are the reason the vocabulary
763
+ * exists:
764
+ *
765
+ * - `settled` — an `execution.completed` or `execution.failed` closed it. The
766
+ * runtime watched the outcome and wrote down what it saw.
767
+ * - `open` — a start with no outcome, written by a runtime that MEANT to watch
768
+ * one. This is the dangling execution: a crash between `execution.started`
769
+ * and its outcome, repairable by a person with `execution resolve`. It is
770
+ * debris, and `approval status` says so.
771
+ * - `delegated` — a start carrying `payload.execution: "harness"` (APRV-117,
772
+ * APRV-141). **Terminal by design, and never debris.** The harness runs the
773
+ * command and this runtime never observes an exit status, so no outcome event
774
+ * will ever follow; the record is complete as written. Reporting these as
775
+ * dangling — which is what happened before this state existed, to every
776
+ * harness execution in the reference repository's own log — trains operators
777
+ * to ignore the one list that is supposed to mean something.
778
+ * - `indeterminate` — an `execution.indeterminate`: the side effect was
779
+ * attempted and nobody knows whether it committed. The token is spent and the
780
+ * key is burned, and a re-run is refused, because a retry against an unknown
781
+ * outcome is a blind double-execution.
782
+ * - `reconciled` — a person established which it was, and said so in an
783
+ * `execution.reconciled` that sits beside the indeterminate record rather
784
+ * than over it.
785
+ */
786
+ export declare const CUSTODY_STATES: readonly ["settled", "open", "delegated", "indeterminate", "reconciled"];
787
+ export type CustodyState = (typeof CUSTODY_STATES)[number];
788
+ /** Where an indeterminate outcome's unknowing began. Closed (schema §8). */
789
+ export declare const INDETERMINATE_REASONS: readonly ["act-threw"];
790
+ export type IndeterminateReason = (typeof INDETERMINATE_REASONS)[number];
791
+ /** What a reconciliation established. Closed, and the two are distinct. */
792
+ export declare const RECONCILE_RESOLUTIONS: readonly ["executed", "not-executed"];
793
+ export type ReconcileResolution = (typeof RECONCILE_RESOLUTIONS)[number];
794
+ export declare function isIndeterminateReason(value: unknown): value is IndeterminateReason;
795
+ export declare function isReconcileResolution(value: unknown): value is ReconcileResolution;
796
+ /** One action key's latest execution cycle, and what may still be done with it. */
797
+ export interface ExecutionCustody {
798
+ actionKey: string;
799
+ task: string | null;
800
+ state: CustodyState;
801
+ /** The `execution.started` record's timestamp, position and actor. */
802
+ ts: string;
803
+ seq: number;
804
+ actor: string;
805
+ /** The closing record's position: the outcome, or the reconciliation. */
806
+ closedSeq: number | null;
807
+ /** The `execution.indeterminate` record's position, when there is one. */
808
+ indeterminateSeq: number | null;
809
+ /** Its closed reason, when there is one. */
810
+ reason: IndeterminateReason | null;
811
+ /** What a reconciliation established, when `state` is `reconciled`. */
812
+ resolution: ReconcileResolution | null;
813
+ }
814
+ /** An execution that began and whose outcome the log does not know. */
815
+ export interface DanglingExecution {
816
+ actionKey: string;
817
+ task: string | null;
818
+ /** The `execution.started` record's timestamp and position. */
819
+ ts: string;
820
+ seq: number;
821
+ actor: string;
822
+ }
823
+ /**
824
+ * The custody state of every started execution, in log order.
825
+ *
826
+ * Pure: no I/O, no clock. Per action key, only the **latest cycle** counts — a
827
+ * start followed by an outcome is closed, and a later start reopens the key.
828
+ * (The gate refuses a second start for a key anyway; this function does not
829
+ * assume that, because a projection that only works on well-formed logs is a
830
+ * projection that goes quiet exactly when something has gone wrong.)
831
+ */
832
+ export declare function executionCustody(records: EventRecord[]): ExecutionCustody[];
833
+ /**
834
+ * Executions that started, were meant to be watched, and never finished.
835
+ *
836
+ * This is the state a crash between `execution.started` and its outcome leaves
837
+ * behind, and it is reported as itself: not as completed, not as failed, not as
838
+ * clean. `approval status` lists it; `approval queue` does not, because nobody
839
+ * is being asked to decide anything.
840
+ *
841
+ * A `delegated` start is NOT here (APRV-120). The harness ran the command and
842
+ * this runtime never sees an exit status, so its record was never going to gain
843
+ * an outcome; listing it as debris says something false about a log that is
844
+ * exactly right.
845
+ */
846
+ export declare function danglingExecutions(records: EventRecord[]): DanglingExecution[];
847
+ /**
848
+ * Executions whose side effect was attempted and whose outcome nobody knows,
849
+ * and which no one has reconciled yet.
850
+ *
851
+ * Distinct from {@link danglingExecutions} in what it asks of a person. A
852
+ * dangling execution needs someone to look at what the runtime did; an
853
+ * indeterminate one needs someone to establish, from the relying party's own
854
+ * evidence, whether the effect happened at all. Both are debris and both make
855
+ * `approval status` unhealthy; only one of them is repaired with
856
+ * `execution resolve`.
857
+ */
858
+ export declare function indeterminateExecutions(records: EventRecord[]): ExecutionCustody[];