@sema-agent/server 7.132.0 → 7.133.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 (402) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +174 -55
  5. package/deploy/sema-up/README.md +19 -1
  6. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
  7. package/deploy/sema-up/chart/templates/server.yaml +5 -2
  8. package/deploy/sema-up/stack/compose.yaml +3 -0
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  10. package/dist/adoption/plan.d.ts +3 -2
  11. package/dist/adoption/plan.js +7 -5
  12. package/dist/adoption/runner.js +3 -10
  13. package/dist/adoption/sql.d.ts +3 -2
  14. package/dist/adoption/sql.js +3 -5
  15. package/dist/adoption/wire.d.ts +4 -1
  16. package/dist/adoption/wire.js +1 -0
  17. package/dist/approval-ask-audit-store.d.ts +31 -13
  18. package/dist/approval-ask-audit-store.js +272 -74
  19. package/dist/approval-ask-machine.d.ts +15 -0
  20. package/dist/approval-ask-machine.js +6 -0
  21. package/dist/approval-ask-retention.d.ts +136 -0
  22. package/dist/approval-ask-retention.js +102 -0
  23. package/dist/approval-card.d.ts +79 -19
  24. package/dist/approval-card.js +44 -13
  25. package/dist/approval-deny-reasons.d.ts +1 -1
  26. package/dist/approval.d.ts +9 -0
  27. package/dist/approval.js +24 -3
  28. package/dist/audit.d.ts +27 -10
  29. package/dist/audit.js +12 -16
  30. package/dist/bake-runner/main.js +3 -2
  31. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  32. package/dist/boot/execution-env.js +9 -7
  33. package/dist/boot/execution-lane.js +11 -0
  34. package/dist/boot/leader.js +2 -0
  35. package/dist/boot/memory-boundary.js +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +5 -4
  37. package/dist/boot/memory-consolidation.js +7 -6
  38. package/dist/boot/org-delivery.d.ts +28 -0
  39. package/dist/boot/org-delivery.js +20 -0
  40. package/dist/boot/reapers.d.ts +3 -0
  41. package/dist/boot/reapers.js +21 -1
  42. package/dist/boot/resolve-spec.d.ts +3 -0
  43. package/dist/boot/resolve-spec.js +60 -81
  44. package/dist/boot/runner-deps.js +1 -0
  45. package/dist/boot/session-faces.d.ts +1 -0
  46. package/dist/boot/session-faces.js +4 -2
  47. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  48. package/dist/boot/stage-06-runners.d.ts +2 -0
  49. package/dist/boot/stage-06-runners.js +6 -3
  50. package/dist/boot/stage-07-capability-layer.d.ts +2 -0
  51. package/dist/boot/stage-07-capability-layer.js +24 -3
  52. package/dist/boot/stage-08-reapers.d.ts +2 -0
  53. package/dist/boot/stage-08-reapers.js +2 -2
  54. package/dist/boot/stage-09-leader.d.ts +3 -0
  55. package/dist/boot/stage-10-http-server.d.ts +3 -0
  56. package/dist/boot/stage-10-http-server.js +5 -6
  57. package/dist/boot/stores.js +2 -2
  58. package/dist/boot/web-admin.d.ts +4 -0
  59. package/dist/boot/web-admin.js +151 -63
  60. package/dist/boot/workflow-orchestration.d.ts +2 -0
  61. package/dist/boot/workflow-orchestration.js +2 -1
  62. package/dist/brain.js +8 -3
  63. package/dist/breaker-key.d.ts +6 -0
  64. package/dist/breaker-key.js +29 -0
  65. package/dist/caller-text-echo.d.ts +9 -6
  66. package/dist/caller-text-echo.js +8 -11
  67. package/dist/capabilities/center-plugins.d.ts +7 -0
  68. package/dist/capabilities/center-plugins.js +1 -1
  69. package/dist/capabilities/composite-members.d.ts +1 -1
  70. package/dist/capabilities/composite-members.js +1 -1
  71. package/dist/capabilities/execution-lane.js +2 -2
  72. package/dist/capabilities/hands-lane.d.ts +21 -0
  73. package/dist/capabilities/hands-lane.js +10 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -4
  75. package/dist/capabilities/mcp-probe.js +27 -4
  76. package/dist/capabilities/scenarios.d.ts +63 -7
  77. package/dist/capabilities/scenarios.js +39 -7
  78. package/dist/capability-facts.d.ts +34 -4
  79. package/dist/capability-facts.js +28 -2
  80. package/dist/center-config-tier.d.ts +69 -0
  81. package/dist/center-config-tier.js +42 -0
  82. package/dist/checkpoint-identity.d.ts +16 -0
  83. package/dist/checkpoint-identity.js +19 -0
  84. package/dist/config-catalog.d.ts +17 -10
  85. package/dist/config-catalog.js +43 -28
  86. package/dist/config-d-migration.d.ts +3 -1
  87. package/dist/config-d-migration.js +2 -2
  88. package/dist/config-dir-trust.d.ts +114 -0
  89. package/dist/config-dir-trust.js +388 -0
  90. package/dist/config-invariants.d.ts +18 -5
  91. package/dist/config-invariants.js +35 -3
  92. package/dist/config-provider.d.ts +6 -1
  93. package/dist/config-provider.js +6 -2
  94. package/dist/config-types.d.ts +71 -14
  95. package/dist/config-types.js +8 -0
  96. package/dist/config.d.ts +13 -0
  97. package/dist/config.js +121 -59
  98. package/dist/delegated-session-owner.d.ts +185 -0
  99. package/dist/delegated-session-owner.js +119 -0
  100. package/dist/device-protocol.d.ts +5 -3
  101. package/dist/device-protocol.js +1 -1
  102. package/dist/device-ws-hub.js +21 -2
  103. package/dist/device-ws-protocol.d.ts +70 -6
  104. package/dist/device-ws-protocol.js +50 -23
  105. package/dist/elicitation.js +3 -2
  106. package/dist/entry-problems.d.ts +34 -0
  107. package/dist/entry-problems.js +14 -0
  108. package/dist/execution-lane-caps.d.ts +49 -10
  109. package/dist/execution-lane-caps.js +43 -4
  110. package/dist/field-conflict.d.ts +43 -0
  111. package/dist/field-conflict.js +20 -0
  112. package/dist/fleet/fleet-bus.d.ts +80 -6
  113. package/dist/fleet/fleet-bus.js +41 -9
  114. package/dist/hooks/classic-stations.d.ts +10 -5
  115. package/dist/hooks/classic-stations.js +4 -4
  116. package/dist/hooks/command-spawn.d.ts +57 -0
  117. package/dist/hooks/command-spawn.js +139 -0
  118. package/dist/hooks/hook-llm.js +2 -2
  119. package/dist/hooks/hook-runner.d.ts +127 -25
  120. package/dist/hooks/hook-runner.js +668 -287
  121. package/dist/hooks/hook-source.d.ts +44 -0
  122. package/dist/hooks/hook-source.js +40 -0
  123. package/dist/http/active-run-conflict.d.ts +5 -13
  124. package/dist/http/active-run-conflict.js +17 -27
  125. package/dist/http/admission.js +103 -60
  126. package/dist/http/approval-sweeps.js +55 -18
  127. package/dist/http/caller-trust.d.ts +77 -0
  128. package/dist/http/caller-trust.js +143 -0
  129. package/dist/http/checkpoint-ctx.d.ts +16 -16
  130. package/dist/http/checkpoint-ctx.js +13 -10
  131. package/dist/http/claimed-run.d.ts +4 -3
  132. package/dist/http/device-placement.d.ts +2 -4
  133. package/dist/http/device-placement.js +2 -1
  134. package/dist/http/dispatch.js +10 -4
  135. package/dist/http/durable-run-leg.d.ts +4 -32
  136. package/dist/http/durable-run-leg.js +34 -10
  137. package/dist/http/owner-access.d.ts +56 -0
  138. package/dist/http/owner-access.js +42 -0
  139. package/dist/http/principal-gate.d.ts +48 -23
  140. package/dist/http/principal-gate.js +39 -12
  141. package/dist/http/readiness.d.ts +11 -5
  142. package/dist/http/readiness.js +10 -3
  143. package/dist/http/request-guards.d.ts +16 -23
  144. package/dist/http/request-guards.js +30 -15
  145. package/dist/http/resume-ctx-read.d.ts +2 -5
  146. package/dist/http/resume-ctx-read.js +7 -4
  147. package/dist/http/resume-legs.d.ts +3 -4
  148. package/dist/http/resume-legs.js +126 -69
  149. package/dist/http/retired-service-deps.d.ts +43 -0
  150. package/dist/http/retired-service-deps.js +18 -0
  151. package/dist/http/route-ctx.d.ts +44 -28
  152. package/dist/http/route-ctx.js +4 -0
  153. package/dist/http/route-table.js +2 -0
  154. package/dist/http/routes/a2a-serve.js +3 -4
  155. package/dist/http/routes/admin-config-refresh.js +1 -1
  156. package/dist/http/routes/admin-drain.js +1 -1
  157. package/dist/http/routes/admin-memory-optout.js +5 -1
  158. package/dist/http/routes/adoption.js +2 -9
  159. package/dist/http/routes/approvals-assistant.js +49 -48
  160. package/dist/http/routes/background.js +3 -2
  161. package/dist/http/routes/capabilities.js +20 -4
  162. package/dist/http/routes/config-catalog.d.ts +3 -3
  163. package/dist/http/routes/config-catalog.js +1 -1
  164. package/dist/http/routes/devices.js +3 -17
  165. package/dist/http/routes/diagnostics.js +5 -1
  166. package/dist/http/routes/images.js +6 -7
  167. package/dist/http/routes/leader.js +1 -8
  168. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  169. package/dist/http/routes/mcp-reconnect.js +3 -2
  170. package/dist/http/routes/memory-bundle.js +1 -1
  171. package/dist/http/routes/memory-compliance.js +1 -1
  172. package/dist/http/routes/memory-consolidation.js +1 -1
  173. package/dist/http/routes/memory-origin.js +1 -1
  174. package/dist/http/routes/memory-policy.d.ts +1 -1
  175. package/dist/http/routes/memory-policy.js +3 -2
  176. package/dist/http/routes/notify-wake.js +8 -21
  177. package/dist/http/routes/observability.js +2 -2
  178. package/dist/http/routes/principal-inbox.d.ts +39 -0
  179. package/dist/http/routes/principal-inbox.js +76 -0
  180. package/dist/http/routes/retention-ops.js +2 -9
  181. package/dist/http/routes/rules.js +5 -18
  182. package/dist/http/routes/runs.d.ts +5 -0
  183. package/dist/http/routes/runs.js +33 -106
  184. package/dist/http/routes/session-sync.js +15 -28
  185. package/dist/http/routes/sessions.js +30 -40
  186. package/dist/http/routes/side-query.js +6 -4
  187. package/dist/http/routes/tasks.js +50 -14
  188. package/dist/http/routes/workflows.d.ts +351 -0
  189. package/dist/http/routes/workflows.js +153 -119
  190. package/dist/http/send.d.ts +24 -3
  191. package/dist/http/send.js +14 -2
  192. package/dist/http/server.d.ts +17 -18
  193. package/dist/http/server.js +15 -13
  194. package/dist/http/stored-body-replay.js +17 -4
  195. package/dist/http/submit-ctx.js +4 -3
  196. package/dist/http/submit-replay.d.ts +4 -3
  197. package/dist/http/submit-replay.js +5 -4
  198. package/dist/http/wire-types.d.ts +10 -7
  199. package/dist/index.d.ts +1 -1
  200. package/dist/index.js +0 -1
  201. package/dist/lane-home-probe.d.ts +78 -9
  202. package/dist/lane-home-probe.js +78 -12
  203. package/dist/leader/endpoint.d.ts +4 -1
  204. package/dist/leader/endpoint.js +11 -10
  205. package/dist/limit-refusal.d.ts +17 -5
  206. package/dist/limit-refusal.js +4 -1
  207. package/dist/mcp-assertion-header.d.ts +31 -10
  208. package/dist/mcp-assertion-header.js +10 -3
  209. package/dist/mcp-tool-faces.js +1 -3
  210. package/dist/memory-project-layer.d.ts +37 -0
  211. package/dist/memory-project-layer.js +46 -0
  212. package/dist/memory-scope.d.ts +23 -16
  213. package/dist/memory-scope.js +24 -8
  214. package/dist/memory-sync-client.d.ts +27 -13
  215. package/dist/memory-sync-client.js +107 -18
  216. package/dist/model-entry-refusal.d.ts +9 -1
  217. package/dist/model-entry-refusal.js +6 -1
  218. package/dist/model-plane-face.d.ts +51 -0
  219. package/dist/model-plane-face.js +24 -0
  220. package/dist/model-provider.d.ts +5 -0
  221. package/dist/model-provider.js +4 -2
  222. package/dist/model-route-endpoint.d.ts +8 -11
  223. package/dist/model-route-endpoint.js +4 -7
  224. package/dist/model-route-env.d.ts +22 -0
  225. package/dist/model-route-env.js +2 -0
  226. package/dist/model-route-gap.d.ts +60 -30
  227. package/dist/model-route-gap.js +21 -15
  228. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  229. package/dist/observability/corrupt-read-seat.js +9 -0
  230. package/dist/observability/err-text.d.ts +6 -0
  231. package/dist/observability/err-text.js +18 -4
  232. package/dist/observability/fail-open.d.ts +35 -9
  233. package/dist/observability/fail-open.js +35 -9
  234. package/dist/observability/metrics.js +1 -0
  235. package/dist/observability/prompt-manifest.d.ts +3 -17
  236. package/dist/observability/prompt-manifest.js +45 -42
  237. package/dist/observability/tool-trace.js +9 -9
  238. package/dist/operator-ask.d.ts +10 -0
  239. package/dist/operator-ask.js +3 -0
  240. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  241. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  242. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  243. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  244. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  245. package/dist/orchestration/workflow-notify-journal.js +27 -34
  246. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  247. package/dist/orchestration/workflow-park-decision.js +118 -0
  248. package/dist/org-delivery-gate.d.ts +81 -0
  249. package/dist/org-delivery-gate.js +59 -0
  250. package/dist/panel-tool-names.d.ts +23 -0
  251. package/dist/panel-tool-names.js +8 -0
  252. package/dist/parked-decide.d.ts +39 -6
  253. package/dist/parked-decide.js +98 -51
  254. package/dist/parked-revive-surface.d.ts +9 -2
  255. package/dist/parked-revive-surface.js +11 -8
  256. package/dist/permission-rule-list.d.ts +1 -1
  257. package/dist/permission-rule-list.js +2 -2
  258. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  259. package/dist/plugins/approval-ask-store-file.js +21 -0
  260. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  261. package/dist/plugins/approval-ask-store-memory.js +34 -2
  262. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  263. package/dist/plugins/approval-ask-store-sql.js +37 -1
  264. package/dist/plugins/blob-backend.d.ts +38 -25
  265. package/dist/plugins/blob-backend.js +29 -19
  266. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  267. package/dist/plugins/breaker-state-sql.js +4 -4
  268. package/dist/plugins/caching-session-store.d.ts +3 -0
  269. package/dist/plugins/caching-session-store.js +3 -0
  270. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  271. package/dist/plugins/checkpoint-store-sql.js +20 -2
  272. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  273. package/dist/plugins/file-history-store-sql.js +3 -1
  274. package/dist/plugins/file-run-store.d.ts +5 -13
  275. package/dist/plugins/file-run-store.js +9 -0
  276. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  277. package/dist/plugins/fork-routing-session-store.js +12 -1
  278. package/dist/plugins/host-platform.d.ts +2 -0
  279. package/dist/plugins/host-platform.js +2 -1
  280. package/dist/plugins/local-checkpoint-store.d.ts +2 -0
  281. package/dist/plugins/local-checkpoint-store.js +6 -1
  282. package/dist/plugins/local-session-store.d.ts +1 -1
  283. package/dist/plugins/local-session-window.d.ts +39 -0
  284. package/dist/plugins/local-session-window.js +54 -0
  285. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  286. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  287. package/dist/plugins/mailbox-store-sql.js +77 -52
  288. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  289. package/dist/plugins/memory-run-store.d.ts +5 -13
  290. package/dist/plugins/memory-run-store.js +8 -0
  291. package/dist/plugins/one-time-migrations.d.ts +110 -5
  292. package/dist/plugins/one-time-migrations.js +320 -8
  293. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  294. package/dist/plugins/org-delivery-quota-store.js +228 -0
  295. package/dist/plugins/pg-pool.js +39 -7
  296. package/dist/plugins/pg-session-storage.d.ts +17 -4
  297. package/dist/plugins/pg-session-storage.js +29 -6
  298. package/dist/plugins/remote-env-adb.d.ts +6 -12
  299. package/dist/plugins/remote-env-adb.js +12 -2
  300. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  301. package/dist/plugins/remote-env-k8s.js +23 -5
  302. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  303. package/dist/plugins/remote-env-local-docker.js +12 -9
  304. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  305. package/dist/plugins/remote-env-ssh.js +17 -11
  306. package/dist/plugins/run-store-sql.d.ts +16 -14
  307. package/dist/plugins/run-store-sql.js +22 -2
  308. package/dist/plugins/schema-indexes.js +2 -0
  309. package/dist/plugins/send-user-file.js +2 -2
  310. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  311. package/dist/plugins/session-epoch-anchor.js +2 -3
  312. package/dist/plugins/session-store.d.ts +18 -8
  313. package/dist/plugins/session-store.js +15 -4
  314. package/dist/plugins/sql-driver.d.ts +74 -11
  315. package/dist/plugins/sql-driver.js +210 -12
  316. package/dist/plugins/sql-errors.d.ts +23 -0
  317. package/dist/plugins/sql-errors.js +21 -0
  318. package/dist/plugins/store-backend.d.ts +36 -1
  319. package/dist/plugins/store-backend.js +40 -14
  320. package/dist/plugins/store-contracts.d.ts +42 -0
  321. package/dist/plugins/store-contracts.js +1 -0
  322. package/dist/plugins/tidb-pool.d.ts +6 -1
  323. package/dist/plugins/tidb-pool.js +44 -10
  324. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  325. package/dist/plugins/tidb-session-storage.js +2 -2
  326. package/dist/plugins/tidb-session-store.d.ts +24 -4
  327. package/dist/plugins/tidb-session-store.js +28 -4
  328. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  329. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  330. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  331. package/dist/plugins/workflow-run-store-sql.js +50 -9
  332. package/dist/posture-source.d.ts +18 -10
  333. package/dist/posture-source.js +4 -1
  334. package/dist/project-identity.d.ts +45 -6
  335. package/dist/project-identity.js +162 -8
  336. package/dist/project-layer-key.d.ts +28 -0
  337. package/dist/project-layer-key.js +29 -0
  338. package/dist/question.js +3 -2
  339. package/dist/read-deny-entry.d.ts +25 -0
  340. package/dist/read-deny-entry.js +12 -0
  341. package/dist/read-face-posture.d.ts +37 -11
  342. package/dist/read-face-posture.js +32 -3
  343. package/dist/request-key-closure.d.ts +7 -0
  344. package/dist/request-key-closure.js +9 -1
  345. package/dist/request-value-closure.d.ts +5 -0
  346. package/dist/request-value-closure.js +11 -3
  347. package/dist/rule-entry-list.d.ts +26 -2
  348. package/dist/rule-entry-list.js +72 -12
  349. package/dist/rules-consent.js +3 -4
  350. package/dist/run-acceptance.d.ts +19 -3
  351. package/dist/run-acceptance.js +13 -1
  352. package/dist/run-local.js +36 -6
  353. package/dist/runs.d.ts +10 -9
  354. package/dist/runs.js +21 -3
  355. package/dist/runtime-caps-resolver.d.ts +16 -6
  356. package/dist/runtime-caps-resolver.js +18 -5
  357. package/dist/sandbox-pkg-source.d.ts +19 -1
  358. package/dist/sandbox-pkg-source.js +29 -7
  359. package/dist/security.d.ts +87 -9
  360. package/dist/security.js +52 -9
  361. package/dist/session-error-wire.d.ts +19 -2
  362. package/dist/session-error-wire.js +5 -1
  363. package/dist/session-titled-frame.d.ts +33 -0
  364. package/dist/session-titled-frame.js +4 -0
  365. package/dist/session-titler.d.ts +9 -1
  366. package/dist/session-titler.js +15 -4
  367. package/dist/spec-fields.js +21 -2
  368. package/dist/sql-engine-posture.d.ts +3 -1
  369. package/dist/task-cwd.d.ts +1 -1
  370. package/dist/task-mcp.d.ts +3 -3
  371. package/dist/task-mcp.js +7 -66
  372. package/dist/task-settings.d.ts +89 -88
  373. package/dist/task-settings.js +71 -63
  374. package/dist/text-bidi.d.ts +0 -4
  375. package/dist/text-bidi.js +1 -3
  376. package/dist/tighten-only-seats.d.ts +150 -0
  377. package/dist/tighten-only-seats.js +196 -0
  378. package/dist/tool-approval.d.ts +303 -87
  379. package/dist/tool-approval.js +623 -239
  380. package/dist/trace/core-keyset-guard.d.ts +104 -22
  381. package/dist/trace/exit-seat.d.ts +28 -0
  382. package/dist/trace/exit-seat.js +76 -0
  383. package/dist/trace/ledger-events.d.ts +3 -2
  384. package/dist/trace/project.d.ts +80 -12
  385. package/dist/trace/project.js +41 -28
  386. package/dist/trace/projection-table.d.ts +100 -0
  387. package/dist/trace/projection-table.js +130 -0
  388. package/dist/trace/redact.d.ts +54 -0
  389. package/dist/trace/redact.js +778 -152
  390. package/dist/trace/self-minted-wire.d.ts +345 -1
  391. package/dist/trace/self-minted-wire.js +117 -1
  392. package/dist/trace/wire-projection-faces.d.ts +298 -2
  393. package/dist/trace/wire-projection-faces.js +154 -13
  394. package/dist/url-refusal.js +2 -0
  395. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  396. package/dist/web-admin-client/apply-effective.js +56 -5
  397. package/dist/web-admin-client/facade.d.ts +1 -1
  398. package/dist/web-admin-client/facade.js +1 -1
  399. package/dist/web-admin-client/read-face.js +5 -9
  400. package/dist/web-admin-client/skills-mcp.js +4 -5
  401. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  402. package/package.json +7 -3
