@sema-agent/core 7.48.0 → 7.49.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 (418) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/README.md +2 -2
  3. package/dist/agents/cascade.d.ts +2 -5
  4. package/dist/agents/child-failure.d.ts +20 -0
  5. package/dist/agents/child-failure.js +28 -0
  6. package/dist/agents/cross-session-ref.d.ts +11 -3
  7. package/dist/agents/cross-session-ref.js +6 -0
  8. package/dist/agents/cumulative-stats.d.ts +12 -15
  9. package/dist/agents/cumulative-stats.js +1 -1
  10. package/dist/agents/extra-tool-names.js +1 -1
  11. package/dist/agents/inherited-tool-pool.d.ts +17 -13
  12. package/dist/agents/inherited-tool-pool.js +4 -3
  13. package/dist/agents/peer-session-drain.d.ts +3 -2
  14. package/dist/agents/peer-session-drain.js +5 -4
  15. package/dist/agents/send-message-tool.js +2 -9
  16. package/dist/agents/subagent.d.ts +10 -29
  17. package/dist/agents/subagent.js +65 -53
  18. package/dist/agents/suspend-guard.d.ts +1 -1
  19. package/dist/agents/tool-filter.d.ts +76 -65
  20. package/dist/agents/tool-filter.js +122 -92
  21. package/dist/agents/verify.d.ts +4 -7
  22. package/dist/agents/verify.js +6 -7
  23. package/dist/bench/metrics.d.ts +1 -2
  24. package/dist/brain/anthropic.js +133 -79
  25. package/dist/brain/errors.d.ts +53 -6
  26. package/dist/brain/errors.js +22 -2
  27. package/dist/brain/input-too-long.d.ts +40 -19
  28. package/dist/brain/input-too-long.js +3 -16
  29. package/dist/brain/open-responses.js +19 -10
  30. package/dist/brain/openai.js +123 -32
  31. package/dist/brain/provider-dialect.d.ts +214 -0
  32. package/dist/brain/provider-dialect.js +186 -0
  33. package/dist/brain/request-params.d.ts +27 -8
  34. package/dist/brain/request-params.js +7 -12
  35. package/dist/brain/request-route.d.ts +84 -0
  36. package/dist/brain/request-route.js +41 -0
  37. package/dist/brain/retry.d.ts +8 -0
  38. package/dist/brain/retry.js +10 -4
  39. package/dist/brain/route-adjudicator.d.ts +7 -0
  40. package/dist/brain/route-adjudicator.js +5 -1
  41. package/dist/brain/status-sink.d.ts +20 -0
  42. package/dist/brain/status-sink.js +11 -0
  43. package/dist/brain/stream-engine.d.ts +52 -1
  44. package/dist/brain/stream-engine.js +81 -24
  45. package/dist/brain/stream-shared.d.ts +57 -11
  46. package/dist/brain/stream-shared.js +28 -2
  47. package/dist/core/a2a.d.ts +1 -0
  48. package/dist/core/agent-config-dir.js +3 -2
  49. package/dist/core/allow-entry.d.ts +21 -12
  50. package/dist/core/allow-entry.js +38 -31
  51. package/dist/core/arg-summary.d.ts +17 -24
  52. package/dist/core/arg-summary.js +169 -68
  53. package/dist/core/ask-origin.d.ts +42 -24
  54. package/dist/core/ask-origin.js +18 -13
  55. package/dist/core/auto-mode-prompt-assets.d.ts +2 -2
  56. package/dist/core/auto-mode-prompt-assets.js +1 -1
  57. package/dist/core/auto-mode-prompt.d.ts +11 -1
  58. package/dist/core/auto-mode-prompt.js +13 -10
  59. package/dist/core/auto-mode-rebuild.d.ts +7 -0
  60. package/dist/core/auto-mode-rebuild.js +4 -1
  61. package/dist/core/canonical-json.d.ts +3 -0
  62. package/dist/core/canonical-json.js +2 -1
  63. package/dist/core/checkpoint-requirements.d.ts +892 -0
  64. package/dist/core/checkpoint-requirements.js +333 -0
  65. package/dist/core/checkpoint-store.d.ts +94 -65
  66. package/dist/core/checkpoint-store.js +14 -26
  67. package/dist/core/child-clamp-seats.d.ts +228 -69
  68. package/dist/core/child-clamp-seats.js +211 -17
  69. package/dist/core/classifier-unavailable-arm.d.ts +5 -1
  70. package/dist/core/classifier-unavailable-arm.js +3 -3
  71. package/dist/core/compliance.d.ts +3 -0
  72. package/dist/core/compliance.js +9 -4
  73. package/dist/core/credential-vocabulary.d.ts +140 -0
  74. package/dist/core/credential-vocabulary.js +131 -0
  75. package/dist/core/decision-receipt.js +1 -1
  76. package/dist/core/effective-path-target.d.ts +73 -16
  77. package/dist/core/effective-path-target.js +63 -33
  78. package/dist/core/engine-notice.d.ts +32 -7
  79. package/dist/core/file-history-store.js +2 -2
  80. package/dist/core/fs-write-gate-policy.d.ts +4 -0
  81. package/dist/core/fs-write-gate-policy.js +22 -5
  82. package/dist/core/gate-bounded-await.d.ts +28 -13
  83. package/dist/core/gate-bounded-await.js +41 -4
  84. package/dist/core/gate-fold.d.ts +1 -1
  85. package/dist/core/gate-fold.js +24 -25
  86. package/dist/core/gate-lanes.js +16 -15
  87. package/dist/core/gate-outcome.d.ts +20 -5
  88. package/dist/core/gate-outcome.js +5 -0
  89. package/dist/core/governance-codes.d.ts +11 -4
  90. package/dist/core/governance-codes.js +14 -3
  91. package/dist/core/hooks.d.ts +4 -0
  92. package/dist/core/locked-config.d.ts +8 -0
  93. package/dist/core/locked-config.js +14 -2
  94. package/dist/core/lsp.js +3 -5
  95. package/dist/core/mailbox-store.d.ts +3 -4
  96. package/dist/core/mcp-connection-pool.d.ts +75 -0
  97. package/dist/core/mcp-connection-pool.js +128 -0
  98. package/dist/core/mcp-failure.d.ts +2 -1
  99. package/dist/core/mcp-failure.js +13 -2
  100. package/dist/core/mcp.d.ts +4 -0
  101. package/dist/core/mcp.js +70 -43
  102. package/dist/core/memory-engine/content-origin.js +2 -14
  103. package/dist/core/memory-engine/engine.d.ts +32 -22
  104. package/dist/core/memory-engine/engine.js +103 -55
  105. package/dist/core/memory-engine/file-backend.d.ts +13 -30
  106. package/dist/core/memory-engine/file-backend.js +37 -252
  107. package/dist/core/memory-engine/index.d.ts +1 -1
  108. package/dist/core/memory-engine/index.js +1 -1
  109. package/dist/core/memory-engine/layout.d.ts +139 -9
  110. package/dist/core/memory-engine/layout.js +350 -7
  111. package/dist/core/memory-engine/scope-contract.d.ts +10 -0
  112. package/dist/core/memory-engine/scope-contract.js +8 -0
  113. package/dist/core/memory-engine/session-incarnation.d.ts +14 -4
  114. package/dist/core/memory-engine/session-incarnation.js +5 -2
  115. package/dist/core/memory-engine/types.d.ts +10 -1
  116. package/dist/core/memory.js +5 -17
  117. package/dist/core/model-seat.d.ts +4 -4
  118. package/dist/core/model-seat.js +1 -1
  119. package/dist/core/mounted-mcp.d.ts +57 -0
  120. package/dist/core/mounted-mcp.js +19 -0
  121. package/dist/core/namespaced-rule-name.d.ts +29 -0
  122. package/dist/core/namespaced-rule-name.js +52 -0
  123. package/dist/core/park-selfcheck.d.ts +1 -1
  124. package/dist/core/park-selfcheck.js +16 -6
  125. package/dist/core/path-guard-rows.d.ts +99 -0
  126. package/dist/core/path-guard-rows.js +45 -0
  127. package/dist/core/path-identity.d.ts +22 -16
  128. package/dist/core/path-identity.js +10 -10
  129. package/dist/core/path-rule-binding.d.ts +2 -1
  130. package/dist/core/path-rule-binding.js +3 -1
  131. package/dist/core/pause-registry.d.ts +16 -2
  132. package/dist/core/pause-registry.js +6 -0
  133. package/dist/core/permission-rule-consent.js +0 -7
  134. package/dist/core/permission-rule-model.d.ts +68 -24
  135. package/dist/core/permission-rule-model.js +105 -24
  136. package/dist/core/permission-rule-store.js +2 -2
  137. package/dist/core/permission-rules.d.ts +3 -30
  138. package/dist/core/permission-rules.js +4 -54
  139. package/dist/core/read-root-candidate.d.ts +1 -3
  140. package/dist/core/read-root-candidate.js +4 -15
  141. package/dist/core/real-approval.d.ts +4 -3
  142. package/dist/core/retired-keys.d.ts +13 -0
  143. package/dist/core/retired-keys.js +14 -0
  144. package/dist/core/roles.d.ts +2 -3
  145. package/dist/core/roles.js +15 -6
  146. package/dist/core/runner/assemble-result.d.ts +0 -7
  147. package/dist/core/runner/assemble-result.js +19 -31
  148. package/dist/core/runner/caller-roster.d.ts +78 -0
  149. package/dist/core/runner/caller-roster.js +219 -0
  150. package/dist/core/runner/checkpoint-scope.d.ts +16 -16
  151. package/dist/core/runner/checkpoint-scope.js +2 -2
  152. package/dist/core/runner/child-tree.js +2 -2
  153. package/dist/core/runner/contracts.d.ts +68 -35
  154. package/dist/core/runner/decide-continuation.d.ts +51 -3
  155. package/dist/core/runner/decide-continuation.js +76 -21
  156. package/dist/core/runner/denial-limit-arms.d.ts +17 -5
  157. package/dist/core/runner/denial-limit-arms.js +11 -9
  158. package/dist/core/runner/extra-body-dropped-seat.d.ts +8 -0
  159. package/dist/core/runner/extra-body-dropped-seat.js +22 -0
  160. package/dist/core/runner/finish-reason-seat.d.ts +9 -0
  161. package/dist/core/runner/finish-reason-seat.js +40 -0
  162. package/dist/core/runner/gate-exit.d.ts +44 -32
  163. package/dist/core/runner/gate-exit.js +112 -25
  164. package/dist/core/runner/inherited-ask-grants.js +6 -4
  165. package/dist/core/runner/mcp-redial.d.ts +3 -3
  166. package/dist/core/runner/mcp-redial.js +3 -4
  167. package/dist/core/runner/permission-rule-lanes.d.ts +1 -2
  168. package/dist/core/runner/permission-rule-lanes.js +5 -4
  169. package/dist/core/runner/prepare-artifact.d.ts +4 -0
  170. package/dist/core/runner/prepare-artifact.js +2 -1
  171. package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
  172. package/dist/core/runner/prepare-ask-lane.js +12 -6
  173. package/dist/core/runner/prepare-boundary-parks.d.ts +5 -8
  174. package/dist/core/runner/prepare-boundary-parks.js +4 -7
  175. package/dist/core/runner/prepare-caps-and-workflow.d.ts +11 -15
  176. package/dist/core/runner/prepare-caps-and-workflow.js +19 -27
  177. package/dist/core/runner/prepare-config-doors.d.ts +9 -9
  178. package/dist/core/runner/prepare-config-doors.js +10 -5
  179. package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
  180. package/dist/core/runner/prepare-defer-classify.js +5 -3
  181. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
  182. package/dist/core/runner/prepare-delegation-surface.js +3 -3
  183. package/dist/core/runner/prepare-gate-stations.d.ts +4 -4
  184. package/dist/core/runner/prepare-gate-stations.js +9 -7
  185. package/dist/core/runner/prepare-hands-readface.d.ts +7 -7
  186. package/dist/core/runner/prepare-hands-readface.js +7 -7
  187. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -6
  188. package/dist/core/runner/prepare-inherited-gate.js +14 -15
  189. package/dist/core/runner/prepare-listings.d.ts +7 -24
  190. package/dist/core/runner/prepare-listings.js +4 -5
  191. package/dist/core/runner/prepare-memory-engine-session.d.ts +2 -0
  192. package/dist/core/runner/prepare-memory-engine-session.js +4 -3
  193. package/dist/core/runner/prepare-memory.d.ts +6 -0
  194. package/dist/core/runner/prepare-memory.js +13 -13
  195. package/dist/core/runner/prepare-offload-wrappers.d.ts +5 -0
  196. package/dist/core/runner/prepare-offload-wrappers.js +3 -3
  197. package/dist/core/runner/prepare-park-ask.d.ts +7 -8
  198. package/dist/core/runner/prepare-park-ask.js +17 -10
  199. package/dist/core/runner/prepare-policy-chain.js +24 -44
  200. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  201. package/dist/core/runner/prepare-project-context.js +3 -4
  202. package/dist/core/runner/prepare-protocol-tools.d.ts +9 -3
  203. package/dist/core/runner/prepare-protocol-tools.js +3 -4
  204. package/dist/core/runner/prepare-run-refs.d.ts +3 -0
  205. package/dist/core/runner/prepare-run-refs.js +2 -1
  206. package/dist/core/runner/prepare-safety-scan.d.ts +4 -8
  207. package/dist/core/runner/prepare-safety-scan.js +1 -2
  208. package/dist/core/runner/prepare-suspend-saga.d.ts +7 -1
  209. package/dist/core/runner/prepare-suspend-saga.js +28 -12
  210. package/dist/core/runner/prepare-task.d.ts +1 -1
  211. package/dist/core/runner/prepare-task.js +67 -56
  212. package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -10
  213. package/dist/core/runner/prepare-wiring-manifest.js +5 -8
  214. package/dist/core/runner/resume-apply.js +7 -14
  215. package/dist/core/runner/resume-checkpoint-screen.js +11 -4
  216. package/dist/core/runner/resume-internals-and-config.js +2 -2
  217. package/dist/core/runner/resume-policy-outcome.js +5 -32
  218. package/dist/core/runner/resume-review-outcome.js +4 -1
  219. package/dist/core/runner/retired-runner-deps.d.ts +6 -0
  220. package/dist/core/runner/retired-runner-deps.js +10 -1
  221. package/dist/core/runner/run-brain-sinks.d.ts +4 -0
  222. package/dist/core/runner/run-brain-sinks.js +5 -2
  223. package/dist/core/runner/run-harness-handlers.js +27 -34
  224. package/dist/core/runner/run-leg.d.ts +0 -2
  225. package/dist/core/runner/run-leg.js +32 -33
  226. package/dist/core/runner/run-terminal-adoption.js +4 -3
  227. package/dist/core/runner/runtask.js +8 -8
  228. package/dist/core/runner/session-rule-policy.js +5 -5
  229. package/dist/core/runner/stream-settle-backstop.js +7 -4
  230. package/dist/core/runner/sub-agent-deny-lists.d.ts +51 -12
  231. package/dist/core/runner/sub-agent-deny-lists.js +111 -80
  232. package/dist/core/runner/terminal-account.d.ts +48 -0
  233. package/dist/core/runner/terminal-account.js +186 -0
  234. package/dist/core/runner/tool-end-body.d.ts +54 -17
  235. package/dist/core/runner/tool-end-body.js +27 -2
  236. package/dist/core/runner/tool-face-overlay.d.ts +3 -1
  237. package/dist/core/runner/tool-face-overlay.js +6 -4
  238. package/dist/core/runner/tool-occurrence.d.ts +106 -0
  239. package/dist/core/runner/tool-occurrence.js +123 -0
  240. package/dist/core/runner/tool-progress-lane.d.ts +16 -8
  241. package/dist/core/runner/tool-progress-lane.js +25 -34
  242. package/dist/core/runner/turn-attachments.d.ts +1 -1
  243. package/dist/core/runner/write-protection-station.d.ts +21 -9
  244. package/dist/core/runner/write-protection-station.js +20 -17
  245. package/dist/core/runner-deps.d.ts +12 -22
  246. package/dist/core/secret-env.d.ts +16 -5
  247. package/dist/core/secret-env.js +23 -63
  248. package/dist/core/sensitive-path-policy.d.ts +19 -18
  249. package/dist/core/sensitive-path-policy.js +73 -49
  250. package/dist/core/session-reconcile.d.ts +3 -0
  251. package/dist/core/session-reconcile.js +5 -4
  252. package/dist/core/session-store.d.ts +5 -0
  253. package/dist/core/session-store.js +12 -0
  254. package/dist/core/session.d.ts +13 -0
  255. package/dist/core/shared-memory/normalize.d.ts +17 -4
  256. package/dist/core/shared-memory/normalize.js +7 -7
  257. package/dist/core/shell-lexer.js +5 -4
  258. package/dist/core/skill-grant.js +1 -1
  259. package/dist/core/store-contracts/checkpoint-store-contract.js +13 -0
  260. package/dist/core/store-contracts/mailbox-store-contract.d.ts +7 -14
  261. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +27 -0
  262. package/dist/core/store-contracts/session-store-incarnation-contract.js +92 -0
  263. package/dist/core/stub-env.d.ts +2 -0
  264. package/dist/core/stub-env.js +1 -0
  265. package/dist/core/synthetic-read-verdict.js +2 -2
  266. package/dist/core/task-event.d.ts +59 -5
  267. package/dist/core/task-result.d.ts +1 -1
  268. package/dist/core/task-spec.d.ts +42 -9
  269. package/dist/core/terminal-cause.d.ts +94 -11
  270. package/dist/core/tighten-task-spec.d.ts +5 -4
  271. package/dist/core/tighten-task-spec.js +20 -25
  272. package/dist/core/tool-catalog-entries.d.ts +5 -7
  273. package/dist/core/tool-catalog-entries.js +14 -15
  274. package/dist/core/tool-face.d.ts +47 -0
  275. package/dist/core/tool-face.js +5 -0
  276. package/dist/core/tool-model-gate.d.ts +5 -5
  277. package/dist/core/tool-name-aliases.d.ts +26 -2
  278. package/dist/core/tool-name-aliases.js +13 -0
  279. package/dist/core/tool-name-list.d.ts +30 -0
  280. package/dist/core/tool-name-list.js +55 -0
  281. package/dist/core/tool-name-readings.d.ts +1 -9
  282. package/dist/core/tool-name-readings.js +1 -3
  283. package/dist/core/tool-policy.d.ts +195 -85
  284. package/dist/core/tool-policy.js +165 -380
  285. package/dist/core/tool-registry.d.ts +44 -1
  286. package/dist/core/tool-registry.js +26 -0
  287. package/dist/core/tool-roster.d.ts +19 -1
  288. package/dist/core/tool-roster.js +21 -2
  289. package/dist/core/tool-spec-members.d.ts +45 -0
  290. package/dist/core/tool-spec-members.js +169 -0
  291. package/dist/core/tool-spec.d.ts +46 -8
  292. package/dist/core/tools.d.ts +6 -2
  293. package/dist/core/tools.js +8 -4
  294. package/dist/core/untrusted-egress.js +29 -26
  295. package/dist/core/untrusted-text.d.ts +13 -10
  296. package/dist/core/untrusted-text.js +6 -44
  297. package/dist/core/workflow-journal-store.d.ts +4 -1
  298. package/dist/core/write-protect.d.ts +65 -29
  299. package/dist/core/write-protect.js +235 -177
  300. package/dist/engine/compaction/compaction.d.ts +2 -2
  301. package/dist/engine/execution-env/node-execution-env.d.ts +5 -23
  302. package/dist/engine/execution-env/node-execution-env.js +16 -38
  303. package/dist/engine/execution-env/symlink-chain.d.ts +23 -0
  304. package/dist/engine/execution-env/symlink-chain.js +32 -0
  305. package/dist/engine/harness/types.d.ts +7 -0
  306. package/dist/engine/llm/tool-turns.d.ts +3 -0
  307. package/dist/engine/llm/tool-turns.js +1 -0
  308. package/dist/engine/llm/types.d.ts +59 -23
  309. package/dist/engine/loop/agent-loop.js +16 -2
  310. package/dist/engine/session/memory-repo.js +11 -3
  311. package/dist/engine/session/repo-utils.d.ts +8 -0
  312. package/dist/engine/session/repo-utils.js +13 -0
  313. package/dist/index.d.ts +20 -23
  314. package/dist/index.js +17 -20
  315. package/dist/internal/harness.d.ts +1 -1
  316. package/dist/internal/harness.js +1 -1
  317. package/dist/internal/llm.d.ts +2 -2
  318. package/dist/internal/llm.js +1 -1
  319. package/dist/orchestration/governance-baseline-validity.js +1 -1
  320. package/dist/orchestration/run-spec.js +3 -3
  321. package/dist/orchestration/run-workflow-tool.d.ts +16 -35
  322. package/dist/orchestration/run-workflow-tool.js +38 -78
  323. package/dist/orchestration/workflow-governance.d.ts +21 -25
  324. package/dist/orchestration/workflow-governance.js +38 -78
  325. package/dist/orchestration/workflow-primitives.d.ts +7 -22
  326. package/dist/orchestration/workflow-primitives.js +5 -12
  327. package/dist/orchestration/workflow-types.d.ts +1 -1
  328. package/dist/orchestration/workflow.d.ts +61 -11
  329. package/dist/orchestration/workflow.js +189 -117
  330. package/dist/scenarios/full-body.d.ts +1 -1
  331. package/dist/shell/lex.d.ts +0 -21
  332. package/dist/shell/lex.js +250 -68
  333. package/dist/shell/shell-words.d.ts +35 -0
  334. package/dist/shell/shell-words.js +65 -0
  335. package/dist/stores/file/checkpoint-store.d.ts +5 -5
  336. package/dist/stores/file/checkpoint-store.js +5 -2
  337. package/dist/stores/file/index.d.ts +0 -8
  338. package/dist/stores/file/index.js +0 -12
  339. package/dist/stores/file/session-store.d.ts +3 -0
  340. package/dist/stores/file/session-store.js +44 -19
  341. package/dist/tools/artifact/artifact-tool.js +2 -0
  342. package/dist/tools/fs/bash-program-position.d.ts +10 -3
  343. package/dist/tools/fs/bash-program-position.js +21 -9
  344. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  345. package/dist/tools/fs/bash-readonly-classifier.js +8 -7
  346. package/dist/tools/fs/containment-face.d.ts +4 -0
  347. package/dist/tools/fs/containment-face.js +3 -0
  348. package/dist/tools/fs/fs-shared.d.ts +10 -0
  349. package/dist/tools/fs/fs-shared.js +18 -0
  350. package/dist/tools/fs/fs-write.js +4 -2
  351. package/dist/tools/fs/index.d.ts +1 -1
  352. package/dist/tools/fs/program-read-model.d.ts +39 -18
  353. package/dist/tools/fs/program-read-model.js +29 -19
  354. package/dist/tools/fs/program-read-model.rows.d.ts +0 -14
  355. package/dist/tools/fs/program-read-model.rows.js +5 -5
  356. package/dist/tools/fs/read-deny-home.d.ts +33 -47
  357. package/dist/tools/fs/read-deny-home.js +35 -49
  358. package/dist/tools/fs/read-deny.d.ts +23 -22
  359. package/dist/tools/fs/read-deny.js +114 -89
  360. package/dist/tools/fs/removal-targets.d.ts +56 -23
  361. package/dist/tools/fs/removal-targets.js +65 -87
  362. package/dist/tools/fs/safety.d.ts +29 -22
  363. package/dist/tools/fs/safety.js +17 -11
  364. package/dist/tools/fs/search.js +10 -8
  365. package/dist/tools/fs/shell-read-argv.js +1 -1
  366. package/dist/tools/fs/shell-read-bindings.d.ts +169 -37
  367. package/dist/tools/fs/shell-read-bindings.js +324 -86
  368. package/dist/tools/fs/shell-read-builtins.d.ts +35 -0
  369. package/dist/tools/fs/shell-read-builtins.js +43 -0
  370. package/dist/tools/fs/shell-read-check.d.ts +15 -1
  371. package/dist/tools/fs/shell-read-check.js +51 -42
  372. package/dist/tools/fs/shell-read-establish.js +6 -6
  373. package/dist/tools/fs/shell-read-functions.d.ts +22 -0
  374. package/dist/tools/fs/shell-read-functions.js +29 -0
  375. package/dist/tools/fs/shell-read-judge.d.ts +48 -18
  376. package/dist/tools/fs/shell-read-judge.js +33 -4
  377. package/dist/tools/fs/shell-read-plan.d.ts +12 -6
  378. package/dist/tools/fs/shell-read-plan.js +118 -113
  379. package/dist/tools/fs/shell-read-start.d.ts +29 -6
  380. package/dist/tools/fs/shell-read-start.js +16 -9
  381. package/dist/tools/fs/shell-read-word.js +4 -0
  382. package/dist/tools/fs/shell-read-workdir.d.ts +156 -0
  383. package/dist/tools/fs/shell-read-workdir.js +281 -0
  384. package/dist/tools/fs/shell-read-write-targets.d.ts +5 -2
  385. package/dist/tools/fs/shell-read-write-targets.js +14 -5
  386. package/dist/tools/fs/shell-reentry-text.d.ts +4 -6
  387. package/dist/tools/fs/shell-reentry-text.js +3 -11
  388. package/dist/tools/task-list.d.ts +1 -2
  389. package/dist/tools/web.js +1 -1
  390. package/package.json +4 -1
  391. package/test/export-surface.snapshot.json +107 -143
  392. package/wire-changes/core@7.49.0.json +4765 -0
  393. package/dist/agents/teacher.d.ts +0 -159
  394. package/dist/agents/teacher.js +0 -577
  395. package/dist/agents/team.d.ts +0 -226
  396. package/dist/agents/team.js +0 -257
  397. package/dist/core/strategy-store.d.ts +0 -238
  398. package/dist/core/strategy-store.js +0 -253
  399. package/dist/orchestration/builtin-workflows.d.ts +0 -133
  400. package/dist/orchestration/builtin-workflows.js +0 -159
  401. package/dist/scenarios/env.d.ts +0 -40
  402. package/dist/scenarios/env.js +0 -39
  403. package/dist/scenarios/scenario-registry.d.ts +0 -110
  404. package/dist/scenarios/scenario-registry.js +0 -140
  405. package/dist/scenarios/teacher-quickstart.d.ts +0 -40
  406. package/dist/scenarios/teacher-quickstart.js +0 -12
  407. package/dist/stores/cc/lockfile.d.ts +0 -10
  408. package/dist/stores/cc/lockfile.js +0 -98
  409. package/dist/stores/cc/mailbox-store.d.ts +0 -20
  410. package/dist/stores/cc/mailbox-store.js +0 -255
  411. package/dist/stores/cc/roster-adapter.d.ts +0 -12
  412. package/dist/stores/cc/roster-adapter.js +0 -117
  413. package/dist/stores/cc/sidecar-transcript.d.ts +0 -97
  414. package/dist/stores/cc/sidecar-transcript.js +0 -264
  415. package/dist/stores/cc/task-list-store.d.ts +0 -2
  416. package/dist/stores/cc/task-list-store.js +0 -125
  417. package/dist/stores/file/strategy-store.d.ts +0 -97
  418. package/dist/stores/file/strategy-store.js +0 -341
