@sema-agent/core 5.20.0 → 5.21.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 (312) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/agents/agent-definition.d.ts +7 -0
  3. package/dist/agents/agent-transcript-tool.d.ts +30 -0
  4. package/dist/agents/builtin-agents.d.ts +60 -0
  5. package/dist/agents/cascade.d.ts +107 -0
  6. package/dist/agents/cumulative-stats.d.ts +56 -0
  7. package/dist/agents/observer.d.ts +162 -0
  8. package/dist/agents/peer-admission.d.ts +95 -0
  9. package/dist/agents/repair-loop.d.ts +226 -0
  10. package/dist/agents/retain-ledger.d.ts +141 -0
  11. package/dist/agents/roster-store.d.ts +105 -0
  12. package/dist/agents/send-message-tool.d.ts +92 -0
  13. package/dist/agents/session-util.d.ts +5 -0
  14. package/dist/agents/subagent-steps.d.ts +66 -0
  15. package/dist/agents/subagent.d.ts +600 -0
  16. package/dist/agents/suspend-guard.d.ts +29 -0
  17. package/dist/agents/teacher.d.ts +75 -0
  18. package/dist/agents/team.d.ts +120 -1
  19. package/dist/agents/tool-filter.d.ts +34 -0
  20. package/dist/agents/verify.d.ts +198 -0
  21. package/dist/bench/metrics.d.ts +455 -0
  22. package/dist/brain/anthropic.d.ts +30 -0
  23. package/dist/brain/circuit-breaker.d.ts +33 -0
  24. package/dist/brain/context-overflow.d.ts +60 -3
  25. package/dist/brain/degrading.d.ts +67 -0
  26. package/dist/brain/errors.d.ts +42 -0
  27. package/dist/brain/failover.d.ts +15 -0
  28. package/dist/brain/media-degrade.d.ts +39 -0
  29. package/dist/brain/model-presets.d.ts +31 -0
  30. package/dist/brain/open-responses.d.ts +19 -0
  31. package/dist/brain/openai.d.ts +46 -0
  32. package/dist/brain/reasoning.d.ts +106 -1
  33. package/dist/brain/repetition.d.ts +83 -0
  34. package/dist/brain/request-params.d.ts +56 -0
  35. package/dist/brain/retry.d.ts +46 -0
  36. package/dist/brain/routing.d.ts +12 -0
  37. package/dist/brain/status-sink.d.ts +30 -1
  38. package/dist/brain/stream-engine.d.ts +147 -0
  39. package/dist/brain/stream-shared.d.ts +34 -0
  40. package/dist/brain/terminal-cause.d.ts +31 -0
  41. package/dist/brain/timeout.d.ts +107 -0
  42. package/dist/brain/tool-call-id.d.ts +20 -0
  43. package/dist/brain/tool-call-repair.d.ts +13 -0
  44. package/dist/config/catalog.d.ts +47 -0
  45. package/dist/config/defaults.d.ts +33 -0
  46. package/dist/core/a2a-task-state.d.ts +53 -0
  47. package/dist/core/a2a.d.ts +51 -0
  48. package/dist/core/arg-summary.d.ts +62 -0
  49. package/dist/core/ask-question.d.ts +272 -2
  50. package/dist/core/auto-compaction.d.ts +467 -0
  51. package/dist/core/auto-compaction.js +20 -6
  52. package/dist/core/auto-mode-prompt-assets.d.ts +8 -0
  53. package/dist/core/auto-mode-prompt.d.ts +27 -0
  54. package/dist/core/auto-mode.d.ts +54 -3
  55. package/dist/core/auto-promote.d.ts +100 -0
  56. package/dist/core/background-agent-store.d.ts +292 -0
  57. package/dist/core/background-shell.d.ts +110 -0
  58. package/dist/core/cache-break-detector.d.ts +34 -0
  59. package/dist/core/canonical-json.d.ts +57 -0
  60. package/dist/core/checkpoint-store.d.ts +1574 -16
  61. package/dist/core/compliance.d.ts +30 -0
  62. package/dist/core/consolidate-scope.d.ts +75 -0
  63. package/dist/core/context-edit.d.ts +99 -0
  64. package/dist/core/context-guard.d.ts +46 -0
  65. package/dist/core/exec-gate.d.ts +44 -0
  66. package/dist/core/exec-output-tail.d.ts +61 -0
  67. package/dist/core/file-snapshot-store.d.ts +104 -0
  68. package/dist/core/fs-write-gate-policy.d.ts +36 -0
  69. package/dist/core/git-worktree-env.d.ts +31 -0
  70. package/dist/core/governance-codes.d.ts +19 -0
  71. package/dist/core/hooks.d.ts +608 -1
  72. package/dist/core/human-input-projection.d.ts +37 -0
  73. package/dist/core/human-input-projection.js +13 -0
  74. package/dist/core/image-downsample.d.ts +74 -0
  75. package/dist/core/locked-config.d.ts +37 -0
  76. package/dist/core/lsp-diagnostics.d.ts +77 -0
  77. package/dist/core/lsp-protocol.d.ts +29 -0
  78. package/dist/core/lsp-session.d.ts +60 -1
  79. package/dist/core/lsp.d.ts +150 -1
  80. package/dist/core/mailbox-store.d.ts +57 -0
  81. package/dist/core/mcp.d.ts +381 -0
  82. package/dist/core/media-byte-cap.d.ts +21 -0
  83. package/dist/core/memory-admission.d.ts +71 -0
  84. package/dist/core/memory-engine/content-origin.d.ts +64 -0
  85. package/dist/core/memory-engine/data-plane.d.ts +43 -0
  86. package/dist/core/memory-engine/dual-root.d.ts +24 -0
  87. package/dist/core/memory-engine/engine.d.ts +230 -0
  88. package/dist/core/memory-engine/engine.js +103 -35
  89. package/dist/core/memory-engine/file-backend.d.ts +155 -0
  90. package/dist/core/memory-engine/frontmatter.d.ts +16 -0
  91. package/dist/core/memory-engine/layout.d.ts +217 -0
  92. package/dist/core/memory-engine/memory-backend-contract.d.ts +12 -0
  93. package/dist/core/memory-engine/migrate.d.ts +9 -0
  94. package/dist/core/memory-engine/scan.d.ts +12 -0
  95. package/dist/core/memory-engine/scope-contract.d.ts +61 -1
  96. package/dist/core/memory-engine/sync-client.d.ts +87 -0
  97. package/dist/core/memory-engine/sync.d.ts +60 -0
  98. package/dist/core/memory-engine/tools.d.ts +41 -0
  99. package/dist/core/memory-engine/types.d.ts +188 -0
  100. package/dist/core/memory-recall.d.ts +141 -0
  101. package/dist/core/memory-vector.d.ts +20 -0
  102. package/dist/core/memory.d.ts +458 -0
  103. package/dist/core/message-utils.d.ts +6 -0
  104. package/dist/core/oracle-isolation.d.ts +69 -0
  105. package/dist/core/permission-rule-consent.d.ts +138 -0
  106. package/dist/core/permission-rule-model.d.ts +122 -0
  107. package/dist/core/permission-rule-store.d.ts +119 -3
  108. package/dist/core/permission-rules.d.ts +87 -1
  109. package/dist/core/present-plan-tool.d.ts +20 -0
  110. package/dist/core/pricing.d.ts +26 -0
  111. package/dist/core/property-harness.d.ts +86 -0
  112. package/dist/core/protocol-naming.d.ts +38 -0
  113. package/dist/core/protocol-table.d.ts +61 -0
  114. package/dist/core/push-queue.d.ts +1 -0
  115. package/dist/core/remote-env.d.ts +383 -1
  116. package/dist/core/retention-policy.d.ts +33 -0
  117. package/dist/core/retention.d.ts +51 -0
  118. package/dist/core/roles.d.ts +59 -0
  119. package/dist/core/runner/active-skill-scope.d.ts +34 -0
  120. package/dist/core/runner/assemble-result.d.ts +134 -0
  121. package/dist/core/runner/compaction-call-options.d.ts +97 -1
  122. package/dist/core/runner/grounding-signal.d.ts +10 -0
  123. package/dist/core/runner/image.d.ts +17 -0
  124. package/dist/core/runner/image.js +29 -15
  125. package/dist/core/runner/memory-consolidation.d.ts +60 -0
  126. package/dist/core/runner/prepare-memory.d.ts +59 -0
  127. package/dist/core/runner/prepare-task.d.ts +1011 -2
  128. package/dist/core/runner/prepare-task.js +12 -11
  129. package/dist/core/runner/prompt-suggestions.d.ts +7 -0
  130. package/dist/core/runner/runtask.d.ts +304 -3
  131. package/dist/core/runner/session-file-state-replay.d.ts +49 -0
  132. package/dist/core/runner/session-rule-policy.d.ts +58 -0
  133. package/dist/core/runner/strict-output-schema.d.ts +18 -3
  134. package/dist/core/runner/synthetic-tools.d.ts +113 -0
  135. package/dist/core/runner/teardown-bounded.d.ts +34 -0
  136. package/dist/core/runner/tool-disclosure.d.ts +227 -0
  137. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  138. package/dist/core/runner/turn-attachments.d.ts +634 -0
  139. package/dist/core/runner/usage-accounting.d.ts +32 -0
  140. package/dist/core/runtime.d.ts +9 -0
  141. package/dist/core/safe-notify.d.ts +64 -0
  142. package/dist/core/safety-axis-vocab.d.ts +23 -0
  143. package/dist/core/safety-merge-corpus.d.ts +37 -0
  144. package/dist/core/scheduler.d.ts +121 -0
  145. package/dist/core/secret-env.d.ts +32 -0
  146. package/dist/core/select-model.d.ts +15 -0
  147. package/dist/core/sensitive-path-policy.d.ts +42 -0
  148. package/dist/core/session-policy-store.d.ts +94 -0
  149. package/dist/core/session-reconcile.d.ts +80 -0
  150. package/dist/core/session-store.d.ts +85 -0
  151. package/dist/core/session.d.ts +153 -0
  152. package/dist/core/shared-memory/contract.d.ts +22 -0
  153. package/dist/core/shared-memory/normalize.d.ts +123 -2
  154. package/dist/core/shared-memory/tools.d.ts +14 -0
  155. package/dist/core/shared-memory/types.d.ts +105 -0
  156. package/dist/core/shutdown-debug.d.ts +6 -0
  157. package/dist/core/side-query.d.ts +38 -0
  158. package/dist/core/side-query.js +6 -1
  159. package/dist/core/skill-tool-specifier.d.ts +72 -0
  160. package/dist/core/skills-directory.d.ts +100 -1
  161. package/dist/core/spec-contract.d.ts +89 -0
  162. package/dist/core/store-contracts/background-agent-store-contract.d.ts +31 -0
  163. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +26 -0
  164. package/dist/core/store-contracts/contract-harness.d.ts +20 -0
  165. package/dist/core/store-contracts/contract-kit-version.d.ts +27 -0
  166. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +8 -0
  167. package/dist/core/store-contracts/mailbox-store-contract.d.ts +42 -0
  168. package/dist/core/store-contracts/tool-result-store-contract.d.ts +13 -0
  169. package/dist/core/strategy-store.d.ts +37 -0
  170. package/dist/core/stub-env.d.ts +7 -0
  171. package/dist/core/surrogate-safe-slice.d.ts +35 -0
  172. package/dist/core/task-notification.d.ts +202 -0
  173. package/dist/core/task-outcome.d.ts +53 -0
  174. package/dist/core/task-registry-agent.d.ts +337 -1
  175. package/dist/core/task-registry-monitor.d.ts +12 -0
  176. package/dist/core/task-registry-shared.d.ts +540 -0
  177. package/dist/core/task-registry.d.ts +343 -0
  178. package/dist/core/task-registry.js +13 -2
  179. package/dist/core/task-tool-shape.d.ts +44 -0
  180. package/dist/core/tighten-task-spec.d.ts +21 -0
  181. package/dist/core/tool-detach.d.ts +21 -0
  182. package/dist/core/tool-errors.d.ts +131 -0
  183. package/dist/core/tool-errors.js +4 -0
  184. package/dist/core/tool-name-aliases.d.ts +27 -0
  185. package/dist/core/tool-policy.d.ts +555 -0
  186. package/dist/core/tool-result-budget.d.ts +32 -0
  187. package/dist/core/tool-result-store.d.ts +174 -1
  188. package/dist/core/tools.d.ts +45 -0
  189. package/dist/core/trace.d.ts +323 -0
  190. package/dist/core/types.d.ts +3859 -2
  191. package/dist/core/untrusted-egress.d.ts +8 -0
  192. package/dist/core/untrusted-text.d.ts +156 -0
  193. package/dist/core/usage-window-store.d.ts +95 -0
  194. package/dist/core/version.d.ts +1 -0
  195. package/dist/core/warm-resume.d.ts +17 -0
  196. package/dist/core/wiring-manifest.d.ts +169 -0
  197. package/dist/core/with-retry.d.ts +24 -0
  198. package/dist/core/workflow-journal-store.d.ts +160 -0
  199. package/dist/core/workflow-run-store-contract.d.ts +25 -0
  200. package/dist/core/workflow-run-store.d.ts +119 -0
  201. package/dist/engine/compaction/compaction.d.ts +256 -1
  202. package/dist/engine/compaction/utils.d.ts +94 -0
  203. package/dist/engine/execution-env/kill-tree.d.ts +29 -0
  204. package/dist/engine/execution-env/node-execution-env.d.ts +94 -0
  205. package/dist/engine/harness/agent-harness.d.ts +116 -0
  206. package/dist/engine/harness/agent-harness.js +3 -14
  207. package/dist/engine/harness/messages.d.ts +15 -0
  208. package/dist/engine/harness/types.d.ts +464 -2
  209. package/dist/engine/llm/diagnostics.d.ts +4 -0
  210. package/dist/engine/llm/event-stream.d.ts +3 -0
  211. package/dist/engine/llm/index.d.ts +7 -0
  212. package/dist/engine/llm/types.d.ts +500 -3
  213. package/dist/engine/llm/validation.d.ts +3 -0
  214. package/dist/engine/loop/agent-loop.d.ts +87 -2
  215. package/dist/engine/loop/runtime-deps.d.ts +7 -0
  216. package/dist/engine/loop/types.d.ts +424 -0
  217. package/dist/engine/lsp/frame-decoder.d.ts +13 -0
  218. package/dist/engine/lsp/node-lsp-manager.d.ts +107 -0
  219. package/dist/engine/lsp/stdio-lsp-transport.d.ts +7 -0
  220. package/dist/engine/session/import-validate.d.ts +27 -0
  221. package/dist/engine/session/log-digest.d.ts +93 -0
  222. package/dist/engine/session/memory-repo.d.ts +6 -0
  223. package/dist/engine/session/memory-storage.d.ts +2 -0
  224. package/dist/engine/session/session.d.ts +75 -0
  225. package/dist/engine/session/storage-base.d.ts +8 -0
  226. package/dist/fixtures/index.d.ts +36 -0
  227. package/dist/index.d.ts +16 -1
  228. package/dist/index.js +0 -1
  229. package/dist/internal/harness-types.d.ts +6 -0
  230. package/dist/internal/harness.d.ts +11 -0
  231. package/dist/internal/llm.d.ts +6 -0
  232. package/dist/orchestration/builtin-workflows.d.ts +53 -0
  233. package/dist/orchestration/dev-vm-script-runner.d.ts +19 -0
  234. package/dist/orchestration/goal.d.ts +57 -0
  235. package/dist/orchestration/goal.js +3 -0
  236. package/dist/orchestration/run-spec.d.ts +42 -0
  237. package/dist/orchestration/run-workflow-tool.d.ts +169 -0
  238. package/dist/orchestration/workflow-governance.d.ts +61 -0
  239. package/dist/orchestration/workflow-meta.d.ts +28 -0
  240. package/dist/orchestration/workflow-observe.d.ts +60 -0
  241. package/dist/orchestration/workflow-primitives.d.ts +23 -1
  242. package/dist/orchestration/workflow-sandbox-conformance.d.ts +38 -0
  243. package/dist/orchestration/workflow-script-runner.d.ts +88 -0
  244. package/dist/orchestration/workflow-script-store.d.ts +98 -0
  245. package/dist/orchestration/workflow-size-guideline.d.ts +67 -0
  246. package/dist/orchestration/workflow-types.d.ts +169 -2
  247. package/dist/orchestration/workflow.d.ts +358 -0
  248. package/dist/prompt-assembly/artifact-store.d.ts +33 -0
  249. package/dist/prompt-assembly/artifact.d.ts +25 -0
  250. package/dist/prompt-assembly/assemble.d.ts +20 -0
  251. package/dist/prompt-assembly/composer.d.ts +29 -0
  252. package/dist/prompt-assembly/epoch.d.ts +55 -1
  253. package/dist/prompt-assembly/event-registry.d.ts +35 -0
  254. package/dist/prompt-assembly/explain.d.ts +12 -0
  255. package/dist/prompt-assembly/tool-catalog.d.ts +38 -0
  256. package/dist/prompt-assembly/turn-snapshot.d.ts +40 -0
  257. package/dist/prompt-assembly/types.d.ts +115 -0
  258. package/dist/prompts/coordinator.d.ts +27 -0
  259. package/dist/prompts/default.d.ts +539 -0
  260. package/dist/prompts/simple-sections.d.ts +45 -0
  261. package/dist/prompts/supervisor.d.ts +66 -0
  262. package/dist/scenarios/env.d.ts +28 -0
  263. package/dist/scenarios/full-body.d.ts +50 -0
  264. package/dist/scenarios/scenario-registry.d.ts +60 -0
  265. package/dist/scenarios/teacher-quickstart.d.ts +27 -0
  266. package/dist/server/http.d.ts +17 -0
  267. package/dist/stores/cc/lockfile.d.ts +6 -0
  268. package/dist/stores/cc/mailbox-store.d.ts +8 -0
  269. package/dist/stores/cc/roster-adapter.d.ts +4 -0
  270. package/dist/stores/cc/sidecar-transcript.d.ts +66 -0
  271. package/dist/stores/file/background-agent-store.d.ts +24 -0
  272. package/dist/stores/file/checkpoint-store.d.ts +38 -0
  273. package/dist/stores/file/file-snapshot-store.d.ts +25 -0
  274. package/dist/stores/file/fs-atomic.d.ts +155 -0
  275. package/dist/stores/file/index.d.ts +89 -0
  276. package/dist/stores/file/mailbox-store.d.ts +36 -0
  277. package/dist/stores/file/memory-store.d.ts +82 -0
  278. package/dist/stores/file/permission-rule-store.d.ts +79 -0
  279. package/dist/stores/file/session-policy-store.d.ts +28 -0
  280. package/dist/stores/file/session-store.d.ts +40 -0
  281. package/dist/stores/file/shared-ledger.d.ts +83 -0
  282. package/dist/stores/file/tool-result-store.d.ts +11 -0
  283. package/dist/stores/file/usage-window-store.d.ts +18 -0
  284. package/dist/stores/file/workflow-journal-store.d.ts +100 -0
  285. package/dist/stores/file/workflow-run-store.d.ts +11 -0
  286. package/dist/tools/fs/bash-readonly-classifier.d.ts +282 -0
  287. package/dist/tools/fs/encoding.d.ts +60 -0
  288. package/dist/tools/fs/fs-bash.d.ts +133 -0
  289. package/dist/tools/fs/fs-pdf.d.ts +28 -0
  290. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  291. package/dist/tools/fs/fs-shared.d.ts +360 -0
  292. package/dist/tools/fs/fs-write.d.ts +16 -0
  293. package/dist/tools/fs/gh-rate-limit.d.ts +22 -0
  294. package/dist/tools/fs/index.d.ts +79 -0
  295. package/dist/tools/fs/notebook.d.ts +56 -0
  296. package/dist/tools/fs/pdf.d.ts +70 -0
  297. package/dist/tools/fs/pdf.js +8 -2
  298. package/dist/tools/fs/repo-map.d.ts +10 -0
  299. package/dist/tools/fs/safety.d.ts +376 -0
  300. package/dist/tools/fs/search.d.ts +131 -0
  301. package/dist/tools/loop-tick.d.ts +24 -0
  302. package/dist/tools/monitor.d.ts +55 -0
  303. package/dist/tools/scheduler-tools.d.ts +51 -0
  304. package/dist/tools/sql-adapters.d.ts +18 -0
  305. package/dist/tools/sql.d.ts +25 -0
  306. package/dist/tools/task-list.d.ts +77 -0
  307. package/dist/tools/todo.d.ts +8 -0
  308. package/dist/tools/web.d.ts +184 -0
  309. package/dist/tools/worktree.d.ts +81 -0
  310. package/package.json +2 -2
  311. package/dist/tools/gitea-issue.d.ts +0 -13
  312. package/dist/tools/gitea-issue.js +0 -75
@@ -7,48 +7,225 @@ import type { ConsolidationNote } from "./runner/memory-consolidation.js";
7
7
  import type { SessionPermissionRules } from "./session-policy-store.js";
8
8
  import type { NestedUsageAccum, TaskResult } from "./types.js";
9
9
  import type { ActiveWorktreeSession } from "../internal/harness-types.js";