@@ -11,17 +11,50 @@
11
11
  * journal is LOAD-BEARING (unlike the best-effort {@link import("./run-store-sql.js")} observation layer):
12
12
  * append/load failures MUST throw — a silently-dropped entry would silently re-run already-done work.
13
13
  *
14
- * Schema = `workflow_journal (run_id, ordinal) PK` (DDL in tidb-pool.ts / pg-pool.ts ensureSchema). `ordinal`
15
- * is the positional index a callKey was minted at, derived with core's {@link callKeyOrdinal} (NEVER
14
+ * Schema = two tables (DDL in tidb-pool.ts / pg-pool.ts ensureSchema), the SQL normal form of core's per-run
15
+ * record (InMemory `{ scope, byOrdinal }` / File "first line names the scope, one line per ordinal"):
16
+ * - `workflow_journal_owner (run_id) PK` — the run's ONE owning scope, the `incarnation` token minted by the claim
17
+ * that wrote the record, and `appended_at_ms` (the owner's last append; the GC clock of this row). Written by the
18
+ * run's first append; never a second scope (S-197, below).
19
+ * - `workflow_journal (run_id, incarnation, ordinal) PK` — one row per recorded call, keyed by the owner record that
20
+ * wrote it. NO scope column: a run's scope lives in exactly one place, so "one run, two scopes" is not a state this
21
+ * schema can hold.
22
+ * `ordinal` is the positional index a callKey was minted at, derived with core's {@link callKeyOrdinal} (NEVER
16
23
  * hand-parsed — a malformed key falls back to ordinal 0 there, matching the InMemory store). `result` holds