@@ -107,6 +107,19 @@ export declare function deriveControlPlaneDir(configRoot: string, key: string):
107
107
  * memory dir would strand one of the two on every alias-reached mount. Safe to canonicalize here —
108
108
  * the key input is the host-declared repo root, not a directory the model can replace. */
109
109
  export declare function deriveRepoControlPlaneDir(configRoot: string, repoRoot: string): string;
110
+ /**
111
+ * THE control plane of the memory store at `dir` — the one reading every opener (the file backend, the
112
+ * engine, the runner's plane assembly) and {@link reanchorMemoryStoreRoot} resolve through, so the plane a
113
+ * store's root anchor is read from is the plane it was written to. A backend that pins its own control
114
+ * plane (`pinnedBy.controlPlaneRoot` — the file backend) wins, else the explicit `controlDir`, else the
115
+ * derivation off the config root ({@link deriveControlPlaneDir}) — preceded by the one-shot move of a plane
116
+ * minted under the canonical key ({@link adoptCanonicalKeyedControlDir}), which is why that arm is not pure.
117
+ */
118
+ export declare function effectiveControlRootOf(dir: string, opts?: {
119
+ controlDir?: string;
120
+ pinnedBy?: unknown;
121
+ onIncident?: MemoryPartitionIncidentSink;
122
+ }): string;
110
123
  /**
111
124
  * Resolve the memory-engine config root: explicit (`RunnerDeps.memoryEngineDir`) → `AGENT_DATA_DIR`
112
125
  * env → `~/.ai-agent` (the same default family as the file stores' `resolveDataRoot`; re-stated here
@@ -277,11 +290,17 @@ declare const SCOPE_HOME_FORM_PHRASE: {
277
290
  readonly other: "a special file";
278
291
  };
279
292
  export type HomeFormPhraseCoversEveryForm = AssertAllKeysHandled<Exclude<ScopeHomeForm, keyof typeof SCOPE_HOME_FORM_PHRASE>>;
280
- /** What a store-form refusal carries beside its sentence. The ONE host path lives here and never in the
281
- * sentence; `form` is present on {@link SCOPE_HOME_NOT_DIRECTORY} only. */
293
+ /** What a store-form refusal carries beside its sentence. Host paths live here and never in the sentence;
294
+ * `form` is present on {@link SCOPE_HOME_NOT_DIRECTORY} only; `controlDir`, `recorded` and `now` are present
295
+ * together on a moved store root only ({@link storeRootMovedRefusal}): the control plane whose root anchor
296
+ * refused (the `controlDir` to hand {@link reanchorMemoryStoreRoot}), the physical root the anchor records,
297
+ * and where the root resolves now. */
282
298
  export type StoreFormRefusalDetail = {
283
299
  readonly path: string;
284
300
  readonly form?: ScopeHomeForm;
301
+ readonly controlDir?: string;
302
+ readonly recorded?: string;
303
+ readonly now?: string;
285
304
  };
