@sema-agent/core 7.54.0 → 7.55.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 (403) hide show
  1. package/CHANGELOG.md +451 -0
  2. package/dist/agents/agent-transcript-tool.js +2 -1
  3. package/dist/agents/builtin-agents.d.ts +12 -14
  4. package/dist/agents/cc-peer-directory.d.ts +36 -0
  5. package/dist/agents/cc-peer-directory.js +138 -0
  6. package/dist/agents/cc-peer-wire.d.ts +105 -0
  7. package/dist/agents/cc-peer-wire.js +98 -0
  8. package/dist/agents/cc-uds-peer-channel.d.ts +40 -0
  9. package/dist/agents/cc-uds-peer-channel.js +457 -0
  10. package/dist/agents/cross-session-envelope.d.ts +4 -1
  11. package/dist/agents/cross-session-envelope.js +8 -1
  12. package/dist/agents/cross-session-judge.d.ts +2 -2
  13. package/dist/agents/list-agents-tool.js +2 -1
  14. package/dist/agents/observer.d.ts +4 -5
  15. package/dist/agents/observer.js +2 -1
  16. package/dist/agents/peer-admission.d.ts +6 -0
  17. package/dist/agents/peer-admission.js +4 -1
  18. package/dist/agents/peer-directory.d.ts +43 -9
  19. package/dist/agents/peer-directory.js +66 -23
  20. package/dist/agents/peer-notices.d.ts +1 -1
  21. package/dist/agents/peer-session-drain.d.ts +3 -1
  22. package/dist/agents/peer-session-drain.js +1 -0
  23. package/dist/agents/read-inbox-tool.js +2 -1
  24. package/dist/agents/retain-ledger.d.ts +1 -1
  25. package/dist/agents/roster-store.d.ts +1 -1
  26. package/dist/agents/send-message-tool.d.ts +10 -0
  27. package/dist/agents/send-message-tool.js +34 -5
  28. package/dist/agents/subagent.d.ts +34 -79
  29. package/dist/agents/subagent.js +45 -46
  30. package/dist/agents/tool-filter.d.ts +29 -46
  31. package/dist/agents/tool-filter.js +8 -31
  32. package/dist/agents/verify.d.ts +3 -3
  33. package/dist/brain/anthropic.js +14 -9
  34. package/dist/brain/errors.d.ts +15 -0
  35. package/dist/brain/errors.js +10 -0
  36. package/dist/brain/model-presets.d.ts +4 -4
  37. package/dist/brain/open-responses.js +15 -10
  38. package/dist/brain/openai.js +17 -12
  39. package/dist/brain/provider-dialect.d.ts +39 -3
  40. package/dist/brain/provider-dialect.js +28 -0
  41. package/dist/brain/reasoning.d.ts +1 -1
  42. package/dist/brain/request-params.d.ts +55 -5
  43. package/dist/brain/request-params.js +35 -3
  44. package/dist/brain/retry.d.ts +1 -1
  45. package/dist/brain/route-adjudicator.d.ts +15 -0
  46. package/dist/brain/route-adjudicator.js +15 -0
  47. package/dist/brain/stream-engine.js +6 -5
  48. package/dist/brain/stream-shared.d.ts +1 -1
  49. package/dist/core/advisor-verdict.d.ts +120 -0
  50. package/dist/core/advisor-verdict.js +92 -0
  51. package/dist/core/agent-definition.d.ts +7 -8
  52. package/dist/core/allow-entry.d.ts +49 -23
  53. package/dist/core/allow-entry.js +57 -32
  54. package/dist/core/allow-rule-shadow.d.ts +7 -0
  55. package/dist/core/approval-object.d.ts +2 -1
  56. package/dist/core/approval-object.js +6 -6
  57. package/dist/core/ask-origin.d.ts +63 -11
  58. package/dist/core/ask-origin.js +27 -5
  59. package/dist/core/ask-question.js +10 -4
  60. package/dist/core/auto-compaction.d.ts +11 -8
  61. package/dist/core/auto-compaction.js +4 -2
  62. package/dist/core/auto-mode-arming.d.ts +2 -2
  63. package/dist/core/auto-mode-defaults.d.ts +8 -8
  64. package/dist/core/auto-mode-prompt-assets.d.ts +3 -3
  65. package/dist/core/auto-mode-prompt.d.ts +1 -1
  66. package/dist/core/auto-mode.d.ts +17 -9
  67. package/dist/core/auto-mode.js +9 -6
  68. package/dist/core/background-agent-store.d.ts +3 -3
  69. package/dist/core/canonical-json.d.ts +1 -1
  70. package/dist/core/canonical-json.js +2 -2
  71. package/dist/core/checkpoint-requirements.d.ts +6 -3
  72. package/dist/core/checkpoint-requirements.js +4 -14
  73. package/dist/core/checkpoint-store.d.ts +80 -51
  74. package/dist/core/checkpoint-store.js +56 -74
  75. package/dist/core/child-clamp-seats.d.ts +6 -0
  76. package/dist/core/child-clamp-seats.js +3 -16
  77. package/dist/core/compliance.d.ts +2 -2
  78. package/dist/core/context-edit.d.ts +6 -7
  79. package/dist/core/cross-boundary-taint.d.ts +9 -3
  80. package/dist/core/cross-boundary-taint.js +5 -3
  81. package/dist/core/decision-receipt.js +2 -2
  82. package/dist/core/effective-path-target.d.ts +1 -1
  83. package/dist/core/engine-notice.d.ts +19 -5
  84. package/dist/core/exec-output-tail.d.ts +1 -1
  85. package/dist/core/file-history-retention.d.ts +1 -1
  86. package/dist/core/file-history-store.d.ts +59 -9
  87. package/dist/core/file-history-store.js +35 -17
  88. package/dist/core/gate-fold.js +3 -2
  89. package/dist/core/gate-lanes.js +12 -8
  90. package/dist/core/gate-outcome.d.ts +17 -4
  91. package/dist/core/gate-outcome.js +30 -0
  92. package/dist/core/governance-codes.d.ts +13 -3
  93. package/dist/core/governance-codes.js +10 -0
  94. package/dist/core/hands-band.d.ts +1 -1
  95. package/dist/core/hooks.d.ts +4 -4
  96. package/dist/core/host-target.d.ts +49 -0
  97. package/dist/core/host-target.js +59 -0
  98. package/dist/core/image-downsample.d.ts +1 -1
  99. package/dist/core/lsp-diagnostics.d.ts +4 -4
  100. package/dist/core/lsp.d.ts +3 -3
  101. package/dist/core/mailbox-store.d.ts +4 -0
  102. package/dist/core/mcp-connection-pool.d.ts +18 -2
  103. package/dist/core/mcp-connection-pool.js +53 -6
  104. package/dist/core/mcp-failure.d.ts +23 -4
  105. package/dist/core/mcp-failure.js +26 -11
  106. package/dist/core/mcp-listing-drift.d.ts +10 -0
  107. package/dist/core/mcp-listing-drift.js +6 -0
  108. package/dist/core/mcp-server-link.d.ts +173 -0
  109. package/dist/core/mcp-server-link.js +127 -0
  110. package/dist/core/mcp-server-spec.d.ts +4 -2
  111. package/dist/core/mcp-session-end.d.ts +26 -0
  112. package/dist/core/mcp-session-end.js +29 -0
  113. package/dist/core/mcp-startup-request.d.ts +17 -0
  114. package/dist/core/mcp-startup-request.js +17 -0
  115. package/dist/core/mcp.d.ts +13 -13
  116. package/dist/core/mcp.js +187 -162
  117. package/dist/core/media-byte-cap.d.ts +1 -1
  118. package/dist/core/memory-engine/engine.d.ts +9 -9
  119. package/dist/core/memory-engine/provenance-wording.d.ts +1 -1
  120. package/dist/core/memory-engine/tools.js +4 -1
  121. package/dist/core/mode-policy.d.ts +1 -1
  122. package/dist/core/namespaced-rule-name.d.ts +33 -7
  123. package/dist/core/namespaced-rule-name.js +39 -25
  124. package/dist/core/output-style.d.ts +1 -1
  125. package/dist/core/param-rule-reach.d.ts +15 -0
  126. package/dist/core/param-rule-reach.js +30 -0
  127. package/dist/core/parent-spec-seat.d.ts +8 -1
  128. package/dist/core/parent-spec-seat.js +4 -0
  129. package/dist/core/pause-registry.d.ts +16 -5
  130. package/dist/core/pause-registry.js +6 -2
  131. package/dist/core/peer-channel.d.ts +70 -0
  132. package/dist/core/peer-channel.js +1 -0
  133. package/dist/core/permission-rule-consent.d.ts +11 -5
  134. package/dist/core/permission-rule-consent.js +37 -30
  135. package/dist/core/permission-rule-model.d.ts +233 -64
  136. package/dist/core/permission-rule-model.js +384 -85
  137. package/dist/core/permission-rule-provider.d.ts +2 -1
  138. package/dist/core/permission-rule-store.d.ts +20 -25
  139. package/dist/core/permission-rule-store.js +1 -4
  140. package/dist/core/permission-rule-sync.d.ts +11 -2
  141. package/dist/core/permission-rule-sync.js +8 -1
  142. package/dist/core/permission-rules.d.ts +49 -11
  143. package/dist/core/permission-rules.js +123 -89
  144. package/dist/core/persisted-rule-arms.d.ts +4 -2
  145. package/dist/core/present-plan-tool.js +3 -1
  146. package/dist/core/protocol-naming.d.ts +2 -2
  147. package/dist/core/read-only-shell-table.d.ts +1 -1
  148. package/dist/core/read-only-shell.d.ts +1 -1
  149. package/dist/core/roles.d.ts +6 -2
  150. package/dist/core/roles.js +45 -44
  151. package/dist/core/rule-offer-read.d.ts +53 -0
  152. package/dist/core/rule-offer-read.js +146 -0
  153. package/dist/core/rule-owner.d.ts +46 -0
  154. package/dist/core/rule-owner.js +28 -0
  155. package/dist/core/runner/advisor-instructions.d.ts +70 -0
  156. package/dist/core/runner/advisor-instructions.js +61 -0
  157. package/dist/core/runner/advisor-tool.d.ts +33 -0
  158. package/dist/core/runner/advisor-tool.js +112 -0
  159. package/dist/core/runner/announce-once-ledger.d.ts +4 -26
  160. package/dist/core/runner/announce-once-ledger.js +14 -46
  161. package/dist/core/runner/approval-feedback.js +6 -3
  162. package/dist/core/runner/assemble-result.d.ts +1 -1
  163. package/dist/core/runner/caller-roster.d.ts +7 -0
  164. package/dist/core/runner/caller-roster.js +11 -0
  165. package/dist/core/runner/classifier-ladder.d.ts +27 -5
  166. package/dist/core/runner/classifier-ladder.js +12 -11
  167. package/dist/core/runner/compaction-restate.d.ts +10 -6
  168. package/dist/core/runner/compaction-restate.js +9 -3
  169. package/dist/core/runner/contracts.d.ts +13 -9
  170. package/dist/core/runner/conversation-segment.d.ts +28 -0
  171. package/dist/core/runner/conversation-segment.js +42 -0
  172. package/dist/core/runner/cross-boundary-lane.d.ts +6 -5
  173. package/dist/core/runner/cross-boundary-lane.js +26 -15
  174. package/dist/core/runner/decide-continuation.d.ts +7 -4
  175. package/dist/core/runner/decide-continuation.js +2 -2
  176. package/dist/core/runner/denial-limit-arms.d.ts +2 -2
  177. package/dist/core/runner/derived-route-fallback.d.ts +24 -24
  178. package/dist/core/runner/derived-route-fallback.js +9 -7
  179. package/dist/core/runner/gate-exit.d.ts +6 -0
  180. package/dist/core/runner/gate-exit.js +7 -2
  181. package/dist/core/runner/initial-run-state.js +1 -1
  182. package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
  183. package/dist/core/runner/permission-rule-lanes.js +14 -7
  184. package/dist/core/runner/person-path-rows.js +1 -1
  185. package/dist/core/runner/prepare-acquire-reconcile.d.ts +4 -5
  186. package/dist/core/runner/prepare-acquire-reconcile.js +5 -3
  187. package/dist/core/runner/prepare-advisor.d.ts +63 -0
  188. package/dist/core/runner/prepare-advisor.js +77 -0
  189. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  190. package/dist/core/runner/prepare-config-doors.d.ts +6 -4
  191. package/dist/core/runner/prepare-config-doors.js +18 -12
  192. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
  193. package/dist/core/runner/prepare-delegation-surface.js +1 -1
  194. package/dist/core/runner/prepare-file-history.js +39 -39
  195. package/dist/core/runner/prepare-gate-stations.d.ts +1 -4
  196. package/dist/core/runner/prepare-gate-stations.js +2 -7
  197. package/dist/core/runner/prepare-inherited-gate.d.ts +5 -4
  198. package/dist/core/runner/prepare-inherited-gate.js +3 -3
  199. package/dist/core/runner/prepare-park-ask.js +7 -7
  200. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  201. package/dist/core/runner/prepare-policy-chain.js +4 -10
  202. package/dist/core/runner/prepare-project-context.d.ts +5 -2
  203. package/dist/core/runner/prepare-project-context.js +6 -3
  204. package/dist/core/runner/prepare-prompt-inputs.js +1 -1
  205. package/dist/core/runner/prepare-protocol-tools.d.ts +2 -2
  206. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  207. package/dist/core/runner/prepare-safety-scan.d.ts +9 -12
  208. package/dist/core/runner/prepare-safety-scan.js +8 -6
  209. package/dist/core/runner/prepare-task.js +15 -18
  210. package/dist/core/runner/prepare-tool-disclosure-mount.js +6 -4
  211. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -3
  212. package/dist/core/runner/prepare-wiring-manifest.js +2 -2
  213. package/dist/core/runner/resume-policy-outcome.js +1 -1
  214. package/dist/core/runner/retired-runner-deps.js +1 -1
  215. package/dist/core/runner/run-attachment-seats.js +0 -1
  216. package/dist/core/runner/run-compaction-machinery.d.ts +1 -1
  217. package/dist/core/runner/run-compaction-machinery.js +2 -12
  218. package/dist/core/runner/run-git-lane.d.ts +1 -0
  219. package/dist/core/runner/run-harness-handlers.js +2 -3
  220. package/dist/core/runner/run-leg.js +13 -0
  221. package/dist/core/runner/run-settle-and-teardown.js +1 -1
  222. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  223. package/dist/core/runner/run-turn-boundary.d.ts +1 -0
  224. package/dist/core/runner/run-turn-boundary.js +14 -12
  225. package/dist/core/runner/session-rule-policy.d.ts +22 -21
  226. package/dist/core/runner/session-rule-policy.js +13 -28
  227. package/dist/core/runner/side-call-account.d.ts +32 -8
  228. package/dist/core/runner/side-call-account.js +23 -1
  229. package/dist/core/runner/strict-output-schema.d.ts +1 -1
  230. package/dist/core/runner/sub-agent-deny-lists.js +4 -3
  231. package/dist/core/runner/synthetic-tools.d.ts +10 -13
  232. package/dist/core/runner/synthetic-tools.js +4 -2
  233. package/dist/core/runner/tool-disclosure.js +2 -1
  234. package/dist/core/runner/tool-face-overlay.d.ts +2 -1
  235. package/dist/core/runner/tool-face-overlay.js +17 -1
  236. package/dist/core/runner/tool-progress-lane.d.ts +2 -2
  237. package/dist/core/runner/tool-turn-repair-seat.d.ts +3 -2
  238. package/dist/core/runner/turn-attachments.d.ts +60 -67
  239. package/dist/core/runner-deps.d.ts +37 -37
  240. package/dist/core/runtime-caps.d.ts +4 -4
  241. package/dist/core/safety-merge-corpus.js +17 -1
  242. package/dist/core/same-plain-value.d.ts +40 -0
  243. package/dist/core/same-plain-value.js +49 -0
  244. package/dist/core/scheduler.d.ts +1 -1
  245. package/dist/core/sensitive-path-policy.d.ts +1 -1
  246. package/dist/core/sensitive-path-policy.js +1 -1
  247. package/dist/core/session-policy-store.d.ts +77 -22
  248. package/dist/core/session-policy-store.js +56 -14
  249. package/dist/core/session-reconcile.d.ts +2 -2
  250. package/dist/core/session-store.d.ts +80 -30
  251. package/dist/core/session-store.js +118 -76
  252. package/dist/core/session.d.ts +24 -46
  253. package/dist/core/shared-memory/tools.js +3 -1
  254. package/dist/core/shared-memory/types.d.ts +1 -1
  255. package/dist/core/side-query.d.ts +10 -0
  256. package/dist/core/side-query.js +5 -0
  257. package/dist/core/store-contracts/checkpoint-store-contract.js +29 -2
  258. package/dist/core/store-contracts/file-history-store-contract.js +43 -3
  259. package/dist/core/store-contracts/session-policy-store-contract.d.ts +7 -0
  260. package/dist/core/store-contracts/session-policy-store-contract.js +130 -0
  261. package/dist/core/store-contracts/session-repo-contract.js +125 -0
  262. package/dist/core/store-contracts/session-store-contract.d.ts +4 -7
  263. package/dist/core/store-contracts/session-store-contract.js +259 -41
  264. package/dist/core/store-contracts/workflow-journal-store-contract.js +26 -1
  265. package/dist/core/surrogate-safe-slice.d.ts +3 -3
  266. package/dist/core/task-event.d.ts +10 -4
  267. package/dist/core/task-notification.d.ts +3 -3
  268. package/dist/core/task-registry-agent.d.ts +4 -4
  269. package/dist/core/task-registry-shared.d.ts +4 -4
  270. package/dist/core/task-registry-shared.js +1 -0
  271. package/dist/core/task-registry.d.ts +4 -4
  272. package/dist/core/task-registry.js +3 -1
  273. package/dist/core/task-result.d.ts +3 -3
  274. package/dist/core/task-spec.d.ts +52 -52
  275. package/dist/core/task-stream.d.ts +6 -6
  276. package/dist/core/task-tool-shape.d.ts +4 -4
  277. package/dist/core/tighten-task-spec.d.ts +1 -1
  278. package/dist/core/tool-catalog-entries.d.ts +19 -2
  279. package/dist/core/tool-catalog-entries.js +57 -42
  280. package/dist/core/tool-conformance.js +1 -0
  281. package/dist/core/tool-errors.d.ts +2 -2
  282. package/dist/core/tool-face-reader.js +5 -3
  283. package/dist/core/tool-face.d.ts +21 -2
  284. package/dist/core/tool-face.js +1 -1
  285. package/dist/core/tool-model-gate.d.ts +3 -3
  286. package/dist/core/tool-name-aliases.d.ts +29 -8
  287. package/dist/core/tool-name-aliases.js +27 -3
  288. package/dist/core/tool-name-readings.d.ts +5 -8
  289. package/dist/core/tool-name-readings.js +1 -2
  290. package/dist/core/tool-policy.d.ts +24 -5
  291. package/dist/core/tool-policy.js +34 -10
  292. package/dist/core/tool-registry.d.ts +14 -7
  293. package/dist/core/tool-registry.js +17 -7
  294. package/dist/core/tool-result-store.js +2 -1
  295. package/dist/core/tool-roster.d.ts +47 -18
  296. package/dist/core/tool-roster.js +53 -32
  297. package/dist/core/tool-spec-members.js +1 -1
  298. package/dist/core/tool-spec.d.ts +35 -21
  299. package/dist/core/tools.js +3 -2
  300. package/dist/core/trace.d.ts +4 -0
  301. package/dist/core/untrusted-egress.d.ts +1 -1
  302. package/dist/core/untrusted-text.d.ts +1 -1
  303. package/dist/core/warm-resume.d.ts +1 -1
  304. package/dist/core/wiring-manifest.d.ts +49 -0
  305. package/dist/core/wiring-manifest.js +13 -5
  306. package/dist/core/workflow-journal-store.d.ts +62 -0
  307. package/dist/core/workflow-journal-store.js +77 -1
  308. package/dist/core/workflow-run-store.d.ts +6 -0
  309. package/dist/core/workflow-run-store.js +2 -0
  310. package/dist/engine/compaction/compaction.d.ts +9 -9
  311. package/dist/engine/execution-env/node-execution-env.d.ts +19 -5
  312. package/dist/engine/execution-env/node-execution-env.js +43 -22
  313. package/dist/engine/harness/agent-harness.d.ts +14 -8
  314. package/dist/engine/harness/agent-harness.js +33 -26
  315. package/dist/engine/harness/types.d.ts +29 -7
  316. package/dist/engine/harness/types.js +12 -1
  317. package/dist/engine/llm/types.d.ts +9 -9
  318. package/dist/engine/llm/validation.d.ts +4 -1
  319. package/dist/engine/llm/validation.js +13 -1
  320. package/dist/engine/loop/agent-loop.d.ts +1 -1
  321. package/dist/engine/loop/agent-loop.js +10 -8
  322. package/dist/engine/loop/types.d.ts +15 -13
  323. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  324. package/dist/engine/session/memory-repo.d.ts +10 -11
  325. package/dist/engine/session/memory-repo.js +13 -8
  326. package/dist/engine/session/parent-chain-guard.d.ts +11 -0
  327. package/dist/engine/session/parent-chain-guard.js +10 -0
  328. package/dist/engine/session/repo-utils.d.ts +31 -3
  329. package/dist/engine/session/repo-utils.js +42 -5
  330. package/dist/engine/session/storage-base.js +3 -0
  331. package/dist/index.d.ts +27 -15
  332. package/dist/index.js +21 -11
  333. package/dist/internal/harness-types.d.ts +1 -1
  334. package/dist/internal/harness.d.ts +4 -3
  335. package/dist/internal/harness.js +3 -2
  336. package/dist/internal/llm.d.ts +1 -1
  337. package/dist/internal/llm.js +1 -1
  338. package/dist/orchestration/goal.js +2 -2
  339. package/dist/orchestration/run-workflow-tool.d.ts +2 -2
  340. package/dist/orchestration/run-workflow-tool.js +5 -3
  341. package/dist/orchestration/workflow-size-guideline.d.ts +4 -4
  342. package/dist/orchestration/workflow-types.d.ts +18 -0
  343. package/dist/orchestration/workflow-types.js +6 -0
  344. package/dist/orchestration/workflow.d.ts +2 -13
  345. package/dist/orchestration/workflow.js +21 -31
  346. package/dist/prompt-assembly/event-registry.js +1 -0
  347. package/dist/prompt-assembly/tool-catalog.d.ts +3 -0
  348. package/dist/prompt-assembly/tool-catalog.js +3 -0
  349. package/dist/prompts/default.d.ts +14 -14
  350. package/dist/scenarios/full-body.d.ts +4 -4
  351. package/dist/stores/file/adoption/adopt.d.ts +4 -3
  352. package/dist/stores/file/adoption/adopt.js +24 -31
  353. package/dist/stores/file/adoption/marker.d.ts +6 -13
  354. package/dist/stores/file/adoption/marker.js +6 -15
  355. package/dist/stores/file/checkpoint-store.d.ts +3 -0
  356. package/dist/stores/file/checkpoint-store.js +9 -3
  357. package/dist/stores/file/file-history-store.d.ts +20 -19
  358. package/dist/stores/file/file-history-store.js +152 -135
  359. package/dist/stores/file/fs-atomic.d.ts +15 -0
  360. package/dist/stores/file/fs-atomic.js +89 -14
  361. package/dist/stores/file/index.d.ts +1 -1
  362. package/dist/stores/file/mailbox-store.d.ts +3 -0
  363. package/dist/stores/file/mailbox-store.js +3 -1
  364. package/dist/stores/file/permission-rule-adopt.d.ts +1 -1
  365. package/dist/stores/file/permission-rule-adopt.js +15 -16
  366. package/dist/stores/file/permission-rule-store.d.ts +18 -2
  367. package/dist/stores/file/permission-rule-store.js +21 -14
  368. package/dist/stores/file/session-policy-store.d.ts +3 -3
  369. package/dist/stores/file/session-store.d.ts +26 -11
  370. package/dist/stores/file/session-store.js +77 -40
  371. package/dist/stores/file/workflow-journal-store.d.ts +10 -4
  372. package/dist/stores/file/workflow-journal-store.js +28 -13
  373. package/dist/tools/artifact/artifact-text.d.ts +1 -1
  374. package/dist/tools/artifact/artifact-tool.js +2 -1
  375. package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -3
  376. package/dist/tools/fs/fs-bash.d.ts +2 -2
  377. package/dist/tools/fs/fs-bash.js +3 -1
  378. package/dist/tools/fs/fs-shared.d.ts +8 -8
  379. package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
  380. package/dist/tools/fs/notebook.d.ts +2 -2
  381. package/dist/tools/fs/read-deny.d.ts +29 -7
  382. package/dist/tools/fs/read-deny.js +63 -22
  383. package/dist/tools/fs/removal-targets.d.ts +1 -1
  384. package/dist/tools/fs/safety.d.ts +9 -9
  385. package/dist/tools/fs/search.d.ts +6 -6
  386. package/dist/tools/fs/shell-read-check.js +6 -1
  387. package/dist/tools/fs/shell-read-judge.d.ts +4 -0
  388. package/dist/tools/fs/shell-read-judge.js +5 -2
  389. package/dist/tools/loop-tick.d.ts +2 -2
  390. package/dist/tools/scheduler-tools.js +5 -1
  391. package/dist/tools/task-list.d.ts +7 -7
  392. package/dist/tools/task-list.js +5 -1
  393. package/dist/tools/todo.js +2 -1
  394. package/dist/tools/web.d.ts +1 -1
  395. package/dist/tools/web.js +40 -37
  396. package/dist/tools/worktree.d.ts +3 -3
  397. package/dist/tools/worktree.js +3 -1
  398. package/package.json +1 -1
  399. package/test/export-surface.snapshot.json +5756 -5638
  400. package/wire-changes/core@7.54.1.json +37 -0
  401. package/wire-changes/core@7.55.0.json +3318 -0
  402. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +0 -39
  403. package/dist/core/store-contracts/session-store-incarnation-contract.js +0 -95
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
2
2
  import { appendFileSync, closeSync, existsSync, fsyncSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { thrownText } from "../../core/thrown-value.js";
5
- import { applyFileRestoreViaEnv, boundaryPublishVerdict, worldStillMatchesMint, captureFileBackupViaEnv, modeProvenDifferent, previewFileDelta, describeTrackKey, fileHistoryExportDigest, resolveTrackKey, trackKeyOf, validateFileHistoryExport, } from "../../core/file-history-store.js";
5
+ import { applyFileRestoreViaEnv, boundaryPublishVerdict, worldStillMatchesMint, captureFileBackupViaEnv, modeProvenDifferent, previewFileDelta, describeTrackKey, fileHistoryExportDigest, caughtFault, DAMAGED_RECORD, resolveTrackKey, trackKeyOf, validateFileHistoryExport, } from "../../core/file-history-store.js";
6
6
  import { fileHistoryBoundariesToKeep, resolveFileHistoryRetention } from "../../core/file-history-retention.js";
7
7
  import { atomicWriteFile, ensureDir, sanitizePathComponent, sanitizeScope, writeThenLink } from "./fs-atomic.js";
8
8
  import { guardCorruptSink } from "./corrupt-read-sink.js";
@@ -109,6 +109,12 @@ function nestedMapSet(map, scope, key, value) {
109
109
  map.set(scope, (inner = new Map()));
110
110
  inner.set(key, value);
111
111
  }
112
+ function worstFault(faults) {
113
+ const terminal = faults.find((f) => f.permanence === "terminal");
114
+ if (terminal !== undefined)
115
+ return terminal;
116
+ return faults.length > 0 && faults.every((f) => f.permanence === "transient") ? faults[0] : {};
117
+ }
112
118
  function safeRm(path) {
113
119
  try {
114
120
  rmSync(path, { force: true, recursive: true });
@@ -227,21 +233,21 @@ export class FileFileHistoryStore {
227
233
  if (err.code === "ENOENT")
228
234
  return { corrupt: false, tracked: new Map() };
229
235
  this.disclose(path, `tracked set unreadable (${err.code ?? "unknown"}) — this scope's history is UNUSABLE until it is repaired`);
230
- return { corrupt: true, tracked: new Map() };
236
+ return { corrupt: true, tracked: new Map(), fault: caughtFault(err) };
231
237
  }
232
238
  const parsed = parseJsonRecord(raw);
233
239
  if (parsed === null) {
234
240
  this.disclose(path, "unparseable tracked set (torn/zero-byte write?) — this scope's history is UNUSABLE until it is repaired");
235
- return { corrupt: true, tracked: new Map() };
241
+ return { corrupt: true, tracked: new Map(), fault: DAMAGED_RECORD };
236
242
  }
237
243
  const rows = trackedRowsOf(parsed);
238
244
  if (rows === null) {
239
245
  this.disclose(path, "tracked set is not a pair array of [key, record] rows — this scope's history is UNUSABLE until it is repaired");
240
- return { corrupt: true, tracked: new Map() };
246
+ return { corrupt: true, tracked: new Map(), fault: DAMAGED_RECORD };
241
247
  }
242
248
  if (parsed.digest !== undefined && (typeof parsed.digest !== "string" || parsed.digest !== trackedDigest(rows))) {
243
249
  this.disclose(path, "tracked set fails its own integrity digest (edited in place, or written by something that is not this store) — this scope's history is UNUSABLE until it is repaired");
244
- return { corrupt: true, tracked: new Map() };
250
+ return { corrupt: true, tracked: new Map(), fault: DAMAGED_RECORD };
245
251
  }
246
252
  return { corrupt: false, tracked: new Map(rows) };
247
253
  }
@@ -256,6 +262,10 @@ export class FileFileHistoryStore {
256
262
  atomicWriteFile(join(this.base, "tmp"), this.trackedPath(scope), JSON.stringify(doc));
257
263
  }
258
264
  readBoundary(path) {
265
+ const read = this.readBoundaryState(path);
266
+ return read instanceof Map ? read : null;
267
+ }
268
+ readBoundaryState(path) {
259
269
  let raw;
260
270
  try {
261
271
  raw = readFileSync(path, "utf8");
@@ -263,33 +273,33 @@ export class FileFileHistoryStore {
263
273
  catch (err) {
264
274
  if (err.code === "ENOENT")
265
275
  return null;
266
- this.disclose(path, `boundary unreadable (${err.code ?? "unknown"}) — read as no boundary`);
267
- return null;
276
+ this.disclose(path, `boundary unreadable (${err.code ?? "unknown"}) — not a usable boundary`);
277
+ return caughtFault(err);
268
278
  }
269
279
  {
270
280
  const doc = parseJsonRecord(raw);
271
281
  if (doc === null) {
272
- this.disclose(path, "unparseable boundary (torn/zero-byte write?) — read as no boundary");
273
- return null;
282
+ this.disclose(path, "unparseable boundary (torn/zero-byte write?) — not a usable boundary");
283
+ return DAMAGED_RECORD;
274
284
  }
275
285
  if (doc.formatVersion !== "file-history/1" || !Array.isArray(doc.files)) {
276
- this.disclose(path, "boundary is not a file-history/1 pair array — read as no boundary");
277
- return null;
286
+ this.disclose(path, "boundary is not a file-history/1 pair array — not a usable boundary");
287
+ return DAMAGED_RECORD;
278
288
  }
279
289
  const parsed = { formatVersion: doc.formatVersion, files: doc.files, digest: doc.digest };
280
290
  for (const pair of parsed.files) {
281
291
  if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string" || !Number.isInteger(pair[1]) || pair[1] < 1) {
282
- this.disclose(path, "boundary carries a malformed mapping row — read as no boundary");
283
- return null;
292
+ this.disclose(path, "boundary carries a malformed mapping row — not a usable boundary");
293
+ return DAMAGED_RECORD;
284
294
  }
285
295
  }
286
296
  if (new Set(parsed.files.map(([k]) => k)).size !== parsed.files.length) {
287
- this.disclose(path, "boundary maps the same path twice — read as no boundary");
288
- return null;
297
+ this.disclose(path, "boundary maps the same path twice — not a usable boundary");
298
+ return DAMAGED_RECORD;
289
299
  }
290
300
  if (typeof parsed.digest !== "string" || parsed.digest !== boundaryDigest(parsed.files)) {
291
- this.disclose(path, "boundary fails its own integrity digest (rows added or dropped?) — read as no boundary");
292
- return null;
301
+ this.disclose(path, "boundary fails its own integrity digest (rows added or dropped?) — not a usable boundary");
302
+ return DAMAGED_RECORD;
293
303
  }
294
304
  return new Map(parsed.files);
295
305
  }
@@ -304,23 +314,11 @@ export class FileFileHistoryStore {
304
314
  return undefined;
305
315
  }
306
316
  }
307
- unjudgeableBoundaryIdsOf(scope) {
308
- const dir = this.boundaryDir(scope);
309
- let names;
310
- try {
311
- names = readdirSync(dir);
312
- }
313
- catch (err) {
314
- return err.code === "ENOENT" ? null : [];
315
- }
316
- const bad = names.filter((f) => f.endsWith(".json") && this.readBoundary(join(dir, f)) === null).map((f) => f.slice(0, -".json".length));
317
- return bad.length > 0 ? bad : null;
318
- }
319
317
  carryConsumedIds(fromScope, toScope) {
320
318
  const spent = this.readReapedIds(fromScope);
321
- if (spent === null) {
319
+ if (!(spent instanceof Set)) {
322
320
  this.disclose(this.reapedLogPath(fromScope), "source consumed-id ledger unreadable — the adoption is refused rather than published without its lineage's tombstones");
323
- return false;
321
+ return spent;
324
322
  }
325
323
  if (spent.size === 0)
326
324
  return true;
@@ -339,7 +337,7 @@ export class FileFileHistoryStore {
339
337
  if (err.code === "ENOENT")
340
338
  return new Set();
341
339
  this.disclose(path, `consumed-id ledger unreadable (${err.code ?? "unknown"})`);
342
- return null;
340
+ return caughtFault(err);
343
341
  }
344
342
  const cached = this.reapedCache.get(scope);
345
343
  if (cached !== undefined && cached.size === size)
@@ -354,7 +352,7 @@ export class FileFileHistoryStore {
354
352
  }
355
353
  catch (err) {
356
354
  this.disclose(path, `consumed-id ledger unreadable (${err.code ?? "unknown"})`);
357
- return null;
355
+ return caughtFault(err);
358
356
  }
359
357
  }
360
358
  markReaped(scope, ids) {
@@ -381,7 +379,7 @@ export class FileFileHistoryStore {
381
379
  }
382
380
  catch (err) {
383
381
  this.disclose(path, `consumed-id ledger could not be appended (${err.code ?? "unknown"}) — the boundaries it names are NOT being reaped, so this scope is not bounded until it can be written`);
384
- return false;
382
+ return caughtFault(err);
385
383
  }
386
384
  }
387
385
  boundaryOrderOf(path) {
@@ -409,7 +407,7 @@ export class FileFileHistoryStore {
409
407
  const { key, kind, family } = describeTrackKey(root, absPath);
410
408
  const before = this.readTrackedState(scope);
411
409
  if (before.corrupt)
412
- return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" is unreadable — refusing to mint a first-touch record over an unusable graph` } };
410
+ return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" is unreadable — refusing to mint a first-touch record over an unusable graph`, ...before.fault } };
413
411
  const existing = before.tracked.get(key);
414
412
  if (existing !== undefined) {
415
413
  if (existing.observedByLaterTouch !== true) {
@@ -421,7 +419,7 @@ export class FileFileHistoryStore {
421
419
  const cap = await captureFileBackupViaEnv(env, absPath, root, signal);
422
420
  const after = this.readTrackedState(scope);
423
421
  if (after.corrupt)
424
- return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" became unreadable during the first-touch capture — nothing was recorded` } };
422
+ return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" became unreadable during the first-touch capture — nothing was recorded`, ...after.fault } };
425
423
  const tracked = after.tracked;
426
424
  const raced = tracked.get(key);
427
425
  if (raced !== undefined) {
@@ -452,7 +450,7 @@ export class FileFileHistoryStore {
452
450
  return { ok: true, minted: true };
453
451
  }
454
452
  catch (err) {
455
- return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${thrownText(err)}` } };
453
+ return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${thrownText(err)}`, ...caughtFault(err) } };
456
454
  }
457
455
  }
458
456
  async annulTrack(scope, absPath, root, verify) {
@@ -460,13 +458,16 @@ export class FileFileHistoryStore {
460
458
  const key = trackKeyOf(root, absPath);
461
459
  const state = this.readTrackedState(scope);
462
460
  if (state.corrupt) {
463
- return { ok: false, error: { code: "conflict", message: `file history for scope "${scope}" is unreadable — the first-touch record (if any) was left in place; repair the graph before retraction can be judged` } };
461
+ return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" is unreadable — the first-touch record (if any) was left in place; repair the graph before retraction can be judged`, ...state.fault } };
464
462
  }
465
463
  const tracked = state.tracked;
466
464
  const rec = tracked.get(key);
467
465
  if (rec === undefined)
468
466
  return { ok: true };
469
- for (const entryId of this.listBoundaryIds(scope)) {
467
+ const listed = this.listBoundaryIds(scope);
468
+ if (!Array.isArray(listed))
469
+ return { ok: false, error: { code: "track_failed", message: `scope "${scope}" has ${listed.what} — the first-touch record for ${absPath} was left in place, since whether a boundary references it cannot be judged`, ...listed.fault } };
470
+ for (const entryId of listed) {
470
471
  const bp = this.tryBoundaryPath(scope, entryId);
471
472
  const mapping = bp === null ? null : this.readBoundary(bp);
472
473
  if (mapping?.has(key) === true) {
@@ -485,7 +486,7 @@ export class FileFileHistoryStore {
485
486
  }
486
487
  const fresh = this.readTrackedState(scope);
487
488
  if (fresh.corrupt)
488
- return { ok: false, error: { code: "conflict", message: `file history for scope "${scope}" became unreadable — the record was left in place` } };
489
+ return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" became unreadable — the record was left in place`, ...fresh.fault } };
489
490
  const still = fresh.tracked.get(key);
490
491
  if (still === undefined)
491
492
  return { ok: true };
@@ -495,7 +496,10 @@ export class FileFileHistoryStore {
495
496
  if (still.mintId !== rec.mintId || still.baseAvailable !== rec.baseAvailable || still.versions.length !== rec.versions.length || still.versions[0]?.blobHash !== rec.versions[0]?.blobHash || still.versions[0]?.backupTime !== rec.versions[0]?.backupTime) {
496
497
  return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} was replaced while this retraction was being proven — the record now there belongs to a newer edit` } };
497
498
  }
498
- for (const entryId of this.listBoundaryIds(scope)) {
499
+ const relisted = this.listBoundaryIds(scope);
500
+ if (!Array.isArray(relisted))
501
+ return { ok: false, error: { code: "track_failed", message: `scope "${scope}" came to have ${relisted.what} while this retraction was being proven — the record was left in place`, ...relisted.fault } };
502
+ for (const entryId of relisted) {
499
503
  const bp2 = this.tryBoundaryPath(scope, entryId);
500
504
  const mapping2 = bp2 === null ? null : this.readBoundary(bp2);
501
505
  if (mapping2?.has(key) === true) {
@@ -507,53 +511,65 @@ export class FileFileHistoryStore {
507
511
  return { ok: true };
508
512
  }
509
513
  catch (err) {
510
- return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${thrownText(err)}` } };
514
+ return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${thrownText(err)}`, ...caughtFault(err) } };
511
515
  }
512
516
  }
513
517
  scopeIsEmptySync(scope) {
514
518
  const state = this.readTrackedState(scope);
515
519
  if (state.corrupt)
516
- return false;
517
- return state.tracked.size === 0 && this.listBoundaryIds(scope).length === 0;
520
+ return { fault: state.fault, what: "an unreadable tracked set" };
521
+ const listed = this.listBoundaryIds(scope);
522
+ if (!Array.isArray(listed))
523
+ return listed;
524
+ return state.tracked.size === 0 && listed.length === 0 ? "empty" : "not_empty";
525
+ }
526
+ async importTargetOf(scope, data, when) {
527
+ const emptiness = this.scopeIsEmptySync(scope);
528
+ if (emptiness === "empty")
529
+ return "empty";
530
+ if (emptiness !== "not_empty") {
531
+ return { ok: false, error: { code: "adopt_failed", message: `importHistory target "${scope}" ${when === "before" ? "has" : "came to have"} ${emptiness.what} — a scope whose history cannot be judged is not an empty target; nothing was ${when === "before" ? "fetched or " : ""}published`, ...emptiness.fault } };
532
+ }
533
+ const existing = await this.exportHistory(scope);
534
+ if (existing.ok && existing.history !== null && fileHistoryExportDigest(existing.history) === fileHistoryExportDigest(data))
535
+ return "identical";
536
+ const message = when === "before"
537
+ ? `importHistory target "${scope}" was judged not empty and not this envelope — it holds a DIFFERENT history, or it changed (or could not be read) between the two reads that judge it — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)`
538
+ : `importHistory target "${scope}" changed while this import was fetching blobs — it gained a DIFFERENT history, or changed (or could not be read) between the two reads that judge it — nothing was published`;
539
+ return { ok: false, error: { code: "conflict", message } };
518
540
  }
519
541
  publishStagedScopeSync(scope, staging) {
520
- if (!this.scopeIsEmptySync(scope))
521
- return "not_empty";
522
- const target = this.scopeDir(scope);
523
- const destroyed = [];
524
- for (const f of (() => {
525
- try {
526
- return readdirSync(this.boundaryDir(scope));
527
- }
528
- catch {
529
- return [];
530
- }
531
- })()) {
532
- if (f.endsWith(".json"))
533
- destroyed.push(f.slice(0, -".json".length));
534
- }
535
- if (!this.markReaped(scope, destroyed))
536
- return "ledger_failed";
537
- safeRm(target);
538
- renameSync(staging, target);
542
+ const emptiness = this.scopeIsEmptySync(scope);
543
+ if (emptiness !== "empty")
544
+ return emptiness;
545
+ safeRm(this.scopeDir(scope));
546
+ renameSync(staging, this.scopeDir(scope));
539
547
  return "published";
540
548
  }
541
549
  listBoundaryIds(scope) {
542
550
  const dir = this.boundaryDir(scope);
543
- let entries;
551
+ let names;
544
552
  try {
545
- entries = readdirSync(dir);
553
+ names = readdirSync(dir).filter((f) => f.endsWith(".json"));
546
554
  }
547
555
  catch (err) {
548
- if (err.code !== "ENOENT") {
549
- this.disclose(dir, `boundary directory unreadable (${err.code ?? "unknown"}) — listed as no boundaries`);
550
- }
551
- return [];
552
- }
553
- return entries
554
- .filter((f) => f.endsWith(".json"))
555
- .filter((f) => this.readBoundary(join(dir, f)) !== null)
556
- .map((f) => f.slice(0, -".json".length));
556
+ if (err.code === "ENOENT")
557
+ return [];
558
+ this.disclose(dir, `boundary directory unreadable (${err.code ?? "unknown"}) — this scope's history cannot be judged until it can be listed`);
559
+ return { fault: caughtFault(err), what: "a boundary directory that cannot be listed" };
560
+ }
561
+ const ids = [];
562
+ const unusable = [];
563
+ const faults = [];
564
+ for (const f of names) {
565
+ const read = this.readBoundaryState(join(dir, f));
566
+ (read instanceof Map ? ids : unusable).push(f.slice(0, -".json".length));
567
+ if (!(read instanceof Map))
568
+ faults.push(read ?? {});
569
+ }
570
+ if (unusable.length === 0)
571
+ return ids;
572
+ return { fault: worstFault(faults), what: `boundaries that cannot be used (${unusable.slice(0, 3).map((i) => JSON.stringify(i)).join(", ")}${unusable.length > 3 ? ", …" : ""})` };
557
573
  }
558
574
  async snapshot(scope, entryId, env, root, signal) {
559
575
  const bpath = this.tryBoundaryPath(scope, entryId);
@@ -562,8 +578,8 @@ export class FileFileHistoryStore {
562
578
  if (this.readBoundary(bpath) !== null)
563
579
  return { ok: true };
564
580
  const consumed = this.readReapedIds(scope);
565
- if (consumed === null) {
566
- return { ok: false, error: { code: "snapshot_failed", message: `the consumed-entry ledger for scope "${scope}" cannot be read — refusing to mint a boundary while it is impossible to tell a fresh entry id from one retention already reaped` } };
581
+ if (!(consumed instanceof Set)) {
582
+ return { ok: false, error: { code: "snapshot_failed", message: `the consumed-entry ledger for scope "${scope}" cannot be read — refusing to mint a boundary while it is impossible to tell a fresh entry id from one retention already reaped`, ...consumed } };
567
583
  }
568
584
  if (consumed.has(entryId)) {
569
585
  return { ok: false, error: { code: "snapshot_failed", message: `entry "${entryId}" already had a boundary and it was reaped by retention — a fresh capture now would record post-turn bytes under a pre-turn key, so this entry has no boundary and cannot get one back` } };
@@ -582,7 +598,7 @@ export class FileFileHistoryStore {
582
598
  const state = this.readTrackedState(scope);
583
599
  if (state.corrupt) {
584
600
  nestedSetAdd(this.spentAttempts, scope, entryId);
585
- return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" is unreadable — a boundary minted over an unusable graph would claim to restore state that is not there` } };
601
+ return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" is unreadable — a boundary minted over an unusable graph would claim to restore state that is not there`, ...state.fault } };
586
602
  }
587
603
  const tracked = state.tracked;
588
604
  const staged = new Map();
@@ -628,7 +644,7 @@ export class FileFileHistoryStore {
628
644
  const currentState = this.readTrackedState(scope);
629
645
  if (currentState.corrupt) {
630
646
  nestedSetAdd(this.spentAttempts, scope, entryId);
631
- return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" became unreadable during the capture — no boundary was published` } };
647
+ return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" became unreadable during the capture — no boundary was published`, ...currentState.fault } };
632
648
  }
633
649
  const current = currentState.tracked;
634
650
  for (const [key, cap] of staged) {
@@ -663,9 +679,10 @@ export class FileFileHistoryStore {
663
679
  catch (err) {
664
680
  if (err.code !== "EEXIST")
665
681
  throw err;
666
- if (this.readBoundary(bpath) === null) {
682
+ const existing = this.readBoundaryState(bpath);
683
+ if (!(existing instanceof Map)) {
667
684
  nestedSetAdd(this.spentAttempts, scope, entryId);
668
- return { ok: false, error: { code: "snapshot_failed", message: `a boundary file for entry "${entryId}" already exists but cannot be read — this turn has no usable boundary (the unreadable file is left in place for an operator rather than silently replaced)` } };
685
+ return { ok: false, error: { code: "snapshot_failed", message: `a boundary file for entry "${entryId}" already exists but cannot be read — this turn has no usable boundary (the unreadable file is left in place for an operator rather than silently replaced)`, ...existing } };
669
686
  }
670
687
  }
671
688
  this.applyBoundaryRetention(scope, entryId);
@@ -673,7 +690,7 @@ export class FileFileHistoryStore {
673
690
  }
674
691
  catch (err) {
675
692
  nestedSetAdd(this.spentAttempts, scope, entryId);
676
- return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${thrownText(err)}` } };
693
+ return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${thrownText(err)}`, ...caughtFault(err) } };
677
694
  }
678
695
  finally {
679
696
  this.activeCaptures.get(scope)?.delete(entryId);
@@ -685,13 +702,16 @@ export class FileFileHistoryStore {
685
702
  let modeNotPreserved = 0;
686
703
  try {
687
704
  const bpath = this.tryBoundaryPath(scope, entryId);
688
- const boundary = bpath === null ? null : this.readBoundary(bpath);
705
+ const boundary = bpath === null ? null : this.readBoundaryState(bpath);
689
706
  if (boundary === null) {
690
707
  return { outcome: "failed", ...ledger, error: { code: "not_found", message: `no history boundary for entry "${entryId}"` } };
691
708
  }
709
+ if (!(boundary instanceof Map)) {
710
+ return { outcome: "failed", ...ledger, error: { code: "restore_failed", message: `the history boundary for entry "${entryId}" is there but cannot be used — NOTHING was restored`, ...boundary } };
711
+ }
692
712
  const state = this.readTrackedState(scope);
693
713
  if (state.corrupt) {
694
- return { outcome: "failed", ...ledger, error: { code: "restore_failed", message: `file history for scope "${scope}" is unreadable — the boundary exists but its version records cannot be read, so NOTHING was restored` } };
714
+ return { outcome: "failed", ...ledger, error: { code: "restore_failed", message: `file history for scope "${scope}" is unreadable — the boundary exists but its version records cannot be read, so NOTHING was restored`, ...state.fault } };
695
715
  }
696
716
  const tracked = state.tracked;
697
717
  for (const key of [...tracked.keys()].sort()) {
@@ -767,7 +787,7 @@ export class FileFileHistoryStore {
767
787
  }
768
788
  catch (err) {
769
789
  const touched = ledger.filesChanged.length > 0 || ledger.identicalSkipped.length > 0 || ledger.refused.length > 0 || ledger.failed.length > 0;
770
- return { outcome: touched ? "partial" : "failed", ...ledger, error: { code: "restore_failed", message: thrownText(err) } };
790
+ return { outcome: touched ? "partial" : "failed", ...ledger, error: { code: "restore_failed", message: thrownText(err), ...caughtFault(err) } };
771
791
  }
772
792
  }
773
793
  async canRestore(scope, entryId) {
@@ -854,7 +874,7 @@ export class FileFileHistoryStore {
854
874
  const id = file.slice(0, -".json".length);
855
875
  if (keep.has(id))
856
876
  continue;
857
- if (!this.markReaped(scope, [id])) {
877
+ if (this.markReaped(scope, [id]) !== true) {
858
878
  survivors.add(id);
859
879
  continue;
860
880
  }
@@ -1006,28 +1026,25 @@ export class FileFileHistoryStore {
1006
1026
  try {
1007
1027
  if (fromScope === toScope)
1008
1028
  return { ok: true };
1009
- const targetState = this.readTrackedState(toScope);
1010
- if (targetState.corrupt) {
1011
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope target "${toScope}" has an unreadable tracked set — a scope whose bytes cannot be judged is not an empty target` } };
1012
- }
1013
- const targetTracked = targetState.tracked;
1014
- const targetBoundaries = this.listBoundaryIds(toScope);
1015
- if (targetTracked.size > 0 || targetBoundaries.length > 0) {
1029
+ const target = this.scopeIsEmptySync(toScope);
1030
+ if (target === "not_empty") {
1016
1031
  return { ok: false, error: { code: "conflict", message: `adoptScope target "${toScope}" already has history of its own — adoption is legal only onto an EMPTY scope (the fork/resume boundary before the new session's first turn)` } };
1017
1032
  }
1033
+ if (target !== "empty")
1034
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope target "${toScope}" has ${target.what} — a scope whose history cannot be judged is not an empty target; nothing was published`, ...target.fault } };
1018
1035
  const sourceState = this.readTrackedState(fromScope);
1019
1036
  if (sourceState.corrupt) {
1020
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has an unreadable tracked set — adopting it would publish boundaries with no version records behind them` } };
1037
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has an unreadable tracked set — adopting it would publish boundaries with no version records behind them`, ...sourceState.fault } };
1021
1038
  }
1022
1039
  const source = sourceState.tracked;
1023
- const unjudgeable = this.unjudgeableBoundaryIdsOf(fromScope);
1024
- if (unjudgeable !== null) {
1025
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has ${unjudgeable.length > 0 ? `boundaries that cannot be judged (${unjudgeable.slice(0, 3).map((i) => JSON.stringify(i)).join(", ")}${unjudgeable.length > 3 ? ", …" : ""})` : "a boundary directory that cannot be listed"} — adopting it would publish a graph that silently drops those turns and hands their entry ids back to the fork; nothing was published` } };
1026
- }
1027
1040
  const sourceBoundaries = this.listBoundaryIds(fromScope);
1041
+ if (!Array.isArray(sourceBoundaries)) {
1042
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has ${sourceBoundaries.what} — adopting it would publish a graph that silently drops those turns and hands their entry ids back to the fork; nothing was published`, ...sourceBoundaries.fault } };
1043
+ }
1028
1044
  if (source.size === 0 && sourceBoundaries.length === 0) {
1029
- if (!this.carryConsumedIds(fromScope, toScope)) {
1030
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — refusing rather than reporting a carry that would let an already-consumed entry id be minted again in the fork` } };
1045
+ const carried = this.carryConsumedIds(fromScope, toScope);
1046
+ if (carried !== true) {
1047
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — refusing rather than reporting a carry that would let an already-consumed entry id be minted again in the fork`, ...carried } };
1031
1048
  }
1032
1049
  return { ok: true };
1033
1050
  }
@@ -1035,15 +1052,16 @@ export class FileFileHistoryStore {
1035
1052
  for (const v of rec.versions) {
1036
1053
  if (v.blobHash === null)
1037
1054
  continue;
1038
- let bytes;
1055
+ let fault;
1039
1056
  try {
1040
- bytes = readFileSync(this.blobPath(v.blobHash));
1057
+ if (createHash("sha256").update(readFileSync(this.blobPath(v.blobHash))).digest("hex") !== v.blobHash)
1058
+ fault = DAMAGED_RECORD;
1041
1059
  }
1042
- catch {
1043
- bytes = undefined;
1060
+ catch (err) {
1061
+ fault = err.code === "ENOENT" ? DAMAGED_RECORD : caughtFault(err);
1044
1062
  }
1045
- if (bytes === undefined || createHash("sha256").update(bytes).digest("hex") !== v.blobHash) {
1046
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" references blob ${v.blobHash} for ${key}@v${v.version}, which is missing or fails content-address integrity — nothing was published` } };
1063
+ if (fault !== undefined) {
1064
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" references blob ${v.blobHash} for ${key}@v${v.version}, which is missing or fails content-address integrity — nothing was published`, ...fault } };
1047
1065
  }
1048
1066
  }
1049
1067
  }
@@ -1068,16 +1086,17 @@ export class FileFileHistoryStore {
1068
1086
  const bdoc = { formatVersion: "file-history/1", files: [...mapping], digest: boundaryDigest([...mapping]), ...(sourceCommittedAt !== undefined ? { committedAt: sourceCommittedAt } : {}) };
1069
1087
  atomicWriteFile(join(this.base, "tmp"), join(staging, "boundaries", `${historyPathComponent(id)}.json`), JSON.stringify(bdoc));
1070
1088
  }
1071
- if (!this.carryConsumedIds(fromScope, toScope)) {
1089
+ const carried = this.carryConsumedIds(fromScope, toScope);
1090
+ if (carried !== true) {
1072
1091
  safeRm(staging);
1073
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — nothing was published, because a fork missing its lineage's tombstones can mint an already-consumed entry id` } };
1092
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — nothing was published, because a fork missing its lineage's tombstones can mint an already-consumed entry id`, ...carried } };
1074
1093
  }
1075
1094
  const published = this.publishStagedScopeSync(toScope, staging);
1076
1095
  if (published !== "published") {
1077
1096
  safeRm(staging);
1078
1097
  return published === "not_empty"
1079
1098
  ? { ok: false, error: { code: "conflict", message: `adoptScope target "${toScope}" gained history of its own while this adoption was being staged — nothing was published` } }
1080
- : { ok: false, error: { code: "adopt_failed", message: `adoptScope could not record the consumed-entry tombstones for target "${toScope}"'s unreadable boundary residue — nothing was published, because replacing records nothing remembers would hand their already-consumed entry ids back to the fork` } };
1099
+ : { ok: false, error: { code: "adopt_failed", message: `adoptScope target "${toScope}" came to have ${published.what} while this adoption was being staged — a scope whose history cannot be judged is not an empty target; nothing was published`, ...published.fault } };
1081
1100
  }
1082
1101
  }
1083
1102
  catch (err) {
@@ -1087,27 +1106,29 @@ export class FileFileHistoryStore {
1087
1106
  return { ok: true };
1088
1107
  }
1089
1108
  catch (err) {
1090
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${thrownText(err)}` } };
1109
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${thrownText(err)}`, ...caughtFault(err) } };
1091
1110
  }
1092
1111
  }
1093
1112
  async exportHistory(scope) {
1113
+ const unexportable = (what, fault) => ({ ok: false, error: { code: "adopt_failed", message: `exportHistory scope "${scope}" has ${what} — its history cannot be exported whole, so nothing was exported`, ...fault } });
1094
1114
  const state = this.readTrackedState(scope);
1095
- if (state.corrupt) {
1096
- return null;
1097
- }
1115
+ if (state.corrupt)
1116
+ return unexportable("an unreadable tracked set", state.fault);
1098
1117
  const tracked = state.tracked;
1099
1118
  const boundaryIds = this.listBoundaryIds(scope);
1119
+ if (!Array.isArray(boundaryIds))
1120
+ return unexportable(boundaryIds.what, boundaryIds.fault);
1100
1121
  if (tracked.size === 0 && boundaryIds.length === 0)
1101
- return null;
1122
+ return { ok: true, history: null };
1102
1123
  const boundaries = [];
1103
1124
  for (const entryId of [...boundaryIds].sort()) {
1104
1125
  const bp = this.tryBoundaryPath(scope, entryId);
1105
1126
  const mapping = bp === null ? null : this.readBoundary(bp);
1106
1127
  if (mapping === null)
1107
- continue;
1128
+ return unexportable(`a boundary ("${entryId}") that stopped being usable while it was exported`, {});
1108
1129
  boundaries.push({ entryId, files: [...mapping.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([relOrAbsPath, version]) => ({ relOrAbsPath, version })) });
1109
1130
  }
1110
- return {
1131
+ const history = {
1111
1132
  formatVersion: "file-history/1",
1112
1133
  tracked: [...tracked.entries()]
1113
1134
  .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
@@ -1120,20 +1141,17 @@ export class FileFileHistoryStore {
1120
1141
  })),
1121
1142
  boundaries,
1122
1143
  };
1144
+ return { ok: true, history };
1123
1145
  }
1124
1146
  async importHistory(scope, data, srcGetBlob) {
1125
1147
  try {
1126
1148
  const shaped = validateFileHistoryExport(data);
1127
1149
  if (!shaped.ok)
1128
1150
  return shaped;
1129
- const existing = await this.exportHistory(scope);
1130
- let identicalRepublish = false;
1131
- if (existing !== null) {
1132
- if (fileHistoryExportDigest(existing) === fileHistoryExportDigest(data))
1133
- identicalRepublish = true;
1134
- else
1135
- return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" already holds a DIFFERENT history — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)` } };
1136
- }
1151
+ const target = await this.importTargetOf(scope, data, "before");
1152
+ if (typeof target === "object")
1153
+ return target;
1154
+ const identicalRepublish = target === "identical";
1137
1155
  const importJournal = join(this.scopeDir(scope), "staging", `import-${Date.now().toString(36)}-${randomBytes(6).toString("hex")}.jsonl`);
1138
1156
  ensureDir(join(this.scopeDir(scope), "staging"));
1139
1157
  appendFileSync(importJournal, `${JSON.stringify({ entryId: `import:${scope}`, pid: process.pid, startedAt: new Date().toISOString() })}\n`);
@@ -1147,7 +1165,7 @@ export class FileFileHistoryStore {
1147
1165
  return await this.runImport(scope, data, srcGetBlob, importJournal, identicalRepublish);
1148
1166
  }
1149
1167
  catch (err) {
1150
- return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}` } };
1168
+ return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
1151
1169
  }
1152
1170
  }
1153
1171
  async runImport(scope, data, srcGetBlob, importJournal, identicalRepublish) {
@@ -1174,10 +1192,10 @@ export class FileFileHistoryStore {
1174
1192
  bytes = await srcGetBlob(v.blobHash);
1175
1193
  }
1176
1194
  catch (err) {
1177
- return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fetch failed: ${thrownText(err)}` } };
1195
+ return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fetch failed: ${thrownText(err)}`, ...caughtFault(err) } };
1178
1196
  }
1179
1197
  if (bytes === undefined)
1180
- return { ok: false, error: { code: "not_found", message: `source blob ${v.blobHash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
1198
+ return { ok: false, error: { code: "adopt_failed", message: `source blob ${v.blobHash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
1181
1199
  if (createHash("sha256").update(bytes).digest("hex") !== v.blobHash) {
1182
1200
  return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fails content-address integrity — refused, nothing published` } };
1183
1201
  }
@@ -1186,12 +1204,11 @@ export class FileFileHistoryStore {
1186
1204
  }
1187
1205
  if (identicalRepublish)
1188
1206
  return { ok: true };
1189
- const now = await this.exportHistory(scope);
1190
- if (now !== null) {
1191
- if (fileHistoryExportDigest(now) === fileHistoryExportDigest(data))
1192
- return { ok: true };
1193
- return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" gained a DIFFERENT history while this import was fetching blobs — nothing was published` } };
1194
- }
1207
+ const now = await this.importTargetOf(scope, data, "after");
1208
+ if (now === "identical")
1209
+ return { ok: true };
1210
+ if (now !== "empty")
1211
+ return now;
1195
1212
  const staging = `${this.scopeDir(scope)}.importing-${process.pid}-${randomBytes(6).toString("hex")}`;
1196
1213
  safeRm(staging);
1197
1214
  try {
@@ -1218,13 +1235,13 @@ export class FileFileHistoryStore {
1218
1235
  }
1219
1236
  catch (err) {
1220
1237
  safeRm(staging);
1221
- return { ok: false, error: { code: "adopt_failed", message: `importHistory could not publish the staged graph for "${scope}": ${thrownText(err)} — nothing was published (a local storage fault, not a malformed envelope)` } };
1238
+ return { ok: false, error: { code: "adopt_failed", message: `importHistory could not publish the staged graph for "${scope}": ${thrownText(err)} — nothing was published (a local storage fault, not a malformed envelope)`, ...caughtFault(err) } };
1222
1239
  }
1223
1240
  if (published !== "published") {
1224
1241
  safeRm(staging);
1225
1242
  return published === "not_empty"
1226
1243
  ? { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" gained history of its own while this import was being staged — nothing was published` } }
1227
- : { ok: false, error: { code: "adopt_failed", message: `importHistory could not record the consumed-entry tombstones for target "${scope}"'s unreadable boundary residue — nothing was published, because replacing records nothing remembers would hand their already-consumed entry ids back to this scope` } };
1244
+ : { ok: false, error: { code: "adopt_failed", message: `importHistory target "${scope}" came to have ${published.what} while this import was being staged — a scope whose history cannot be judged is not an empty target; nothing was published`, ...published.fault } };
1228
1245
  }
1229
1246
  }
1230
1247
  catch (err) {
@@ -1234,7 +1251,7 @@ export class FileFileHistoryStore {
1234
1251
  return { ok: true };
1235
1252
  }
1236
1253
  catch (err) {
1237
- return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}` } };
1254
+ return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
1238
1255
  }
1239
1256
  finally {
1240
1257
  safeRm(importJournal);