17
24
  * `JSON.stringify(entry.result)` (a TaskResult is plain serializable data). The PK makes append IDEMPOTENT per
18
- * (run_id, ordinal): a second append for the same ordinal OVERWRITES (last-write-wins) — a resumed run
19
- * re-appends its replayed prefix verbatim, so this is the exact InMemory `byOrdinal.set(ordinal, …)` semantics,
20
- * not a duplicate row.
25
+ * (run_id, ordinal) under the run's owner record (a run has one at a time): a second append for the same ordinal
26
+ * OVERWRITES (last-write-wins) — a resumed run re-appends its replayed prefix verbatim, so this is the exact InMemory
27
+ * `byOrdinal.set(ordinal, …)` semantics, not a duplicate row.
28
+ *
29
+ * 🔴 **The scope wall — ONE predicate, both faces** (S-197; core `workflowJournalStoreContract` "scope wall" case;
30
+ * core's InMemory / File stores refuse the same way): a run's journal belongs to the scope of its first append. Every
31
+ * face answers through the owner row and the same pair `workflow_journal_owner.run_id = ? AND scope = ?`:
32
+ * - `append` writes its row through `INSERT … SELECT … FROM workflow_journal_owner WHERE run_id = ? AND scope = ?` —
33
+ * the judge sits in the WRITE's own WHERE (no read-then-write), and `affected = 0` ⇒ refuse, nothing written; the
34
+ * row takes its `incarnation` from that same owner record, in that same statement;
35
+ * - `load` / `loadPage` read through `JOIN workflow_journal_owner USING (run_id, incarnation)` + the same
36
+ * `run_id = ? AND scope = ?`.
37
+ * Why the owner record's incarnation is in the row's PRIMARY KEY (one rule: **a row belongs to the owner record that
38
+ * wrote it — readable only through it, overwritable only by a write made through it**): a write can outlive the owner
39
+ * record it was judged against — `deleteByRun` / the GC sweep racing an append, an append stalled between its two
40
+ * statements. Keyed and joined on run_id alone, (a) a leftover row would be adopted by the next claim of that run_id by
41
+ * ANY scope, and (b) on PostgreSQL READ COMMITTED a stale guarded write whose SELECT still saw the old record would, via
42
+ * the ON CONFLICT arm (which updates a row its snapshot never saw, without re-checking the source), overwrite the NEW
43
+ * owner's row at the same ordinal. With the incarnation in the key, the stale write lands on a key of the dead record —
44
+ * a row readable by no one, removed by the sweep — and the new record's rows are untouchable by it, on every engine,
45
+ * without a locking clause.
46
+ * Before S-197 the scope sat on every row: the read faces filtered it, the write face never judged it (a cross-scope
47
+ * append on a NEW ordinal inserted a second scope's row into the run; on an EXISTING ordinal it overwrote the owner's
48
+ * result under the owner's scope), held up by "runId is unique per scope and the owner is the sole writer".
21
49
  *
22
50
  * ── Dialect deltas, kept EXPLICIT ────────────────────────────────────────────────────────────────────
23
- * - `?` placeholders vs `$n`
24
- * - `ON DUPLICATE KEY UPDATE … VALUES(col)` vs `ON CONFLICT (run_id, ordinal) DO UPDATE SET … EXCLUDED.col`
51
+ * - `?` placeholders vs `$n` (`append`'s two statements bind ONE params array on both dialects: PG numbers its
52
+ * placeholders in text order there, so no per-dialect reordering exists on that face)
53
+ * - `ON DUPLICATE KEY UPDATE … VALUES(col)` vs `ON CONFLICT (run_id, incarnation, ordinal) DO UPDATE SET … EXCLUDED.col`
54
+ * - the owner claim's "refresh only for the owner": MySQL protocol `IF(scope = VALUES(scope), …)` inside
55
+ * `ON DUPLICATE KEY UPDATE` (no WHERE there) vs PG `ON CONFLICT (run_id) DO UPDATE … WHERE workflow_journal_owner.scope = EXCLUDED.scope`
56
+ * - PG casts the guarded write's bound values (`$1::int` …): they sit in a SELECT list, where an untyped
57
+ * parameter has no column to take its type from
25
58
  * - `LENGTH(result)` (TiDB — byte length) vs `OCTET_LENGTH(result)` (PG — `LENGTH(text)` is CHARACTER count;
26
59
  * a multi-byte UTF-8 payload would silently slip past the byte-budget cap on PG with plain `LENGTH`, so
27
60
  * the twin uses the byte-counting function instead — codex R3, real dual-DB-suite-confirmed) in `loadPage`'s
@@ -31,8 +64,14 @@
31
64
  * `$n` are POSITIONAL (not text-order), so its array is `[runId, scope, maxResultBytes, limit, offset]`
32
65
  * (`$3` inside the CASE refers to the third element regardless of where `$3` sits in the text). Same
33
66
  * placeholder-arity-fallout class the sql-driver.ts header calls out for null-safe compares.
34
- * - affectedRows vs rowCount (via SqlDriver's normalized `affected`).
35
- * - schema ownership: TiDB DDL in tidb-pool.ts, PG DDL in pg-pool.ts (neither store creates tables).
67
+ * - affectedRows vs rowCount (via SqlDriver's normalized `affected`). The guarded write reads it as the wall's
68
+ * verdict: 0 ⇔ the SELECT found no owner row for (run_id, scope). A matched row always counts ≥ 1 — PG 1;
69
+ * MySQL protocol 1 (insert) / 2 (update) / 1 for a duplicate left unchanged, because mysql2 connects with
70
+ * CLIENT_FOUND_ROWS by default (TiDB answers 1 there too) — pinned by the identical-bytes re-append cell of
71
+ * workflow-journal-store-integration.test.ts.
72
+ * - schema ownership: TiDB DDL in tidb-pool.ts, PG DDL in pg-pool.ts (neither store creates tables); the 7.132.x
73
+ * row-level scope column is moved into the owner table by the one-time migration M24 (window in
74
+ * plugins/one-time-migrations.ts).
36
75
  */
37
76
  import type { Pool as MySqlPool } from "mysql2/promise";
38
77
  import type { Pool as PgPool } from "pg";
@@ -54,21 +93,24 @@ export declare class SqlWorkflowJournalStore implements WorkflowJournalStore {
54
93
  constructor(db: SqlDriver, opts?: SqlWorkflowJournalStoreOptions);
55
94
  /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
56
95
  private q;
57
- /** Record one agent's result. Idempotent per (run_id, ordinal): the PK upsert OVERWRITES (last-write-wins), so a
96
+ /** Record one agent's result. Idempotent per (run_id, ordinal) under the run's owner record: the PK upsert OVERWRITES (last-write-wins), so a
58
97
  * resumed run re-appending its replayed prefix verbatim leaves no duplicate. callKeyOrdinal is the SOLE ordinal
59
98
  * source (core's parser — a malformed key collapses to 0, matching InMemory). Throws on a REAL failure
60
- * (LOAD-BEARING) — an oversize entry included, on BOTH arms (S-194 / core 7.12.0 [ref]; see below). */
99
+ * (LOAD-BEARING) — an oversize entry included, on BOTH arms (S-194 / core 7.12.0 [ref]; see below) — and REFUSES an
100
+ * append under a scope other than the run's owner, on any ordinal and both arms, writing nothing (S-197; the scope
101
+ * wall in the file header). */
61
102
  append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
62
- /** Entries for `runId` IF its recorded scope === `scope`, ASCENDING by ordinal; otherwise EMPTY. CORE-9 audit
63
- * BLOCKER: scope in the WHERE (twin of WorkflowRunStore) — the LLM controls `resumeFromRunId`, so a cross-scope
64
- * runId resolves to an empty journal (resume safely diverges to live, never discloses another tenant's results).
65
- * Each row's `result` is the persisted JSON.stringify(TaskResult) → JSON.parse back. Throws on failure (LOAD-BEARING). */
103
+ /** Entries for `runId` IF its owner scope === `scope`, ASCENDING by ordinal; otherwise EMPTY. CORE-9 audit
104
+ * BLOCKER: the scope wall (S-197: the owner row, the same predicate append writes through) — the LLM controls
105
+ * `resumeFromRunId`, so a cross-scope runId resolves to an empty journal (resume safely diverges to live, never
106
+ * discloses another tenant's results). Each row's `result` is the persisted JSON.stringify(TaskResult) → JSON.parse
107
+ * back. Throws on failure (LOAD-BEARING). */
66
108
  load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
67
109
  /** [ref] locator 云形(core 1.353 WorkflowJournalStore.locator additive):SQL 店的 journal 坐标=
68
110
  * [ref] HTTP 读面路由(file 店返 jsonl 路径=CC 本地形;此处返云形,core 铸 diagnostics 教句时单源引用)。 */
69
111
  locator(runId: string, _scope: string): string;
70
112
  /** codex R1-H2:分页投影读——LIMIT/OFFSET 界行数,字节门界单行字节(超限行不取 result 全文,只回字节数;
71
- * 读面标 truncated)。ORDER BY ordinal 与 load 同轴。
113
+ * 读面标 truncated)。ORDER BY ordinal 与 load 同轴;scope 墙与 load 同一只 JOIN(S-197)。
72
114
  * 🔴 param ORDER differs by dialect (see file header): TiDB's `?`s bind in text order (maxResultBytes first,
73
115
  * inside the CASE); PG's `$n` are positional, so its array keeps runId/scope first. Never unify — a swap
74
116
  * silently mis-binds the OTHER dialect's placeholders. */
@@ -81,8 +123,10 @@ export declare class SqlWorkflowJournalStore implements WorkflowJournalStore {
81
123
  resultJson: string | null;
82
124
  resultBytes: number;
83
125
  }>>;
84
- /** GC — purge ALL journal entries for a run (the run reaper calls this once the run is terminal + retained). Scoped
85
- * by run_id alone; idempotent (affected-row count). Mirrors the resume-anchor store's deleteBySession. */
126
+ /** GC — purge ALL journal entries for a run (every incarnation), then its owner row (S-197). Scoped by run_id alone;
127
+ * idempotent (returns the journal rows removed). Mirrors the resume-anchor store's deleteBySession. An append racing the
128
+ * two DELETEs can leave a row behind without its owner record; it is keyed by the dead incarnation — readable and
129
+ * overwritable by no one (file header) — and the GC sweep removes it by age. */
86
130
  deleteByRun(runId: string): Promise<number>;
87
131
  /** [ref] / WF2([ref] core 拍板 a 形,签名逐字)—— cross-replica resume admission lease。
88
132
  *
@@ -111,7 +155,17 @@ export declare class SqlWorkflowJournalStore implements WorkflowJournalStore {
111
155
  * per-run deleteByRun has no production caller (a run-store reap gives no per-run hook), so this bounded sweep
112
156
  * — wired into the service reaper — is what keeps the heaviest table (TaskResult-bearing) from growing without
113
157
  * bound. A resume of a journal older than the retention window simply re-runs live (resume is an optimization).
114
- * Idempotent; returns the rows purged. */
158
+ * Idempotent; returns the journal rows purged.
159
+ *
160
+ * S-197: the same cutoff then purges owner rows whose OWN clock (`appended_at_ms`, pushed by every append of the
161
+ * owner) is older. Each DELETE's predicate reads only the row it deletes — a cross-table "owner with no rows left"
162
+ * test inside the DELETE would be a non-current read on every engine here (the B-022 lesson: TiDB pins start_ts, PG
163
+ * READ COMMITTED re-checks only the target row), so a concurrent append could have its owner deleted from under it.
164
+ * Every row of a run was written by an append that first pushed the owner's clock (monotonically — GREATEST) to at least
165
+ * that row's `created_at_ms`, so an owner past the cutoff normally has no row left after the first DELETE. The one
166
+ * exception — an append whose guarded write lands after the first DELETE although its claim is older than the cutoff (a
167
+ * stall longer than the retention window between its two statements) — leaves a row without its owner record: keyed by
168
+ * the dead incarnation, readable by no one (file header), removed by a later sweep. */
115
169
  reapExpired(now: number, maxAgeMs: number): Promise<number>;
116
170
  }
117
171
  /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
@@ -1,6 +1,7 @@
1
1
  import { callKeyOrdinal } from "@sema-agent/core";
2
2
  import { assertJournalEntryFits } from "@sema-agent/core";
3
3
  import { journalEntryOfBody, journalRowPayloadOf, readJournalRowBody } from "../orchestration/workflow-journal-entry.js";
4
+ import { randomUUID } from "node:crypto";
4
5
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
5
6
  import { decodeJsonColumn, foldJsonColumn, jsonColumnText, jsonColumnTextExpr } from "./sql-json-column.js";
6
7
  const DEFAULT_RESUME_CLAIM_TTL_MS = 60 * 60 * 1000;
@@ -25,12 +26,21 @@ export class SqlWorkflowJournalStore {
25
26
  async append(runId, scope, entry) {
26
27
  assertJournalEntryFits(entry);
27
28
  const serialized = journalRowPayloadOf(entry);
28
- await this.db.query(this.q("INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at_ms) VALUES (?,?,?,?,?,?) " +
29
- "ON DUPLICATE KEY UPDATE call_key = VALUES(call_key), result = VALUES(result)", "INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at_ms) VALUES ($1,$2,$3,$4,$5,$6) " +
30
- "ON CONFLICT (run_id, ordinal) DO UPDATE SET call_key = EXCLUDED.call_key, result = EXCLUDED.result"), [runId, callKeyOrdinal(entry.callKey), scope, entry.callKey, serialized, Date.now()]);
29
+ const now = Date.now();
30
+ await this.db.query(this.q("INSERT INTO workflow_journal_owner (run_id, scope, incarnation, appended_at_ms) VALUES (?,?,?,?) " +
31
+ "ON DUPLICATE KEY UPDATE appended_at_ms = IF(scope = VALUES(scope), GREATEST(appended_at_ms, VALUES(appended_at_ms)), appended_at_ms)", "INSERT INTO workflow_journal_owner (run_id, scope, incarnation, appended_at_ms) VALUES ($1,$2,$3,$4) " +
32
+ "ON CONFLICT (run_id) DO UPDATE SET appended_at_ms = GREATEST(workflow_journal_owner.appended_at_ms, EXCLUDED.appended_at_ms) WHERE workflow_journal_owner.scope = EXCLUDED.scope"), [runId, scope, randomUUID(), now]);
33
+ const written = await this.db.query(this.q("INSERT INTO workflow_journal (run_id, incarnation, ordinal, call_key, result, created_at_ms) " +
34
+ "SELECT run_id, incarnation, ?, ?, ?, ? FROM workflow_journal_owner WHERE run_id = ? AND scope = ? " +
35
+ "ON DUPLICATE KEY UPDATE call_key = VALUES(call_key), result = VALUES(result)", "INSERT INTO workflow_journal (run_id, incarnation, ordinal, call_key, result, created_at_ms) " +
36
+ "SELECT run_id, incarnation, $1::int, $2::varchar, $3::text, $4::bigint FROM workflow_journal_owner WHERE run_id = $5 AND scope = $6 " +
37
+ "ON CONFLICT (run_id, incarnation, ordinal) DO UPDATE SET call_key = EXCLUDED.call_key, result = EXCLUDED.result"), [callKeyOrdinal(entry.callKey), entry.callKey, serialized, now, runId, scope]);
38
+ if (written.affected === 0) {
39
+ throw new Error(`workflow journal: runId ${runId} already recorded under a different scope — refusing the append; nothing was written`);
40
+ }
31
41
  }
32
42
  async load(runId, scope) {
33
- const { rows } = await this.db.query(this.q(`SELECT call_key, ${jsonColumnText("tidb", "result")} FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC`, `SELECT call_key, ${jsonColumnText("pg", "result")} FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC`), [runId, scope]);
43
+ const { rows } = await this.db.query(this.q(`SELECT call_key, ${jsonColumnText("tidb", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC`, `SELECT call_key, ${jsonColumnText("pg", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC`), [runId, scope]);
34
44
  return rows.map((r) => {
35
45
  const callKey = String(r.call_key);
36
46
  return journalEntryOfBody(callKey, decodeJournalResult(r.result, `workflow_journal.result for run ${runId} entry ${callKey}`));
@@ -43,11 +53,12 @@ export class SqlWorkflowJournalStore {
43
53
  const params = this.db.dialect === "tidb"
44
54
  ? [opts.maxResultBytes, runId, scope, opts.limit, opts.offset]
45
55
  : [runId, scope, opts.maxResultBytes, opts.limit, opts.offset];
46
- const { rows } = await this.db.query(this.q(`SELECT call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE ${jsonColumnTextExpr("tidb", "result")} END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC LIMIT ? OFFSET ?`, `SELECT call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE ${jsonColumnTextExpr("pg", "result")} END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC LIMIT $4 OFFSET $5`), params);
56
+ const { rows } = await this.db.query(this.q(`SELECT call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE ${jsonColumnTextExpr("tidb", "result")} END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC LIMIT ? OFFSET ?`, `SELECT call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE ${jsonColumnTextExpr("pg", "result")} END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC LIMIT $4 OFFSET $5`), params);
47
57
  return rows.map((r) => ({ callKey: String(r.call_key), resultJson: r.result_json === null ? null : String(r.result_json), resultBytes: Number(r.result_bytes) }));
48
58
  }
49
59
  async deleteByRun(runId) {
50
60
  const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE run_id = ?", "DELETE FROM workflow_journal WHERE run_id = $1"), [runId]);
61
+ await this.db.query(this.q("DELETE FROM workflow_journal_owner WHERE run_id = ?", "DELETE FROM workflow_journal_owner WHERE run_id = $1"), [runId]);
51
62
  return res.affected;
52
63
  }
53
64
  async resumeClaim(input) {
@@ -72,7 +83,9 @@ export class SqlWorkflowJournalStore {
72
83
  await this.db.query(this.q("DELETE FROM workflow_resume_claim WHERE source_run_id = ? AND scope = ? AND new_run_id = ?", "DELETE FROM workflow_resume_claim WHERE source_run_id = $1 AND scope = $2 AND new_run_id = $3"), [input.sourceRunId, input.scope, input.newRunId]);
73
84
  }
74
85
  async reapExpired(now, maxAgeMs) {
75
- const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE created_at_ms < ?", "DELETE FROM workflow_journal WHERE created_at_ms < $1"), [now - maxAgeMs]);
86
+ const cutoff = now - maxAgeMs;
87
+ const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE created_at_ms < ?", "DELETE FROM workflow_journal WHERE created_at_ms < $1"), [cutoff]);
88
+ await this.db.query(this.q("DELETE FROM workflow_journal_owner WHERE appended_at_ms < ?", "DELETE FROM workflow_journal_owner WHERE appended_at_ms < $1"), [cutoff]);
76
89
  return res.affected;
77
90
  }
78
91
  }
@@ -56,10 +56,12 @@
56
56
  */
57
57
  import type { Pool as MySqlPool } from "mysql2/promise";
58
58
  import type { Pool as PgPool } from "pg";
59
- import { summarizeWorkflowRun, type WorkflowRunStore } from "@sema-agent/core";
59
+ import { summarizeWorkflowRun, type CheckpointToken, type WorkflowRunStore } from "@sema-agent/core";
60
60
  import { type WorkflowCompletionInbox, type WorkflowCompletionInboxEntry } from "../orchestration/workflow-completion-inbox.js";
61
61
  import type { WorkflowNotifyJournalStore, WorkflowNotifyJournalEntry } from "../orchestration/workflow-notify-journal.js";
62
62
  import { type WorkflowAgentSessionIndex, type WorkflowAgentSessionRow } from "../orchestration/workflow-agent-session-index.js";
63
+ import { type WorkflowParkDecision, type WorkflowParkDecisionStore } from "../orchestration/workflow-park-decision.js";
64
+ import type { SqlRunStore } from "./run-store-sql.js";
63
65
  import { type SqlDriver } from "./sql-driver.js";
64
66
  type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
65
67
  /** Row cap guard: a run blob beyond this is NOT stored on update (the prior revision stays) — TiDB's
@@ -189,23 +191,43 @@ export declare class SqlWorkflowNotifyJournalStore implements WorkflowNotifyJour
189
191
  * 收割在删 run 行的同一个方法里按同一张待删 id 表删它们的指针({@link SqlWorkflowRunStore.reap})。
190
192
  *
191
193
  * ── 方言差,保持显式 ──────────────────────────────────────────────────────────────────────────
192
- * - `?` 占位 vs `$n`,且 `syncRun` 的删除腿在 PG 上要按 `params.length` 记位(与 `listByScope` 同因)。
193
- * - 「删掉这条 run 里已不在 rows 里的旧行」:TiDB `session_id NOT IN (?,?,…)`(动态占位数)vs
194
- * PG `session_id <> ALL($n::text[])`(数组绑定)—— 两边同一件事,语句结构不同(reap 的批删同先例)。
194
+ * - `?` 占位 vs `$n`。
195
195
  * - upsert(主键 `(session_id, run_id)`):TiDB `REPLACE INTO`(整行替换)vs PG `INSERT … ON CONFLICT (session_id, run_id) DO UPDATE`。
196
196
  * - schema 属主同店内其余表:TiDB DDL 在 tidb-pool.ts,PG DDL 在 pg-pool.ts(店不建表)。
197
197
  *
198
- * 🔴 **写序**:先 upsert 再删。反过来(先删后 upsert)会在两条语句之间留出一个**指针不存在**的窗口,
199
- * 而这期间进来的 `/decide` 会诚实 miss ⇒ 一次本可受理的批准被退回。先 upsert 的最坏形是窗口内多一枚
200
- * 旧指针(判决从记录读 ⇒ 多读一条记录,不会错配)。
198
+ * 指针只增不删(契约「指针先于记录」):本店没有删除腿,唯一的删除是 {@link SqlWorkflowRunStore.reap} 的级联。
201
199
  */
202
200
  export declare class SqlWorkflowAgentSessionIndex implements WorkflowAgentSessionIndex {
203
201
  private readonly db;
204
202
  constructor(db: SqlDriver);
205
203
  private q;
206
- syncRun(runId: string, rows: readonly WorkflowAgentSessionRow[]): Promise<void>;
204
+ ensurePointers(_runId: string, rows: readonly WorkflowAgentSessionRow[]): Promise<void>;
207
205
  listByAgentSession(sessionId: string): Promise<WorkflowAgentSessionRow[]>;
208
206
  }
207
+ /**
208
+ * workflow 子代停驻卡的耐久决定行 —— SQL 双生(契约与律的唯一属主 =
209
+ * {@link import("../orchestration/workflow-park-decision.js").WorkflowParkDecisionStore} 的文件头,这里只记方言差)。
210
+ * 写者只有 `/decide` 的 workflow 车道(开跑之前那一笔);删除只有 run 收割的级联({@link SqlWorkflowRunStore.reap})。
211
+ *
212
+ * 构造时绑在 SQL run 店上(`claims`):写在那只 run 店的认领栅栏事务里(`SqlRunStore.whileHoldingClaim` 交出的那条事务连接 —— 栅栏攥着
213
+ * run 行与认领行的锁直到提交),读走那只 run 店的池(`claims.db`)—— 决定行、认领、读写同一只池,池只有这一处来源。
214
+ *
215
+ * ── 方言差,保持显式 ──────────────────────────────────────────────────────────────────────────
216
+ * - `?` 占位 vs `$n`。
217
+ * - 整行 upsert(主键 `token`):TiDB `INSERT … ON DUPLICATE KEY UPDATE col = VALUES(col)` vs PG `INSERT … ON CONFLICT (token) DO UPDATE SET col = EXCLUDED.col`。
218
+ * 无条件:序由认领栅栏定(同一宿主会话上的受理由认领串行,栅栏事务的提交序就是受理序),`accepted_at_ms` 只是记下的值。
219
+ * - `outcome` 是 JSON 承载的文本列(TiDB MEDIUMTEXT / PG TEXT,与 `workflow_run.run` 同形:两方言同一条 `JSON.stringify` 绑定),
220
+ * 读腿按文本投影、恰好解一次({@link jsonColumnText} / {@link decodeJsonColumn})。
221
+ * - schema 属主同店内其余表:TiDB DDL 在 tidb-pool.ts,PG DDL 在 pg-pool.ts(店不建表)。
222
+ */
223
+ export declare class SqlWorkflowParkDecisionStore implements WorkflowParkDecisionStore {
224
+ readonly claims: SqlRunStore;
225
+ constructor(claims: SqlRunStore);
226
+ private q;
227
+ record(d: WorkflowParkDecision): Promise<boolean>;
228
+ get(token: CheckpointToken): Promise<WorkflowParkDecision | null>;
229
+ listByHostSession(hostSessionId: string): Promise<WorkflowParkDecision[]>;
230
+ }
209
231
  /** MySQL-protocol (TiDB) bindings — historical class names preserved; `onWarn` is an ADDITIVE optional 2nd
210
232
  * ctor arg (existing 1-arg call sites are unaffected — see {@link SqlWorkflowRunStore}'s C5 trace channel). */
211
233
  export declare class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
@@ -2,6 +2,7 @@ import { redactErrorMessage, redactHead } from "../observability/run-terminal.js
2
2
  import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
3
3
  import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
4
4
  import {} from "../orchestration/workflow-agent-session-index.js";
5
+ import { readWorkflowParkOutcome } from "../orchestration/workflow-park-decision.js";
5
6
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
6
7
  import { createObjectColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText } from "./sql-json-column.js";
7
8
  import { isDupKeyError } from "./sql-errors.js";
@@ -161,6 +162,7 @@ export class SqlWorkflowRunStore {
161
162
  return 0;
162
163
  const res = await this.db.query(this.q(`DELETE FROM workflow_run WHERE scope = ? AND id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_run WHERE scope = $1 AND id = ANY($2::text[])"), this.db.dialect === "tidb" ? [scope, ...doomed] : [scope, doomed]);
163
164
  await this.db.query(this.q(`DELETE FROM workflow_agent_session WHERE scope = ? AND run_id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_agent_session WHERE scope = $1 AND run_id = ANY($2::text[])"), this.db.dialect === "tidb" ? [scope, ...doomed] : [scope, doomed]);
165
+ await this.db.query(this.q(`DELETE FROM workflow_park_decision WHERE run_id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_park_decision WHERE run_id = ANY($1::text[])"), this.db.dialect === "tidb" ? [...doomed] : [doomed]);
164
166
  return res.affected;
165
167
  }
166
168
  }
@@ -309,25 +311,64 @@ export class SqlWorkflowAgentSessionIndex {
309
311
  q(tidb, pg) {
310
312
  return this.db.dialect === "tidb" ? tidb : pg;
311
313
  }
312
- async syncRun(runId, rows) {
314
+ async ensurePointers(_runId, rows) {
313
315
  for (const r of rows) {
314
316
  await this.db.query(this.q("REPLACE INTO workflow_agent_session (session_id, run_id, agent_key, scope) VALUES (?,?,?,?)", `INSERT INTO workflow_agent_session (session_id, run_id, agent_key, scope) VALUES ($1,$2,$3,$4)
315
317
  ON CONFLICT (session_id, run_id) DO UPDATE SET agent_key = EXCLUDED.agent_key, scope = EXCLUDED.scope`), [r.sessionId, r.runId, r.agentKey, r.scope]);
316
318
  }
317
- const keep = rows.map((r) => r.sessionId);
318
- if (this.db.dialect === "tidb") {
319
- const holes = keep.map(() => "?").join(",");
320
- await this.db.query(`DELETE FROM workflow_agent_session WHERE run_id = ?${keep.length > 0 ? ` AND session_id NOT IN (${holes})` : ""}`, [runId, ...keep]);
321
- }
322
- else {
323
- await this.db.query(`DELETE FROM workflow_agent_session WHERE run_id = $1${keep.length > 0 ? " AND session_id <> ALL($2::text[])" : ""}`, keep.length > 0 ? [runId, keep] : [runId]);
324
- }
325
319
  }
326
320
  async listByAgentSession(sessionId) {
327
321
  const { rows } = await this.db.query(this.q("SELECT session_id, run_id, agent_key, scope FROM workflow_agent_session WHERE session_id = ? ORDER BY run_id", "SELECT session_id, run_id, agent_key, scope FROM workflow_agent_session WHERE session_id = $1 ORDER BY run_id"), [sessionId]);
328
322
  return rows.map((r) => ({ sessionId: String(r.session_id), runId: String(r.run_id), agentKey: String(r.agent_key), scope: String(r.scope) }));
329
323
  }
330
324
  }
325
+ function readParkDecisionOutcomeColumn(v, where) {
326
+ try {
327
+ return decodeJsonColumn(v, readWorkflowParkOutcome, `workflow_park_decision.outcome ${where}`);
328
+ }
329
+ catch (e) {
330
+ throw foldJsonColumn(e, (m) => `SqlWorkflowParkDecisionStore: ${m} — refusing rather than hand the engine a decision nobody can read`);
331
+ }
332
+ }
333
+ export class SqlWorkflowParkDecisionStore {
334
+ claims;
335
+ constructor(claims) {
336
+ this.claims = claims;
337
+ }
338
+ q(tidb, pg) {
339
+ return this.claims.db.dialect === "tidb" ? tidb : pg;
340
+ }
341
+ async record(d) {
342
+ const fenced = await this.claims.whileHoldingClaim(d.hostSessionId, d.deliveredTaskId, async (tx) => {
343
+ await tx.query(this.q(`INSERT INTO workflow_park_decision (token, run_id, host_session_id, outcome, fingerprint, delivered_task_id, accepted_at_ms) VALUES (?,?,?,?,?,?,?)
344
+ ON DUPLICATE KEY UPDATE ${PARK_DECISION_REPLACED_COLUMNS.map((c) => `${c} = VALUES(${c})`).join(", ")}`, `INSERT INTO workflow_park_decision (token, run_id, host_session_id, outcome, fingerprint, delivered_task_id, accepted_at_ms) VALUES ($1,$2,$3,$4,$5,$6,$7)
345
+ ON CONFLICT (token) DO UPDATE SET ${PARK_DECISION_REPLACED_COLUMNS.map((c) => `${c} = EXCLUDED.${c}`).join(", ")}`), [d.token, d.runId, d.hostSessionId, JSON.stringify(d.outcome), d.fingerprint, d.deliveredTaskId, d.acceptedAtMs]);
346
+ });
347
+ return fenced.held;
348
+ }
349
+ async get(token) {
350
+ const { rows } = await this.claims.db.query(this.q(`SELECT token, run_id, host_session_id, ${jsonColumnText("tidb", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE token = ?`, `SELECT token, run_id, host_session_id, ${jsonColumnText("pg", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE token = $1`), [token]);
351
+ const r = rows[0];
352
+ return r ? rowToParkDecision(r) : null;
353
+ }
354
+ async listByHostSession(hostSessionId) {
355
+ const { rows } = await this.claims.db.query(this.q(`SELECT token, run_id, host_session_id, ${jsonColumnText("tidb", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE host_session_id = ?`, `SELECT token, run_id, host_session_id, ${jsonColumnText("pg", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE host_session_id = $1`), [hostSessionId]);
356
+ return rows.map(rowToParkDecision);
357
+ }
358
+ }
359
+ const PARK_DECISION_REPLACED_COLUMNS = ["run_id", "host_session_id", "outcome", "fingerprint", "delivered_task_id", "accepted_at_ms"];
360
+ function rowToParkDecision(r) {
361
+ const token = String(r.token);
362
+ return {
363
+ token,
364
+ runId: String(r.run_id),
365
+ hostSessionId: String(r.host_session_id),
366
+ outcome: readParkDecisionOutcomeColumn(r.outcome, `for run ${String(r.run_id)}`),
367
+ fingerprint: String(r.fingerprint),
368
+ deliveredTaskId: String(r.delivered_task_id),
369
+ acceptedAtMs: Number(r.accepted_at_ms),
370
+ };
371
+ }
331
372
  export class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
332
373
  constructor(pool, onWarn) {
333
374
  super(mysqlDriver(pool), onWarn);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * S-167 → S-178 —— **一个部署级旋钮的值是谁定的**,四词闭集 + 一句运维指路。
2
+ * S-167 → S-178 → S-1492 —— **一个部署级旋钮的值是谁定的**,五词闭集 + 一句运维指路。
3
3
  *
4
4
  * ## 为什么是一个共享件而不是每键一份
5
5
  *
@@ -10,7 +10,10 @@
10
10
  * ——四词表抄第二遍的那天,两份表会在加词时分家,而「这个值是谁定的」在运维面上是个是非题,不能有两个答案。
11
11
  *
12
12
  * ## 优先序(与 `config.ts` 的 `postureOn` 同形)
13
- * `env 显式` > `center 发布` > `posture 派生` > `引擎/服务端默认`。
13
+ * `env 显式` > `center 发布` > `车道声明` > `posture 派生` > `引擎/服务端默认`。
14
+ * `lane`(S-1492,[ref])= **执行车道的契约行替运维声明了这个旋钮的缺省**(目前只有 `READ_FACE`:隔离型车道的读面缺省 `open`,声明在 `execution-lane-caps.ts`)。
15
+ * 它是出处,不是表态:运维从没写过这个键,所以与 `posture` 一样不算「有人要求过」({@link isExplicitPosture});比 `posture` 优先,因为车道声明比整机 posture 更具体
16
+ * (同一台单机上 `e2b` 沙箱里的读面缺省 `open`,理由是沙箱边界而不是「一个可信主人」)。
14
17
  *
15
18
  * ## 定界
16
19
  * 本模块只说**来源**。值本身、以及**两条键专属臂**的句子——「这台机器凭什么派生出这个值」
@@ -24,9 +27,9 @@
24
27
  * `read-face` 重启片也不再为它签理由)——运维照那句话去滚动重启一片 fleet,重启的是一个已经生效的变更。
25
28
  * 生效时机是**键**的事实,不是四词表的属性,所以它跟 `postureMeaning` 一样归键。
26
29
  */
27
- /** 旋钮取值来源的闭集词表(顺序 = 优先序)。 */
28
- export declare const POSTURE_SOURCE_WORDS: readonly ["env", "center", "posture", "engine-default"];
29
- /** 一个旋钮的取值来源(闭集四词)。加词 ⇒ {@link postureSourceNote} 的穷举 switch 编译红。 */
30
+ /** 旋钮取值来源的闭集词表(顺序 = 优先序;五词,S-1492 起多 `lane`)。 */
31
+ export declare const POSTURE_SOURCE_WORDS: readonly ["env", "center", "lane", "posture", "engine-default"];
32
+ /** 一个旋钮的取值来源(闭集五词)。加词 ⇒ {@link postureSourceNote} 的穷举 switch 编译红。 */
30
33
  export type PostureSource = (typeof POSTURE_SOURCE_WORDS)[number];
31
34
  /** 一个旋钮报出「我是谁定的」所需要的三件事。 */
32
35
  export interface PostureKnobFacts {
@@ -43,15 +46,20 @@ export interface PostureKnobFacts {
43
46
  * 它们)—— 一句共享的硬编码必然对其中一类说谎。
44
47
  */
45
48
  readonly centerMeaning: string;
49
+ /**
50
+ * `lane` 那一臂**专属**的一句话(S-1492):执行车道的契约行为什么替这个旋钮声明了缺省、这一声明的风险是什么、怎么钉回。只在 `source === "lane"` 时被读到。
51
+ * 与 `centerMeaning` 同理由归键:没有 lane 腿的旋钮(今天除 `READ_FACE` 外都是)写一句「本键没有 lane 腿」,比一句会骗人的通用话诚实。
52
+ */
53
+ readonly laneMeaning: string;
46
54
  /**
47
55
  * `posture` 那一臂**专属**的一句话:这台机器为什么派生出了这个值,以及怎么把它钉回去。
48
56
  * 只在 `source === "posture"` 时被读到 —— 别的臂的句子是通用的(见 {@link postureSourceNote})。
49
57
  */
50
58
  readonly postureMeaning: string;
51
59
  }
52
- /** 运维指路句 —— **穷举 switch,无缺省臂**:四词闭集加一个成员这里就编译红。
53
- * 两条**通用**臂(`env` / `engine-default`)的句子在这里写死并点名旋钮;两条**键专属**臂
54
- * (`center` / `posture`)原样取键给的那句话(理由见 {@link PostureKnobFacts} 的两个字段注)。 */
60
+ /** 运维指路句 —— **穷举 switch,无缺省臂**:五词闭集加一个成员这里就编译红。
61
+ * 两条**通用**臂(`env` / `engine-default`)的句子在这里写死并点名旋钮;三条**键专属**臂
62
+ * (`center` / `lane` / `posture`)原样取键给的那句话(理由见 {@link PostureKnobFacts} 的三个字段注)。 */
55
63
  export declare function postureSourceNote(facts: PostureKnobFacts): string;
56
64
  /**
57
65
  * 这个值是**运维显式表态**的吗?
@@ -62,8 +70,8 @@ export declare function postureSourceNote(facts: PostureKnobFacts): string;
62
70
  * **结构上没有 durable 门**的腿在每次 boot 打一条补偿 warn,而它其实正处在「零门意图的既定姿势」里。
63
71
  * 这与 S-167 头注①记的是**同一形**病(用一个会被 posture 铸出来的量当「有人表态过」的代理)。
64
72
  *
65
- * ⇒ 判据落到**来源**上,穷举 switch(四词闭集加员即编译红):`env`(这台机器的运维写下的)与 `center`
66
- * (组织发布的)是表态;`posture`(姿态派生)与 `engine-default`(内建缺省)都**不是**「有人要求过」。
73
+ * ⇒ 判据落到**来源**上,穷举 switch(五词闭集加员即编译红):`env`(这台机器的运维写下的)与 `center`
74
+ * (组织发布的)是表态;`lane`(车道契约行声明的缺省)、`posture`(姿态派生)与 `engine-default`(内建缺省)都**不是**「有人要求过」。
67
75
  */
68
76
  export declare function isExplicitPosture(source: PostureSource): boolean;
69
77
  /** operator 诊断面的一行读数:值 + 来源 + 指路句。三个 S-178 旋钮与 S-167 的 readFace 同形。 */
@@ -1,10 +1,12 @@
1
- export const POSTURE_SOURCE_WORDS = ["env", "center", "posture", "engine-default"];
1
+ export const POSTURE_SOURCE_WORDS = ["env", "center", "lane", "posture", "engine-default"];
2
2
  export function postureSourceNote(facts) {
3
3
  switch (facts.source) {
4
4
  case "env":
5
5
  return `pinned by this machine's ${facts.knob} env var (deployment sovereignty — it wins over any published value for this key)`;
6
6
  case "center":
7
7
  return facts.centerMeaning;
8
+ case "lane":
9
+ return facts.laneMeaning;
8
10
  case "posture":
9
11
  return facts.postureMeaning;
10
12
  case "engine-default":
@@ -16,6 +18,7 @@ export function isExplicitPosture(source) {
16
18
  case "env":
17
19
  case "center":
18
20
  return true;
21
+ case "lane":
19
22
  case "posture":
20
23
  case "engine-default":
21
24
  return false;
@@ -1,4 +1,5 @@
1
- import type { MemoryProjectKey, MemoryProjectKeyAbsentReason } from "@sema-agent/core";
1
+ import { type MemoryProjectKey, type MemoryProjectKeyAbsentReason } from "@sema-agent/core";
2
+ import { type ProjectLayerIdentity } from "./project-layer-key.js";
2
3
  /** core 的项目身份声明席(`TaskSpec.memory.projectKey`)—— core 包根导出的型,原样转出(本仓调用方从这里取)。 */
3
4
  export type { MemoryProjectKey };
4
5
  /** 本仓会说的缺席词。`not_git` 刻意排除:裁 D 起非 git 目录按目录键控,不是缺席(core 闭集仍收它,本仓不铸)。 */
@@ -16,12 +17,50 @@ export type ProjectIdentity = {
16
17
  * 一次向上走,规则三条按优先序:最近的 `.git`(① ② ③)> 链上最近的裸仓布局(④ bare)> 非 git 按目录键控(④)。
17
18
  */
18
19
  export declare function resolveProjectIdentity(taskRoot: string): ProjectIdentity;
19
- /** 本仓**唯一**的 `repo:` 键铸点([ref] ①:`repo:` + sha256 hex 前 24;`root` 为 realpath 字符串,不加尾斜杠、不折大小写)。 */
20
+ /**
21
+ * 退役 `repo:` 键的**唯一**铸点([ref] ①:`repo:` + sha256 hex 前 24;`root` 为 realpath 字符串,不加尾斜杠、不折大小写)。
22
+ * S-640 起它只是一次性迁移的来源(`memory-project-layer.ts` 把这一层的条目折进新键;窗见 `plugins/one-time-migrations.ts` 登记表)。
23
+ */
20
24
  export declare function mintRepoScopeKey(projectRoot: string): string;
25
+ /** 这把键是不是本仓铸的项目键族(现行 `userrepo:` 或退役的 `repo:`)—— 运维不许把它们点名成全局层 / 同步层。 */
26
+ export declare function isServerMintedProjectKey(key: string): boolean;
27
+ /**
28
+ * 起服判据:这个全局层(或同步层)能不能拥有项目层 —— 不是本仓铸的族,且属主段编码得出来(core 段编码 ≤ 64)。不能 ⇒ 抛一句点名出路的配置错。
29
+ * 调用方:`config.ts` 记忆域(单用户 + 记忆引擎开着时问 `MEMORY_SCOPE`;开同步时问同步层)。
30
+ */
31
+ export declare function assertProjectLayerOwnable(scope: string, envName: string): void;
32
+ /**
33
+ * 远端 URL 归一(`r` 档的输入)—— 同一个仓的不同写法收成同一串,两个仓**不许**并成一串,凭据不进串:
34
+ * · scp 形 `[user@]host:path`(主机至少两个字符 —— 单字母是 Windows 盘符)与 scheme 形 `scheme://[userinfo@]host[:port]/path` ⇒ `host[:port]/path`
35
+ * (去 userinfo;主机小写;端口是该 scheme 的缺省值才去掉;路径大小写保留:不同托管对大小写的读法不同,折了会把两个仓合成一个);
36
+ * · 本地路径远端(`file://`、绝对 / 相对路径)⇒ `undefined`:它不是跨设备身份(相对路径还取决于 checkout 在哪),调用方落本 checkout 的 `p` 档;
37
+ * · 路径去首尾 `/`、去尾 `.git`、连续 `/` 收成一个;主机为空 ⇒ `undefined`。
38
+ */
39
+ export declare function normalizeGitRemoteUrl(url: string): string | undefined;
40
+ /**
41
+ * 任务根 → 项目层身份(S-640):当前档(`r` 优先,没有 origin 退 `p`)+ 本 checkout 的低档(`r` 档时 = 它的 `p` 档)。
42
+ * 缺席形与 {@link resolveProjectIdentity} 同;git 配置在而读不了 ⇒ `unreadable`。
43
+ */
44
+ export declare function projectLayerIdentityOf(taskRoot: string): {
45
+ readonly root: string;
46
+ readonly identity: ProjectLayerIdentity;
47
+ readonly lowerTiers: readonly ProjectLayerIdentity[];
48
+ } | {
49
+ readonly absent: ProjectIdentityAbsentReason;
50
+ };
51
+ /** 一个任务的项目层:core 声明席 + 本 checkout 要折进来的低档 / 退役层(按序,`memory-project-layer.ts` 折)。 */
52
+ export interface ProjectLayerOfTask {
53
+ readonly key: MemoryProjectKey;
54
+ readonly foldFrom: readonly string[];
55
+ }
21
56
  /**
22
- * 任务根 → core 声明席 `TaskSpec.memory.projectKey`。
23
- * `taskRoot === undefined` = 调用方**此刻不知道**任务根在本机哪里(根在沙箱/设备盘上;或 host 车道会话没登记 cwd、
24
- * 手落在 factory 现铸的临时目录)⇒ `unreadable` —— 「我看不到」,不是「这里没有项目」。
57
+ * 任务根 → 项目层(S-640:`userrepo:<属主段>/<档>.<摘要>`,属主 = `owner`,调用方由 `projectLayerOwnerOf(全局层)` 求得)。
58
+ * `foldFrom` = 同一 checkout 能推出的旧层:退役 `repo:<同一路径哈希>`(一次性迁移,窗见 `plugins/one-time-migrations.ts` 登记表 M25)
59
+ * + 当前档是 `r` 时它的 `p` 档(常设律)。
60
+ * `taskRoot === undefined` = 调用方**此刻不知道**任务根在本机哪里(根在沙箱/设备盘上;或 host 车道会话没登记 cwd、手落在 factory 现铸的
61
+ * 临时目录)⇒ `unreadable` —— 「我看不到」,不是「这里没有项目」。
25
62
  */
26
- export declare function projectKeyForTaskRoot(taskRoot: string | undefined): MemoryProjectKey;
63
+ export declare function projectLayerForTaskRoot(taskRoot: string | undefined, owner: string): ProjectLayerOfTask;
64
+ /** {@link projectLayerForTaskRoot} 的声明席投影(core `TaskSpec.memory.projectKey`)。 */
65
+ export declare function projectKeyForTaskRoot(taskRoot: string | undefined, owner: string): MemoryProjectKey;
27
66
  //# sourceMappingURL=project-identity.d.ts.map