286
305
  /** A refusal the store's physical form earns: a plain `Error` (never the control-plane-damage class —
287
306
  * the engine's own account is intact, the layout it was handed is not the layout it needs) carrying a
@@ -293,14 +312,28 @@ export type StoreFormRefusal = Error & {
293
312
  /** Seat 1 — an act the store's physical form refuses: `act` names what was refused (no path), `path` is the
294
313
  * target whose directory resolves outside the store ({@link landsOutsideStore}). */
295
314
  export declare function storeActRefusal(act: string, path: string): StoreFormRefusal;
296
- /** Seat 2 — the store root now resolves to another directory than the one the `holder` first opened; `path` is
297
- * the root's spelling. */
298
- export declare function storeRootMovedRefusal(holder: "backend" | "engine", path: string): StoreFormRefusal;
315
+ /** Seat 2 — the store root no longer resolves to the physical root its control plane's anchor records
316
+ * ({@link anchorStoreRoot}); `path` is the root's spelling. The remedy names its arguments by their
317
+ * `detail` keys, so the sentence stays path-free and the call is still copyable. */
318
+ export declare function storeRootMovedRefusal(path: string, anchor: {
319
+ controlDir: string;
320
+ recorded: string;
321
+ now: string;
322
+ }): StoreFormRefusal;
323
+ /** Seat 2, the root gone: the anchored root no longer exists while its control plane holds a store's account
324
+ * ({@link anchorStoreRoot}). A missing root lands where it would be created — the recorded one — so only
325
+ * its absence tells a store moved away (or deleted) from one never opened; the root is not re-created. */
326
+ export declare function storeRootAbsentRefusal(path: string, anchor: {
327
+ controlDir: string;
328
+ recorded: string;
329
+ now: string;
330
+ }): StoreFormRefusal;
299
331
  /** Seat 3 — a scope home that is not a real directory. */