10
+ /**
11
+ * design/45 — the **durable-checkpoint** primitive: cross-process / resumable `suspend`/`resume`.
12
+ *
13
+ * A {@link CheckpointStore} is the third pluggable durable seam (alongside `ToolResultStore` and
14
+ * `MemoryStore`): a token-addressable store of "enough state to resume a suspended task", with an
15
+ * **atomic CAS `resolve`** so a token is acted on **exactly once** even across replicas / double
16
+ * approvals. Two profiles share this one seam (design/43 原语 4, proven by two service instances):
17
+ *
18
+ * - **F4** (`gate.kind === "human"`) — a running task hit a tool-policy `ask` that no in-process
19
+ * approver can answer (headless / long / cross-replica), so it persists a checkpoint and returns
20
+ * `status:"suspended"`. Later `runner.resume(token, {gate:"policy_ask", decision})` continues it.
21
+ * This is the cross-process version of the synchronous `onAsk` gate (design/37).
22
+ * - **1C** (`gate.kind === "task_done"`) — design/38 Path A persists a background sub-task handle here
23
+ * so a caller can `get` its outcome across replicas. v1 stores the handle only; the parent does NOT
24
+ * mid-task suspend (Path A unchanged) — `runner.resume()` does NOT serve this gate in v1.
25
+ *
26
+ * **opt-in, default-off**: with no `CheckpointStore` wired (and no durable approval mode requested), a
27
+ * policy `ask` still resolves the 1.63 way (synchronous `onAsk` / headless auto-deny) — behavior
28
+ * unchanged. Default {@link InMemoryCheckpointStore} is process-only (single instance / tests); a
29
+ * durable backend (service's `tidb-approval-store` / `tidb-run-store`) makes it cross-replica.
30
+ *
31
+ * See `design/45-durable-checkpoint原语-suspend-resume.md`.
32
+ */
33
+ /**
34
+ * A high-entropy, single-use checkpoint token. Branded so it can't be confused with a `sessionId` /
35
+ * `taskId` (council Question #2): the token doubles as the resume capability (token-as-auth, §6), so a
36
+ * mix-up would be a security bug, not just a type slip. Mint with {@link mintCheckpointToken}.
37
+ */
10
38
  export type CheckpointToken = string & {
11
39
  readonly __brand: "CheckpointToken";
12
40
  };
41
+ /** Mint a CSPRNG 128-bit checkpoint token (token-as-auth, §6/Q5): unguessable, never logged/in-URL. */
13
42
  export declare function mintCheckpointToken(): CheckpointToken;
43
+ /**
44
+ * Who resumes a checkpoint, and how the resume `outcome` is interpreted. The `kind` is the discriminant
45
+ * that {@link ResumeOutcome} must match at the resume entry (council #3: prevents a `task_done` gate
46
+ * being resumed with a `policy_ask` outcome → type confusion / corruption).
47
+ */
48
+ /** design/74: why a resource-slice suspend fired. `"budget"` covers cost OR tokens (both are checked by
49
+ * `overBudget`); `"walltime"` is the soft slice deadline; `"turns"` the slice turn cap.
50
+ * design/80 Seam #2: `"preempt"` is NOT a resource limit — it is an EXTERNAL scheduler yield (the scheduler
51
+ * raised `spec.preemptSignal` to free resources for a higher-priority task). It rides the SAME durable-suspend
52
+ * mechanism: the gate.kind stays `resource_limit` (the mechanism), so the resume path is byte-identical to a
53
+ * budget/turns/walltime resume (continue the work, no decision); `"preempt"` is only the CAUSE. */
54
+ /**
55
+ * Why a `resource_limit` suspend was minted. design/164 split the single `budget` member: a token
56
+ * window and a $ window are different allowances with different owners, and a scheduler reading a
57
+ * checkpoint inbox to decide "grant more of what?" could not tell them apart. The reasons match the
58
+ * terminal codes the non-suspending tier reports (`limits.max_*_exceeded`), so one vocabulary covers
59
+ * both tiers.
60
+ *
61
+ * **Consumers pinning the old closed set**: `"budget"` is gone — a switch on it now falls through.
62
+ *
63
+ * design/164 件四/件五 added two more members, and they are a DIFFERENT CLASS from the task axes above:
64
+ * `"env_lifetime"` (the execution environment's platform lifetime is about to expire) and
65
+ * `"usage_window"` (a deployment-level usage-governance window is exhausted) are EXTERNAL causes — the
66
+ * task did not choose them and cannot avoid them by asking for less. They therefore suspend on the
67
+ * INFRASTRUCTURE eligibility alone (checkpoint store + non-volatile offload + remote/no owned env),
68
+ * without the `TaskSpec.resourceSuspend` opt-in the task axes require. When several causes land on one
69
+ * turn boundary the attribution order is
70
+ * `preempt > env_lifetime > usage_window > budget_tokens > budget_cost > turns > walltime`
71
+ * (external causes above self-chosen allowances; preempt keeps the top seat it always had).
72
+ *
73
+ * **Consumers pinning the closed set again**: a `switch` that was exhaustive over the five 5.x members
74
+ * now falls through on these two.
75
+ */
14
76
  export type ResourceLimitReason = "budget_tokens" | "budget_cost" | "walltime" | "turns" | "preempt" | "env_lifetime" | "usage_window";
77
+ /**
78
+ * design/164 件四/件五 — the EXTERNAL subset of {@link ResourceLimitReason}: causes that come from the
79
+ * platform or the operator rather than from the task's own declared allowances. Written as an `Extract`
80
+ * of the parent union so the two can never drift into disagreeing about what "external" means, and so a
81
+ * seam that may only be reached by an external cause (the infrastructure-only suspend lane) cannot be
82
+ * handed a task-axis reason by mistake.
83
+ */
15
84
  export type PlatformLimitReason = Extract<ResourceLimitReason, "env_lifetime" | "usage_window">;
85
+ /**
86
+ * design/80 D-2: which SAFETY MARK(s) on the tool caused an `ask` to mint an {@link CheckpointGate}
87
+ * `irreversible_ask`. Derived from the tool's STATIC spec marks (`ToolSpec.egress` / `ToolSpec.irreversibility`,
88
+ * round-1 council fix — NOT the per-call `decisionReason`, which missed a policy/hook pre-ask and was
89
+ * forgeable). Persisted on the gate so a network budget/escalation resolver reads WHY this is a safety ask FROM
90
+ * THE DURABLE RECORD (never re-derives risk from a model-controlled value — the model self-reports nothing
91
+ * here). Both can be true (a tool marked BOTH egress AND irreversible).
92
+ */
16
93
  export interface SafetyAxis {
94
+ /** design/70: the tool is egress-marked (`ToolSpec.egress` — an external write: push, open PR, send). */
17
95
  egress?: boolean;
96
+ /** design/77 §4: the tool's irreversibility tier is `always` or `maybe` (`ToolSpec.irreversibility`). */
18
97
  irreversible?: boolean;
19
98
  }
99
+ /**
100
+ * design/80 §D-E: a DETERMINISTIC, REDACTED risk summary attached AT MINT to an escalation
101
+ * {@link CheckpointGate} (`human` / `irreversible_ask` — the tool-call approval escalations) so a supervisor
102
+ * INBOX can sort/triage by TRUE severity without re-deriving risk. **Display/triage metadata ONLY** — core
103
+ * NEVER reads it to gate / budget / suppress anything (structural ask strictly wins via `combinePolicies`;
104
+ * no mint suppression reads it). The profile/inbox READS it; core only ATTACHES it. Pure function of the
105
+ * call (no clock/random) — same call ⇒ identical descriptor.
106
+ */
20
107
  export interface RiskDescriptor {
108
+ /** ToolEmu-style severity tier 1..5 (5 = most severe). The inbox sorts DESC by this. Deterministic — the
109
+ * pure {@link riskSeverity} of {@link axes}. The ORDERING is what matters (the inbox's triage key). */
21
110
  severity: 1 | 2 | 3 | 4 | 5;
111
+ /** Which safety axes tripped — for `irreversible_ask`, derived from the D-2 {@link SafetyAxis} (+ the
112
+ * shell-gate). `shell` marks a coarse shellGate tighten (a bash command gated only because the deployment
113
+ * set `shellGate`, with NO explicit per-tool egress/irreversible mark). Empty `{}` for a plain budgetable
114
+ * `human` ask. Self-contained so the inbox needn't cross-ref `safetyAxis`. */
22
115
  axes: {
23
116
  egress?: boolean;
24
117
  irreversible?: boolean;
25
118
  shell?: boolean;
26
119
  };
120
+ /** The tool whose call is gated (mirrors the gate's `toolName`). */
27
121
  toolName: string;
122
+ /** Ruled 2026-08-05 (forensics): the run's resolved shellGate doctrine, present ONLY when this ask
123
+ * was minted through the shell gate (coarse `always`, or a `classify` probe that declined to
124
+ * auto-allow). Absent for per-tool safety-axis / plain policy asks. Answers "which doctrine was
125
+ * live when this gate fired" from the persisted row alone — three artifact layers could not
126
+ * answer that in the field. */
28
127
  shellGateDoctrine?: "classify" | "always";
128
+ /** A REDACTED, length-capped ONE-LINE summary of the call (the command for a shell gate; a brief key-arg
129
+ * digest otherwise) for the inbox preview. NEUTRALIZED via {@link import("./untrusted-text.js").inlineUntrusted}
130
+ * + length-capped (it is persisted + shown to a human inbox, so a crafted tool arg carrying a
131
+ * `</system-reminder>` variant / newline / huge string must NOT inject into the render or bloat storage).
132
+ * NEVER raw secrets / full args / env. Deterministic. Optional (omitted when nothing safe to summarize). */
29
133
  summary?: string;
134
+ /** Best-effort file paths the action touches (fs-tool `path` args; shell parsing is deliberately NOT
135
+ * attempted — over-reaching a shell parse risks a wrong/forgeable path). Each path `inlineUntrusted`-capped.
136
+ * Omitted when none derivable. */
30
137
  touchedPaths?: string[];
31
138
  }
139
+ /**
140
+ * design/80 §D-E: the DETERMINISTIC severity tier (1..5) for an escalation checkpoint, a PURE function of the
141
+ * tripped {@link RiskDescriptor.axes} (NO LLM, NO clock/random) so the inbox's sort order is stable and
142
+ * unit-testable in isolation. The ORDERING is the contract; the absolute numbers map to ToolEmu's 5 tiers:
143
+ * - irreversible AND egress → **5** (the most severe — an external, irreversible write)
144
+ * - irreversible only → **4**
145
+ * - egress only → **3**
146
+ * - shell-gated tighten → **3** (a coarse shellGate ask with no explicit egress/irreversible mark — the
147
+ * command MAY be benign, so it is not auto-graded as high as a marked tool)
148
+ * - plain `human` ask → **2** (no safety axis tripped — a budgetable approval)
149
+ * - (1 reserved — least-severe / informational; not expected from these gates.)
150
+ * `egress` dominates the shell coarse-grade (an explicitly egress-marked shell tighten is still ≥3). A future
151
+ * arm may refine this — but only with a stated reason, and the ORDERING must stay monotone in risk.
152
+ */
32
153
  export declare function riskSeverity(axes: {
33
154
  egress?: boolean;
34
155
  irreversible?: boolean;
35
156
  shell?: boolean;
36
157
  }): 1 | 2 | 3 | 4 | 5;
158
+ /** design S1e (service [204]): the char cap for the {@link CheckpointSummary.toolInput} BOUNDED raw preview of a
159
+ * `tool_approval` pendingAction's `args` (`JSON.stringify`-ed). Caps the `listByScope` payload size; over-cap
160
+ * is truncated with a trailing `…`. Bounded raw (NOT neutralized) — redaction is the consumer's job (echo-only). */
37
161
  export declare const MAX_TOOL_INPUT_PREVIEW_CHARS = 512;
162
+ /**
163
+ * HRD-APV-4: the char cap {@link validatePendingSteer} enforces on a parked operator steer — the last
164
+ * persisted, model-facing string on a checkpoint row that had none.
165
+ *
166
+ * Sized as an INSTRUCTION budget, not a preview budget: the siblings above cap echo-only projections at
167
+ * 200/512 chars, while a steer is prose a human wrote for the model to act on and is re-delivered on
168
+ * every resume of the checkpoint. 16k chars (~4k tokens) holds any realistic operator instruction and
169
+ * still bounds the durable row and the per-resume context cost. Exported so a BFF can validate before it
170
+ * calls {@link CheckpointStore.setPendingSteer} rather than discovering the reject at persist time.
171
+ */
38
172
  export declare const MAX_PENDING_STEER_CHARS = 16000;
173
+ /**
174
+ * design/171 §6.3 — the BYTE budget the whole parked-steer queue must fit inside, measured on its JSON
175
+ * serialization (the form every backend actually persists).
176
+ *
177
+ * 48,000 bytes sits under a MySQL `TEXT` column's 65,535-BYTE ceiling with room for UTF-8 expansion: the
178
+ * per-entry cap above counts CHARACTERS, and 16,000 CJK characters serialize to ~48,000 bytes, so a
179
+ * char-only cap does not bound the column at all. Both limits are enforced and either one can bind.
180
+ */
39
181
  export declare const PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES = 48000;
182
+ /**
183
+ * design/171 §6.3 — the queue's entry-count cap, DERIVED from the byte budget and the per-entry char cap
184
+ * (how many maximum-size entries the budget can hold). It bounds the count independently of the byte
185
+ * measure so a flood of tiny steers cannot grow the row unboundedly through per-entry overhead alone.
186
+ * Exceeding either limit is a fail-loud `steering.queue_full` — never a silent drop of the oldest entry
187
+ * (a dropped operator instruction is exactly the failure this queue exists to remove).
188
+ */
40
189
  export declare const MAX_PENDING_STEER_ENTRIES: number;
190
+ /**
191
+ * design/171 §6.3 — the FROZEN field set of a persisted {@link PendingSteerEntry}. `validatePendingSteer`
192
+ * copies by this set and REFUSES an unknown key, so a producer one release ahead of this worker gets a
193
+ * loud rejection instead of having its new field silently dropped on the parked leg. Consumers (other
194
+ * repos' backends) read it to assert they round-trip every field rather than hand-listing their own copy.
195
+ */
41
196
  export declare const PENDING_STEER_FROZEN_FIELDS: readonly ["text", "trusted", "actor", "seq", "inputId", "priority"];
197
+ /**
198
+ * design/171 §6.1 — WHO a piece of human input came from, as an attribution label. ONE definition:
199
+ * the shape lives at its layering-legal home (`engine/llm/types.ts`, where `UserMessage.actor`
200
+ * carries it and the engine cannot import core) and this module re-exports it — the live message
201
+ * seat and the parked entry can never drift apart. Normative clauses (never authority; ingress-only
202
+ * `hostAsserted`) are on the definition.
203
+ */
42
204
  import type { ActorAssertion } from "../internal/llm.js";
43
205
  export type { ActorAssertion } from "../internal/llm.js";
206
+ /**
207
+ * design/171 §5.3/§6.3 — one entry of the parked-steer QUEUE. Replaces the former single seat, whose
208
+ * last-writer-wins overwrite silently destroyed an undelivered operator instruction whenever two people
209
+ * steered the same suspended run.
210
+ */
44
211
  export interface PendingSteerEntry {
212
+ /** The operator's instruction text (validated exactly like the pre-queue single seat). */
45
213
  text: string;
214
+ /** Frozen at append time from the SERVICE's verified-principal check; never recomputed on resume. */
46
215
  trusted: boolean;
216
+ /** Attribution only (see {@link ActorAssertion}) — absent = the caller supplied none (anonymous). */
47
217
  actor?: ActorAssertion;
218
+ /** Queue-monotonic ordering key, minted by the store on append (first entry = 1). Drain order. */
48
219
  seq: number;
220
+ /** Caller-supplied correlation/idempotency id (a service passes the message id it already minted);
221
+ * a uuidv7 is minted when absent. Re-appending the SAME `inputId` is a no-op, which is what keeps
222
+ * `setPendingSteer` retry-safe now that it appends instead of overwriting. */
49
223
  inputId: string;
224
+ /** Carried VERBATIM for the serving layer; it does NOT reorder the drain (drain is `seq` order). */
50
225
  priority?: SystemInjectionPriority;
51
226
  }