300
332
  export declare function scopeHomeRefusal(form: ScopeHomeForm, path: string): StoreFormRefusal;
301
333
  /** Where an act at `p` lands: the physical path of its normalized spelling (the spelling a child path
302
334
  * joined under it carries), a MISSING tail rejoined under the deepest existing directory (what the act
303
- * creates). Any other resolution fault (EACCES, ELOOP, ENOTDIR) is THROWN as itself: an act that
335
+ * creates) — a dangling link on that tail followed to where it points (what the act would create through
336
+ * it). Any other resolution fault (EACCES, ELOOP, ENOTDIR) is THROWN as itself: an act that
304
337
  * cannot be placed is not performed — a failed resolution never implies the act would fail too
305
338
  * (a link whose own mode refuses `realpath` is still followed by `unlink` and `rename`). */
306
339
  export declare function landingPathOf(p: string): string;
@@ -320,6 +353,103 @@ export declare function landingPathOf(p: string): string;
320
353
  export declare function landsOutsideStore(physicalRoot: string, target: string): boolean;
321
354
  /** mkdir -p convenience used by the engine/backend (0o700 like the file stores). */
322
355
  export declare function ensureDirExists(dir: string): void;
356
+ /** The committed-rev ledger's file name (the file backend's account). Named here because the anchor reads
357
+ * its presence as "this control plane already holds a store". */
358
+ export declare const LEDGER_FILE = "revs.json";
359
+ /** The control-plane file recording the store root's physical path ({@link anchorStoreRoot}). */
360
+ export declare const ROOT_ANCHOR_FILE = "root-anchor.json";
361
+ /** What an ABSENT anchor means: a control plane holding nothing records the root (a first open); one already
362
+ * holding a store backfills while the arm is open and refuses once it is not. */
363
+ export declare function absentAnchorReading(controlPlaneHoldsStore: boolean, backfillArmOpen?: boolean): "record" | "backfill" | "refuse";
364
+ /** Options of {@link anchorStoreRoot}. */
365
+ export interface AnchorStoreRootOptions {
366
+ /** Where `memory.root_anchor_backfilled` goes. */
367
+ onIncident?: MemoryPartitionIncidentSink;
368
+ /** The clock `recordedAt` reads (the opener's injected clock). */
369
+ now?: () => number;
370
+ }
371
+ /**
372
+ * THE store-root check, run by every opener (a file backend's construction, an engine's mount) and at the
373
+ * head of every face that acts on the store: where the root `dir` physically resolves NOW, against the
374
+ * anchor its control plane records. Equal ⇒ the anchored root, the one every act at a store path is judged
375
+ * against. Unequal ⇒ {@link storeRootMovedRefusal}; equal but the root gone while the control plane holds a
376
+ * store ⇒ {@link storeRootAbsentRefusal} — both thrown before anything in the store is written or the root is
377
+ * created (only the anchor answers where the root is; a `mkdir` in its place does not). No anchor ⇒ {@link absentAnchorReading}, decided under the anchor's lock (a racing opener
378
+ * that recorded it first is judged against): a control plane holding nothing creates the root and records
379
+ * it; one already holding a store backfills it and discloses `memory.root_anchor_backfilled` (its root may
380
+ * already have been re-pointed — undecidable, said). Nothing is kept between calls: the anchor is read every
381
+ * time, so a re-anchor ({@link reanchorMemoryStoreRoot}) reaches every live holder at its next face.
382
+ */
383
+ export declare function anchorStoreRoot(dir: string, controlDir: string, opts?: AnchorStoreRootOptions): string;
384
+ /** The redo journal a File store's transaction writes as its commit marker (control plane). */
385
+ export declare const STORE_JOURNAL_FILE = "journal.json";
386
+ /** The transaction lock's directory in the control plane; `owner` inside holds the holder's token. */
387
+ export declare const STORE_TXN_LOCK_DIR = "txn.lock";
388
+ /** A lock older than this is a crashed holder (stolen; the journal recovery inside the lock makes the steal safe). */
389
+ export declare const TXN_LOCK_STALE_MS = 30000;
390
+ /** Test hook: small constants so a crash-window leg does not really wait 15–30 s. */
391
+ export interface TxnLockTimings {
392
+ staleMs?: number;
393
+ waitMs?: number;
394
+ stealGraceMs?: number;
395
+ }
396
+ /** A held transaction lock: its token, and a release that removes the lock only while the token is still ours. */
397
+ export interface StoreTxnLock {
398
+ readonly token: string;
399
+ readonly release: () => void;
400
+ }
401
+ /**
402
+ * One attempt at the store's transaction lock (synchronous): the lock, or `undefined` while another holder has
403
+ * it. Only the lock directory's EEXIST is contention; every other failure — the control plane unmakeable, a
404
+ * probe or steal that faults, an occupant that is not a directory — throws {@link ControlPlaneCorruptError}
405
+ * (a fault is never read as "busy, retry later"). Every acquisition writes an owner token (crypto-random)
406
+ * before it returns. A held lock past its stale line (`now` against its mtime) that carries a token is a
407
+ * crashed holder: stolen single-winner (an atomic rename to a unique tombstone; a racer's lost rename is
408
+ * ENOENT) and re-attempted through a normal `mkdir` — never a bare rm + mkdir. A tokenless lock is a
409
+ * mid-acquisition, never stolen. `observed` hears each held lock's mtime (a waiter's deadline extension).
410
+ */
411
+ export declare function tryStoreTxnLock(controlDir: string, opts: {
412
+ now: () => number;
413
+ staleMs?: number;
414
+ observed?: (mtimeMs: number) => void;
415
+ }): StoreTxnLock | undefined;
416
+ /** The commit boundary of a store transaction: the lock still names `token` — an absent or unreadable owner (a
417
+ * stolen, tombstoned lock) is lost too. A holder that stalled past the stale line was stolen from; acting on
418
+ * would overwrite the stealer's transaction. */
419
+ export declare function assertStoreTxnLockOwnership(controlDir: string, token: string): void;
420
+ /** Wait for the store's transaction lock: one {@link tryStoreTxnLock} per round, paced 20–60 ms (the wait
421
+ * yields the event loop every round), until the deadline — `waitMs`, extended to an observed lock's stale
422
+ * line plus the steal grace. Past it: a loud timeout. */
423
+ export declare function acquireStoreTxnLock(controlDir: string, opts: {
424
+ now: () => number;
425
+ timings?: TxnLockTimings;
426
+ }): Promise<StoreTxnLock>;
427
+ /** What {@link reanchorMemoryStoreRoot} changed. */
428
+ export interface StoreRootReanchor {
429
+ /** The physical root the anchor recorded before; absent when no readable anchor stood. */
430
+ readonly from?: string;
431
+ /** The physical root it records now. */
432
+ readonly to: string;
433
+ /** The control plane the anchor lives in. */
434
+ readonly controlDir: string;
435
+ }
436
+ /**
437
+ * The ONE way a recorded root anchor changes: point it at where `dir` resolves now — a store moved on
438
+ * purpose, or an anchor that is damaged. `opts.controlDir` names the store's control plane exactly as its
439
+ * opener does (the explicit `controlDir` the backend / engine was given, absent for the derived one) and
440
+ * resolves through the same reading ({@link effectiveControlRootOf}). Refused (`config.memory_reanchor`) when
441
+ * `dir` is not an existing directory, and when that control plane holds neither an anchor nor a store — a
442
+ * mistaken `controlDir` would otherwise record an anchor no opener reads. Moves nothing and says nothing
443
+ * about whether the directory holds the same store. A re-anchor is a transaction of the store: it holds the
444
+ * store's transaction lock ({@link acquireStoreTxnLock}; a commit in progress is waited for, never crossed) and
445
+ * is refused while a committed transaction is pending recovery (its journal names paths under the recorded
446
+ * root: replayed there first, never carried over). Every live holder judges against the new anchor at its
447
+ * next face. Discloses `memory.root_reanchored` on `opts.onIncident`.
448
+ */
449
+ export declare function reanchorMemoryStoreRoot(dir: string, opts?: {
450
+ controlDir?: string;
451
+ onIncident?: MemoryPartitionIncidentSink;
452
+ }): Promise<StoreRootReanchor>;
323
453
  /** The one remedy every surfaced permission refusal carries — `undefined` unless `err` is a write
324
454
  * the file system refused on PERMISSIONS (`EACCES` / `EPERM`) at a path INSIDE `memoryDir` (a
325
455
  * refusal elsewhere — a control plane kept outside the store — is not this store's to fix). The
@@ -446,9 +576,9 @@ export interface SidecarLockOptions {
446
576
  * Wait-bound (ruled 2026-08-04): EVERY round runs the same three steps — attempt, diagnose, then
447
577
  * the deadline check and the pause. The two "retry immediately" shortcuts this loop used to take
448
578
  * (holder released between mkdir and stat; steal attempted) skipped both of the last two, so a
449
- * condition that keeps answering the same way — an unwritable control dir (read-only mount, full
450
- * volume, revoked permission) makes mkdir fail forever while the lock dir it would stat never
451
- * exists — spun this synchronous loop at full speed and never reached its own cap. */
579
+ * condition that keeps answering the same way spun this synchronous loop at full speed and never
580
+ * reached its own cap. Only a held lock (EEXIST) is waited on: a lock directory that cannot be created
581
+ * (read-only mount, full volume, revoked permission, a missing parent) throws its own error at once. */
452
582
  export declare function acquireSidecarLock(lockDir: string, opts?: SidecarLockOptions): string;
453
583
  /** 证伪式复审 L2-analog: re-verify the owner token at the COMMIT POINT — a stolen-from holder
454
584
  * aborts instead of writing over the stealer. A FOREIGN token always aborts (positive proof the
@@ -1,7 +1,7 @@
1
- import { closeSync, constants as fsConstants, copyFileSync, existsSync, fstatSync, fsyncSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync, writeSync } from "node:fs";
1
+ import { closeSync, constants as fsConstants, copyFileSync, existsSync, fstatSync, fsyncSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync, writeSync } from "node:fs";
2
2
  const { O_WRONLY, O_RDONLY, O_CREAT, O_NOFOLLOW, O_NONBLOCK, O_EXCL } = fsConstants;
3
3
  import { homedir } from "node:os";
4
- import { createHash } from "node:crypto";
4
+ import { createHash, randomUUID } from "node:crypto";
5
5
  import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
6
6
  import { MEMORY_ORIGIN_CAUSES } from "./types.js";
7
7
  import { physicalPathOf, physicalPathOfExisting, priorCallerCasedPathOf } from "../physical-path.js";
@@ -187,6 +187,16 @@ export function deriveControlPlaneDir(configRoot, key) {
187
187
  export function deriveRepoControlPlaneDir(configRoot, repoRoot) {
188
188
  return join(configRoot, deriveRepoKey(repoRoot), CONTROL_PLANE_DIR);
189
189
  }
190
+ export function effectiveControlRootOf(dir, opts = {}) {
191
+ const pinned = opts.pinnedBy?.controlPlaneRoot;
192
+ if (typeof pinned === "string" && pinned !== "")
193
+ return pinned;
194
+ if (opts.controlDir !== undefined)
195
+ return opts.controlDir;
196
+ const configRoot = resolveMemoryEngineRoot();
197
+ adoptCanonicalKeyedControlDir(configRoot, dir, opts.onIncident);
198
+ return deriveControlPlaneDir(configRoot, dir);
199
+ }
190
200
  export function resolveMemoryEngineRoot(explicit) {
191
201
  const raw = explicit ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
192
202
  return isAbsolute(raw) ? raw : resolve(raw);
@@ -749,21 +759,45 @@ function storeFormRefusal(code, sentence, detail) {
749
759
  export function storeActRefusal(act, path) {
750
760
  return storeFormRefusal(TARGET_OUTSIDE_STORE, `${act} was refused — its directory resolves outside the store root; not performed`, { path });
751
761
  }
752
- export function storeRootMovedRefusal(holder, path) {
753
- return storeFormRefusal(TARGET_OUTSIDE_STORE, `the ${holder === "backend" ? "store root" : "memory dir"} now resolves to a different directory than the one this ${holder} ${holder === "backend" ? "opened" : "first mounted"} — refused; open a new ${holder} on the directory you mean`, { path });
762
+ export function storeRootMovedRefusal(path, anchor) {
763
+ return storeFormRefusal(TARGET_OUTSIDE_STORE, "the store root now resolves to a different directory than the one its control plane records — refused, nothing in the store was written; put the directory back, or, if the store was moved on purpose, call reanchorMemoryStoreRoot(detail.path, { controlDir: detail.controlDir }) and reopen", { path, controlDir: anchor.controlDir, recorded: anchor.recorded, now: anchor.now });
764
+ }
765
+ export function storeRootAbsentRefusal(path, anchor) {
766
+ return storeFormRefusal(TARGET_OUTSIDE_STORE, "the store root no longer exists where its control plane records it — refused, nothing in the store was written; put the directory back, or, if the store was moved on purpose, call reanchorMemoryStoreRoot with its new directory and { controlDir: detail.controlDir }; to start over in place, create the directory, then call reanchorMemoryStoreRoot(detail.path, { controlDir: detail.controlDir }) and reopen", { path, controlDir: anchor.controlDir, recorded: anchor.recorded, now: anchor.now });
754
767
  }
755
768
  export function scopeHomeRefusal(form, path) {
756
769
  return storeFormRefusal(SCOPE_HOME_NOT_DIRECTORY, `a scope home must be a real directory, but this one is ${SCOPE_HOME_FORM_PHRASE[form]} — refused (fail-closed); put the real directory back`, { path, form });
757
770
  }
758
771
  export function landingPathOf(p) {
759
- const normalized = resolve(p);
772
+ return landingPathWithin(resolve(p), 0);
773
+ }
774
+ const MAX_LANDING_LINK_HOPS = 40;
775
+ function landingPathWithin(normalized, hops) {
760
776
  try {
761
777
  return physicalPathOf(normalized);
762
778
  }
763
779
  catch (err) {
764
780
  if (err.code !== "ENOENT")
765
781
  throw err;
766
- return canonicalize(normalized);
782
+ }
783
+ const tail = [];
784
+ for (let cur = normalized;;) {
785
+ const st = lstatOrAbsent(cur);
786
+ if (st?.isSymbolicLink() === true) {
787
+ if (hops >= MAX_LANDING_LINK_HOPS) {
788
+ const e = new Error(`ELOOP: too many symbolic links resolving ${normalized}`);
789
+ e.code = "ELOOP";
790
+ throw e;
791
+ }
792
+ return landingPathWithin(join(resolve(dirname(cur), readlinkSync(cur)), ...[...tail].reverse()), hops + 1);
793
+ }
794
+ if (st !== undefined)
795
+ return canonicalize(normalized);
796
+ const parent = dirname(cur);
797
+ if (parent === cur)
798
+ return normalized;
799
+ tail.push(basename(cur));
800
+ cur = parent;
767
801
  }
768
802
  }
769
803
  export function landsOutsideStore(physicalRoot, target) {
@@ -774,6 +808,313 @@ export function ensureDirExists(dir) {
774
808
  if (!existsSync(dir))
775
809
  mkdirSync(dir, { recursive: true, mode: 0o700 });
776
810
  }
811
+ export const LEDGER_FILE = "revs.json";
812
+ export const ROOT_ANCHOR_FILE = "root-anchor.json";
813
+ const ANCHOR_BACKFILL_ARM_OPEN = true;
814
+ export function absentAnchorReading(controlPlaneHoldsStore, backfillArmOpen = ANCHOR_BACKFILL_ARM_OPEN) {
815
+ if (!controlPlaneHoldsStore)
816
+ return "record";
817
+ return backfillArmOpen ? "backfill" : "refuse";
818
+ }
819
+ function reanchorRemedy(dir, controlDir) {
820
+ return `once the store root is where it belongs, call reanchorMemoryStoreRoot(${JSON.stringify(dir)}, { controlDir: ${JSON.stringify(controlDir)} }) and reopen`;
821
+ }
822
+ function damagedAnchorError(controlDir, dir, why, cause) {
823
+ return new ControlPlaneCorruptError(`memory store root anchor ${join(controlDir, ROOT_ANCHOR_FILE)} is damaged (${why}) — the store root cannot be checked against it, so nothing in the store was opened; ${reanchorRemedy(dir, controlDir)}`, cause !== undefined ? { cause } : undefined);
824
+ }
825
+ function readStoreRootAnchor(controlDir, dir) {
826
+ let raw;
827
+ let fd;
828
+ try {
829
+ fd = openSync(join(controlDir, ROOT_ANCHOR_FILE), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
830
+ if (!fstatSync(fd).isFile())
831
+ throw damagedAnchorError(controlDir, dir, "not a regular file");
832
+ raw = readFileSync(fd, "utf8");
833
+ }
834
+ catch (err) {
835
+ if (err instanceof ControlPlaneCorruptError)
836
+ throw err;
837
+ const code = err.code;
838
+ if (code === "ENOENT" && fd === undefined)
839
+ return undefined;
840
+ throw damagedAnchorError(controlDir, dir, code === "ELOOP" ? "a symbolic link, not the record" : `unreadable: ${code ?? thrownLabel(err)}`, err);
841
+ }
842
+ finally {
843
+ if (fd !== undefined)
844
+ closeSync(fd);
845
+ }
846
+ let parsed;
847
+ try {
848
+ parsed = JSON.parse(raw);
849
+ }
850
+ catch (err) {
851
+ throw damagedAnchorError(controlDir, dir, "not JSON", err);
852
+ }
853
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
854
+ throw damagedAnchorError(controlDir, dir, "not an object");
855
+ const a = parsed;
856
+ if (a.v !== 1)
857
+ throw damagedAnchorError(controlDir, dir, `unknown version ${JSON.stringify(a.v) ?? typeof a.v}`);
858
+ if (typeof a.realpath !== "string" || !isAbsolute(a.realpath))
859
+ throw damagedAnchorError(controlDir, dir, "realpath is not an absolute path");
860
+ if (typeof a.spelling !== "string" || typeof a.recordedAt !== "number" || !Number.isFinite(a.recordedAt))
861
+ throw damagedAnchorError(controlDir, dir, "spelling / recordedAt out of shape");
862
+ return { v: 1, realpath: a.realpath, spelling: a.spelling, recordedAt: a.recordedAt };
863
+ }
864
+ function controlPlaneHoldsStore(controlDir) {
865
+ return lstatOrAbsent(join(controlDir, SCOPES_FILE)) !== undefined || lstatOrAbsent(join(controlDir, LEDGER_FILE)) !== undefined;
866
+ }
867
+ function judgeAgainstAnchor(dir, controlDir, anchor) {
868
+ const now = landingPathOf(dir);
869
+ if (now !== anchor.realpath)
870
+ throw storeRootMovedRefusal(dir, { controlDir, recorded: anchor.realpath, now });
871
+ if (lstatOrAbsent(now) === undefined && controlPlaneHoldsStore(controlDir))
872
+ throw storeRootAbsentRefusal(dir, { controlDir, recorded: anchor.realpath, now });
873
+ return now;
874
+ }
875
+ function writeStoreRootAnchor(controlDir, lock, token, anchor) {
876
+ assertSidecarLockOwnership(lock, token, "memory store root anchor", "strict");
877
+ atomicWriteFileSync(join(controlDir, ROOT_ANCHOR_FILE), `${JSON.stringify(anchor, null, 2)}\n`);
878
+ }
879
+ export function anchorStoreRoot(dir, controlDir, opts = {}) {
880
+ const anchor = readStoreRootAnchor(controlDir, dir);
881
+ if (anchor !== undefined)
882
+ return judgeAgainstAnchor(dir, controlDir, anchor);
883
+ ensureDirExists(controlDir);
884
+ const lock = join(controlDir, `${ROOT_ANCHOR_FILE}.lock`);
885
+ const token = acquireSidecarLock(lock, { onDeadline: "throw" });
886
+ try {
887
+ const raced = readStoreRootAnchor(controlDir, dir);
888
+ if (raced !== undefined)
889
+ return judgeAgainstAnchor(dir, controlDir, raced);
890
+ const reading = absentAnchorReading(controlPlaneHoldsStore(controlDir));
891
+ if (reading === "refuse")
892
+ throw damagedAnchorError(controlDir, dir, "absent, while the control plane holds a store");
893
+ ensureDirExists(dir);
894
+ const realpath = landingPathOf(dir);
895
+ writeStoreRootAnchor(controlDir, lock, token, { v: 1, realpath, spelling: dir, recordedAt: (opts.now ?? Date.now)() });
896
+ if (reading === "backfill") {
897
+ discloseLayoutIncident(opts.onIncident, "memory.root_anchor_backfilled", `memory store root anchor recorded for a store that had none: ${dir} resolves to ${realpath} (control plane ${controlDir}) — from now on the store opens only there; where the root resolved before this open cannot be told`);
898
+ }
899
+ return realpath;
900
+ }
901
+ finally {
902
+ releaseSidecarLock(lock, token);
903
+ }
904
+ }
905
+ export const STORE_JOURNAL_FILE = "journal.json";
906
+ export const STORE_TXN_LOCK_DIR = "txn.lock";
907
+ export const TXN_LOCK_STALE_MS = 30_000;
908
+ const TXN_LOCK_WAIT_MS = 15_000;
909
+ const TXN_LOCK_STEAL_GRACE_MS = 250;
910
+ export function tryStoreTxnLock(controlDir, opts) {
911
+ const lockDir = join(controlDir, STORE_TXN_LOCK_DIR);
912
+ const ownerPath = join(lockDir, "owner");
913
+ const staleMs = opts.staleMs ?? TXN_LOCK_STALE_MS;
914
+ const faultText = (err) => err.code ?? "io error";
915
+ const tryMkdir = () => {
916
+ try {
917
+ mkdirSync(controlDir, { recursive: true });
918
+ }
919
+ catch (err) {
920
+ throw new ControlPlaneCorruptError(`memory control-plane root could not be created (${faultText(err)}) at ${controlDir} — a lock-acquisition fault is not contention (fail-closed)`, { cause: err });
921
+ }
922
+ try {
923
+ mkdirSync(lockDir);
924
+ return true;
925
+ }
926
+ catch (err) {
927
+ if (err.code === "EEXIST")
928
+ return false;
929
+ throw new ControlPlaneCorruptError(`memory txn lock could not be created (${faultText(err)}) at ${lockDir} — a lock-acquisition fault is not contention (fail-closed)`, { cause: err });
930
+ }
931
+ };
932
+ let acquired = tryMkdir();
933
+ if (!acquired) {
934
+ let mtimeMs;
935
+ try {
936
+ mtimeMs = statSync(lockDir).mtimeMs;
937
+ opts.observed?.(mtimeMs);
938
+ }
939
+ catch (err) {
940
+ if (err.code !== "ENOENT") {
941
+ throw new ControlPlaneCorruptError(`memory txn lock state could not be probed (${faultText(err)}) at ${lockDir} — a probe failure is not contention (fail-closed)`, { cause: err });
942
+ }
943
+ let occupant;
944
+ try {
945
+ occupant = lstatSync(lockDir);
946
+ }
947
+ catch (lerr) {
948
+ if (lerr.code !== "ENOENT") {
949
+ throw new ControlPlaneCorruptError(`memory txn lock re-probe failed (${faultText(lerr)}) at ${lockDir} — a probe failure is not contention (fail-closed)`, { cause: lerr });
950
+ }
951
+ }
952
+ if (occupant !== undefined && !occupant.isDirectory()) {
953
+ throw new ControlPlaneCorruptError(`memory txn lock path is occupied by an invalid node (a dangling symlink?) at ${lockDir} — not contention and not a release race; remove the occupant (fail-closed)`, { cause: err });
954
+ }
955
+ mtimeMs = undefined;
956
+ }
957
+ if (mtimeMs !== undefined && opts.now() - mtimeMs > staleMs) {
958
+ let tokenPresent = false;
959
+ try {
960
+ statSync(ownerPath);
961
+ tokenPresent = true;
962
+ }
963
+ catch (err) {
964
+ if (err.code !== "ENOENT") {
965
+ throw new ControlPlaneCorruptError(`memory txn lock owner token could not be probed (${faultText(err)}) at ${ownerPath} — a probe failure is not a mid-acquisition (fail-closed)`, { cause: err });
966
+ }
967
+ }
968
+ if (tokenPresent) {
969
+ const tomb = join(controlDir, `${STORE_TXN_LOCK_DIR}.stale-${randomUUID()}`);
970
+ let stolen = false;
971
+ try {
972
+ renameSync(lockDir, tomb);
973
+ stolen = true;
974
+ }
975
+ catch (err) {
976
+ if (err.code !== "ENOENT") {
977
+ throw new ControlPlaneCorruptError(`memory txn lock stale-steal rename failed (${faultText(err)}) at ${lockDir} — a rename fault is not a lost race (fail-closed)`, { cause: err });
978
+ }
979
+ }
980
+ if (stolen) {
981
+ rmSync(tomb, { recursive: true, force: true });
982
+ acquired = tryMkdir();
983
+ }
984
+ }
985
+ }
986
+ if (!acquired)
987
+ return undefined;
988
+ }
989
+ const token = randomUUID();
990
+ try {
991
+ const fd = openSync(ownerPath, "w", 0o600);
992
+ try {
993
+ writeAllSync(fd, token);
994
+ fsyncSync(fd);
995
+ }
996
+ finally {
997
+ closeSync(fd);
998
+ }
999
+ }
1000
+ catch (err) {
1001
+ try {
1002
+ rmSync(lockDir, { recursive: true, force: true });
1003
+ }
1004
+ catch {
1005
+ }
1006
+ throw new Error(`memory txn lock owner token could not be written at ${ownerPath}: ${thrownText(err)}`);
1007
+ }
1008
+ return {
1009
+ token,
1010
+ release: () => {
1011
+ try {
1012
+ if (readFileSync(ownerPath, "utf8") !== token)
1013
+ return;
1014
+ }
1015
+ catch {
1016
+ return;
1017
+ }
1018
+ try {
1019
+ rmSync(lockDir, { recursive: true, force: true });
1020
+ }
1021
+ catch {
1022
+ }
1023
+ },
1024
+ };
1025
+ }
1026
+ export function assertStoreTxnLockOwnership(controlDir, token) {
1027
+ let held;
1028
+ try {
1029
+ held = readFileSync(join(controlDir, STORE_TXN_LOCK_DIR, "owner"), "utf8");
1030
+ }
1031
+ catch {
1032
+ held = undefined;
1033
+ }
1034
+ if (held !== token) {
1035
+ throw new ControlPlaneCorruptError("txn lock ownership lost before commit (holder stalled past the stale deadline and was stolen from?) — transaction aborted, nothing was committed", { facet: "lock-ownership" });
1036
+ }
1037
+ }
1038
+ export async function acquireStoreTxnLock(controlDir, opts) {
1039
+ const staleMs = opts.timings?.staleMs ?? TXN_LOCK_STALE_MS;
1040
+ const waitMs = opts.timings?.waitMs ?? TXN_LOCK_WAIT_MS;
1041
+ const stealGraceMs = opts.timings?.stealGraceMs ?? TXN_LOCK_STEAL_GRACE_MS;
1042
+ let deadline = Date.now() + waitMs;
1043
+ for (;;) {
1044
+ const lock = tryStoreTxnLock(controlDir, { now: opts.now, staleMs, observed: (mtimeMs) => (deadline = Math.max(deadline, mtimeMs + staleMs + stealGraceMs)) });
1045
+ if (lock !== undefined)
1046
+ return lock;
1047
+ if (Date.now() > deadline) {
1048
+ throw new Error(`memory txn lock timeout (${waitMs}ms, extended to the lock's stale line) at ${join(controlDir, STORE_TXN_LOCK_DIR)} — another writer is wedged?`);
1049
+ }
1050
+ await new Promise((r) => setTimeout(r, 20 + Math.floor(Math.random() * 40)));
1051
+ }
1052
+ }
1053
+ function reanchorDestination(dir) {
1054
+ let to;
1055
+ try {
1056
+ to = physicalPathOf(resolve(dir));
1057
+ }
1058
+ catch (err) {
1059
+ throw reanchorRefusal(`the store root ${JSON.stringify(dir)} does not resolve (${err.code ?? thrownLabel(err)}) — put the directory in place first`);
1060
+ }
1061
+ if (lstatOrAbsent(to)?.isDirectory() !== true)
1062
+ throw reanchorRefusal(`the store root ${JSON.stringify(dir)} resolves to ${to}, which is not a directory`);
1063
+ return to;
1064
+ }
1065
+ function reanchorRefusal(sentence) {
1066
+ const e = new Error(`reanchorMemoryStoreRoot: ${sentence}`);
1067
+ e.code = "config.memory_reanchor";
1068
+ return e;
1069
+ }
1070
+ export async function reanchorMemoryStoreRoot(dir, opts = {}) {
1071
+ const controlDir = effectiveControlRootOf(dir, { controlDir: opts.controlDir, onIncident: opts.onIncident });
1072
+ const to = reanchorDestination(dir);
1073
+ const lock = join(controlDir, `${ROOT_ANCHOR_FILE}.lock`);
1074
+ if (lstatOrAbsent(join(controlDir, ROOT_ANCHOR_FILE)) === undefined && !controlPlaneHoldsStore(controlDir)) {
1075
+ throw reanchorRefusal(`the control plane ${controlDir} holds no store and no root anchor — nothing to re-anchor; pass the controlDir this store's backend / engine is opened with`);
1076
+ }
1077
+ const txn = await acquireStoreTxnLock(controlDir, { now: Date.now });
1078
+ let from;
1079
+ try {
1080
+ const token = acquireSidecarLock(lock, { onDeadline: "throw" });
1081
+ try {
1082
+ try {
1083
+ from = readStoreRootAnchor(controlDir, dir)?.realpath;
1084
+ }
1085
+ catch {
1086
+ from = undefined;
1087
+ }
1088
+ try {
1089
+ assertStoreTxnLockOwnership(controlDir, txn.token);
1090
+ }
1091
+ catch {
1092
+ throw reanchorRefusal(`the store's transaction lock was lost while re-anchoring (held past its stale line and taken by another writer) — nothing changed; retry (control plane ${controlDir})`);
1093
+ }
1094
+ if (lstatOrAbsent(join(controlDir, STORE_JOURNAL_FILE)) !== undefined) {
1095
+ throw reanchorRefusal(`a committed transaction is still pending recovery at the recorded root — put the store back at ${from ?? "the root it was last opened at"}, open it once so the journal replays, then re-anchor (control plane ${controlDir})`);
1096
+ }
1097
+ let still;
1098
+ try {
1099
+ still = reanchorDestination(dir);
1100
+ }
1101
+ catch {
1102
+ still = undefined;
1103
+ }
1104
+ if (still !== to)
1105
+ throw reanchorRefusal(`the store root ${JSON.stringify(dir)} moved while re-anchoring (it resolved to ${to} when the call began) — nothing changed; retry`);
1106
+ writeStoreRootAnchor(controlDir, lock, token, { v: 1, realpath: to, spelling: dir, recordedAt: Date.now() });
1107
+ }
1108
+ finally {
1109
+ releaseSidecarLock(lock, token);
1110
+ }
1111
+ }
1112
+ finally {
1113
+ txn.release();
1114
+ }
1115
+ discloseLayoutIncident(opts.onIncident, "memory.root_reanchored", `memory store root re-anchored: ${dir} now opens at ${to}${from !== undefined ? ` (was ${from})` : ""} — control plane ${controlDir}`);
1116
+ return from !== undefined ? { from, to, controlDir } : { to, controlDir };
1117
+ }
777
1118
  export function permissionRemedyOf(err, memoryDir) {
778
1119
  const e = typeof err === "object" && err !== null ? err : undefined;
779
1120
  if (e === undefined || (e.code !== "EACCES" && e.code !== "EPERM") || typeof e.path !== "string" || !isContainedIn(memoryDir, e.path))
@@ -965,7 +1306,9 @@ export function acquireSidecarLock(lockDir, opts = {}) {
965
1306
  }
966
1307
  return token;
967
1308
  }
968
- catch {
1309
+ catch (err) {
1310
+ if (err.code !== "EEXIST")
1311
+ throw err;
969
1312
  }
970
1313
  try {
971
1314
  const age = Date.now() - statSync(lockDir).mtimeMs;
@@ -48,6 +48,16 @@ export declare function formatUserProjScope(principal: string, projectId: string
48
48
  * opaque behavior. NEVER call this on a deployment that has not opted in (see module header).
49
49
  */