227
+ /** design/171 §6.3 — the caller-supplied half of a {@link PendingSteerEntry}: everything except `seq`,
228
+ * which only the store may mint. `inputId` is optional on the way in (minted when absent). */
52
229
  export interface PendingSteerInput {
53
230
  text: string;
54
231
  trusted: boolean;
@@ -56,59 +233,281 @@ export interface PendingSteerInput {
56
233
  inputId?: string;
57
234
  priority?: SystemInjectionPriority;
58
235
  }
236
+ /**
237
+ * design/80 §D-E: build the DETERMINISTIC, REDACTED {@link RiskDescriptor} for an escalation checkpoint at
238
+ * MINT. Pure — a function ONLY of (`toolName`, `args`, the D-2 `safety` axis, the `shellGated` flag); it reads
239
+ * NO clock/random, so the SAME call ⇒ an IDENTICAL descriptor (pinned by a test).
240
+ *
241
+ * **Determinism contract = plain-DATA args** (the real flow: model-JSON / hook-rewritten plain objects). A
242
+ * Proxy whose `ownKeys` trap returns a DIFFERENT key set per call is OUT OF CONTRACT — JS cannot detect a Proxy
243
+ * (codex review Item 2), so its (display-only) digest may vary. This NEVER affects a security/budget/mint
244
+ * decision: `riskDescriptor` is INERT (no core path reads it to gate), so an out-of-contract input can at worst
245
+ * degrade an inbox preview, never a permission outcome.
246
+ *
247
+ * **Redaction is load-bearing** (the `summary`/`touchedPaths` are PERSISTED + surfaced to a human inbox):
248
+ * every model-controlled value goes through {@link inlineUntrusted} (folds CR/LF/Unicode separators to one
249
+ * space, defuses `</system-reminder>` variants + `<<<`/`>>>` fence sentinels) AND a length cap, so a
250
+ * crafted arg carrying a break-out tag / newline / huge string can neither inject into the inbox render nor
251
+ * bloat the durable row. NEVER dumps full args / env / secrets — only a bash command string or a short
252
+ * `name=value` digest of the SHOWN args.
253
+ *
254
+ * `summary` for a shell gate = the `command` string (capped 200 cp); otherwise a `name=value` digest of the
255
+ * call's top-level string/number/boolean args (each value capped), omitted when nothing safe to summarize.
256
+ * `touchedPaths` reads ONLY the obvious fs `path` arg (read_file/edit_file/write_file) — shell parsing is
257
+ * deliberately NOT attempted (over-reaching a shell parse risks a wrong/forgeable path, so OMIT for bash).
258
+ */
59
259
  export declare function buildRiskDescriptor(input: {
60
260
  toolName: string;
61
261
  args: unknown;
262
+ /** The D-2 {@link SafetyAxis} threaded to the mint (egress/irreversible), or `undefined` for a plain ask. */
62
263
  safety?: SafetyAxis;
264
+ /** True ONLY when this is a `bash` call gated coarsely by `shellGate` (no explicit per-tool egress/irreversible
265
+ * mark) — drives the `shell` axis + severity-3 coarse grade. */
63
266
  shellGated?: boolean;
267
+ /** The resolved doctrine to persist when `shellGated` (see {@link RiskDescriptor.shellGateDoctrine}). */
64
268
  shellGateDoctrine?: "classify" | "always";
65
269
  }): RiskDescriptor;
66
- export type CheckpointGate = {
270
+ export type CheckpointGate =
271
+ /** F4: a human (or any external authority) must allow/deny a pending tool call. design/80 §D-E:
272
+ * carries an OPTIONAL display-only {@link RiskDescriptor} (severity/axes/summary) for the supervisor
273
+ * inbox to triage by — INERT (core never reads it to gate/budget). */
274
+ {
67
275
  kind: "human";
68
276
  reason: string;
69
277
  toolName: string;
70
278
  riskDescriptor?: RiskDescriptor;
71
- } | {
279
+ }
280
+ /** design/77 §4 (Gate 4) + design/80 D-2: a PRE-ACTION human approval before a SAFETY-tightened tool runs —
281
+ * an IRREVERSIBLE tool (send money/email, file a return) OR an EGRESS tool (push, open PR, send). Same
282
+ * approval family as `human` (a pending tool call to allow/deny → suspendRef → `status:"suspended"`, resumed
283
+ * with a `policy_ask` outcome). **design/80 D-2 (load-bearing):** minted whenever an `ask` is for a tool
284
+ * carrying the egress / irreversibility (`always`/`maybe`) SAFETY MARKS — regardless of how the ask arose
285
+ * (a gate tighten OR a policy/hook that already asked; round-1 council fix: keying on the per-call
286
+ * `decisionReason` missed the policy-ask case AND was forgeable) — **EVEN WHEN `durableApproval` is wired**
287
+ * (`durableApproval` supplies scope/ttl, NOT the kind). The DISJOINT-from-`human` kind is what a
288
+ * network budget resolver keys on to NEVER auto-approve a safety ask (a budget may only down-budget a plain
289
+ * `human` ask). {@link SafetyAxis} records which axis(es) tightened. NOT a dry-run review. */
290
+ | {
72
291
  kind: "irreversible_ask";
73
292
  reason: string;
74
293
  toolName: string;
75
294
  safetyAxis?: SafetyAxis;
76
295
  riskDescriptor?: RiskDescriptor;
77
- } | {
296
+ }
297
+ /** design/74: a resource slice limit (budget/walltime/turns) was reached — suspend (resumable) instead of
298
+ * fail. There is NO pending tool to adjudicate; resume just continues the run with the next slice's
299
+ * allowance (sized from {@link Checkpoint.resourceLedger}). The matching {@link ResumeOutcome} arm is
300
+ * `{ gate: "resource_limit"; decision: "continue" }`.
301
+ *
302
+ * design/164 件五: `resumeAfterMs` is the RESUME-TIMING hint that pairs with {@link Checkpoint.deadline}
303
+ * — how long the driver should wait before resuming, because resuming sooner would only re-suspend.
304
+ * **In-scope condition**: present ONLY for `reason: "usage_window"` (the one cause that knows when it
305
+ * clears); every other reason omits it, so a consumer must not read its presence as "this is
306
+ * resumable". Additive on the existing checkpoint version — a reader that ignores it is unchanged. */
307
+ | {
78
308
  kind: "resource_limit";
79
309
  reason: ResourceLimitReason;
80
310
  resumeAfterMs?: number;
81
- } | {
311
+ }
312
+ /** design/76 §2.5 (dry-run / shadow): a POST-PREDICTION REVIEW pause. A profile's dry-run interception ran a
313
+ * predicted action and produced a buffered state-diff a human (or judge) must REVIEW before it is applied —
314
+ * so the task suspends to the durable `needs_review` TERMINAL (`TaskStatus:"needs_review"`) instead of
315
+ * finishing. **DISJOINT from the approval family** (`human`/`irreversible_ask`): those are PRE-ACTION
316
+ * *approvals* (suspendRef → `status:"suspended"`, resumed with a `policy_ask` outcome); this is a
317
+ * POST-prediction *review* (reviewRef → `status:"needs_review"`, resumed with a `dry_run_review` outcome).
318
+ * The split is load-bearing — reusing the approval family would make assemble-result report `"suspended"`
319
+ * and the `needs_review`/`review.pending` branch dead code (v4 MAJOR-A). It is ALSO disjoint from the
320
+ * `RepairTerminal.needs_human_oracle` (design/78) — a different type space; never cross-use the two.
321
+ * The state-diff itself is a PROFILE concern (a REF, not stored here — design/76 §9 / §2.5); core only
322
+ * owns the gate/status/discriminant plumbing. NOT a pre-action approval. */
323
+ | {
82
324
  kind: "needs_review";
83
325
  reason: string;
84
- } | {
326
+ }
327
+ /** design/80 D-B (plan-gate): a PRE-ACTION human PLAN REVIEW. Before an agent acts on a high-blast-radius
328
+ * plan (a profile's plan-gate fires on shell⇒gate + an irreversible/egress/finance mark present + a
329
+ * blast-radius trigger — NOT every task), the run pauses so a human can **approve / edit / reject the
330
+ * proposed PLAN** before any step runs. It is its OWN gate kind (r2 ruling — do NOT reuse `needs_review`,
331
+ * whose gateMatch arm forces a `dry_run_review` outcome; reusing it would make a plan resume a
332
+ * type-confusion mismatch), but it shares the **review-pause** machinery: it routes to `reviewRef` +
333
+ * `status:"needs_review"` (a human-review pause), resumed with a `plan_review` outcome.
334
+ *
335
+ * **THREE-WAY SPLIT (load-bearing):**
336
+ * - the APPROVAL family (`human`/`irreversible_ask`) → PRE-ACTION *approvals* of a pending TOOL CALL
337
+ * (suspendRef → `status:"suspended"`, resumed with a `policy_ask` outcome bound to a tool call);
338
+ * - `needs_review` → a POST-prediction *review* of a buffered state-diff (reviewRef →
339
+ * `status:"needs_review"`, resumed with a `dry_run_review` outcome);
340
+ * - `plan_review` (this) → a PRE-ACTION *review* of a PLAN (reviewRef → `status:"needs_review"`, resumed
341
+ * with a `plan_review` outcome). It binds NO tool call — the human reviews the PLAN, not a pending arg —
342
+ * so it carries no `tool_approval` pendingAction (its pendingAction is `{kind:"plan_review"}`, with no
343
+ * tool fields) and the `policy_ask` decision-action binding machinery never touches it. The plan/diff
344
+ * artifact is a PROFILE concern (a REF, not stored here — design/76 §9); core owns the
345
+ * gate/status/discriminant plumbing only. NOT a pre-action approval, NOT a dry-run review. */
346
+ | {
85
347
  kind: "plan_review";
86
348
  reason: string;
87
- } | {
349
+ }
350
+ /** 1C: the checkpoint tracks a background sub-task's completion (design/38 Path A). v1 = handle only. */
351
+ | {
88
352
  kind: "task_done";
89
353
  };
354
+ /**
355
+ * The outcome a caller supplies to `runner.resume(token, outcome)` — a **discriminated union** keyed by
356
+ * `gate`, validated against `checkpoint.gate.kind` at the entry (council #3). **v1 implements only the
357
+ * `policy_ask` arm**: `task_done` is a dead branch under 1C Path A (the caller orchestrates, never
358
+ * mid-task `resume()`), typed here for completeness and the v2 join-suspend option.
359
+ */
90
360
  export type ResumeOutcome = {
91
361
  gate: "policy_ask";
362
+ /**
363
+ * design/80 D-1 (decision-action binding): the `toolCallId` the human actually saw/approved. The
364
+ * resume is REJECTED (`checkpoint.invalid_outcome`, fail-closed, pre-CAS) unless it matches the
365
+ * checkpoint's pending tool call (`pendingAction.toolCallId`) — this closes the TOCTOU re-suspend
366
+ * wrong-apply: a stale `yes` minted against pending call X must not resolve a DIFFERENT pending call
367
+ * Y (approve vendor-A $5k applied to vendor-B). The decision must name the action it bound to; the
368
+ * runner never trusts that the caller matched the correct pending call (design/77 doctrine —
369
+ * deterministic structural backstop, not a model self-report).
370
+ *
371
+ * The correct value is the resumed checkpoint's `pendingAction.toolCallId`; the caller has it via the
372
+ * `suspendRef` / pending record it is answering.
373
+ */
92
374
  boundCallId: string;
375
+ /**
376
+ * design/80 D-1 §2 (slice 1a.2): the **server-minted opaque** hash of the pending tool call's input
377
+ * that the human actually saw/approved (`PendingAction.tool_approval.boundInputHash`, computed by the
378
+ * engine at suspend-mint via {@link import("./canonical-json.js").boundInputHashOf} over the post-hook
379
+ * `args`). The caller echoes it VERBATIM — it is opaque; the SDK/service NEVER re-canonicalize args
380
+ * (review r3: a second runtime's serialization could diverge → false mismatch → fail-closed a
381
+ * legitimate approval). The resume verifies it by **string equality** against the checkpoint's
382
+ * persisted value (`checkpoint.invalid_outcome`, fail-closed, pre-CAS) — closing the TOCTOU "same call
383
+ * id, different input" variant that `boundCallId` alone misses (a re-mint that swapped the input under
384
+ * the same tool-call id). It binds the SHOWN input, NOT `updatedInput`: an `allow` edit is the same
385
+ * operator's authorized rewrite, applied AFTER this binding check (design/37,末位应用), so the echoed
386
+ * hash is always the pending record's value regardless of any edit. (Legacy pre-1a.2 checkpoints have
387
+ * no persisted hash → the resume skips this check, binding on `boundCallId` alone; new mints enforce.)
388
+ */
93
389
  boundInputHash: string;
390
+ /** `allow` → execute the pending tool call; `deny` → inject a denial result and continue. */
94
391
  decision: "allow" | "deny";
392
+ /** A re-written arg payload (design/37 policy `allow` rewrite); re-validated on execute. */
95
393
  updatedInput?: unknown;
394
+ /** Model-readable reason attached to a `deny` (else a default is used). */
96
395
  reason?: string;
396
+ /**
397
+ * The ANSWER to a CONTENT-ASK (the reserved AskUserQuestion tool), carried by the decide itself
398
+ * (ruled 2026-08-04). A content-ask is a question TO the user: `allow` alone says "let the tool
399
+ * run" but supplies nothing to run it AGAINST, and the only pre-existing answer seat was the
400
+ * resumed leg's `onQuestion` config closure — a seat an OFFLINE background child has no operator
401
+ * on, because its resume config is rebuilt from the parked row rather than handed in. With the
402
+ * answer on the decision payload, the same `/decide`-shaped call serves both arms: the resume
403
+ * binds it as the resumed leg's answering face, so the pending call executes against the SAME
404
+ * tool, the SAME `selected ⊆ options` fence and the SAME untrusted free-text framing (design/80
405
+ * D-F) as a live answer — one support surface, not two.
406
+ *
407
+ * TRUST: untrusted wire data. It is NEVER read as instructions — the tool splits it into the
408
+ * trusted selections (which must be the model's own option labels) and a fenced free-text
409
+ * remainder. Off-list `selected` values and `note` are exactly the "Other" free-text path.
410
+ *
411
+ * ARM-SCOPED, like `updatedInput`/`reason` on this same arm: only a `policy_ask` `allow` whose
412
+ * pending call IS the reserved tool may carry one. Supplying it anywhere else is rejected
413
+ * pre-CAS (`checkpoint.invalid_outcome`, `detail.field: "answer"`) rather than silently dropped,
414
+ * and OMITTING it on a content-ask allow with no live answering face is rejected the same way —
415
+ * consuming the approval to hand the model a fabricated "no human available" default is the
416
+ * silent-continue this seat exists to end.
417
+ */
97
418
  answer?: QuestionAnswer;
419
+ /**
420
+ * WHAT ENDED THIS APPROVAL — `"human"` (a person gave the final verdict), `"timeout"` (the
421
+ * configured window elapsed with no answer), or `"aborted"` (every other non-human end: the run
422
+ * was cancelled, the connection dropped, the store gave way, retries ran out). See
423
+ * {@link import("./tool-policy.js").ApprovalSettledBy} for the vocabulary itself.
424
+ *
425
+ * Attribution travels WITH the decision because it is a property OF the decision, not something
426
+ * a later reader can reconstruct: by the time an approval reaches a consumer, "denied" and "the
427
+ * window closed" have already collapsed into the same `decision:"deny"`, and only the caller that
428
+ * settled it still knows which happened. Core does not derive it, does not default it, and never
429
+ * infers it from some other outcome — it carries what the settling caller wrote onto the
430
+ * `tool_end` frame of the call this resume resolves.
431
+ *
432
+ * **Fill it at every settlement site.** A deployment resolving an approval from several places
433
+ * (an operator decide, an SLA sweep at the deadline, a cancel path) names the cause at each of
434
+ * them. Deriving it once, centrally, from the last outcome at hand is precisely how a window that
435
+ * elapsed gets reported to a person as another person's refusal.
436
+ *
437
+ * OPTIONAL and additive: absent means the caller named no source (a caller written before this
438
+ * field existed), and a consumer must NOT read a semantic out of the absence — core neither
439
+ * substitutes a default nor guesses. A value outside the three words is refused pre-CAS
440
+ * (`checkpoint.invalid_outcome`), like every other out-of-domain field on this arm: a settlement
441
+ * source nobody can read is worse than none at all. So is one that contradicts its own decision:
442
+ * `decision:"allow"` accepts only `"human"` or omission, because "nobody answered, therefore it
443
+ * ran" is not a record of anything that happened (a deployment whose CONFIGURATION auto-allows an
444
+ * elapsed window omits the field — no one ended that wait).
445
+ *
446
+ * SCOPE — this rides the settlement, NOT the persisted winner: `winnerFromOutcome` records the
447
+ * decision (and its rewrite/answer/reason), which is what an `env_failed` replay must reproduce
448
+ * exactly, and the source is not part of that authorization. A replay therefore states its own
449
+ * source and is not held to the first one. Binding it into the persisted winner would also make a
450
+ * re-supply that merely OMITS the field fail closed on the recovery path, which is the wrong place
451
+ * to add a new refusal; carrying it into the durable row is a store-shape change owned jointly
452
+ * with the backends and belongs to its own change, not to this one.
453
+ */
98
454
  settledBy?: import("./tool-policy.js").ApprovalSettledBy;
99
- } | {
455
+ }
456
+ /** design/74: continue a resource-suspended run with the next slice's allowance. NO decision payload and
457
+ * NO budget figure — the allowance is computed from {@link Checkpoint.resourceLedger}, so money never
458
+ * reaches the caller or the model. Matches `CheckpointGate.kind === "resource_limit"`. */
459
+ | {
100
460
  gate: "resource_limit";
101
461
  decision: "continue";
102
- } | {
462
+ }
463
+ /** design/76 §2.5 (dry-run / shadow): resolve a `needs_review` suspend after a human/judge reviewed the
464
+ * buffered predicted state-diff. `decision:"approve"` → the profile applies the buffered diff (atomically,
465
+ * invalidating `readFileState` on touched paths — design/76 §2.5; the apply itself is PROFILE, not core)
466
+ * then continues; `decision:"reject"` → the prediction is discarded and the run continues without it. Kept
467
+ * DISJOINT from `policy_ask` (the approval family) so the resume-side discriminant never confuses a
468
+ * post-prediction review with a pre-action approval (the third `needs_review`/`dry_run_review` gateMatch arm).
469
+ * No money/diff payload reaches the caller here — the diff lives in the profile's REF store (design/76 §9). */
470
+ | {
103
471
  gate: "dry_run_review";
104
472
  decision: "approve" | "reject";
105
473
  reason?: string;
106
- } | {
474
+ }
475
+ /** design/80 D-B (plan-gate): resolve a `plan_review` PRE-ACTION plan pause after a human reviewed the
476
+ * proposed PLAN. `decision:"approve"` → proceed with the plan as-is; `decision:"edit"` → proceed with the
477
+ * human's `editedPlan` (a TYPED sibling of the plan — NEVER a raw tool `updatedInput`; a plan is not a tool
478
+ * arg, so this binds NO action and never enters the `policy_ask` boundCallId/boundInputHash machinery);
479
+ * `decision:"reject"` → the model RE-PLANS (the rejected plan is discarded; a `reason` may steer the
480
+ * re-plan). Kept DISJOINT from `policy_ask` (approval) AND `dry_run_review` (post-prediction review) so the
481
+ * resume-side discriminant never confuses the three (the `plan_review`/`plan_review` gateMatch arm). It
482
+ * binds no TOOL CALL, but since RB-64 it does record a winner: `winnerFromOutcome` returns the decision
483
+ * (plus `editedPlan` as `updatedInput` and the reviewer `reason`) under the synthetic `gate:plan_review`
484
+ * marker, so an `env_failed` reopen can replay it. The `boundCallId`/`boundInputHash` TOOL-CALL binding
485
+ * is what stays `policy_ask`-only. No plan/diff payload (beyond the human's
486
+ * `editedPlan` text) reaches core — the plan artifact lives in the PROFILE's REF store (design/76 §9). */
487
+ | {
107
488
  gate: "plan_review";
108
489
  decision: "approve" | "edit" | "reject";
109
490
  editedPlan?: string;
110
491
  reason?: string;
111
- } | {
492
+ }
493
+ /** design/144 §3 (message-wake): wake a PARKED (non-gate) checkpoint and drive it to continue — a pure
494
+ * un-park verb, **NOT a gate decision**: it never allows/denies/approves anything. Governance-gate
495
+ * purity is enforced at the resume entry (fail-closed, pre-CAS): a checkpoint awaiting ANY pending
496
+ * gate decision (`gate.kind` ∈ {human, irreversible_ask, needs_review, plan_review, resource_limit} —
497
+ * i.e. the tool_approval / dry_run_review / plan_review / resource_limit decision families) REJECTS a
498
+ * wake with `wake.gate_pending`, so wake can never become a side door around an approval. Only a
499
+ * non-gate park (e.g. a deployment-minted approval-timeout park / pure suspend) is wakeable.
500
+ *
501
+ * `message` (optional): operator content delivered to the resumed run — the ATOMIC equivalent of
502
+ * `setPendingSteer` + resume in one verb (same `validatePendingSteer` persist-time guards, same
503
+ * trusted/untrusted delivery framing as {@link CheckpointState.pendingSteer}). It rides the resume
504
+ * continuation's steer tail as its OWN segment, appended AFTER any already-parked `pendingSteer`
505
+ * (X5: both are delivered — the wake message never displaces a parked, undelivered supervisor steer;
506
+ * each segment is framed under its own `trusted` semantics, never one merged trust domain). WITHOUT a
507
+ * `message`, the checkpoint must already carry a parked `pendingSteer` — a wake with nothing to
508
+ * deliver is rejected (`wake.nothing_to_deliver`, anti spin-wake). Idempotency rides the existing
509
+ * resolve CAS: a second wake of the same token loses the CAS (`checkpoint.already_resolved`). */
510
+ | {
112
511
  gate: "wake";
113
512
  message?: {
114
513
  text: string;
@@ -118,46 +517,220 @@ export type ResumeOutcome = {
118
517
  gate: "task_done";
119
518
  result: TaskResult;
120
519
  };
520
+ /**
521
+ * The pending action a checkpoint suspends *before* — a **discriminated union** keyed by `kind`. Every
522
+ * consumer that reads the tool fields MUST branch on `kind` first: the `resource_limit` arm (design/74)
523
+ * has none (a resource slice suspend has no pending tool to resolve).
524
+ *
525
+ * `tool_approval` (§4): a tool call adjudicated `ask` in durable mode, captured with its **post-hook args**
526
+ * (the design/37 rewrite already applied) so resume executes the exact same call without re-running
527
+ * PreToolUse hooks (§11 M2). `batchToolCallIds` / `completedCallIds` capture the **mid-batch** position
528
+ * (§4.ter): one assistant turn can emit a batch of tool calls; if call #k hits `ask`, calls #1..k-1 already
529
+ * executed (in `completedCallIds`) and #k+1..N are still pending. ID-based (a `Set<toolCallId>`), not
530
+ * positional — immune to reordering / off-by-one (council Question #1), and it doubles as the reconcile
531
+ * suspended-batch discriminant (§15.2 net-add #7) so wake-reconcile never closes a suspended call.
532
+ */
121
533
  export type PendingAction = {
122
534
  kind: "tool_approval";
123
535
  toolCallId: string;
124
536
  toolName: string;
537
+ /** Post-hook (design/37-rewritten) args to execute on `allow`. */
125
538
  args: unknown;
539
+ /** [1245] (additive) — the tool's approval display projection of `args`, minted alongside the
540
+ * row (see AgentTool.approvalPreview; control-char-sanitized + 16KiB-clamped at mint).
541
+ * **UNTRUSTED, ADVISORY**: inboxes must escape contextually and surface `args` alongside.
542
+ * NEVER read by the resume path (args + boundInputHash stay the sole execution contract). */
126
543
  preview?: unknown;
544
+ /** design/179 §4 (additive) — the persistable allow-rule forms this parked call could be covered by,
545
+ * minted from the SAME post-hook args the row executes. Advisory display metadata for the approval
546
+ * inbox's "stop asking me this" option; never read by the resume path, and never a rule by itself
547
+ * (minting one goes through the approval-record protocol). Absent when no rule lane is armed or the
548
+ * command is one the lane cannot speak for. */
127
549
  ruleSuggestions?: readonly import("./permission-rule-model.js").RuleSuggestion[];
550
+ /**
551
+ * design/80 D-1 §2 (slice 1a.2): the server-minted **opaque** boundInputHash of {@link args} — a
552
+ * SHA-256 (hex) via {@link import("./canonical-json.js").boundInputHashOf}, computed ONCE here at
553
+ * suspend-mint and persisted on the row. The operator sees it (surfaced on the pending record) and
554
+ * echoes it back as {@link ResumeOutcome} `boundInputHash`; the resume verifies opaque string equality
555
+ * against THIS value (never re-serializing the args — see the field's doc). Absent only on a legacy
556
+ * pre-1a.2 checkpoint (deserialized without it), in which case the resume skips the hash check.
557
+ */
128
558
  boundInputHash: string;
559
+ /** All tool-call ids in the suspending assistant message, in emission order. */
129
560
  batchToolCallIds: string[];
561
+ /** Ids of calls already executed (results in the session) when the suspend fired — #1..k-1. */
130
562
  completedCallIds: string[];
131
- } | {
563
+ }
564
+ /** design/74: suspended at a resource slice boundary — there is NO pending tool to resolve. Consumers
565
+ * that read the tool-approval fields above MUST branch on `kind` first (this arm has none). */
566
+ | {
132
567
  kind: "resource_limit";
133
568
  reason: ResourceLimitReason;
134
- } | {
569
+ }
570
+ /** design/80 D-B (r3 — an EXPLICIT arm, NOT a reused `resource_limit` placeholder): paused at a PRE-ACTION
571
+ * PLAN REVIEW (`CheckpointGate.kind === "plan_review"`). There is NO pending tool to resolve — the human
572
+ * reviews the PLAN, not a tool call — so this arm has no tool fields. Every consumer that reads the
573
+ * `tool_approval` fields above MUST branch on `kind` FIRST: a `plan_review` checkpoint must never flow into
574
+ * a `resource_limit`- or `tool_approval`-shaped continuation (a contract test pins this). The plan artifact
575
+ * is a PROFILE concern (a REF — design/76 §9); core stores no plan here. */
576
+ | {
135
577
  kind: "plan_review";
136
- } | {
578
+ }
579
+ /** design/144 §3 (X2) — the NON-GATE arm: nothing pends a decision. The shape of a deployment-minted
580
+ * PARK checkpoint (approval-timeout park / pure suspend; paired with `gate.kind === "task_done"`, the
581
+ * non-gate gate arm) and of a 1C `task_done` handle row. It is the ONLY pendingAction kind the `wake`
582
+ * resume entry accepts — every other kind above is a pending GATE decision, and the wake gate rejects
583
+ * it (`wake.gate_pending`) via an EXHAUSTIVE switch (a future kind added here without classifying it
584
+ * there is a compile error, and an unknown kind on a deserialized row fails closed at runtime). Core
585
+ * never mints this arm itself (the park convention is the deployment half); the pre-existing doc
586
+ * references to "a task_done pendingAction" (`getCheckpointSummary` et al.) name exactly this shape. */
587
+ | {
137
588
  kind: "task_done";
138
589
  };
590
+ /**
591
+ * The per-task correctness state a checkpoint must carry so a resumed task runs in the **same state
592
+ * space** it suspended in (§4.bis, the jury head must-fix). An **explicit whitelist** of serializable
593
+ * correctness fields — never a blind `JSON.stringify(Prepared)`, which would silently corrupt the 9+
594
+ * non-serializable runtime objects (`harness`/`session`/`abortController`…) it holds (round-2 BUG#1).
595
+ *
596
+ * Round-trip fidelity of every field is covered by tests; runtime objects are **forbidden** here.
597
+ * `cacheFingerprint` (design/31) is deliberately **excluded** — it is observation-only, so resume just
598
+ * suppresses cache-break detection on the first turn rather than persisting it as correctness state.
599
+ */
139
600
  export interface CheckpointState {
601
+ /** design/36: deferred-tool activation set — else resume re-discloses / diverges from history. */
140
602
  activeTools: string[];
603
+ /** 1.41 submit_output: the validated structured output set pre-suspend, else it's lost on resume. */
141
604
  outputRef?: {
142
605
  value?: unknown;
143
606
  set?: boolean;
144
607
  };
608
+ /** design/38: nested sub-agent cumulative cost — else pre-suspend child cost evaporates (§4.bis/Q7). */
145
609
  nestedStats: NestedUsageAccum;
610
+ /** design/41: consolidation notes collected pre-suspend — else the task-end pass loses them. */
146
611
  consolidationNotes?: ConsolidationNote[];
612
+ /** design/44: the hand's read-file state (content hashes), serialized from the hands-toolkit closure
613
+ * (NOT part of Prepared — §15.2 net-add #8). Without it a resumed `edit_file` is rejected "not read". */
147
614
  readFileState?: Array<[string, ReadEntry]>;
615
+ /**
616
+ * design/78 Slice-1: the SAFE-tier self-repair loop's durable state (`failureTrace`/`diagnostics`/
617
+ * `rejectedHypotheses`/`attemptCount`/`oracleTier` — all JSON/`structuredClone`-safe, no fn/Date). Set ONLY
618
+ * when an orthogonal durable suspend (resource/HITL) interleaves a `runRepairLoop` run — the happy path is
619
+ * in-memory only. On resume it re-seeds `RepairLoopConfig.resumeBundle` so `attemptCount` advances
620
+ * MONOTONICALLY (never reset). `baselinePassTests` is deliberately NOT carried here (grader-computed
621
+ * out-of-process — a worker must not be able to shrink the ratchet). Absent for a run with no repair loop.
622
+ */
148
623
  repairBundle?: RepairBundle;
624
+ /**
625
+ * design/49 v1.5: the remote workspace's serializable identity (E2B sandbox id / provider / mount path /
626
+ * snapshot id). Set ONLY when the suspended task ran with a remote, suspendable {@link WorkspaceHandle}
627
+ * (a per-task `executionEnvFactory` env that was `suspendVM()`-paused rather than destroyed). Resume
628
+ * rebuilds the env via the factory and `resumeVM(snapshotId)` so the restored workspace matches the
629
+ * `readFileState` above. All-string fields → JSON/`structuredClone` round-trips safely (consistent with
630
+ * this whitelist's "no runtime objects" rule). Absent for a process-local (non-remote) suspend.
631
+ */
149
632
  workspaceHandle?: WorkspaceHandle;
633
+ /**
634
+ * design/80 D-A: a durable mid-task STEER for a DURABLY-SUSPENDED task. Live `TaskStream.steer`
635
+ * (runtask.ts) is unreachable while the harness is idle (durably suspended), so a human supervisor's
636
+ * guidance is parked HERE via {@link CheckpointStore.setPendingSteer} and injected on resume (runtask.ts,
637
+ * after the resume-continuation prompt). It is GUIDANCE ONLY — never an approval channel (§3 inv #4) and
638
+ * never parsed into control state (§3 inv #5); a budget/autonomy/gate-threshold is CONFIG, not steer text.
639
+ *
640
+ * `trusted` is FROZEN at `setPendingSteer` from the SERVICE's verified-principal check (an operator-role
641
+ * check, NOT a client header — the service's job, out of scope here) and NEVER recomputed on resume
642
+ * (§3 inv #1). On resume a `trusted:false` steer reaches the model as a PLAIN user message with NO
643
+ * `<system-reminder>` wrapper (no authority laundering, §3 inv #3); a `trusted:true` steer MAY ride the
644
+ * reminder (`formatHookFeedback`, mirroring the live trusted branch). `text` containing `</system-reminder>`
645
+ * is REJECTED at `setPendingSteer` (typed `steering.invalid_content`) so a dirty steer never enters this
646
+ * state (§3 inv #2); the untrusted-injection path ALSO sanitizes the text as untrusted data, belt-and-braces.
647
+ * Absent when no steer is pending. All-string fields → JSON/`structuredClone` round-trips safely.
648
+ *
649
+ * **Delivery is BEST-EFFORT, at-most-meaningfully-once (review-council, by design):** a steer is GUIDANCE,
650
+ * not a correctness-critical message, so the delivery guarantee is intentionally loose:
651
+ * - It rides EVERY resume of THIS checkpoint that runs a turn — including a faithful `env_failed`/
652
+ * `tool_unavailable` reopen→re-resume, which re-shows the guidance (coherent with the reopen REPLAYING the
653
+ * leg; the model re-does the work, re-seeing the steer). It is NOT carried onto a NEW re-suspend checkpoint
654
+ * (serializeCheckpointState stamps `undefined`) — a supervisor steers the new checkpoint afresh.
655
+ * - It is DROPPED (never delivered) on a resume that runs no turn (an exhausted-budget resume) or that is
656
+ * set in the get→resolve race window of an in-flight resume — both rare; the run is ending or the steer
657
+ * just missed its train. A supervisor re-issues `setPendingSteer` if a steer didn't land.
658
+ * A precise exactly-once delivery would need clearing the steer from the persisted row on consume (a reopen
659
+ * clear + a get→resolve interlock); deferred as not worth the cross-backend complexity for guidance text.
660
+ *
661
+ * **Superseded by {@link CheckpointState.pendingSteerQueue} (design/171 §5.3).** New writes go to the
662
+ * queue; this seat remains ONLY so rows written before the queue existed keep delivering their parked
663
+ * steer. Never read it directly — {@link readPendingSteerQueue} is the single read point that folds
664
+ * this legacy seat in as queue member 0.
665
+ */
150
666
  pendingSteer?: {
151
667
  text: string;
152
668
  trusted: boolean;
153
669
  };
670
+ /**
671
+ * design/171 §5.3 — the BOUNDED, ORDERED queue of parked operator steers, drained in `seq` order on
672
+ * resume with every entry framed under its own trust semantics.
673
+ *
674
+ * It replaces the single {@link CheckpointState.pendingSteer} seat, whose last-writer-wins overwrite
675
+ * meant two people steering one suspended run silently destroyed the first instruction — and destroyed
676
+ * it with no attribution, so nobody could tell afterwards that anything was lost.
677
+ *
678
+ * Bounds are fail-loud, never lossy: {@link MAX_PENDING_STEER_ENTRIES} entries and
679
+ * {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES} serialized bytes, whichever binds first, reject the
680
+ * APPEND with `steering.queue_full` rather than evicting an already-accepted instruction.
681
+ *
682
+ * Absent when nothing is parked. A row may legally carry BOTH this and the legacy seat (a pre-queue
683
+ * suspend that was steered again after the upgrade); the read helper orders the legacy seat first.
684
+ */
154
685
  pendingSteerQueue?: PendingSteerEntry[];
686
+ /**
687
+ * SR-7 (CC 2.1.198 orphaned-background-task notice: F6c pretty.js:698391-698398, resume leg
688
+ * :707384-707399 under the `CLAUDE_CODE_RESUME_INTERRUPTED_TURN` gate — the cloud-worker restart
689
+ * leg, exactly sema's durable-resume shape): the background tasks (pending/running, this run's
690
+ * owner triple) still ALIVE at suspend. Background processes never survive a suspend (design/103
691
+ * §3.7 unconditional dispose + 飞轮 [506]③ killed receipts), so on resume any snapshot entry NOT
692
+ * alive in the resume leg's registry is an ORPHAN — aggregated into ONE CC-verbatim "The container
693
+ * was restarted…" reminder appended to the resume continuation (single message: header +
694
+ * `- description (task id)` list + re-create instruction; never a per-task barrage). A survivor
695
+ * (session-resident shell / in-window monitor) is excluded — still reachable via TaskOutput/
696
+ * TaskStop, it needs no obituary. CC's per-orphan `Ku(task_id,"stopped")` status write (:707399)
697
+ * is N/A here: the suspend teardown already settled them killed with receipts. Absent for a run
698
+ * with no live background tasks at suspend, and for pre-1.262 checkpoints (deserialized without
699
+ * the field) — both resume silently, exactly like CC with an empty list.
700
+ */
155
701
  runningBackgroundTasks?: Array<{
156
702
  id: string;
157
703
  description?: string;
158
704
  }>;
705
+ /**
706
+ * codex 终审 1.255 F2: the hands band's LOGICAL working directory at suspend (`handsCwdRef.current` —
707
+ * moved by `cd` and by EnterWorktree). Without it a resume silently reset the task cwd to the task root:
708
+ * relative Read/Edit/Write paths and Bash commands then operated somewhere else than the model believes.
709
+ * Absent when the task has no tracked cwd (no real shell / read-only hands). The directory itself is
710
+ * plain on-disk state that survives a process-local suspend (worktrees included — the tree stays on disk).
711
+ */
159
712
  handsCwd?: string;
713
+ /**
714
+ * codex 终审 1.255 F2: the ACTIVE EnterWorktree session at suspend (worktree.ts keeps it in a shared
715
+ * serializable ref, not a closure-only var, precisely so it lands here). Without it a resume LOST the
716
+ * session: ExitWorktree became a no-op (the unchanged worktree could never be removed), a second
717
+ * EnterWorktree was wrongly accepted, and `handsCwd` pointed into a worktree the tooling no longer
718
+ * owned. All-string fields; the worktree directory survives the suspend on disk (managed under the
719
+ * task root, `git worktree` metadata intact).
720
+ */
160
721
  activeWorktree?: ActiveWorktreeSession;
722
+ /**
723
+ * Parent effective-policy inheritance (tighten-only): the durable projection of the run's
724
+ * {@link import("./runner/prepare-task.js").InheritedGate}. Only the serializable DATA half persists —
725
+ * `ancestorRules` (each ancestor's session-rule snapshot: sessionId/principal/rev/rules, re-compiled on
726
+ * resume against the resumed leg's env) and `shellGate` (the chain's max-rank shell doctrine). The OPAQUE
727
+ * half (`parentConstraints` — live ToolPolicy/onAsk closures) CANNOT persist; `requiresParentConstraint`
728
+ * records only its PRESENCE, so the resume pre-CAS ladder can fail-loud (`resume.parent_constraint_missing`,
729
+ * checkpoint stays `pending`) when the resuming caller did not re-supply the closures via
730
+ * `resumeStream(..., internals)`. Absent for a task that inherited nothing (pre-1.294 checkpoints included)
731
+ * — distinguishable from `requiresParentConstraint: false` with data-only inheritance, which resumes
732
+ * without any re-supplied internals. All-data fields → JSON/`structuredClone` round-trips safely.
733
+ */
161
734
  inheritedGate?: {
162
735
  ancestorRules?: ReadonlyArray<{
163
736
  sessionId: string;
@@ -166,36 +739,174 @@ export interface CheckpointState {
166
739
  rules: SessionPermissionRules;
167
740
  }>;
168
741
  shellGate?: "off" | "always" | "classify";
742
+ /** Org-memory admission freeze (ruled 2026-08-05): the chain's admitted org-scope set at
743
+ * suspend (data half, plain strings). The resume leg folds it seed ∩ live (tighten-only) and
744
+ * re-runs admission under it — a resume must never widen the delegation freeze. Absent on
745
+ * older checkpoints ⇒ the live channel (or, for a delegated child, the fail-closed ∅) stands.
746
+ * Cross-VERSION enforcement (an older worker resuming a newer checkpoint ignores fields it
747
+ * does not know) is the deployment's server/core version-gate concern, not a per-field fence. */
169
748
  admittedOrgScopes?: readonly string[];
749
+ /** THIS task's OWN admitted org verdict at suspend (adversarial-review adoption, 2026-08-05):
750
+ * distinct from {@link admittedOrgScopes} (the incoming PARENT constraint). The resume leg
751
+ * applies it as the SESSION freeze on the final admitted projection — regardless of origin
752
+ * (deployment-origin scopes bypass the chain freeze, never the session freeze) — so a session's
753
+ * verdict can only NARROW across suspend/resume: re-adjudication still runs every leg (a
754
+ * revoked grant refuses the resume), but a policy widened mid-session cannot widen THIS
755
+ * session's mount or the chain its children inherit. Present (possibly EMPTY — an adjudicated
756
+ * empty verdict is not "org-less") iff the suspend leg adjudicated an org plane; absent on
757
+ * older checkpoints / org-less tasks. Checkpoints carrying this state are stamped
758
+ * {@link ORG_ADMISSION_CHECKPOINT_VERSION} so a pre-admission worker rejects them pre-CAS. */
170
759
  ownAdmittedOrgScopes?: readonly string[];
760
+ /** The WRITE half of the session freeze: the org write grant this session held at suspend —
761
+ * `null` = the org write face was read-only (a resume must not gain write authority the
762
+ * suspend leg did not hold); a scope string = the explicit grant. Present iff
763
+ * {@link ownAdmittedOrgScopes} is (same adjudication record). */
171
764
  ownAdmittedOrgWriteScope?: string | null;
765
+ /** The MONOTONIC org-governance provenance bit (adversarial round 5): `true` when any leg of
766
+ * the suspended tree ran with an org-admission surface (or inherited the bit). A resume worker
767
+ * missing the surface still treats the leg as governed — org-shaped scopes respelled outside
768
+ * the v2 contract are refused, even when every admitted set en route was EMPTY. Absent on
769
+ * trees that never had a governance surface. */
172
770
  orgAdmissionGoverned?: true;
173
771
  requiresParentConstraint: boolean;
772
+ /** How many opaque parent constraints the suspended leg ran under (present iff `requiresParentConstraint`).
773
+ * The resume pre-CAS ladder rejects a re-supply whose `parentConstraints.length` differs
774
+ * (`resume.parent_constraint_mismatch`) — a partial re-supply is a typed error, never a silently
775
+ * thinner chain. Absent on older checkpoints ⇒ only the non-empty check applies. */
174
776
  parentConstraintCount?: number;
175
777
  };
778
+ /**
779
+ * [c209-C] Q5 — the listing frames' announced NAME-SETS at suspend (agent types / skills the model
780
+ * has actually been shown this run, committed on first-frame delivery or intact boundary survival).
781
+ * A resume leg seeds its producers from these names and DIFFS the current roster/skill set against
782
+ * them, so cross-leg drift (config-center roster change between suspend and resume) is announced as
783
+ * a delta frame at the first boundary instead of silently swallowed — the exact event the listing
784
+ * eviction from the cached prefix exists to serve. Names only, never contents (a description change
785
+ * on a surviving name is deliberately not re-announced — name-keyed diff, agent_listing parity).
786
+ * Schema ADDITION (backward-compatible, B-5): an older checkpoint without the field resumes via the
787
+ * MED-3② transcript probe (delivered ⇒ seeded as the CURRENT entries, drift undetectable there;
788
+ * not found ⇒ full re-announce at the first boundary — conservative direction preserved).
789
+ *
790
+ * [c209-C] R2 C7 — `models`: the model CATALOG names announced (the agent_listing initial frame's
791
+ * "Models available for the 'model' parameter:" tail, Q4). The resume seed feeds it to the
792
+ * producer; a differing current catalog ⇒ the first boundary's agent_listing frame re-carries the
793
+ * full line — without this a catalog swap between legs was never re-disclosed anywhere (the
794
+ * catalog left the tool description with Q4). Absent (older checkpoint / never announced) ⇒ drift
795
+ * undetectable, silent (conservative, same B-5 direction).
796
+ */
176
797
  announcedListings?: {
177
798
  agents?: string[];
178
799
  skills?: string[];
179
800
  models?: string[];
180
801
  };
181
802
  }
803
+ /**
804
+ * {@link CheckpointState} with **every** field made required-PRESENT, while each value keeps its original
805
+ * type (so an absent optional field is still passed explicitly as `undefined`). The suspend-side
806
+ * serialization builds `state` as this type (design/45 P3 / design/51 §P3): adding a new per-task
807
+ * correctness field to {@link CheckpointState} is then a **compile error** at the serialization site until
808
+ * it is explicitly handled — it can never be silently omitted and lost on resume. (Keying off
809
+ * `keyof Required<…>` makes the mapped type non-homomorphic, so it forces presence of every key yet leaves
810
+ * each value's `| undefined` intact — `workspaceHandle: undefined` for a process-local suspend still
811
+ * type-checks.)
812
+ */
182
813
  export type SerializedCheckpointState = {
183
814
  [K in keyof Required<CheckpointState>]: CheckpointState[K];
184
815
  };
816
+ /**
817
+ * The BASELINE {@link Checkpoint} schema version (design/49 §3, code-ready council round-2): the
818
+ * shape a plain human-approval suspend carried before any feature raised the format. It is NOT the highest
819
+ * a resume accepts — that is {@link MAX_SUPPORTED_CHECKPOINT_VERSION}, which the per-feature stamps
820
+ * (RESOURCE / BINDING / TOKEN / ORG_ADMISSION) have each raised on their own, deliberately without moving
821
+ * this constant. Resume stays forward-compatible by `<=` against MAX, never `===`. Every live mint site
822
+ * stamps one of the feature versions, so nothing in-tree produces this value today; it is kept as the
823
+ * floor of the version ladder and as the reading for a row written before any feature raised the format.
824
+ */
185
825
  export declare const CURRENT_CHECKPOINT_VERSION = 1;
826
+ /** design/74 R3-B: the schema version a `resource_limit` suspend stamps (v2 — it adds `resourceLedger` and a
827
+ * `resource_limit` `pendingAction`/`gate` an old worker can't handle). An old worker (`MAX_SUPPORTED`=1)
828
+ * rejects it pre-CAS (stays `pending`, retryable on a new worker). */
186
829
  export declare const RESOURCE_CHECKPOINT_VERSION = 2;
830
+ /**
831
+ * design/80 D-1 (version-skew downgrade fix): the schema version a **binding-bearing** human/irreversible_ask
832
+ * `tool_approval` suspend stamps. These checkpoints carry the decision-action binding (`boundCallId` +
833
+ * `boundInputHash`) whose enforcement lives ENTIRELY in the resuming worker's resume path. A pre-D-1 worker
834
+ * (released 1.100.0: `MAX_SUPPORTED`=2, and its resume code has ZERO binding logic) would otherwise resume a
835
+ * D-1-minted v1 checkpoint and execute the pending tool with NO decision-action verification — the exact
836
+ * "approve vendor-A $5 → execute vendor-B $5000" bypass the binding exists to prevent (council BLOCKER #1).
837
+ * Stamping these at **v3 (> the old worker's MAX of 2)** forces a pre-D-1 worker to reject them PRE-CAS
838
+ * (`unsupported_version`, stays `pending`, retried on a binding-enforcing worker) instead of silently voiding
839
+ * the binding. A resource_limit suspend keeps stamping v2; a pre-binding (legacy 1.100.0) checkpoint is v1.
840
+ */
187
841
  export declare const BINDING_CHECKPOINT_VERSION = 3;
842
+ /**
843
+ * design/164 (the BINDING_CHECKPOINT_VERSION precedent replayed a third time): the schema version a suspend
844
+ * stamps when its ledger CARRIES a cross-slice TOKEN allocation (`resourceLedger.totalTokens` set). The
845
+ * enforcement — window clamp against the remaining allocation, exhausted-resume fail-fast — lives ENTIRELY
846
+ * in the resuming worker, so a pre-164 worker (MAX_SUPPORTED=4) would accept the row, ignore
847
+ * `spentTokens` as a bound, and grant a fresh full window. Stamping v5 forces it to reject PRE-CAS
848
+ * (`unsupported_version`, stays `pending`, retried on an enforcing worker). A ledger with NO token total
849
+ * keeps the historic stamps (v2 resource / v3 binding) — deployments not using it see zero version movement.
850
+ *
851
+ * v4 (the retired wall-clock total) is deliberately NOT reused: this worker still READS v4 rows, and one
852
+ * whose ledger carries the retired axis is refused with its own typed code (see
853
+ * {@link CheckpointError} `checkpoint.walltime_axis_retired`) instead of silently resuming under a bound
854
+ * nobody enforces any more.
855
+ */
188
856
  export declare const TOKEN_CHECKPOINT_VERSION = 5;
857
+ /**
858
+ * Org-memory admission state (adversarial round 2, 2026-08-05 — the BINDING_CHECKPOINT_VERSION
859
+ * precedent replayed a fourth time): the schema version a suspend stamps when its state carries
860
+ * org-admission freeze fields (`inheritedGate.admittedOrgScopes` / `ownAdmittedOrgScopes` /
861
+ * `ownAdmittedOrgWriteScope` — the adjudicated-empty record included). The enforcement — the
862
+ * session/delegation freeze on resume, the tenant-admission door itself — lives ENTIRELY in the
863
+ * resuming worker, so a pre-admission worker (MAX_SUPPORTED=5) would accept the row, ignore the
864
+ * freeze fields, and remount the raw org scopes with no admission at all. Stamping v6 forces it to
865
+ * reject PRE-CAS (`unsupported_version`, stays `pending`, retried on an enforcing worker). A
866
+ * checkpoint with NO org-admission state keeps the historic stamps — deployments not using org
867
+ * memory see zero version movement.
868
+ */
189
869
  export declare const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
870
+ /** The highest {@link Checkpoint.version} `runner.resume` will act on; a higher one is rejected pre-CAS with
871
+ * {@link CheckpointError} `unsupported_version` (the checkpoint stays `pending`, retryable on a newer worker).
872
+ * Raised to 6 for org-admission-bearing checkpoints — this worker reads v1 (legacy human), v2
873
+ * (resource), v3 (binding human/irreversible_ask), v4 (a pre-164 row, refused only when it carries the
874
+ * retired wall-clock allocation), v5 (token-allocation-bearing resource/approval) and v6
875
+ * (org-admission freeze state). */
190
876
  export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 6;
877
+ /**
878
+ * Read a checkpoint's schema version, defaulting an absent field to **legacy `0`** (a 1.67-era checkpoint
879
+ * written before the field existed — it carries no `workspaceHandle`, so resuming it the v1 way is safe).
880
+ * Compare with `<= MAX_SUPPORTED_CHECKPOINT_VERSION`, never `=== CURRENT_CHECKPOINT_VERSION` (design/49 §3).
881
+ */
191
882
  export declare function checkpointVersionOf(cp: Pick<Checkpoint, "version">): number;
883
+ /**
884
+ * design/74 R3-B: cross-slice resource accounting carried by a `resource_limit` {@link Checkpoint}. The
885
+ * total budget is the human's allocation; `spent*` accumulate across the resume→re-suspend chain (debited
886
+ * at each suspend's `put`). A resumed slice's effective `maxCostUsd` = `min(window, totalBudgetMicroUsd -
887
+ * spentMicroUsd)`; the final {@link TaskResult} stats aggregate `spent* + this slice's stats` so a
888
+ * multi-slice run reports the whole, not just the last segment. Lives ON the checkpoint row so one
889
+ * `resolve`/`put` CAS covers status + ledger atomically (no cross-store split).
890
+ */
192
891
  export interface ResourceLedger {
892
+ /** The human's total $ allocation in micro-USD; `undefined` = no $ ceiling (only window/walltime bound). */
193
893
  totalBudgetMicroUsd?: number;
894
+ /** design/164 — the human's total TOKEN allocation across the whole resume→re-suspend chain;
895
+ * `undefined` = no token ceiling (only the per-slice window binds). Replaces the retired wall-clock
896
+ * total: tokens are additive across processes, wall-clock across a suspend chain is not. */
194
897
  totalTokens?: number;
898
+ /** Cumulative cost across all slices so far (micro-USD), debited at each suspend's `put`. */
195
899
  spentMicroUsd: number;
900
+ /** Cumulative tokens across all slices so far. */
196
901
  spentTokens: number;
902
+ /** Cumulative turns across all slices so far. */
197
903
  spentTurns: number;
904
+ /** Cumulative wall-clock across all slices so far (ms; each slice measured on the monotonic clock).
905
+ * design/164: PURE ACCOUNTING — it is no longer a gate on anything (the wall-clock resource axis
906
+ * retired). Kept because "how long did this chain actually run" is a real observability question.
907
+ * Absent on checkpoints written before it existed; readers treat missing as 0. */
198
908
  spentWalltimeMs?: number;
909
+ /** How many slices have run so far (the resource chain's suspend count). */
199
910
  sliceCount: number;
200
911
  }
201
912
  export declare function debitLedger(prior: ResourceLedger | undefined, slice: {
@@ -209,34 +920,180 @@ export declare function debitLedger(prior: ResourceLedger | undefined, slice: {
209
920
  }, opts?: {
210
921
  countSlice?: boolean;
211
922
  }): ResourceLedger;
923
+ /** design/74 Slice 4: the $ budget (micro-USD) the NEXT slice may still spend = `totalBudgetMicroUsd -
924
+ * spentMicroUsd`, never negative. `undefined` when no total $ ceiling is set (the run is bounded only by the
925
+ * per-slice window / walltime). A resumed slice's effective `maxCostUsd` is `min(window, this)`. NB: `0` is a
926
+ * VALID, exhausted ceiling — NOT "unlimited" (only `undefined` is unlimited). Any spend then immediately trips
927
+ * `overBudget`, and the run loop fails an exhausted resume fast (runtask). Never falsy-test this value (a
928
+ * `if (remaining)` would skip a legitimate 0 ceiling and silently bypass the budget). */
212
929
  export declare function remainingBudgetMicroUsd(ledger: ResourceLedger | undefined): number | undefined;
930
+ /** design/164 (the TOKEN sibling of {@link remainingBudgetMicroUsd}): tokens the NEXT slice may still
931
+ * spend = `totalTokens − spentTokens`, never negative. `undefined` when no token total is set (the run is
932
+ * bounded only by the per-slice window / the $ axis). A resumed slice's effective `limits.maxTokens` is
933
+ * `min(window, this)` — a resume can't refresh the allocation. NB: `0` is a VALID, exhausted ceiling
934
+ * (only `undefined` is unlimited); the run loop fails an exhausted resume fast — never falsy-test it. */
213
935
  export declare function remainingTokens(ledger: ResourceLedger | undefined): number | undefined;
936
+ /**
937
+ * design/80 D-1 (reopen-by-reason + persist-winner): the WINNING resume binding, recorded ON the
938
+ * checkpoint row the moment a {@link CheckpointStore.resolve} CAS wins (pending → resolved). It is the
939
+ * durable record of "which decision was approved against which pending action" so a later reopen→re-resume
940
+ * can be validated against it. A `policy_ask` resolve and BOTH review-gate resolves record a winner: only
941
+ * `policy_ask` is bound to a specific pending tool call, while `plan_review` / `dry_run_review` record
942
+ * their decision under the synthetic `gate:<kind>` marker instead (RB-64). A `resource_limit` / `wake` /
943
+ * `task_done` resolve records none (there is no decision to bind). `boundInputHash` (binding the exact executed bytes) is a SEPARATE
944
+ * later D-1 slice (1a.2) and is deliberately NOT part of this winner.
945
+ */
214
946
  export interface ResolvedOutcome {
947
+ /** The pending tool call this decision was bound to (design/80 D-1 slice 1a). RB-64: a REVIEW gate binds
948
+ * no tool call, so it records the synthetic `gate:<kind>` marker instead — the replay guard compares the
949
+ * DECISION for those, never a call id. */
215
950
  boundCallId: string;
951
+ /** The adjudication the operator made. RB-64 widens this to the review verdicts (`approve`/`reject`/
952
+ * `edit`) so a `plan_review`/`dry_run_review` winner can be persisted and replayed by the same
953
+ * env_failed reopen guard the approval gate has — previously review gates recorded NO winner, which is
954
+ * what left their reopen path with nothing to check a re-vote against. */
216
955
  decision: "allow" | "deny" | "approve" | "reject" | "edit";
956
+ /** A re-written arg payload, if the `allow` rewrote the call (design/37). Carried so an `env_failed`
957
+ * re-resume (a system retry of the SAME approved action) must replay the identical `updatedInput`. */
217
958
  updatedInput?: unknown;
959
+ /** The content-ask ANSWER the `allow` carried, if any (ruled 2026-08-04). Recorded for the same reason
960
+ * `updatedInput` is: the answer is the SUBSTANCE of a content-ask decision, not a decoration on it, so
961
+ * an `env_failed` re-resume — a system retry of the already-approved action — must replay the identical
962
+ * answer. Without it the replay could keep the verdict and swap the answer text going into the model's
963
+ * context (the shape the plan-edit replay guard closed for `editedPlan`). Omitted when absent so the
964
+ * replay equality check does not falsely diverge on `undefined`. */
218
965
  answer?: QuestionAnswer;
966
+ /** The operator's NOTE, if the decision carried one — the approval lane's deny `reason` and the review
967
+ * lanes' reject `reason` alike. Recorded for the same reason `updatedInput` and `answer` are: the note
968
+ * is the substance of the decision, not a decoration on it. It is the text the resume continuation
969
+ * fences into the model-facing reminder that steers the re-plan, so a replay keeping the verdict and
970
+ * swapping the note delivers a different instruction under the same word. Omitted when absent.
971
+ *
972
+ * ADDITIVE and OPTIONAL by contract: rows persisted before this field existed carry no `reason` key,
973
+ * and the key's absence is read as "this dimension was never recorded" — the replay guard skips it —
974
+ * NEVER as "recorded as empty", which would refuse every legitimate retry of an existing row. Absence
975
+ * is therefore indistinguishable from a genuinely note-less decision, which is the accepted price of
976
+ * the additive shape: the binding starts with the rows that record it.
977
+ *
978
+ * RECORDED VERBATIM, and independently of the verdict: any supplied note is kept exactly as given —
979
+ * including `""`, and including a note on a verdict that never relays one (an `allow`, an `approve`, an
980
+ * `edit`). The winner is a record of the decision that was made, not of the subset of it the
981
+ * continuation happens to render, and `updatedInput` has carried the same property since D-1 (a rewrite
982
+ * on a `deny` is recorded and replay-compared although nothing executes it). Normalizing here would
983
+ * mean an empty note collapsed into the "never recorded" hole above and a note on a non-relaying
984
+ * verdict became free to change between the resolve and its replay — both weaken "the retry presents
985
+ * the same decision" for no delivery benefit, since a replay is produced by re-presenting the same
986
+ * outcome, not by reconstructing an equivalent one. */
219
987
  reason?: string;
220
988
  }
989
+ /**
990
+ * design/80 D-1: why a consumed checkpoint was {@link CheckpointStore.reopen | reopened} (resolved →
991
+ * pending). The reason drives re-resume validation (§3 invariant #1): an `env_failed` reopen is a SYSTEM
992
+ * RETRY of the ALREADY-APPROVED action — the re-resume MUST replay the persisted {@link ResolvedOutcome}
993
+ * winner, never a new vote — while a `tool_unavailable` reopen (the action could not run, may now be
994
+ * invalid — P-7) lets a human RE-DECIDE with the tool present, so a fresh operator decision IS allowed.
995
+ */
221
996
  export type ReopenReason = "env_failed" | "tool_unavailable";
997
+ /**
998
+ * design/80 D-1 (atomicity fix): the optimistic-concurrency precondition a {@link CheckpointStore.resolve}
999
+ * caller passes so its CAS is atomic with the validation it did against an earlier `get()` snapshot. The
1000
+ * resume-side decision-action guards (boundCallId/boundInputHash) bind to per-token-IMMUTABLE fields, but the
1001
+ * **reopen-by-reason** guard reads `reopenReason` + `resolvedOutcome` — which a concurrent
1002
+ * {@link CheckpointStore.reopen}/{@link CheckpointStore.resolve} cycle mutates. Passing the monotonic
1003
+ * {@link Checkpoint.rev} the caller observed makes `resolve` additionally require the LIVE row's `rev` to still
1004
+ * equal it; ANY intervening resolve/reopen bumps `rev`, so a cycle in the get→resolve window loses the CAS
1005
+ * (fail-closed), forcing a re-`get` + re-validate.
1006
+ *
1007
+ * **Why a counter, not the reopenReason value (round-2 BLOCKER fix):** `reopenReason` is a 2-valued enum, so a
1008
+ * full `env_failed`→`tool_unavailable`→`env_failed` cycle (the `tool_unavailable` arm permits a fresh P-7
1009
+ * re-decision) returns `reopenReason` to the SAME value while `resolvedOutcome` silently changed to a different
1010
+ * (e.g. RETRACTED) winner — a classic ABA on the OCC key. A monotonic `rev` bumped on every resolve/reopen is
1011
+ * ABA-proof: a returned-to-the-same-value `reopenReason` still has a strictly higher `rev`. Omit `expect`
1012
+ * (legacy callers / direct store tests) → no OCC.
1013
+ */
222
1014
  export interface ResolveExpectation {
1015
+ /** The monotonic {@link Checkpoint.rev} the caller observed at `get()` (absent rev ⇒ legacy `0`). */
223
1016
  rev: number;
224
1017
  }
1018
+ /** A persisted suspension point: enough to resume a task on any replica. `status` drives the 3-state
1019
+ * machine (pending → resolved | expired) that makes resume idempotent (§5). */
225
1020
  export interface Checkpoint {
226
1021
  token: CheckpointToken;
1022
+ /**
1023
+ * Schema version of this checkpoint (design/49 v1.5). Every live suspend stamps the version of the
1024
+ * FEATURE it carries (RESOURCE / BINDING / TOKEN / ORG_ADMISSION); {@link CURRENT_CHECKPOINT_VERSION}
1025
+ * is the baseline at the bottom of that ladder, not the stamp of any current mint site.
1026
+ * absent ⇒ legacy `0` (1.67-era, no `state.workspaceHandle`). Resume rejects `> MAX_SUPPORTED_CHECKPOINT_VERSION`
1027
+ * pre-CAS so an old worker can't silently ignore fields a newer format relies on (forward-compatible by `<=`).
1028
+ * Read via {@link checkpointVersionOf}.
1029
+ */
227
1030
  version?: number;
1031
+ /** Multi-tenant isolation key (forced through `resolve`/`reap` WHERE, §2.1 service [4]). */
228
1032
  scope: string;
1033
+ /** The session to resume (via the SessionStore). */
229
1034
  sessionId: string;
1035
+ /** The session leaf the suspend happened at — the resume point (CAS write base, §5). */
230
1036
  leafId: string;
231
1037
  gate: CheckpointGate;
232
1038
  pendingAction: PendingAction;
233
1039
  state: CheckpointState;
1040
+ /** design/74 R3-B: cross-slice resource accounting. Present for a `resource_limit` gate (debited at the
1041
+ * suspend's `put`; read on resume to size the next slice + aggregate the final stats). **design/80 D-E-core
1042
+ * (A3):** ALSO attached to a human/irreversible_ask APPROVAL suspend — debited with that leg's spend — so a
1043
+ * STATELESS policy can read durable cumulative spend across the resume chain via {@link ToolCallRequest.budget}
1044
+ * (a fresh in-memory counter would reset every leg). On the approval path it is READ-CONTEXT ONLY: the final
1045
+ * -stats aggregate fold (runtask) is gated to the `resource_limit` gate kind, so an approval resume is never
1046
+ * double-counted. Absent for `task_done`. See {@link ResourceLedger}. */
234
1047
  resourceLedger?: ResourceLedger;
1048
+ /**
1049
+ * Absolute epoch-ms deadline for an awaiting-human checkpoint; past it, `reap` expires it (§6).
1050
+ *
1051
+ * **design/80 §D-D semantic contract (the DURABLE FIELD a reaper-loop consumes — read it on
1052
+ * {@link CheckpointStore} too):** for an APPROVAL gate (`gate.kind` ∈ {`human`, `irreversible_ask`}) the
1053
+ * deployment's reaper-loop treats this as an **SLA resolve-deny** time (FACET A — its fast path: at deadline
1054
+ * it `resolve`s the checkpoint as a `deny` so the run ends cleanly), with a LATER `terminalAt` abandonment
1055
+ * backstop the loop ALSO honors (crash-safe if the SLA timer never fired). For an UNATTENDED-TTL gate
1056
+ * (`gate.kind` ∈ {`resource_limit`, `needs_review`, `plan_review`} — the resource + review-pause family) this
1057
+ * is an **abandonment-TTL** → `expire`/{@link reap} ONLY, NEVER a resolve-deny (FACET B — those kinds resume
1058
+ * only with their OWN outcome, so a `policy_ask` resolve-deny hits no `gateMatch` arm → `gate_mismatch`). The
1059
+ * reaper-loop + the gate.kind policy + minting `terminalAt` are the DEPLOYMENT's job
1060
+ * (its store impl + loop — owned service-side); CORE only guarantees this `deadline`
1061
+ * field (for `human`/`irreversible_ask`) + the persisted {@link gate}.kind survive the round-trip so any
1062
+ * reaper consumer (the service's TiDB loop OR a non-service Pg deployment) implements the SAME contract.
1063
+ */
235
1064
  deadline?: number;
236
1065
  status: "pending" | "resolved" | "expired";
237
1066
  createdAt: number;
1067
+ /**
1068
+ * How many times THIS task has suspended, counting this checkpoint (1 = first suspend; design/72
1069
+ * §2.2 (B)). Carried forward across resume→re-suspend (each new checkpoint = the prior count + 1), so
1070
+ * a restart-prone model that keeps re-issuing the gated/egress call — re-suspending every resume —
1071
+ * is capped: past `maxSuspends` the run fails (`suspend.loop`) instead of minting another checkpoint
1072
+ * and looping forever. Absent ⇒ legacy `0` (pre-§2.2 checkpoints; treated as "no prior suspends").
1073
+ */
238
1074
  suspendCount?: number;
1075
+ /**
1076
+ * design/91 — the wall-clock instant (epoch ms, from {@link import("./types.js").RunnerDeps.now}) this task
1077
+ * suspended at a HUMAN-REVIEW gate. Resume derives the human-review latency for this leg as
1078
+ * `now() − suspendedAt` and folds it into `TaskResult.stats.humanReview` (the design/89 §2.4 C2 burden axis).
1079
+ * The human-review family is the APPROVAL gates (`gate.kind` ∈ {`human`, `irreversible_ask`}) — core stamps
1080
+ * this automatically on the approval-suspend mint — AND the REVIEW-PAUSE gates (`needs_review` = a dry-run
1081
+ * diff review, `plan_review` = a pre-action PLAN review). Review-pause checkpoints are minted by a PROFILE
1082
+ * (core owns only the routing/resume discriminants), so a profile that wants the C2 wait counted MUST stamp
1083
+ * `suspendedAt = deps.now()` at mint — the same injectable clock the resume reads, so a test pins the latency.
1084
+ * Absent on a `resource_limit` suspend (a machine backstop, not a human wait), on a review-pause checkpoint
1085
+ * whose profile did not opt in, and on a legacy pre-design/91 checkpoint (⇒ resume skips the durable latency
1086
+ * for that leg — no undercount surprise, just no entry).
1087
+ */
239
1088
  suspendedAt?: number;
1089
+ /**
1090
+ * design/91 — the accumulated {@link import("./types.js").TaskResult.stats}`.humanReview` from all PRIOR legs,
1091
+ * carried forward across resume→re-suspend (like {@link suspendCount} / {@link resourceLedger}) so a
1092
+ * multi-leg suspend/resume chain reports the WHOLE human-review burden, not just the last leg's. It holds
1093
+ * the gates resolved up to and including the suspend BEFORE this one — the latency for THIS suspend is added
1094
+ * by the resume that wakes it (`now() − suspendedAt`). Absent ⇒ no prior human-review time (a first suspend
1095
+ * with no earlier synchronous asks). Budget-EXCLUDED side observable — never a cost/gate input.
1096
+ */
240
1097
  humanReview?: {
241
1098
  count: number;
242
1099
  totalWaitMs: number;
@@ -246,49 +1103,469 @@ export interface Checkpoint {
246
1103
  decision?: string;
247
1104
  }>;
248
1105
  };
1106
+ /**
1107
+ * design/80 D-1 (persist-winner): the winning resume binding, recorded by {@link CheckpointStore.resolve}
1108
+ * when its CAS wins (pending → resolved) — present after a `policy_ask` resolve and after either REVIEW-gate
1109
+ * resolve (`plan_review` / `dry_run_review`, RB-64); absent on a freshly minted pending checkpoint and on
1110
+ * `resource_limit` / `wake` / `task_done` resolves. PRESERVED (never cleared) across a
1111
+ * {@link CheckpointStore.reopen} so an `env_failed` re-resume can be validated against it. See
1112
+ * {@link ResolvedOutcome}.
1113
+ */
249
1114
  resolvedOutcome?: ResolvedOutcome;
1115
+ /**
1116
+ * design/80 D-1 (reopen-by-reason): why this checkpoint was last reopened (resolved → pending), recorded
1117
+ * by {@link CheckpointStore.reopen}. Drives re-resume validation (§3 invariant #1): `env_failed` ⇒ the
1118
+ * re-resume must replay the persisted {@link resolvedOutcome} winner (a system retry of the approved
1119
+ * action); `tool_unavailable` ⇒ a fresh operator decision is allowed (P-7 re-decide with the tool
1120
+ * present). Absent on a never-reopened checkpoint (a first resume is unconstrained by reason).
1121
+ */
250
1122
  reopenReason?: ReopenReason;
1123
+ /**
1124
+ * design/80 D-1 (atomicity fix, round-2 BLOCKER): a **monotonic revision counter** bumped by EVERY
1125
+ * {@link CheckpointStore.resolve} (won) and {@link CheckpointStore.reopen}. It is the optimistic-concurrency
1126
+ * key ({@link ResolveExpectation}): a resume reads it at `get()` and `resolve` requires the live `rev` to
1127
+ * still equal it, so any resolve/reopen cycle landing in the get→resolve window loses the CAS (fail-closed).
1128
+ * Unlike the 2-valued `reopenReason`, it cannot ABA back to a prior value. Absent ⇒ legacy `0` (a checkpoint
1129
+ * minted before this field; treated as never mutated).
1130
+ */
251
1131
  rev?: number;
1132
+ /**
1133
+ * design S1d (source-tag persistence, service [198]/[199]): the issuing task's session id, stamped at the
1134
+ * APPROVAL suspend mint so a supervisor inbox can attribute a paused/awaiting checkpoint to the worker that
1135
+ * raised it (the durable analog of {@link import("./tool-policy.js").AskRequest}.sourceTaskId — the durable
1136
+ * suspend path does NOT invoke `onAsk`, so the synchronous source identity is captured here instead). It is
1137
+ * the SAME value the synchronous ask carries: `= sessionId` at the mint.
1138
+ *
1139
+ * **Security invariant (carried verbatim from the 1.113.0 ask-bubbling contract):** for a Runner-created
1140
+ * DELEGATED subagent this is **worker-unforgeable** (a child never sets its own sessionId — the Runner mints
1141
+ * it); a top-level caller MAY continue its own session id via {@link import("./types.js").TaskSpec.sessionId},
1142
+ * so this is NOT a global "never forgeable" run id — aggregation only aggregates delegated workers, where it
1143
+ * holds. **ECHO-ONLY triage metadata, NEVER a gate input:** no gate / CAS / winner / resume-validation path
1144
+ * reads it (a contract-test pins this). Absent ⇒ no source task id (a non-ask gate, or a legacy checkpoint).
1145
+ */
252
1146
  sourceTaskId?: string;
1147
+ /**
1148
+ * design S1d (source-tag persistence): the issuing task's authenticated end-user {@link
1149
+ * import("./types.js").TaskSpec.principal} (design/62), stamped at the APPROVAL suspend mint so an aggregating
1150
+ * inbox can attribute a paused checkpoint per-user — the durable analog of {@link
1151
+ * import("./tool-policy.js").AskRequest}.principal. Caller-set (never a tool/worker arg), and like
1152
+ * {@link sourceTaskId} it is **ECHO-ONLY triage metadata, NEVER a gate input** (no gate/CAS/winner/resume path
1153
+ * reads it). Absent ⇒ no principal (a non-ask gate, an unauthenticated task, or a legacy checkpoint).
1154
+ */
253
1155
  principal?: string;
1156
+ /**
1157
+ * design/153 §7.4 (1.389.0, RB-42②③): the VERBATIM `TaskSpec.durableApproval` opt-in in force at this
1158
+ * APPROVAL suspend's mint — the engine-recorded provenance of "this task was durable-approval armed, with
1159
+ * this scope and this reaper TTL". Consumed by the parked-resume drive's REVIVE-CONTINUITY re-derivation
1160
+ * (subagent settle path): the drive spec carries no forwarding seat, so without this record a resumed leg's
1161
+ * SECOND plain ask would headless-deny instead of re-parking. Reading it explicitly (instead of inferring
1162
+ * the opt-in from `gate.kind === "human"`) covers a SAFETY-TIER first park (irreversible_ask — the kind
1163
+ * alone never proved the spawn opted in) and carries `ttlMs` across the re-park so the re-minted
1164
+ * checkpoint re-arms its reaper `deadline` (previously unbounded). Absent ⇒ the mint ran with NO
1165
+ * `durableApproval` (an unattended safety suspend, a non-ask gate, or a legacy pre-1.389 checkpoint — the
1166
+ * consumer then falls back to the human-kind scope-only inference for legacy rows and injects NOTHING for
1167
+ * unattended parks, so a spawn that never opted in can never gain the opt-in through a resume). Engine-
1168
+ * minted from the task's own spec — a worker/tool cannot forge it (`durableApproval` never rides tool args).
1169
+ */
254
1170
  durableApproval?: {
255
1171
  scope: string;
256
1172
  ttlMs?: number;
257
1173
  };
258
1174
  }
1175
+ /**
1176
+ * design/80 assistant-scheduler seam #1: a LIGHTWEIGHT projection of one PENDING
1177
+ * checkpoint, returned in bulk by {@link CheckpointStore.listByScope} so a supervisor scheduler can list a
1178
+ * scope's suspended/awaiting tasks in ONE call (no N+1 `get`s, no full-{@link Checkpoint} payloads). It is a
1179
+ * read-only DISPLAY/triage summary — every field is DERIVED from the persisted {@link Checkpoint}; nothing
1180
+ * here is a new source of truth or a gate input.
1181
+ */
259
1182
  export interface CheckpointSummary {
1183
+ /** The resume capability token ({@link Checkpoint.token}). */
260
1184
  token: CheckpointToken;
1185
+ /** The suspended task / session id ({@link Checkpoint.sessionId}). */
261
1186
  sessionId: string;
1187
+ /** The multi-tenant scope this checkpoint lives in ({@link Checkpoint.scope}) — always equal to the query's. */
262
1188
  scope: string;
1189
+ /** The gate's discriminant ({@link CheckpointGate}.kind): which kind of pause this is. */
263
1190
  gateKind: CheckpointGate["kind"];
1191
+ /** The deterministic risk tier (1..5) when the gate carries a {@link RiskDescriptor} (the `human` /
1192
+ * `irreversible_ask` approval escalations), derived via {@link riskSeverity} from the descriptor's axes —
1193
+ * the SAME value as `gate.riskDescriptor.severity`, recomputed from the single source helper rather than
1194
+ * re-stored. `undefined` for a gate with no descriptor (resource_limit / needs_review / plan_review / a
1195
+ * legacy human ask minted before D-E). The inbox sorts DESC by this. */
264
1196
  severity?: 1 | 2 | 3 | 4 | 5;
1197
+ /** Cumulative spend debited to this suspend chain ({@link ResourceLedger.spentMicroUsd}) in micro-USD —
1198
+ * present whenever the checkpoint carries a {@link Checkpoint.resourceLedger} (every resource_limit suspend,
1199
+ * and every approval suspend on the D-E-core budget-read path); `undefined` when no ledger is attached. */
265
1200
  spentMicroUsd?: number;
1201
+ /** The awaiting-human SLA / abandonment deadline ({@link Checkpoint.deadline}, epoch ms) when present. */
266
1202
  deadline?: number;
1203
+ /** design S1d (source-tag): the issuing worker's session id ({@link Checkpoint.sourceTaskId}) — projected so
1204
+ * a supervisor inbox attributes each pending checkpoint to its worker in ONE `listByScope` call (no N+1
1205
+ * `getCheckpoint`). ECHO-ONLY display/triage; absent on a non-ask gate / legacy checkpoint. */
267
1206
  sourceTaskId?: string;
1207
+ /** design S1d (source-tag): the issuing task's end-user principal ({@link Checkpoint.principal}) for per-user
1208
+ * inbox attribution. ECHO-ONLY display/triage; absent when the checkpoint carries no principal. */
268
1209
  principal?: string;
1210
+ /** The pending tool call's id ({@link PendingAction} `tool_approval.toolCallId`) when this is an approval
1211
+ * suspend — projected so the inbox shows WHICH call awaits a decision without an N+1 `getCheckpoint`.
1212
+ * `undefined` for a non-`tool_approval` pendingAction (resource_limit / plan_review / task_done).
1213
+ *
1214
+ * **ABSENCE SHAPE (contract, [1995]②)** — shared by every optional field on this projection: a
1215
+ * tool-less park (`resource_limit` / `plan_review` / `task_done` — none of which binds a tool call)
1216
+ * gets the key **OMITTED**, never `null` and never a present-but-`undefined` property. The
1217
+ * `PendingAction` arms for those kinds declare no tool/binding fields at all, so there is nothing to
1218
+ * project; `summarizeCheckpoint` spreads them conditionally for exactly this reason. A consumer
1219
+ * therefore tests presence (`"toolCallId" in summary`), and a re-projecting service (BFF / SDK wire
1220
+ * face) must not materialize the keys as `null` on the way out. Pinned in
1221
+ * `test/checkpoint-store.test.ts` ("[1995]②③ tool-less park …"). */
269
1222
  toolCallId?: string;
1223
+ /** The pending tool's name ({@link PendingAction} `tool_approval.toolName`) when this is an approval suspend.
1224
+ * OMITTED (not `null`) for a non-`tool_approval` pendingAction — see {@link toolCallId}'s absence-shape note. */
270
1225
  toolName?: string;
1226
+ /** design/99 MF-14 (design-review DoR): the DERIVED content-gate classification — `"content_ask"` when this
1227
+ * `tool_approval` gates the reserved AskUserQuestion tool (a question TO the user, not a side-effecting tool).
1228
+ * A TYPED discriminant so a shell renders the question UI without sniffing `toolName` or parsing the bounded
1229
+ * `toolInput` preview. The full typed questions ride the drilled-in Checkpoint's `pendingAction.args` (kept off
1230
+ * this lightweight `listByScope` projection). Deliberately NOT a new `content_ask` checkpoint gate kind (that
1231
+ * would cross the durable-checkpoint once-only-winner / reaper-deadline / batch invariants — the DoR). */
271
1232
  contentKind?: "content_ask";
1233
+ /** design S1e (service [204]): when the checkpoint was created ({@link Checkpoint.createdAt}, epoch ms) —
1234
+ * projected so a supervisor inbox can sort/age pending entries (oldest-first triage) in ONE `listByScope`
1235
+ * call, no N+1 `getCheckpoint`. ECHO-ONLY display/triage; always present (a Checkpoint always has a
1236
+ * `createdAt`). */
272
1237
  createdAt?: number;
1238
+ /**
1239
+ * design S1e (service [204]): a **BOUNDED, UNREDACTED** raw preview of the pending tool call's input
1240
+ * ({@link PendingAction} `tool_approval.args` `JSON.stringify`-ed, truncated to {@link
1241
+ * MAX_TOOL_INPUT_PREVIEW_CHARS} chars with a `…` marker when over) — projected so a supervisor inbox shows
1242
+ * WHAT a paused tool call will do without an N+1 `getCheckpoint`. `undefined` for a non-`tool_approval`
1243
+ * pendingAction (resource_limit / plan_review / task_done — no args to preview).
1244
+ *
1245
+ * **Contract (load-bearing):**
1246
+ * - **bounded raw preview** — length-capped to {@link MAX_TOOL_INPUT_PREVIEW_CHARS} so it cannot bloat the
1247
+ * `listByScope` payload, but the chars within the cap are the RAW serialized args (no folding/escaping).
1248
+ * - **redaction is the CONSUMER's responsibility** — core gives the inbox the unredacted bounded raw; the
1249
+ * service/inbox decides what (if anything) to mask before showing a human. (Contrast {@link
1250
+ * RiskDescriptor.summary}, which is the NEUTRALIZED+capped descriptor that DOES ride into a render —
1251
+ * `toolInput` is the raw-data sibling for a consumer that wants the actual args.)
1252
+ * - **same data as the full {@link Checkpoint}** — it is a projection of `pendingAction.args`, which the
1253
+ * caller could already read via `get`; this adds NO new exposure, it only saves the round-trip.
1254
+ * - **ECHO-ONLY, like {@link sourceTaskId}** — NO gate / CAS / resume / {@link winnerFromOutcome} path reads
1255
+ * it (a contract test pins this); it is pure display/triage metadata, never a control input.
1256
+ */
273
1257
  toolInput?: string;
1258
+ /**
1259
+ * RB-439-b — HOW this pause's remote workspace comes back, projected from
1260
+ * `state.workspaceHandle.restoreMode` so a supervisor inbox can tell the two apart in ONE `listByScope`
1261
+ * call. `"snapshot"` = the VM was paused into a snapshot (billing typically stops, memory state
1262
+ * captured); `"park_only"` = a non-suspendable target (SSH host / ADB device) was never paused — the
1263
+ * machine is still running and still costing, and resume just reconnects. OMITTED (see the
1264
+ * {@link toolCallId} absence-shape note) for a process-local suspend, which captured no remote
1265
+ * workspace at all. ECHO-ONLY display/triage — nothing gates on it. Mirrors `TaskResult.workspaceRestoreMode`.
1266
+ */
274
1267
  restoreMode?: "snapshot" | "park_only";
1268
+ /** [1245] codex F2 — the tool's approval display projection ({@link PendingAction} `preview`),
1269
+ * projected so the ONE-CALL inbox (`listByScope`) can render a rich confirmation without an N+1
1270
+ * `get`. Same trust posture as `toolInput`: **UNTRUSTED, advisory display metadata** (strings are
1271
+ * sanitized of control chars at mint but may still carry model-authored markdown — renderers must
1272
+ * contextually escape), ECHO-ONLY (no gate/CAS/resume reads it), and surfaces SHOULD show the
1273
+ * bound raw args (`toolInput`/`pendingAction.args`) alongside — the preview never replaces them. */
275
1274
  preview?: unknown;
276
1275
  }
1276
+ /**
1277
+ * design/80 assistant-scheduler seam #1: project a single PENDING {@link Checkpoint} to its lightweight
1278
+ * {@link CheckpointSummary}. Shared by every {@link CheckpointStore} impl so the projection is IDENTICAL
1279
+ * across the in-memory, file, and durable backends (the anti-drift guard) — `severity` always derives from the
1280
+ * gate's {@link RiskDescriptor} via the single {@link riskSeverity} helper, `spentMicroUsd` always reads
1281
+ * {@link ResourceLedger.spentMicroUsd}. Pure; reads no clock/random.
1282
+ */
277
1283
  export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
1284
+ /** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
278
1285
  export declare class CheckpointError extends Error {
279
- readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found" | "checkpoint.gate_mismatch" | "checkpoint.resume_aborted" | "checkpoint.invalid_outcome" | "checkpoint.unsupported_version" | "checkpoint.walltime_axis_retired" | "checkpoint.reopen_revote" | "checkpoint.reopened_concurrently" | "checkpoint.reopen_failed" | "steering.invalid_content" | "steering.queue_full" | "steering.duplicate_input_id" | "wake.gate_pending" | "wake.nothing_to_deliver" | "resume.parent_constraint_missing" | "resume.parent_constraint_mismatch";
1286
+ readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
1287
+ /** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
1288
+ * persisted {@link CheckpointGate} `kind` (council #3 — type confusion guard), or a gate v1
1289
+ * resume does not serve (`task_done`). */
1290
+ | "checkpoint.gate_mismatch"
1291
+ /** RB-77: `runner.resume` was handed an ALREADY-ABORTED `TaskSpec.signal`. Refused BEFORE the CAS, so
1292
+ * the checkpoint stays `pending` — this worker demonstrably cannot execute the approved action, and
1293
+ * consuming the human's decision on a leg that will abort immediately loses BOTH the decision and the
1294
+ * suspended work with nothing to reopen. Re-resume with a live signal. */
1295
+ | "checkpoint.resume_aborted"
1296
+ /** `runner.resume` was handed an outcome whose payload is unsafe to apply — e.g. a deny `reason`
1297
+ * carrying a `</system-reminder>` tag that would escape the model-facing wrapper (round-2 #1). */
1298
+ | "checkpoint.invalid_outcome"
1299
+ /** The checkpoint's {@link Checkpoint.version} is newer than this worker supports
1300
+ * (`> MAX_SUPPORTED_CHECKPOINT_VERSION`), or it carries a remote `workspaceHandle` but no
1301
+ * `executionEnvFactory` is wired to rebuild the env — either way this worker cannot safely resume it.
1302
+ * Rejected pre-CAS so the checkpoint stays `pending` and a capable worker can still resume it
1303
+ * (design/49 §2/§3, code-ready council round-2). */
1304
+ | "checkpoint.unsupported_version"
1305
+ /** design/164: the checkpoint was written by a pre-164 worker AND its ledger carries the RETIRED
1306
+ * cross-slice wall-clock allocation (`resourceLedger.totalWalltimeSec`). The axis no longer exists,
1307
+ * so resuming would run the leg with the operator's time ceiling silently unenforced. Refused
1308
+ * PRE-CAS (the row stays `pending`, so a worker of the previous release can still finish it) rather
1309
+ * than migrated: there is no honest conversion from a wall-clock allocation to a token one. */
1310
+ | "checkpoint.walltime_axis_retired"
1311
+ /** design/80 D-1 (reopen-by-reason): a re-resume of an `env_failed`-reopened checkpoint supplied an
1312
+ * outcome that does NOT equal the persisted {@link ResolvedOutcome} winner. An `env_failed` reopen is
1313
+ * a SYSTEM RETRY of the already-approved action (the infra failed, not the decision) — it must replay
1314
+ * the exact winning binding, never a new vote. Rejected pre-CAS, fail-closed, so the checkpoint stays
1315
+ * `pending` for the correctly-replayed retry. (A `tool_unavailable` reopen — P-7 — is exempt: it lets
1316
+ * a human re-decide with the tool present, so a fresh decision is permitted there.) */
1317
+ | "checkpoint.reopen_revote"
1318
+ /** design/80 D-1 (atomicity fix): the resume validated its decision-action / reopen-by-reason guards
1319
+ * against a `get()` snapshot, but a concurrent {@link CheckpointStore.resolve}/{@link CheckpointStore.reopen}
1320
+ * cycle advanced the monotonic {@link Checkpoint.rev} in the get→resolve window, so the
1321
+ * {@link CheckpointStore.resolve} CAS's optimistic-concurrency check lost (the validation is stale). The
1322
+ * row is still `pending` — fail-closed; the caller must re-`get` and re-validate against the CURRENT state
1323
+ * (a fresh resume), never blindly retry. */
1324
+ | "checkpoint.reopened_concurrently"
1325
+ /** A post-CAS compensation {@link CheckpointStore.reopen} returned `false` — the consumed row could not
1326
+ * be flipped back to `pending` (missing, expired, or changed under a concurrent cycle), so no retryable
1327
+ * checkpoint exists. Surfaced instead of silently treating the failed reopen as committed, so the
1328
+ * caller's compensation path settles the token as terminally consumed. */
1329
+ | "checkpoint.reopen_failed"
1330
+ /** design/80 D-A: {@link CheckpointStore.setPendingSteer} was handed `text` containing a
1331
+ * `</system-reminder>` close tag — it would escape the model-facing `<system-reminder>` wrapper a
1332
+ * trusted steer rides on resume. Rejected fail-closed so a dirty steer NEVER enters {@link CheckpointState}
1333
+ * (§3 inv #2). Mirrors the trusted-steer reject at runtask.ts (`steering.invalid_content`). */
1334
+ | "steering.invalid_content"
1335
+ /** design/171 §6.3: the append would cross the parked-steer queue's entry-count or byte bound
1336
+ * ({@link MAX_PENDING_STEER_ENTRIES} / {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES}). Fail-loud
1337
+ * rather than evicting: an already-accepted operator instruction is never dropped to make room —
1338
+ * the caller delivers or removes a parked steer first. Nothing is written on this path. */
1339
+ | "steering.queue_full"
1340
+ /** design/171 §6.3 (adversarial review adoption): the append reused an `inputId` already parked on
1341
+ * this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
1342
+ * idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
1343
+ * second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
1344
+ * Nothing is written on this path. */
1345
+ | "steering.duplicate_input_id"
1346
+ /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
1347
+ * checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
1348
+ * resource-limit continue). Wake is a pure un-park verb and must never bypass an approval — resume
1349
+ * the checkpoint through its own decide entry instead (the error message names it). */
1350
+ | "wake.gate_pending"
1351
+ /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
1352
+ * parked `pendingSteer` either — there is nothing to deliver, so the wake is rejected pre-CAS
1353
+ * (the checkpoint stays pending) instead of burning the token on an empty continuation. */
1354
+ | "wake.nothing_to_deliver"
1355
+ /** Parent effective-policy inheritance: the checkpoint records `state.inheritedGate.requiresParentConstraint`
1356
+ * (the suspended task ran under inherited OPAQUE parent-policy constraints — live closures that cannot
1357
+ * persist), but the resume supplied no `RunInternals.inheritedGate.parentConstraints` to re-instate them.
1358
+ * Resuming anyway would silently DROP the ancestors' gate (a widening), so this rejects pre-CAS — the
1359
+ * checkpoint stays `pending`; re-resume via `resumeStream(token, outcome, config, internals)` with the
1360
+ * parent chain re-supplied. */
1361
+ | "resume.parent_constraint_missing"
1362
+ /** Parent effective-policy inheritance (shape check): the resume DID re-supply
1363
+ * `internals.inheritedGate.parentConstraints`, but its length differs from the checkpoint's recorded
1364
+ * `parentConstraintCount` — a partial/mismatched re-supply would silently run the child under a
1365
+ * thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
1366
+ * stays `pending`); re-resume with the full original chain. */
1367
+ | "resume.parent_constraint_mismatch";
1368
+ /** Structured discrimination a caller can branch on where the code alone is ambiguous. Today one
1369
+ * member: `field` names WHICH part of the decision payload was rejected on a
1370
+ * `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
1371
+ * re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
1372
+ * action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
1373
+ * decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
1374
+ * `"settledBy"` (the settlement source is outside the three-word vocabulary: re-decide naming one
1375
+ * of them, or omit the field). The throw
1376
+ * sites always knew which arm fired; this carries that fact instead of dropping it (ruled
1377
+ * 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
1378
+ * discriminant. */
280
1379
  readonly detail?: {
281
1380
  field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
282
1381
  } | undefined;
283
- constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found" | "checkpoint.gate_mismatch" | "checkpoint.resume_aborted" | "checkpoint.invalid_outcome" | "checkpoint.unsupported_version" | "checkpoint.walltime_axis_retired" | "checkpoint.reopen_revote" | "checkpoint.reopened_concurrently" | "checkpoint.reopen_failed" | "steering.invalid_content" | "steering.queue_full" | "steering.duplicate_input_id" | "wake.gate_pending" | "wake.nothing_to_deliver" | "resume.parent_constraint_missing" | "resume.parent_constraint_mismatch", message: string, detail?: {
1382
+ constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
1383
+ /** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
1384
+ * persisted {@link CheckpointGate} `kind` (council #3 — type confusion guard), or a gate v1
1385
+ * resume does not serve (`task_done`). */
1386
+ | "checkpoint.gate_mismatch"
1387
+ /** RB-77: `runner.resume` was handed an ALREADY-ABORTED `TaskSpec.signal`. Refused BEFORE the CAS, so
1388
+ * the checkpoint stays `pending` — this worker demonstrably cannot execute the approved action, and
1389
+ * consuming the human's decision on a leg that will abort immediately loses BOTH the decision and the
1390
+ * suspended work with nothing to reopen. Re-resume with a live signal. */
1391
+ | "checkpoint.resume_aborted"
1392
+ /** `runner.resume` was handed an outcome whose payload is unsafe to apply — e.g. a deny `reason`
1393
+ * carrying a `</system-reminder>` tag that would escape the model-facing wrapper (round-2 #1). */
1394
+ | "checkpoint.invalid_outcome"
1395
+ /** The checkpoint's {@link Checkpoint.version} is newer than this worker supports
1396
+ * (`> MAX_SUPPORTED_CHECKPOINT_VERSION`), or it carries a remote `workspaceHandle` but no
1397
+ * `executionEnvFactory` is wired to rebuild the env — either way this worker cannot safely resume it.
1398
+ * Rejected pre-CAS so the checkpoint stays `pending` and a capable worker can still resume it
1399
+ * (design/49 §2/§3, code-ready council round-2). */
1400
+ | "checkpoint.unsupported_version"
1401
+ /** design/164: the checkpoint was written by a pre-164 worker AND its ledger carries the RETIRED
1402
+ * cross-slice wall-clock allocation (`resourceLedger.totalWalltimeSec`). The axis no longer exists,
1403
+ * so resuming would run the leg with the operator's time ceiling silently unenforced. Refused
1404
+ * PRE-CAS (the row stays `pending`, so a worker of the previous release can still finish it) rather
1405
+ * than migrated: there is no honest conversion from a wall-clock allocation to a token one. */
1406
+ | "checkpoint.walltime_axis_retired"
1407
+ /** design/80 D-1 (reopen-by-reason): a re-resume of an `env_failed`-reopened checkpoint supplied an
1408
+ * outcome that does NOT equal the persisted {@link ResolvedOutcome} winner. An `env_failed` reopen is
1409
+ * a SYSTEM RETRY of the already-approved action (the infra failed, not the decision) — it must replay
1410
+ * the exact winning binding, never a new vote. Rejected pre-CAS, fail-closed, so the checkpoint stays
1411
+ * `pending` for the correctly-replayed retry. (A `tool_unavailable` reopen — P-7 — is exempt: it lets
1412
+ * a human re-decide with the tool present, so a fresh decision is permitted there.) */
1413
+ | "checkpoint.reopen_revote"
1414
+ /** design/80 D-1 (atomicity fix): the resume validated its decision-action / reopen-by-reason guards
1415
+ * against a `get()` snapshot, but a concurrent {@link CheckpointStore.resolve}/{@link CheckpointStore.reopen}
1416
+ * cycle advanced the monotonic {@link Checkpoint.rev} in the get→resolve window, so the
1417
+ * {@link CheckpointStore.resolve} CAS's optimistic-concurrency check lost (the validation is stale). The
1418
+ * row is still `pending` — fail-closed; the caller must re-`get` and re-validate against the CURRENT state
1419
+ * (a fresh resume), never blindly retry. */
1420
+ | "checkpoint.reopened_concurrently"
1421
+ /** A post-CAS compensation {@link CheckpointStore.reopen} returned `false` — the consumed row could not
1422
+ * be flipped back to `pending` (missing, expired, or changed under a concurrent cycle), so no retryable
1423
+ * checkpoint exists. Surfaced instead of silently treating the failed reopen as committed, so the
1424
+ * caller's compensation path settles the token as terminally consumed. */
1425
+ | "checkpoint.reopen_failed"
1426
+ /** design/80 D-A: {@link CheckpointStore.setPendingSteer} was handed `text` containing a
1427
+ * `</system-reminder>` close tag — it would escape the model-facing `<system-reminder>` wrapper a
1428
+ * trusted steer rides on resume. Rejected fail-closed so a dirty steer NEVER enters {@link CheckpointState}
1429
+ * (§3 inv #2). Mirrors the trusted-steer reject at runtask.ts (`steering.invalid_content`). */
1430
+ | "steering.invalid_content"
1431
+ /** design/171 §6.3: the append would cross the parked-steer queue's entry-count or byte bound
1432
+ * ({@link MAX_PENDING_STEER_ENTRIES} / {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES}). Fail-loud
1433
+ * rather than evicting: an already-accepted operator instruction is never dropped to make room —
1434
+ * the caller delivers or removes a parked steer first. Nothing is written on this path. */
1435
+ | "steering.queue_full"
1436
+ /** design/171 §6.3 (adversarial review adoption): the append reused an `inputId` already parked on
1437
+ * this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
1438
+ * idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
1439
+ * second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
1440
+ * Nothing is written on this path. */
1441
+ | "steering.duplicate_input_id"
1442
+ /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
1443
+ * checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
1444
+ * resource-limit continue). Wake is a pure un-park verb and must never bypass an approval — resume
1445
+ * the checkpoint through its own decide entry instead (the error message names it). */
1446
+ | "wake.gate_pending"
1447
+ /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
1448
+ * parked `pendingSteer` either — there is nothing to deliver, so the wake is rejected pre-CAS
1449
+ * (the checkpoint stays pending) instead of burning the token on an empty continuation. */
1450
+ | "wake.nothing_to_deliver"
1451
+ /** Parent effective-policy inheritance: the checkpoint records `state.inheritedGate.requiresParentConstraint`
1452
+ * (the suspended task ran under inherited OPAQUE parent-policy constraints — live closures that cannot
1453
+ * persist), but the resume supplied no `RunInternals.inheritedGate.parentConstraints` to re-instate them.
1454
+ * Resuming anyway would silently DROP the ancestors' gate (a widening), so this rejects pre-CAS — the
1455
+ * checkpoint stays `pending`; re-resume via `resumeStream(token, outcome, config, internals)` with the
1456
+ * parent chain re-supplied. */
1457
+ | "resume.parent_constraint_missing"
1458
+ /** Parent effective-policy inheritance (shape check): the resume DID re-supply
1459
+ * `internals.inheritedGate.parentConstraints`, but its length differs from the checkpoint's recorded
1460
+ * `parentConstraintCount` — a partial/mismatched re-supply would silently run the child under a
1461
+ * thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
1462
+ * stays `pending`); re-resume with the full original chain. */
1463
+ | "resume.parent_constraint_mismatch", message: string,
1464
+ /** Structured discrimination a caller can branch on where the code alone is ambiguous. Today one
1465
+ * member: `field` names WHICH part of the decision payload was rejected on a
1466
+ * `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
1467
+ * re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
1468
+ * action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
1469
+ * decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
1470
+ * `"settledBy"` (the settlement source is outside the three-word vocabulary: re-decide naming one
1471
+ * of them, or omit the field). The throw
1472
+ * sites always knew which arm fired; this carries that fact instead of dropping it (ruled
1473
+ * 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
1474
+ * discriminant. */
1475
+ detail?: {
284
1476
  field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
285
1477
  } | undefined);
286
1478
  }
1479
+ /**
1480
+ * The pluggable durable seam (design/45 §2.1). Symmetric with `ToolResultStore`/`MemoryStore`:
1481
+ * create-once `put`, `get`, an **atomic CAS `resolve`** (the once-only foundation), and a `reap` for TTL
1482
+ * expiry. A durable backend (TiDB) makes resume cross-replica; the default in-memory impl is single
1483
+ * instance / tests only.
1484
+ *
1485
+ * **Load-bearing contract (§2.1, service-side report):** `resolve` is the once-only gate. The store guarantees
1486
+ * *exactly one* `resolve(token, ...)` wins the CAS (pending → resolved); the **runner** must treat
1487
+ * "won the CAS" as the *sole* trigger to execute the pending action — that is what makes a pending tool
1488
+ * call run exactly once across retries / double approvals / multiple replicas.
1489
+ *
1490
+ * **design/80 §D-D expiry semantic contract (gate.kind-aware reap + `terminalAt` backstop):** the
1491
+ * reaper-LOOP that calls {@link reap}/{@link expire} is the DEPLOYMENT's (its store impl + loop — OWNED
1492
+ * service-side); core's D-D job is ONLY to keep writing the durable FIELDS the loop
1493
+ * reads ({@link Checkpoint.deadline} for an approval gate + the persisted {@link Checkpoint.gate}.kind) and
1494
+ * to DOCUMENT the contract here so ANY reaper consumer (the service's TiDB loop, or a non-service Pg
1495
+ * deployment) implements it IDENTICALLY:
1496
+ * - **FACET A — APPROVAL gates** (`gate.kind` ∈ {`human`, `irreversible_ask`}): the loop's FAST PATH at
1497
+ * `deadline` is an SLA **resolve-deny** (`resolve(token, {gate:"policy_ask", decision:"deny"}, …)` so the
1498
+ * suspended run ends cleanly), with a LATER `terminalAt` **abandonment backstop** the loop ALSO honors
1499
+ * (crash-safe if the SLA timer never fired — then {@link reap}/{@link expire} flips it `expired`).
1500
+ * - **FACET B — UNATTENDED-TTL gates** (`gate.kind` ∈ {`resource_limit`, `needs_review`, `plan_review`} — the
1501
+ * resource slice + the review-pause family): `deadline` is an abandonment-TTL → {@link expire}/{@link reap}
1502
+ * ONLY, **NEVER** a resolve-deny (a resolve-deny on these kinds hits NO `gateMatch` arm → `gate_mismatch`;
1503
+ * their resume outcome is `resource_limit`/`dry_run_review`/`plan_review` respectively, not `policy_ask`).
1504
+ * NO core-minted `terminalAt`, NO core reaper, NO change to {@link reap} logic — minting `terminalAt` and
1505
+ * running the gate.kind-aware loop are the deployment's, by design.
1506
+ */
1507
+ /**
1508
+ * design/173 §2.3 — the store DURABILITY declaration vocabulary (declaration, never duck-typing —
1509
+ * the exact posture of {@link CheckpointStore.retention}): `"durable"` promises the rows SURVIVE A
1510
+ * PROCESS RESTART of the deployment that owns the store's data domain (a durable park can be
1511
+ * resumed by a later process); `"process-local"` states they die with the process. **This axis
1512
+ * claims restart survival ONLY — it does NOT claim multi-replica coordination** (whether a SECOND
1513
+ * concurrent replica can serve the same rows is a separate property of the backend: the file
1514
+ * backend is single-instance-per-data-dir by design, Pg/TiDB backends coordinate — both honestly
1515
+ * declare `"durable"` on THIS axis; review fold round 1, finding 2). ABSENT is read FAIL-CLOSED as
1516
+ * `"process-local"` (the wiring manifest may under-promise durability, never over-promise it); an
1517
+ * unparseable value is refused loudly (`config.store_durability_invalid`) rather than folded to
1518
+ * either arm. Adapter contract with a conformance obligation: a backend that persists outside the
1519
+ * process (file/Pg/TiDB) SHOULD declare `"durable"` — until it does, restart-surviving resume
1520
+ * capability is reported as absent.
1521
+ */
287
1522
  export type StoreDurability = "durable" | "process-local";
1523
+ /**
1524
+ * The store ENCODING-FIDELITY declaration vocabulary — the third honest-declaration axis, alongside
1525
+ * {@link StoreDurability} and {@link CheckpointStore.retention}, and the one that says WHICH VALUES a
1526
+ * row can hold unchanged.
1527
+ *
1528
+ * `"structured-clone"` promises the structured-clone value domain survives a round trip through the
1529
+ * backend (a `Date` comes back a `Date`, a `Map` a `Map`, `NaN` stays `NaN`); `"json"` states the
1530
+ * backend serialises through JSON, so the row that comes back after a restart carries the JSON
1531
+ * PROJECTION of what was filed (`Date` → string, `Map`/`Set` → `{}`, `NaN`/`Infinity` → `null`, `-0`
1532
+ * → `0`, an `undefined`-valued key gone).
1533
+ *
1534
+ * Why this has to be declared rather than inferred: the mint decides what a durable approval row may
1535
+ * carry, and it decides it with `structuredClone` — which is WIDER than a JSON codec. A value can pass
1536
+ * that check, be filed, degrade on disk, and come back a different value, while the binding hash minted
1537
+ * over the pre-serialisation value still matches the one the operator echoes (the resume compares the
1538
+ * persisted string opaquely and never re-hashes). The approval is then accepted for arguments that are
1539
+ * not the arguments it bound. Only the backend knows its own codec, so only the backend can say.
1540
+ *
1541
+ * ABSENT is read FAIL-CLOSED as `"json"` — the narrower domain, the same "may under-promise, never
1542
+ * over-promise" posture {@link StoreDurability} takes with `"process-local"`: an undeclared backend gets
1543
+ * a row it can certainly hold, and a backend that can hold more earns the wider domain by saying so. An
1544
+ * unparseable value is refused loudly (`config.store_fidelity_invalid`) rather than folded to either arm.
1545
+ */
288
1546
  export type StoreFidelity = "structured-clone" | "json";
1547
+ /**
1548
+ * Resolve a store's declared {@link StoreFidelity} (the read half of the declaration — the exact shape
1549
+ * `resolveDeclaredFidelity`'s durability twin has): ABSENT → the fail-closed `"json"`, a recognized
1550
+ * value verbatim, anything else refused loudly. Never duck-types the backend.
1551
+ */
289
1552
  export declare function resolveDeclaredFidelity(store: {
290
1553
  readonly fidelity?: StoreFidelity;
291
1554
  } | undefined, storeName: string): StoreFidelity;
1555
+ /**
1556
+ * Project an already-captured value onto what a backend of the given {@link StoreFidelity} can hold, so
1557
+ * everything minted from it — the row's args, the approval preview, the binding hash, the risk
1558
+ * descriptor — describes the value that comes BACK, not the one that went in.
1559
+ *
1560
+ * `"structured-clone"` is the identity: the caller's snapshot already is what such a backend stores.
1561
+ * `"json"` runs the backend's own codec (`JSON.stringify` → `JSON.parse`, `toJSON` hooks included, since
1562
+ * that is exactly what the backend will do to it). The input is expected to be an accessor-free snapshot
1563
+ * already, so no property is read a second time here.
1564
+ *
1565
+ * A value JSON cannot ENCODE AT ALL (a `BigInt`, a cycle) fails instead of degrading — those would throw
1566
+ * inside the backend's own write anyway, and failing here means the caller can say why while nothing has
1567
+ * been committed. The thrown value rides out on `cause` so the caller decides how to word the refusal.
1568
+ */
292
1569
  export declare function encodeAtFidelity(fidelity: StoreFidelity, value: unknown): {
293
1570
  ok: true;
294
1571
  value: unknown;
@@ -296,40 +1573,319 @@ export declare function encodeAtFidelity(fidelity: StoreFidelity, value: unknown
296
1573
  ok: false;
297
1574
  cause: unknown;
298
1575
  };
1576
+ /**
1577
+ * CONSERVATIVE "these two are provably the same value" for deciding whether a {@link encodeAtFidelity}
1578
+ * projection moved anything, and whether a policy's rewrite names the very value about to be filed.
1579
+ *
1580
+ * Deliberately NOT the binding hash, which is the wrong instrument for this question in two directions:
1581
+ * it folds `-0` onto `0` (a real projection change the policy may care about, invisible to the hash), and
1582
+ * it is a digest, so a collision would read as sameness. `Object.is` at the leaves keeps the `-0`
1583
+ * distinction; comparing own-key SETS keeps the dropped `undefined`-valued key visible.
1584
+ *
1585
+ * Conservative on purpose — the answer it must never give wrongly is TRUE, because both callers read
1586
+ * TRUE as "nothing moved / the policy named this very value" and act by filing. So sameness has to be
1587
+ * PROVED, not merely un-disproved:
1588
+ * - the PROTOTYPES must match and be the plain-object or array one. `[object Object]` is not proof of
1589
+ * a plain object — a class instance and a null-prototype object both report it, and a tool executing
1590
+ * the arguments can observe inherited behaviour. (Neither input to the projection comparison can be
1591
+ * such a value: `structuredClone` and `JSON.parse` both produce plain data. A policy's rewrite can.)
1592
+ * - own keys are compared with `Reflect.ownKeys` and POSITIONALLY, so a SYMBOL key or a non-enumerable
1593
+ * property on one side is a difference rather than an invisible extra, and so is a different order
1594
+ * (which `Object.keys`, `JSON.stringify` and any tool iterating its arguments can see).
1595
+ * - properties are compared through their DESCRIPTORS — every flag, so a frozen object is not the
1596
+ * mutable value a JSON decode produces — and an accessor on either side answers FALSE without being
1597
+ * read: an approval decision must not be made by running deployment code, and what a getter returns
1598
+ * need not be the same twice.
1599
+ * - reference TOPOLOGY counts: two properties that share one object are not two properties holding
1600
+ * equal objects, and a JSON round trip is precisely what turns the first into the second.
1601
+ * - what it CANNOT prove on its own is that the value it inspected answers ordinary reads the way its
1602
+ * descriptors did — a Proxy reports the target's descriptors and can return anything from a `get`.
1603
+ * A caller comparing a value it did not construct must first establish that it is an inert data
1604
+ * graph (the capture clone does exactly that, and refuses a Proxy outright).
1605
+ * - any brand outside plain object/array that is not already reference-identical answers FALSE (a Date
1606
+ * is never the string a JSON codec leaves behind, a Map is never the `{}`), and so does any
1607
+ * inspection that throws.
1608
+ * An uncertain answer therefore costs a policy consultation or a park, never a silently filed value.
1609
+ */
299
1610
  export declare function samePlainValue(a: unknown, b: unknown): boolean;
300
1611
  export interface CheckpointStore {
1612
+ /**
1613
+ * Managed-retention declaration (ruled 2026-08-05, retention.ts): `"managed"` promises the
1614
+ * `ManagedRetentionCapability` contract — including the reap/unpin gap this interface documents on
1615
+ * {@link reap} (a managed store's scheduled expiry DOES release the session pins). `"none"` (or
1616
+ * ABSENT — read fail-closed as `"none"`) states the store cannot delete on schedule; a deployment
1617
+ * with a LOCKED retention policy refuses to start over it. Declaration, never duck-typing.
1618
+ */
301
1619
  readonly retention?: import("./retention.js").RetentionDeclaration;
1620
+ /**
1621
+ * design/173 §2.3 — durability declaration (see {@link StoreDurability}): whether checkpoints
1622
+ * this store accepts survive a process restart. Same "declaration, never duck-typing" posture as
1623
+ * {@link retention}; the wiring manifest relays it verbatim and reads ABSENT fail-closed as
1624
+ * `"process-local"`.
1625
+ */
302
1626
  readonly durability?: StoreDurability;
1627
+ /**
1628
+ * Encoding-fidelity declaration (see {@link StoreFidelity}): which value domain a row keeps INTACT
1629
+ * through this backend. Same "declaration, never duck-typing" posture as {@link durability}; the park
1630
+ * mint reads it to decide the width of the snapshot it files, and reads ABSENT fail-closed as
1631
+ * `"json"`. A backend that serialises through JSON MUST say so (or leave it absent, which says the
1632
+ * same) — a `"structured-clone"` claim it cannot keep is what lets a row degrade under a binding hash
1633
+ * that still matches.
1634
+ */
303
1635
  readonly fidelity?: StoreFidelity;
1636
+ /** Create-once. Throws {@link CheckpointError} `already_exists` on a token collision (never a silent
1637
+ * overwrite — a reused token would clobber a live suspension). */
304
1638
  put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
305
1639
  get(token: CheckpointToken): Promise<Checkpoint | null>;
1640
+ /**
1641
+ * Atomic CAS: `UPDATE … SET status='resolved' WHERE token=? AND scope=? AND status='pending'`.
1642
+ * Returns `true` for the single winner (was pending → now resolved), `false` if already resolved/
1643
+ * expired (`AlreadyResolved` — the caller treats it as a no-op, never re-executes). `scope` is in the
1644
+ * WHERE for multi-tenant isolation (a wrong-scope resolve must not win).
1645
+ *
1646
+ * `outcome` is supplied so a **durable** backend persists it atomically with the status flip (design/45
1647
+ * v2 resumable-resume hook). **design/80 D-1 (persist-winner):** when the CAS wins, the store ALSO records
1648
+ * the winning binding as {@link Checkpoint.resolvedOutcome} on the row — so a later {@link reopen} →
1649
+ * re-resume can be validated against the approved decision (an `env_failed` reopen must replay the same
1650
+ * winner; design/80 §3 inv #1). A `policy_ask` outcome records the decision bound to its pending tool
1651
+ * call; the two REVIEW outcomes (`plan_review` / `dry_run_review`) record theirs under a synthetic
1652
+ * `gate:` marker; `resource_limit` / `wake` / `task_done` bind nothing and record no winner.
1653
+ *
1654
+ * **Implementers: derive the winner with {@link winnerFromOutcome} and persist the object WHOLE.** Its
1655
+ * field set GROWS (`updatedInput`, then `answer`, then `reason` — each additive and optional), and the
1656
+ * replay guard reads an absent optional field as "this backend never recorded it" so that rows written
1657
+ * before the field existed still replay. A backend that projects a hand-listed subset of columns is
1658
+ * therefore indistinguishable from an old row and silently leaves that dimension UNBOUND for every
1659
+ * decision it ever writes — the replay guard cannot tell the two apart, and there is no capability
1660
+ * negotiation on this interface to tell it. Round-trip the whole record (opaque JSON is what every
1661
+ * in-tree backend does) rather than enumerating its keys.
1662
+ *
1663
+ * (The crash-recovery semantics of *acting* on a won CAS are runner contract — see
1664
+ * {@link Runner.resume}; the store only owns the atomic once-only flip + the durable winner.)
1665
+ */
306
1666
  resolve(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): Promise<boolean>;
1667
+ /**
1668
+ * Inverse of {@link resolve}: CAS `resolved → pending` (`UPDATE … SET status='pending' WHERE token=?
1669
+ * AND scope=? AND status='resolved'`). Returns the CAS winner (`true` = was resolved → now pending again).
1670
+ *
1671
+ * **Compensation for a post-CAS env-restore failure (design/45/49).** `Runner.resumeStream` wins the
1672
+ * `resolve` CAS *before* the workspace `resumeVM` runs; if that restore then fails (`resume.env_failed`)
1673
+ * the pending action never executed yet the checkpoint is consumed — without this the suspended work is
1674
+ * lost to a forced "re-initiate" (fine for a short approval, ruinous for a long unattended task).
1675
+ * Reopening lets a retry re-resume the SAME work. Optional: a store that omits it keeps the prior (lossy)
1676
+ * behavior, and the runner degrades gracefully. Safe by the `status='resolved'` guard — only the run that
1677
+ * consumed the checkpoint (and then failed to restore) can reopen it, and only while it is still `resolved`.
1678
+ *
1679
+ * **design/80 D-1 (reopen-by-reason):** the `reason` distinguishes the two reopen triggers (both shipped at
1680
+ * runtask.ts) so re-resume can validate per reason (§3 inv #1): `env_failed` (the action is still valid,
1681
+ * only infra failed → re-resume MUST replay the persisted {@link Checkpoint.resolvedOutcome} winner) vs
1682
+ * `tool_unavailable` (P-7: the approved tool vanished, the action may now be invalid → a human re-decides,
1683
+ * so a FRESH decision is allowed). The store RECORDS the reason on the row as {@link Checkpoint.reopenReason}
1684
+ * and PRESERVES the persisted winner across the reopen (never clears it).
1685
+ */
307
1686
  reopen?(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
1687
+ /**
1688
+ * design/80 D-A: park a durable mid-task STEER on a DURABLY-SUSPENDED task's checkpoint. CAS-style update
1689
+ * `SET state.pendingSteerQueue=? WHERE token=? AND scope=? AND status='pending'`: returns `true` iff the
1690
+ * checkpoint is still `pending` (a resolved/expired checkpoint can't be steered → `false`, a no-op). Scope
1691
+ * is in the WHERE for multi-tenant isolation, exactly like {@link resolve}/{@link expire}.
1692
+ *
1693
+ * **design/171 §5.3 — this APPENDS to {@link CheckpointState.pendingSteerQueue}; it no longer overwrites a
1694
+ * single seat.** Two people steering one suspended run used to silently destroy the first instruction,
1695
+ * with no record that anything was lost. Idempotency moved from last-writer-wins to the `inputId` key:
1696
+ * re-appending an `inputId` already queued (a service retrying with the message id it already minted)
1697
+ * is a no-op that still returns `true`. Crossing {@link MAX_PENDING_STEER_ENTRIES} or
1698
+ * {@link PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES} throws `steering.queue_full` — bounded fail-loud, never
1699
+ * by evicting an accepted instruction.
1700
+ *
1701
+ * **Migration (additive read):** a row written before the queue existed keeps its legacy
1702
+ * {@link CheckpointState.pendingSteer} seat and is read back as queue member 0 by
1703
+ * {@link readPendingSteerQueue}; new writes only ever touch the queue. A backend must round-trip BOTH
1704
+ * fields, and must build its new queue with {@link appendPendingSteer} so the bounds and the `seq` mint
1705
+ * are one implementation rather than one per store.
1706
+ *
1707
+ * **Trust is FROZEN here** — `steer.trusted` is whatever the SERVICE computed from the verified principal
1708
+ * at this call (an operator-role check, NOT a client header — the service's job); the core stores it
1709
+ * VERBATIM and NEVER recomputes it on resume (§3 inv #1).
1710
+ *
1711
+ * **Validation (load-bearing, §3 inv #2):** `steer.text` containing a `</system-reminder>` close tag is
1712
+ * REJECTED with a typed {@link CheckpointError} `steering.invalid_content` (mirroring runtask.ts's trusted-
1713
+ * steer reject) — a dirty steer NEVER enters {@link CheckpointState}, so the persisted state stays clean
1714
+ * regardless of `trusted`. (The untrusted RESUME-injection path ALSO sanitizes the text as untrusted data;
1715
+ * this persist-time reject is the belt that keeps a forged close tag out of the durable record on BOTH
1716
+ * paths.) Text over {@link MAX_PENDING_STEER_CHARS} is rejected with the same code (HRD-APV-4 — the
1717
+ * steer is re-delivered into the model context on every resume, so it is capped like every other
1718
+ * persisted model-facing field on this row; rejected rather than truncated because cutting an
1719
+ * instruction can invert it). NOT an approval channel: this only writes the parked queue, never
1720
+ * touches status / the resolve path / any decision (§3 inv #4).
1721
+ */
308
1722
  setPendingSteer(token: CheckpointToken, scope: string, steer: PendingSteerInput): Promise<boolean>;
1723
+ /**
1724
+ * CAS-expire a **single** `pending` checkpoint by token: `UPDATE … SET status='expired' WHERE token=?
1725
+ * AND scope=? AND status='pending'`. Returns the CAS winner (`true` = was pending → now expired; `false`
1726
+ * = already resolved/expired). The same UPDATE as {@link reap} but keyed by **token**, not by deadline.
1727
+ *
1728
+ * **Load-bearing contract (design/51 §2/§4):** `expire` and {@link resolve} race the **same `pending`
1729
+ * row**, so the store serializes them — exactly one wins. This is what makes {@link TaskStream.destroy}
1730
+ * a correct *fence-then-reap*: `destroy` calls `expire` FIRST to fence any concurrent `resume` (which
1731
+ * goes through `resolve`); only the `expire` winner then destroys the paused env, so the same checkpoint
1732
+ * is **never both reaped and resumed**. `scope` is in the WHERE for multi-tenant isolation, like
1733
+ * `resolve`/`reap`.
1734
+ */
309
1735
  expire(token: CheckpointToken, scope: string): Promise<boolean>;
1736
+ /**
1737
+ * CAS-expire `pending` checkpoints in `scope` whose `deadline` has passed (`deadline <= cutoff`):
1738
+ * `pending → expired`. Returns the count expired (for metrics). Idempotent across replicas (DB
1739
+ * serializes; only the first wins each row) — no leader election needed (§6).
1740
+ *
1741
+ * **Does NOT unpin the sessions of the checkpoints it expires (council finding #2).** A suspend pinned
1742
+ * its session against idle eviction; reaping the checkpoint here leaves that pin in place. The reaper
1743
+ * that calls `reap` owns releasing the pin. For a **durable** session store this is a no-op anyway
1744
+ * (`pin`/`unpin` are no-ops; it never idle-GCs), so reap-without-unpin is harmless — the only store with
1745
+ * a real pin is the in-memory `TtlSessionStore` (single-instance / tests), where an un-released pin
1746
+ * keeps an abandoned suspended session in the cache until process exit. v1 leaves the runner without an
1747
+ * auto-reaper (the service owns the TTL worker); a reaper over the in-memory store should track and
1748
+ * `unpin` the expired sessions itself if that leak matters for its deployment.
1749
+ */
310
1750
  reap(scope: string, cutoff: number): Promise<number>;
1751
+ /**
1752
+ * design/80 assistant-scheduler seam #1: list a lightweight {@link CheckpointSummary}
1753
+ * for EVERY **pending** checkpoint in `scope` (the suspended / awaiting-human tasks — NOT resolved/expired) in
1754
+ * ONE call, so a supervisor scheduler can enumerate a scope's open suspensions without N+1 `get`s or hauling
1755
+ * full {@link Checkpoint} payloads. Read-only — it never mutates a row, takes no CAS, and has ZERO effect on
1756
+ * the once-only resolve. The PENDING predicate is byte-for-byte the {@link resolve} CAS's (`scope === scope &&
1757
+ * status === 'pending'`), so the list reflects exactly the rows that are still resumable. Order is unspecified
1758
+ * (the inbox sorts by `severity`/`deadline` itself). An empty scope returns `[]`.
1759
+ *
1760
+ * **OPTIONAL** for backward-compat with external {@link CheckpointStore} impls (a caller probes
1761
+ * `store.listByScope?.(scope) ?? []`); ALL THREE first-party impls (in-memory, file, Pg) provide it.
1762
+ */
311
1763
  listByScope?(scope: string): Promise<CheckpointSummary[]>;
1764
+ /**
1765
+ * server [1800] seam: the DISTINCT scopes that currently have at least one PENDING checkpoint — the
1766
+ * enumeration face `listByScope` lacks. A host keeping its own scope registry (e.g. a sidecar file for a
1767
+ * cross-scope inbox) can rebuild it from the data itself when the registry is lost/corrupt, instead of the
1768
+ * enumeration silently going empty and pending approvals turning invisible. Scoped to PENDING deliberately:
1769
+ * it matches `listByScope`'s visible surface, so `listScopes() × listByScope()` is exactly the full inbox.
1770
+ * **OPTIONAL** like {@link listByScope}; the in-memory and file first-party impls provide it.
1771
+ */
312
1772
  listScopes?(): Promise<string[]>;
313
1773
  }
1774
+ /**
1775
+ * The SINGLE resolution of "which CheckpointStore does this run use" (ruled 2026-08-04; re-homed
1776
+ * from prepare-task with the design/173 wiring manifest so the static self-check half shares it
1777
+ * without a runtime cycle). The spec seat is three-valued — a store wins, `undefined` falls through
1778
+ * to the deployment's `RunnerDeps.checkpointStore`, and an explicit `null` means this run has NO
1779
+ * store at all (see `TaskSpec.checkpointStore`: the per-run off switch for the durable machine, so
1780
+ * a machine-started run cannot mint a checkpoint nobody will ever resolve).
1781
+ *
1782
+ * Written as an explicit `=== null` test rather than the obvious `spec.checkpointStore ??
1783
+ * deps.checkpointStore` BECAUSE `??` treats `null` exactly like `undefined`: under a bare nullish
1784
+ * coalesce the null falls through and the run is handed the very deployment store the caller asked
1785
+ * it not to have, re-arming every suspend leg — silently, since the resulting run looks perfectly
1786
+ * normal right up until it parks forever. Every read of the pair goes through here so that trap has
1787
+ * ONE place to be wrong instead of nine.
1788
+ */
314
1789
  export declare function resolveCheckpointStore(spec: {
315
1790
  checkpointStore?: CheckpointStore | null;
316
1791
  }, deps: {
317
1792
  checkpointStore?: CheckpointStore;
318
1793
  }): CheckpointStore | undefined;
1794
+ /**
1795
+ * design/80 D-1 (persist-winner): derive the durable {@link ResolvedOutcome} winner from a resume
1796
+ * `outcome`, or `undefined` when the outcome binds to no pending action. A `policy_ask` resume names a
1797
+ * specific tool call (`boundCallId`) + decision; since RB-64 the REVIEW gates (`plan_review` /
1798
+ * `dry_run_review`) record their decision under a `gate:` sentinel too — the env_failed replay guard can
1799
+ * only compare what is recorded here. Only `resource_limit` / `task_done` bind nothing and record no
1800
+ * winner. Shared by every {@link CheckpointStore} impl so the persisted winner is identical across the
1801
+ * in-memory and durable backends.
1802
+ */
319
1803
  export declare function winnerFromOutcome(outcome: ResumeOutcome): ResolvedOutcome | undefined;
1804
+ /**
1805
+ * design/80 D-A: the persist-time validation every {@link CheckpointStore.setPendingSteer} impl runs so the
1806
+ * reject is IDENTICAL across the in-memory and durable backends. A `steer.text` carrying a `</system-reminder>`
1807
+ * close tag would escape the model-facing `<system-reminder>` wrapper a trusted steer rides on resume — reject
1808
+ * fail-closed (`steering.invalid_content`) so a dirty steer NEVER enters {@link CheckpointState} (§3 inv #2).
1809
+ * Applied for BOTH `trusted` values: the durable record must be clean on either path (the untrusted resume
1810
+ * path ALSO sanitizes as untrusted data, but the close tag must never reach the persisted state at all).
1811
+ * Returns a copy built from the FROZEN field set ({@link PENDING_STEER_FROZEN_FIELDS} minus the
1812
+ * store-minted `seq`), so no extra caller field leaks into the row — and, since design/171 §6.3, an
1813
+ * unknown key is a loud rejection rather than a silent drop: a producer one release ahead of this worker
1814
+ * must learn that its new field would not survive the parked leg.
1815
+ */
320
1816
  export declare function validatePendingSteer(steer: PendingSteerInput): Omit<PendingSteerEntry, "seq">;
1817
+ /** design/171 §6.1 — the `actor` id/issuer bound. Attribution labels ride the durable row and the model
1818
+ * context, so they are capped like every other persisted model-facing string on this row. */
321
1819
  export declare const MAX_ACTOR_FIELD_CHARS = 256;
1820
+ /** design/171 §6.3 — bound on the caller-supplied correlation id (a service's own message id). */
322
1821
  export declare const MAX_STEER_INPUT_ID_CHARS = 128;
1822
+ /** design/171 §6.1 — the frozen field set of an {@link ActorAssertion} (same fail-loud posture as the
1823
+ * entry's own set: an unknown key means a producer's provenance field would vanish silently). */
323
1824
  export declare const ACTOR_ASSERTION_FROZEN_FIELDS: readonly ["id", "hostAsserted", "issuer"];
1825
+ /**
1826
+ * design/171 §5.3 — THE single read point for a checkpoint's parked steers, in delivery (`seq`) order.
1827
+ *
1828
+ * Additive read: a row written before the queue existed carries its steer in the legacy
1829
+ * {@link CheckpointState.pendingSteer} seat, and it is folded in as the FIRST member (it was parked
1830
+ * before anything in the queue could have been). Every consumer — the resume drain, the wake's
1831
+ * nothing-to-deliver guard, a serving layer — goes through here, so no site can grow its own idea of
1832
+ * what "the parked steer" is.
1833
+ */
324
1834
  export declare function readPendingSteerQueue(state: Pick<CheckpointState, "pendingSteer" | "pendingSteerQueue">): PendingSteerEntry[];
1835
+ /** design/171 §5.3 — the synthetic `inputId` a pre-queue single-seat steer reads back as. Stable (never
1836
+ * minted per read) so dedupe and re-reads behave, and reserved so a caller cannot collide with it. */
325
1837
  export declare const LEGACY_PENDING_STEER_INPUT_ID = "legacy-single-seat";
1838
+ /**
1839
+ * design/171 §5.3/§6.3 — THE append, shared by every backend so the bounds and the ordering are one
1840
+ * implementation rather than one per store.
1841
+ *
1842
+ * Returns the NEW queue (the caller persists it); throws `steering.queue_full` when either bound would
1843
+ * be crossed. Re-appending an entry whose `inputId` is already queued is a NO-OP returning the queue
1844
+ * unchanged — that is what keeps `setPendingSteer` retry-safe now that it appends instead of overwriting.
1845
+ */
326
1846
  export declare function appendPendingSteer(state: Pick<CheckpointState, "pendingSteer" | "pendingSteerQueue">, entry: Omit<PendingSteerEntry, "seq">): PendingSteerEntry[];
1847
+ /**
1848
+ * REF-A4 — THE row half of every guarded checkpoint transition's CAS predicate, shared by every backend:
1849
+ * the row must EXIST, its `scope` must match (multi-tenant isolation, §2.1 — a wrong-scope caller never
1850
+ * wins), and it must currently be in `status`. `resolve` requires `"pending"`, `reopen` requires
1851
+ * `"resolved"` (the inverse gate), `setPendingSteer`/`expire` require `"pending"`.
1852
+ *
1853
+ * PURE: it decides nothing about persistence. Each backend keeps its own commit half — the in-memory one
1854
+ * flips fields in place, the file one appends+fsyncs the event BEFORE the flip (RB-150: commit ordering is
1855
+ * that backend's own correctness core and must never be absorbed into a shared abstraction).
1856
+ */
327
1857
  export declare function checkpointRowMatches(cp: Checkpoint | undefined, scope: string, status: Checkpoint["status"]): cp is Checkpoint;
1858
+ /**
1859
+ * REF-A4 — THE optimistic-concurrency half of `resolve`'s CAS predicate (design/80 D-1), shared by every
1860
+ * backend. The caller validated its reopen-by-reason guard against a `get()` snapshot; if a concurrent
1861
+ * resolve/reopen cycle moved the monotonic `rev` in the get→resolve window, that validation is stale ⇒
1862
+ * lose the CAS (fail-closed), forcing a re-get + re-validate. Keyed on `rev`, NOT on `reopenReason` — a
1863
+ * 2-valued enum can ABA back to a prior value while the winner changed (round-2 BLOCKER fix). An absent
1864
+ * expectation opts out of the guard entirely.
1865
+ */
328
1866
  export declare function checkpointOccMatches(cp: Checkpoint, expect?: ResolveExpectation): boolean;
329
- export type CheckpointFaultMode = "resolve-after-commit" | "resolve-before-commit";
1867
+ /** Fault-injection mode for {@link InMemoryCheckpointStore.testInjectFault} (council #5). One-shot. */
1868
+ export type CheckpointFaultMode =
1869
+ /** `resolve` commits the CAS (status → resolved) then throws — simulates a crash *after* the commit
1870
+ * but before the caller is acked, so a retry must see `resolved` and NOT re-execute (idempotency). */
1871
+ "resolve-after-commit"
1872
+ /** `resolve` throws *before* the CAS — simulates a crash before the commit; the row stays `pending`
1873
+ * so a retry can still win it. */
1874
+ | "resolve-before-commit";
1875
+ /**
1876
+ * Default in-process {@link CheckpointStore}. Single-instance / tests only — it does NOT survive a
1877
+ * restart or span replicas, so it cannot deliver the cross-process guarantee a durable backend does.
1878
+ * Single-threaded JS already serializes `resolve`, so the CAS is trivially atomic here;
1879
+ * {@link testInjectFault} simulates the crash-recovery races a real backend must survive.
1880
+ */
330
1881
  export declare class InMemoryCheckpointStore implements CheckpointStore {
1882
+ /** In-memory rows die with the process, but there is no scheduled tenant-scoped deletion contract
1883
+ * here — honestly `"none"` (a locked retention policy must refuse this store). */
331
1884
  readonly retention: "none";
1885
+ /** design/173 §2.3 — honest declaration: in-memory rows die with the process. */
332
1886
  readonly durability: "process-local";
1887
+ /** Honest declaration on the fidelity axis: `put`/`get` are `structuredClone`, so a row keeps the
1888
+ * whole structured-clone domain — a `Date` filed here is a `Date` when it comes back. */
333
1889
  readonly fidelity: "structured-clone";
334
1890
  private cps;
335
1891
  private fault;
@@ -342,6 +1898,8 @@ export declare class InMemoryCheckpointStore implements CheckpointStore {
342
1898
  reap(scope: string, cutoff: number): Promise<number>;
343
1899
  listByScope(scope: string): Promise<CheckpointSummary[]>;
344
1900
  listScopes(): Promise<string[]>;
1901
+ /** Arm a one-shot fault on the next `resolve` (council #5: makes crash-recovery unit-testable). */
345
1902
  testInjectFault(mode: CheckpointFaultMode | null): void;
1903
+ /** Test/inspection helper: number of stored checkpoints. */
346
1904
  get size(): number;
347
1905
  }