50
50
  export declare function parseScopeKey(key: string): ParsedScopeKey;
51
+ /**
52
+ * THE org-scope judge for a host that admits, stores or routes org keys: is `key` a well-formed v2 org
53
+ * scope — `org:` followed by ONE encoded segment ({@link encodeScopeSegment}'s alphabet, at most
54
+ * {@link SCOPE_SEGMENT_MAX_ENCODED} encoded characters, canonical spelling)? It is {@link parseScopeKey}'s own
55
+ * reading, asked as a yes/no: a malformed `org:` key (an unencoded non-ASCII character, an over-long
56
+ * segment, a non-canonical escape) answers `false` here where `parseScopeKey` would throw, and a key of any
57
+ * other kind (`user:`, `proj:`, an unprefixed opaque string) answers `false` too. A prefix-plus-any-non-space
58
+ * pattern restated outside core answers a different question and disagrees on exactly those inputs.
59
+ */
60
+ export declare function isOrgScopeKey(key: string): boolean;
51
61
  /** True when a v2-parsed key belongs to a PERSONAL plane (`user:` / `userproj:`). */
52
62
  export declare function isPersonalScope(parsed: ParsedScopeKey): boolean;
53
63
  /** True when the key's AUTHORITY is central (design/142 §2 表: user/userproj/org rows) — these keys
@@ -111,6 +111,14 @@ export function parseScopeKey(key) {
111
111
  }
112
112
  return { kind: "legacy", raw: key };
113
113
  }
114
+ export function isOrgScopeKey(key) {
115
+ try {
116
+ return parseScopeKey(key).kind === "org";
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ }
114
122
  export function isPersonalScope(parsed) {
115
123
  return parsed.kind === "user" || parsed.kind === "userproj";
116
124
  }
@@ -1,16 +1,26 @@
1
1
  import { type MemoryPartitionIncidentSink, type SessionIdentityRead } from "./layout.js";
2
+ /** The seat's resolver shape ({@link createSessionIncarnationSeat}). */
3
+ export type SessionIncarnationResolver = (session: {
4
+ sessionId: string;
5
+ }) => string | undefined;
6
+ /**
7
+ * The resolver a Runner hands its engines, built from the ACQUIRED session's own row cells — the object
8
+ * the run's transcript lives in, so the seat and the transcript never name two occupants (a second
9
+ * lookup by id can answer a row that replaced it). `own` = that session's `incarnation`; a LINEAGE id
10
+ * answers the occupant recorded with the lineage (`lineage`, e.g. the fork's `forkedFromIncarnation`),
11
+ * never whoever holds that id now. Everything else, and an absent own token, is unknown (binds).
12
+ */
13
+ export declare function sessionIncarnationResolver(sessionId: string, own: string | undefined, lineage: ReadonlyMap<string, string>): SessionIncarnationResolver;
2
14
  /** The current occupant's token, and the identity seat a per-session record read is made under. */
3
15
  export interface SessionIncarnationSeat {
4
16
  incarnationOf(sessionId: string): string | undefined;
5
17
  identityRead(sessionId: string): SessionIdentityRead;
6
18
  }
7
- /** Build the seat over a host resolver (absent ⇒ nothing stamped, nothing asked, every record binds
19
+ /** Build the seat over a resolver (absent ⇒ nothing stamped, nothing asked, every record binds
8
20
  * by id — the pre-incarnation answer). Both disclosures are DEDUPED on the axis their fact belongs to: the resolver
9
21
  * fault once per engine (a wiring fact), a separation once per session id (a fact about that id). */
10
22
  export declare function createSessionIncarnationSeat(input: {
11
- resolve: ((session: {
12
- sessionId: string;
13
- }) => string | undefined) | undefined;
23
+ resolve: SessionIncarnationResolver | undefined;
14
24
  onIncident: MemoryPartitionIncidentSink | undefined;
15
25
  }): SessionIncarnationSeat;
16
26
  /** Remember an in-process record under its session id, FIRST-WINS PER IDENTITY: this map is the