@sema-agent/server 7.150.0 → 7.151.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 (341) hide show
  1. package/MIGRATION.md +185 -0
  2. package/USAGE.md +53 -25
  3. package/deploy/sema-up/README.md +7 -1
  4. package/deploy/sema-up/chart/Chart.yaml +1 -1
  5. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  6. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  8. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  9. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  10. package/deploy/sema-up/chart/values.yaml +8 -1
  11. package/deploy/sema-up/cluster-up.sh +1 -0
  12. package/deploy/sema-up/kube-up.sh +1 -1
  13. package/deploy/sema-up/lib/flagshape.sh +1 -1
  14. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  16. package/dist/adoption/import.js +3 -3
  17. package/dist/approval-ask-machine.d.ts +1 -1
  18. package/dist/approval-card.d.ts +2 -2
  19. package/dist/approval-gate-currency.d.ts +1 -1
  20. package/dist/auth-bridge.d.ts +20 -0
  21. package/dist/auth-bridge.js +9 -0
  22. package/dist/background-session-reap.d.ts +1 -1
  23. package/dist/bake-runner/main.d.ts +4 -2
  24. package/dist/bind-host.d.ts +2 -3
  25. package/dist/bind-host.js +2 -4
  26. package/dist/boot/budget-tracing.js +1 -1
  27. package/dist/boot/engine-lease.d.ts +5 -0
  28. package/dist/boot/engine-lease.js +1 -0
  29. package/dist/boot/execution-env.d.ts +8 -14
  30. package/dist/boot/execution-env.js +54 -17
  31. package/dist/boot/execution-lane.js +32 -5
  32. package/dist/boot/late-binds.d.ts +8 -4
  33. package/dist/boot/late-binds.js +7 -1
  34. package/dist/boot/memory-consolidation.d.ts +3 -2
  35. package/dist/boot/memory-consolidation.js +6 -6
  36. package/dist/boot/reapers.d.ts +5 -0
  37. package/dist/boot/reapers.js +12 -1
  38. package/dist/boot/resolve-spec.d.ts +3 -3
  39. package/dist/boot/resolve-spec.js +31 -22
  40. package/dist/boot/runner-deps.d.ts +9 -2
  41. package/dist/boot/runner-deps.js +3 -3
  42. package/dist/boot/session-age-out.d.ts +36 -0
  43. package/dist/boot/session-age-out.js +101 -0
  44. package/dist/boot/session-faces.d.ts +5 -14
  45. package/dist/boot/session-faces.js +9 -5
  46. package/dist/boot/stage-01-config.js +2 -0
  47. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  48. package/dist/boot/stage-05-execution-env.js +2 -2
  49. package/dist/boot/stage-06-runners.d.ts +7 -4
  50. package/dist/boot/stage-06-runners.js +15 -6
  51. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  52. package/dist/boot/stage-07-capability-layer.js +2 -1
  53. package/dist/boot/stage-08-reapers.d.ts +7 -4
  54. package/dist/boot/stage-08-reapers.js +4 -2
  55. package/dist/boot/stage-09-leader.d.ts +11 -5
  56. package/dist/boot/stage-09-leader.js +12 -3
  57. package/dist/boot/stage-10-http-server.d.ts +11 -5
  58. package/dist/boot/stage-10-http-server.js +3 -6
  59. package/dist/boot/web-admin.js +3 -1
  60. package/dist/boot/workflow-orchestration.d.ts +7 -2
  61. package/dist/boot/workflow-orchestration.js +24 -7
  62. package/dist/bounded-rotation.d.ts +29 -0
  63. package/dist/bounded-rotation.js +24 -0
  64. package/dist/capability-facts.d.ts +22 -13
  65. package/dist/capability-facts.js +28 -18
  66. package/dist/center-credential.d.ts +25 -6
  67. package/dist/center-credential.js +25 -5
  68. package/dist/config-catalog.d.ts +26 -7
  69. package/dist/config-catalog.js +14 -11
  70. package/dist/config-invariants.d.ts +2 -1
  71. package/dist/config-invariants.js +4 -4
  72. package/dist/config-types.d.ts +10 -4
  73. package/dist/config.d.ts +51 -2
  74. package/dist/config.js +35 -24
  75. package/dist/deployment-shape.d.ts +9 -1
  76. package/dist/deployment-shape.js +2 -1
  77. package/dist/device-protocol.d.ts +5 -4
  78. package/dist/device-protocol.js +1 -1
  79. package/dist/device-ws-hub.js +3 -2
  80. package/dist/device-ws-protocol.d.ts +63 -10
  81. package/dist/device-ws-protocol.js +56 -25
  82. package/dist/engine-identity.d.ts +19 -7
  83. package/dist/engine-identity.js +10 -2
  84. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  85. package/dist/execution-env-refusal.d.ts +23 -0
  86. package/dist/execution-env-refusal.js +5 -0
  87. package/dist/execution-lane-caps.d.ts +3 -1
  88. package/dist/field-conflict.d.ts +1 -1
  89. package/dist/file-history-capture.d.ts +11 -4
  90. package/dist/file-history-capture.js +9 -6
  91. package/dist/file-history-disclosure.d.ts +7 -5
  92. package/dist/file-history-disclosure.js +7 -5
  93. package/dist/fleet/drain-entry.d.ts +4 -0
  94. package/dist/hooks/hook-runner.js +2 -2
  95. package/dist/host-lsp-manager.d.ts +2 -2
  96. package/dist/http/admission.js +17 -2
  97. package/dist/http/approval-sweeps.js +5 -5
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +2 -1
  101. package/dist/http/dispatch.js +34 -27
  102. package/dist/http/durable-run-leg.js +16 -16
  103. package/dist/http/health-self-report.d.ts +9 -1
  104. package/dist/http/health-self-report.js +2 -0
  105. package/dist/http/owner-access.d.ts +31 -11
  106. package/dist/http/owner-access.js +37 -6
  107. package/dist/http/resume-ctx-read.js +25 -3
  108. package/dist/http/resume-legs.d.ts +2 -1
  109. package/dist/http/resume-legs.js +259 -79
  110. package/dist/http/route-ctx.d.ts +18 -8
  111. package/dist/http/route-table.d.ts +1 -1
  112. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  113. package/dist/http/routes/agents-roster.js +2 -3
  114. package/dist/http/routes/approvals-assistant.js +4 -3
  115. package/dist/http/routes/background.d.ts +3 -3
  116. package/dist/http/routes/capabilities.js +8 -5
  117. package/dist/http/routes/fleet.js +10 -8
  118. package/dist/http/routes/projects.d.ts +3 -2
  119. package/dist/http/routes/projects.js +4 -5
  120. package/dist/http/routes/runs.js +102 -36
  121. package/dist/http/routes/session-sync.d.ts +4 -3
  122. package/dist/http/routes/session-sync.js +172 -77
  123. package/dist/http/routes/sessions-list.js +13 -7
  124. package/dist/http/routes/sessions.js +10 -6
  125. package/dist/http/routes/tasks.js +30 -16
  126. package/dist/http/routes/trace-usage.js +2 -2
  127. package/dist/http/server.d.ts +21 -27
  128. package/dist/http/server.js +2 -2
  129. package/dist/http/submit-ctx.js +5 -4
  130. package/dist/http/wire-types.d.ts +14 -4
  131. package/dist/identity-source.d.ts +20 -3
  132. package/dist/identity-source.js +14 -0
  133. package/dist/index.d.ts +4 -0
  134. package/dist/lane-home-probe.d.ts +3 -7
  135. package/dist/lane-home-probe.js +0 -1
  136. package/dist/leader/leader.d.ts +2 -1
  137. package/dist/leg-facts.d.ts +13 -5
  138. package/dist/leg-facts.js +2 -1
  139. package/dist/memory-scope.d.ts +5 -4
  140. package/dist/memory-scope.js +4 -4
  141. package/dist/memory-sync-client.d.ts +30 -2
  142. package/dist/memory-sync-client.js +16 -9
  143. package/dist/observability/err-text.d.ts +7 -0
  144. package/dist/observability/err-text.js +5 -0
  145. package/dist/observability/fail-open.d.ts +7 -3
  146. package/dist/observability/fail-open.js +7 -3
  147. package/dist/observability/metrics.js +1 -0
  148. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  149. package/dist/orchestration/workflow-notify-journal.js +12 -1
  150. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  151. package/dist/orchestration/workflow-park-decision.js +49 -10
  152. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  153. package/dist/orchestration/workflow-park-holder.js +98 -0
  154. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  155. package/dist/orchestration/workflow-record-fence.js +42 -0
  156. package/dist/owner-faces.d.ts +1 -1
  157. package/dist/parked-decide.d.ts +104 -53
  158. package/dist/parked-decide.js +153 -117
  159. package/dist/peer-directory.d.ts +2 -2
  160. package/dist/peer-inbound-face.d.ts +2 -6
  161. package/dist/peer-inbound-face.js +0 -3
  162. package/dist/permission-mode-admission.d.ts +41 -5
  163. package/dist/permission-mode-admission.js +29 -3
  164. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  165. package/dist/plugins/adoption-log-sql.js +7 -1
  166. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  167. package/dist/plugins/approval-ask-store-file.js +8 -0
  168. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  169. package/dist/plugins/approval-ask-store-memory.js +28 -9
  170. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  171. package/dist/plugins/approval-ask-store-sql.js +34 -5
  172. package/dist/plugins/approval-exemption-store.js +3 -0
  173. package/dist/plugins/approval-nonce-store.js +2 -0
  174. package/dist/plugins/breaker-state-sql.js +2 -0
  175. package/dist/plugins/caching-session-store.d.ts +9 -15
  176. package/dist/plugins/caching-session-store.js +6 -0
  177. package/dist/plugins/device-store-sql.js +6 -0
  178. package/dist/plugins/error-origin.d.ts +18 -0
  179. package/dist/plugins/error-origin.js +10 -0
  180. package/dist/plugins/execution-placement-sql.js +6 -6
  181. package/dist/plugins/image-bake-store-sql.js +6 -0
  182. package/dist/plugins/image-index-sql.js +4 -0
  183. package/dist/plugins/landed-attendants.d.ts +212 -0
  184. package/dist/plugins/landed-attendants.js +161 -0
  185. package/dist/plugins/leader-run-store-sql.js +4 -0
  186. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  187. package/dist/plugins/local-session-live-reference.js +39 -0
  188. package/dist/plugins/local-session-store.d.ts +71 -33
  189. package/dist/plugins/local-session-store.js +129 -27
  190. package/dist/plugins/memory-engine-pg.js +4 -0
  191. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  192. package/dist/plugins/memory-engine-tidb.js +4 -0
  193. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  194. package/dist/plugins/memory-sync-store-pg.js +5 -0
  195. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  196. package/dist/plugins/one-time-migrations.d.ts +57 -63
  197. package/dist/plugins/one-time-migrations.js +162 -90
  198. package/dist/plugins/org-delivery-quota-store.js +4 -1
  199. package/dist/plugins/outcome-ledger-sql.js +2 -0
  200. package/dist/plugins/permission-rule-store-sql.js +11 -4
  201. package/dist/plugins/pg-cost-quota.js +4 -0
  202. package/dist/plugins/pg-pool.js +24 -1
  203. package/dist/plugins/pg-rate-limiter.js +2 -0
  204. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  205. package/dist/plugins/posix-shell-fs.js +4 -1
  206. package/dist/plugins/remote-env-adb.d.ts +19 -15
  207. package/dist/plugins/remote-env-adb.js +41 -15
  208. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  209. package/dist/plugins/remote-env-connect-seat.js +72 -0
  210. package/dist/plugins/remote-env-device.d.ts +22 -5
  211. package/dist/plugins/remote-env-device.js +106 -8
  212. package/dist/plugins/remote-env-e2b.d.ts +15 -6
  213. package/dist/plugins/remote-env-e2b.js +42 -32
  214. package/dist/plugins/remote-env-file-error.js +6 -2
  215. package/dist/plugins/remote-env-host.d.ts +17 -0
  216. package/dist/plugins/remote-env-host.js +30 -38
  217. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  218. package/dist/plugins/remote-env-k8s.js +53 -30
  219. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  220. package/dist/plugins/remote-env-local-docker.js +34 -41
  221. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  222. package/dist/plugins/remote-env-ssh.js +60 -59
  223. package/dist/plugins/remote-shell.d.ts +98 -1
  224. package/dist/plugins/remote-shell.js +218 -0
  225. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  226. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  227. package/dist/plugins/retention-lane-store-sql.js +4 -0
  228. package/dist/plugins/retention-sql.d.ts +20 -2
  229. package/dist/plugins/retention-sql.js +11 -0
  230. package/dist/plugins/run-store-sql.d.ts +21 -2
  231. package/dist/plugins/run-store-sql.js +31 -7
  232. package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
  233. package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
  234. package/dist/plugins/schema-consistency.d.ts +4 -2
  235. package/dist/plugins/schema-consistency.js +19 -2
  236. package/dist/plugins/send-file-ledger.js +3 -0
  237. package/dist/plugins/serving-requirement.d.ts +26 -7
  238. package/dist/plugins/serving-requirement.js +11 -4
  239. package/dist/plugins/session-birth.d.ts +6 -2
  240. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  241. package/dist/plugins/session-ext-table.d.ts +14 -1
  242. package/dist/plugins/session-ext-table.js +5 -1
  243. package/dist/plugins/session-row-sql.d.ts +49 -2
  244. package/dist/plugins/session-row-sql.js +56 -13
  245. package/dist/plugins/session-store-sql.d.ts +34 -18
  246. package/dist/plugins/session-store-sql.js +98 -17
  247. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  248. package/dist/plugins/shared-memory-store-sql.js +6 -2
  249. package/dist/plugins/sql-driver.d.ts +2 -1
  250. package/dist/plugins/sql-driver.js +18 -3
  251. package/dist/plugins/sql-errors.d.ts +11 -6
  252. package/dist/plugins/sql-errors.js +5 -6
  253. package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
  254. package/dist/plugins/stop-then-start-sentence.js +14 -10
  255. package/dist/plugins/stop-then-start.d.ts +109 -0
  256. package/dist/plugins/stop-then-start.js +104 -0
  257. package/dist/plugins/store-backend.d.ts +40 -13
  258. package/dist/plugins/store-backend.js +169 -27
  259. package/dist/plugins/store-boundary.d.ts +57 -0
  260. package/dist/plugins/store-boundary.js +139 -0
  261. package/dist/plugins/store-contracts.d.ts +17 -0
  262. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  263. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  264. package/dist/plugins/store-fault.d.ts +26 -7
  265. package/dist/plugins/store-fault.js +18 -2
  266. package/dist/plugins/task-attachment-store.js +9 -0
  267. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  268. package/dist/plugins/task-list-store-sql.js +10 -8
  269. package/dist/plugins/tidb-cost-quota.js +4 -0
  270. package/dist/plugins/tidb-pool.js +2 -1
  271. package/dist/plugins/tidb-rate-limiter.js +4 -0
  272. package/dist/plugins/user-input-ledger-sql.js +4 -0
  273. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  274. package/dist/plugins/workflow-run-store-sql.js +64 -4
  275. package/dist/plugins/workspace-fill.d.ts +4 -7
  276. package/dist/plugins/workspace-fill.js +2 -1
  277. package/dist/plugins/write-behind-counter.js +5 -0
  278. package/dist/project-context.d.ts +0 -2
  279. package/dist/project-context.js +4 -8
  280. package/dist/project-registry.d.ts +46 -0
  281. package/dist/project-registry.js +44 -0
  282. package/dist/request-key-closure.js +1 -1
  283. package/dist/request-value-closure.d.ts +4 -0
  284. package/dist/request-value-closure.js +10 -0
  285. package/dist/run-acceptance.d.ts +3 -2
  286. package/dist/run-acceptance.js +4 -6
  287. package/dist/run-local.js +1 -0
  288. package/dist/runs.d.ts +37 -8
  289. package/dist/runs.js +53 -12
  290. package/dist/runtime-governance.d.ts +5 -4
  291. package/dist/security.d.ts +30 -32
  292. package/dist/security.js +41 -7
  293. package/dist/server-secret-env.d.ts +12 -10
  294. package/dist/server-secret-env.js +24 -15
  295. package/dist/session-bindings.d.ts +82 -0
  296. package/dist/session-bindings.js +18 -0
  297. package/dist/session-children-retire.d.ts +2 -2
  298. package/dist/session-children-retire.js +2 -6
  299. package/dist/session-deletion.d.ts +16 -1
  300. package/dist/session-error-wire.d.ts +3 -3
  301. package/dist/session-error-wire.js +3 -3
  302. package/dist/session-incarnation.d.ts +10 -0
  303. package/dist/session-incarnation.js +10 -1
  304. package/dist/session-kind.d.ts +57 -0
  305. package/dist/session-kind.js +22 -0
  306. package/dist/session-row.d.ts +21 -1
  307. package/dist/session-row.js +7 -1
  308. package/dist/session-sync-kernel.d.ts +24 -14
  309. package/dist/session-sync-kernel.js +3 -6
  310. package/dist/session-sync.d.ts +4 -98
  311. package/dist/session-sync.js +1 -71
  312. package/dist/session-titler.d.ts +10 -3
  313. package/dist/session-titler.js +19 -6
  314. package/dist/ssh-host-key.d.ts +5 -0
  315. package/dist/ssh-host-key.js +3 -0
  316. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  317. package/dist/stores-sql/file-history-store-sql.js +108 -70
  318. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  319. package/dist/stores-sql/pg-session-storage.js +37 -13
  320. package/dist/stores-sql/retention-store-sql.d.ts +1 -9
  321. package/dist/stores-sql/retention-store-sql.js +2 -10
  322. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  323. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  324. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  325. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  326. package/dist/stores-sql/tidb-session-store.js +37 -13
  327. package/dist/submission-exit.d.ts +3 -2
  328. package/dist/submission-exit.js +9 -1
  329. package/dist/task-cwd.d.ts +2 -2
  330. package/dist/task-settings.d.ts +6 -5
  331. package/dist/terminal.d.ts +33 -2
  332. package/dist/terminal.js +139 -9
  333. package/dist/tool-approval.d.ts +140 -6
  334. package/dist/tool-approval.js +165 -72
  335. package/dist/trace/self-minted-wire.d.ts +290 -27
  336. package/dist/trace/self-minted-wire.js +101 -13
  337. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
  338. package/package.json +2 -2
  339. package/wire-changes/server@7.151.0.json +762 -0
  340. package/dist/http/session-inherit-env.d.ts +0 -36
  341. package/dist/http/session-inherit-env.js +0 -6
@@ -30,36 +30,39 @@ export async function findParkOrigin(sources, cp) {
30
30
  if (index === undefined || runStore === undefined)
31
31
  return undefined;
32
32
  try {
33
- const pointers = await index.listByAgentSession(cp.sessionId);
34
- if (pointers.length === 0)
35
- return undefined;
36
- const runs = [];
37
- for (const runId of new Set(pointers.map((p) => p.runId))) {
38
- const run = await runStore.get(runId);
39
- if (run !== null)
40
- runs.push(run);
41
- }
42
- const route = routeWorkflowPark(cp.sessionId, runs);
43
- switch (route.kind) {
44
- case "held":
45
- return route.park.token === cp.token ? { kind: "workflow", runId: route.runId, run: route.run, park: route.park } : undefined;
46
- case "forked":
47
- return { kind: "workflow_park_forked", runIds: route.runIds };
48
- case "unproven":
49
- return { kind: "workflow_parks_unreadable", runId: route.runId, run: route.run, ...(route.readableHolderRunId !== undefined ? { readableHolderRunId: route.readableHolderRunId } : {}) };
50
- case "none":
51
- return undefined;
52
- default: {
53
- const unhandled = route;
54
- throw new Error(`unhandled workflow park route: ${JSON.stringify(unhandled)}`);
55
- }
56
- }
33
+ return await routeWorkflowOrigin(index, runStore, cp);
57
34
  }
58
35
  catch (e) {
59
36
  quiet(sources.warn, "workflow_park_probe_failed", { error: e instanceof Error ? e.message : String(e) });
60
37
  return undefined;
61
38
  }
62
39
  }
40
+ async function routeWorkflowOrigin(index, runStore, cp) {
41
+ const pointers = await index.listByAgentSession(cp.sessionId);
42
+ if (pointers.length === 0)
43
+ return undefined;
44
+ const runs = [];
45
+ for (const runId of new Set(pointers.map((p) => p.runId))) {
46
+ const run = await runStore.get(runId);
47
+ if (run !== null)
48
+ runs.push(run);
49
+ }
50
+ const route = routeWorkflowPark(cp.sessionId, runs);
51
+ switch (route.kind) {
52
+ case "held":
53
+ return route.park.token === cp.token ? { kind: "workflow", runId: route.runId, run: route.run, park: route.park } : undefined;
54
+ case "forked":
55
+ return { kind: "workflow_park_forked", runIds: route.runIds };
56
+ case "unproven":
57
+ return { kind: "workflow_parks_unreadable", runId: route.runId, run: route.run, ...(route.readableHolderRunId !== undefined ? { readableHolderRunId: route.readableHolderRunId } : {}) };
58
+ case "none":
59
+ return undefined;
60
+ default: {
61
+ const unhandled = route;
62
+ throw new Error(`unhandled workflow park route: ${JSON.stringify(unhandled)}`);
63
+ }
64
+ }
65
+ }
63
66
  export async function heldWorkflowParkDecisions(sources, hostSessionId, carried) {
64
67
  const entries = [...(carried ?? [])];
65
68
  const { workflowParkDecisions: decisions, checkpointStore: cs, workflowAgentSessionIndex: workflowIndex, workflowRunStore } = sources;
@@ -73,20 +76,38 @@ export async function heldWorkflowParkDecisions(sources, hostSessionId, carried)
73
76
  for (const row of held.rows) {
74
77
  if (own.has(String(row.token)))
75
78
  continue;
76
- const card = await cs.get(row.token).then((cp) => ({ cp }), (err) => ({ err }));
77
- if ("err" in card) {
78
- recordFailOpen("server.workflow-park-decision.card-unreadable", `runId=${row.runId} err=${card.err instanceof Error ? card.err.message : String(card.err)}`);
79
- continue;
80
- }
81
- if (card.cp === null || card.cp.status !== "pending")
82
- continue;
83
- const origin = await findParkOrigin({ workflowIndex, workflowRunStore }, { token: row.token, scope: card.cp.scope, sessionId: card.cp.sessionId });
84
- if (origin?.kind === "workflow")
85
- entries.push({ runId: origin.runId, token: row.token, outcome: row.outcome });
79
+ const deliverable = await deliverableEntryOf({ checkpointStore: cs, workflowAgentSessionIndex: workflowIndex, workflowRunStore }, row);
80
+ if (deliverable.kind === "deliverable")
81
+ entries.push(deliverable.entry);
86
82
  }
87
83
  }
88
84
  return entries.length > 0 ? { workflowParkedResume: entries } : {};
89
85
  }
86
+ export async function deliverableEntryOf(sources, row) {
87
+ const unreadable = (step, err) => {
88
+ recordFailOpen("server.workflow-park-decision.card-unreadable", `step=${step} runId=${row.runId} err=${err instanceof Error ? err.message : String(err)}`);
89
+ return { kind: "unreadable" };
90
+ };
91
+ let cp;
92
+ try {
93
+ cp = await sources.checkpointStore.get(row.token);
94
+ }
95
+ catch (err) {
96
+ return unreadable("card", err);
97
+ }
98
+ if (cp === null || cp.status !== "pending")
99
+ return { kind: "not_deliverable" };
100
+ let origin;
101
+ try {
102
+ origin = await routeWorkflowOrigin(sources.workflowAgentSessionIndex, sources.workflowRunStore, { token: row.token, sessionId: cp.sessionId });
103
+ }
104
+ catch (err) {
105
+ return unreadable("holder", err);
106
+ }
107
+ if (origin?.kind !== "workflow")
108
+ return { kind: "not_deliverable" };
109
+ return { kind: "deliverable", entry: { runId: origin.runId, token: row.token, outcome: row.outcome }, origin, card: { scope: cp.scope, sessionId: cp.sessionId } };
110
+ }
90
111
  function quiet(warn, event, fields) {
91
112
  try {
92
113
  warn?.(event, fields);
@@ -224,63 +245,87 @@ export function planWorkflowParkedDecide(origin, req) {
224
245
  },
225
246
  };
226
247
  }
227
- const verdict = req.decision === "approve" ? "APPROVED" : "DENIED";
228
248
  return {
229
249
  ok: true,
230
250
  hostSessionId,
231
251
  runId: origin.runId,
232
252
  outcome: buildParkedResumeOutcome(req),
233
- message: {
234
- text: `The human approval that workflow run "${origin.runId}" parked on (agent callKey "${origin.park.callKey}") has been ${verdict}. ` +
235
- `Resume that run now: re-invoke Workflow with the SAME script source you launched it from (script / name / scriptPath) ` +
236
- `AND resumeFromRunId: "${origin.runId}" — the decision is already held by the deployment and will be applied to the parked ` +
237
- `agent; completed steps replay from the journal. Do not re-issue the original call.`,
238
- trusted: true,
239
- },
253
+ message: workflowParkGuidance(origin.runId, origin.park.callKey, req.decision),
240
254
  };
241
255
  }
256
+ export function workflowParkGuidance(runId, callKey, decision) {
257
+ const verdict = decision === "approve" ? "APPROVED" : "DENIED";
258
+ return {
259
+ text: `The human approval that workflow run "${runId}" parked on (agent callKey "${callKey}") has been ${verdict}. ` +
260
+ `Resume that run now: re-invoke Workflow with the SAME script source you launched it from (script / name / scriptPath) ` +
261
+ `AND resumeFromRunId: "${runId}" — the decision is already held by the deployment and will be applied to the parked ` +
262
+ `agent; completed steps replay from the journal. Do not re-issue the original call.`,
263
+ trusted: true,
264
+ };
265
+ }
266
+ export function redeemHere(runId, scope, d) {
267
+ return redeemWorkflowParks(runId, scope, [{ token: d.token, outcome: d.outcome }]);
268
+ }
242
269
  function parkedChildResumedReceipt(taskId, decision, evidence) {
243
270
  return { status: 200, body: { taskId, status: "resuming", decision, ...evidence } };
244
271
  }
245
- const LIVE_REDEEM_RECEIPTS = new Map();
246
- async function runChannelEnded(runId, scope) {
247
- const channel = subscribeWorkflow(runId, scope)[Symbol.asyncIterator]();
248
- while (!(await channel.next()).done) {
249
- }
250
- }
251
- function holdLiveReceipt(run, token, fingerprint, answer) {
252
- let byToken = LIVE_REDEEM_RECEIPTS.get(run.id);
253
- if (byToken === undefined) {
254
- byToken = new Map();
255
- LIVE_REDEEM_RECEIPTS.set(run.id, byToken);
256
- const book = byToken;
257
- const drop = () => {
258
- if (LIVE_REDEEM_RECEIPTS.get(run.id) === book)
259
- LIVE_REDEEM_RECEIPTS.delete(run.id);
260
- };
261
- void run.ended().then(drop, (err) => {
262
- recordFailOpen("server.workflow-live-receipt.channel-unreadable", `runId=${run.id} err=${err instanceof Error ? err.message : String(err)}`);
263
- drop();
264
- });
272
+ export function answerOfHolderFence(verdict, runId) {
273
+ switch (verdict) {
274
+ case "not_held":
275
+ return { status: 409, body: refusalBody({ errorCode: "decide.gate_moved", message: "the approval gate moved under this decide (re-parked or already settled) — re-check the pending list", facts: { runId } }) };
276
+ case "restated":
277
+ return {
278
+ status: 400,
279
+ body: refusalBody({
280
+ errorCode: "decide.parked_restatement_unsupported",
281
+ message: "mcpServers / a2aPeers cannot be applied: the run this approval belongs to is still running, and its waiting step continues with the MCP / A2A tables the run already has — re-issue the decide without them to continue it that way",
282
+ facts: { runId },
283
+ }),
284
+ };
285
+ case "other_decision":
286
+ return {
287
+ status: 409,
288
+ body: refusalBody({
289
+ errorCode: "decide.claim_lost",
290
+ message: "another decision for this approval was already handed to the run that is waiting on it and is being applied — re-check the pending list",
291
+ facts: { runId },
292
+ }),
293
+ };
294
+ default: {
295
+ const unhandled = verdict;
296
+ throw new Error(`unhandled workflow holder fence verdict: ${String(unhandled)}`);
297
+ }
265
298
  }
266
- byToken.set(token, { fingerprint, answer });
267
299
  }
268
- const copyOf = (r) => ({ status: r.status, body: { ...r.body } });
269
- export async function deliverToHoldingRun(origin, d) {
270
- const entry = d.restated ? { token: d.token } : { token: d.token, outcome: d.outcome };
271
- const scope = origin.run.scope;
272
- return answerOfParkDelivery(await redeemWorkflowParks(origin.runId, scope, [entry]), { id: origin.runId, ended: () => runChannelEnded(origin.runId, scope) }, d);
300
+ export function holderFenceFaultAnswer(runId) {
301
+ return { status: 503, body: refusalBody({ errorCode: "decide.store_unreachable", message: "the state of this approval could not be read just now, so the run waiting on it kept waiting and nothing was decided — retry", facts: { runId } }) };
273
302
  }
274
- export async function answerOfParkDelivery(delivery, run, d) {
275
- const runId = run.id;
303
+ export function answerOfHolderRedeem(delivery, runId, decision) {
304
+ const receipt = () => parkedChildResumedReceipt(runId, decision, NO_SETTLEMENT_EVIDENCE);
276
305
  switch (delivery.kind) {
277
- case "live":
278
- return { kind: "answered", answer: answerOfLiveResult(delivery.results[0], run, d) };
306
+ case "live": {
307
+ const word = delivery.results[0];
308
+ if (word === undefined)
309
+ throw new Error(`workflow run ${runId} answered a live delivery without a result for the one decision it was handed`);
310
+ switch (word) {
311
+ case "driving":
312
+ case "already_taken":
313
+ case "not_parked_here":
314
+ case "unreadable":
315
+ return receipt();
316
+ case "settled":
317
+ return answerOfHolderFence("not_held", runId);
318
+ case "kept_parked":
319
+ throw new Error(`workflow run ${runId} kept a parked call that was handed a decision — the engine keeps a park only on an acknowledgement`);
320
+ default: {
321
+ const unhandled = word;
322
+ throw new Error(`unhandled workflow park delivery result: ${String(unhandled)}`);
323
+ }
324
+ }
325
+ }
279
326
  case "suspending":
280
- await delivery.ended;
281
- return { kind: "resume_lane" };
282
327
  case "not_active":
283
- return { kind: "resume_lane" };
328
+ return receipt();
284
329
  case "not_armed":
285
330
  throw new Error(`workflow run ${runId} answered not_armed to a decision for a card it holds — a run without a checkpoint seat cannot have parked one; refusing to guess where the decision goes`);
286
331
  default: {
@@ -289,53 +334,44 @@ export async function answerOfParkDelivery(delivery, run, d) {
289
334
  }
290
335
  }
291
336
  }
292
- function answerOfLiveResult(word, run, d) {
293
- const runId = run.id;
294
- if (word === undefined)
295
- throw new Error(`workflow run ${runId} answered a live delivery without a result for the one decision it was handed`);
296
- switch (word) {
297
- case "driving": {
298
- if (d.restated)
299
- throw new Error(`workflow run ${runId} drove a parked call on an acknowledgement — an acknowledgement never authorizes a drive`);
300
- const answer = parkedChildResumedReceipt(runId, d.decision, NO_SETTLEMENT_EVIDENCE);
301
- holdLiveReceipt(run, d.token, d.fingerprint, answer);
302
- return copyOf(answer);
303
- }
304
- case "already_taken": {
305
- const held = LIVE_REDEEM_RECEIPTS.get(runId)?.get(d.token);
306
- if (held !== undefined && held.fingerprint === d.fingerprint)
307
- return copyOf(held.answer);
308
- return {
309
- status: 409,
310
- body: refusalBody({
311
- errorCode: "decide.claim_lost",
312
- message: "another decision for this approval was already handed to the run that is waiting on it and is being applied — re-check the pending list",
313
- facts: { runId },
314
- }),
315
- };
337
+ export function holderPullDisposition(delivery, runId) {
338
+ switch (delivery.kind) {
339
+ case "live": {
340
+ const word = delivery.results[0];
341
+ if (word === undefined)
342
+ throw new Error(`workflow run ${runId} answered a live delivery without a result for the one decision it was handed`);
343
+ switch (word) {
344
+ case "driving":
345
+ case "already_taken":
346
+ case "settled":
347
+ return "delivered";
348
+ case "not_parked_here":
349
+ case "unreadable":
350
+ return "next_beat";
351
+ case "kept_parked":
352
+ throw new Error(`workflow run ${runId} kept a parked call that was handed a decision — the engine keeps a park only on an acknowledgement`);
353
+ default: {
354
+ const unhandled = word;
355
+ throw new Error(`unhandled workflow park delivery result: ${String(unhandled)}`);
356
+ }
357
+ }
316
358
  }
317
- case "settled":
318
- case "not_parked_here":
319
- return { status: 409, body: refusalBody({ errorCode: "decide.gate_moved", message: "the approval gate moved under this decide (re-parked or already settled) — re-check the pending list", facts: { runId } }) };
320
- case "unreadable":
321
- return { status: 503, body: refusalBody({ errorCode: "decide.store_unreachable", message: "the state of this approval could not be read just now, so the run waiting on it kept waiting and nothing was decided — retry", facts: { runId } }) };
322
- case "kept_parked":
323
- if (!d.restated)
324
- throw new Error(`workflow run ${runId} kept a parked call that was handed a decision — the engine keeps a park only on an acknowledgement`);
325
- return {
326
- status: 400,
327
- body: refusalBody({
328
- errorCode: "decide.parked_restatement_unsupported",
329
- message: "mcpServers / a2aPeers cannot be applied: the run this approval belongs to is still running, and its waiting step continues with the MCP / A2A tables the run already has — re-issue the decide without them to continue it that way",
330
- facts: { runId },
331
- }),
332
- };
359
+ case "suspending":
360
+ case "not_active":
361
+ return "left";
362
+ case "not_armed":
363
+ throw new Error(`workflow run ${runId} answered not_armed to a decision for a card it holds — a run without a checkpoint seat cannot have parked one`);
333
364
  default: {
334
- const unhandled = word;
335
- throw new Error(`unhandled workflow park delivery result: ${String(unhandled)}`);
365
+ const unhandled = delivery;
366
+ throw new Error(`unhandled workflow park delivery: ${JSON.stringify(unhandled)}`);
336
367
  }
337
368
  }
338
369
  }
370
+ export async function runChannelEnded(runId, scope) {
371
+ const channel = subscribeWorkflow(runId, scope)[Symbol.asyncIterator]();
372
+ while (!(await channel.next()).done) {
373
+ }
374
+ }
339
375
  function sessionGone(taskId) {
340
376
  return {
341
377
  status: 409,
@@ -37,8 +37,8 @@
37
37
  *
38
38
  * ## scope 轴
39
39
  *
40
- * core 递进来的 `access.scope` = `taskScope` = `internals?.registryScope ?? spec.principal ?? "default"`
41
- * (core `prepare-task.js`)× server `resolve-spec.ts:701 principal: auth?.principal` ⇒ **裸 principal 串**,
40
+ * core 递进来的 `access.scope` = 引擎的任务 scope(core 写侧规则与本仓的唯一换算见 `engine-identity.ts` 的 `engineScopeOf` 头注)
41
+ * × 落点 `taskSpecIdentityOf` 只写人 ⇒ **裸 principal 串**,
42
42
  * 无 principal 的部署上是字面量 `"default"`,而同一情形下 `session_meta.owner` 是 SQL NULL ——
43
43
  * 这正是窄互认臂(`LEGACY_UNOWNED_AMNESTY_TENANT === "default"`)要接住的那一格,不必另铸编解码。
44
44
  *
@@ -11,8 +11,8 @@
11
11
  * receipt / 投递 / 到期计时都是 core 绑在队列上的 sink(引擎在 run 的 drain 里自己 `configure`,宿主**不**接 sink —— 接了就是第二个写者)。
12
12
  * 本面只做两件事:把队列里等人审的卡(见下「只有人能答的卡」)投成 `pending[]` 的 `peer_inbound` 行;把决断口的 approve / deny 原样交给 `settle`。
13
13
  * 没有行店、没有落账:一张卡在不在、谁赢了 CAS、结局是什么,全是 core 的读数。
14
- * · **属主 = 收信方**:列与决都只在调用方**自己**的信箱 scope 里找队列(`peerHeldQueueIfAny(调用方 scope, …)`;scope 读法与 core 的
15
- * `taskScope = spec.principal ?? "default"` 同一条,见 {@link peerBoxScope})—— 别人的卡结构上取不到,决断口答的 404 与「没有这张卡」同码同文。
14
+ * · **属主 = 收信方**:列与决都只在调用方**自己**的信箱 scope 里找队列(`peerHeldQueueIfAny(调用方 scope, …)`;core 按收信 run 的任务 scope 开队列,
15
+ * 调用方那一格只经唯一换算 `engine-identity.ts` 的 `engineScopeOfOwner` 读,路由在调用点换好再交进来)—— 别人的卡结构上取不到,决断口答的 404 与「没有这张卡」同码同文。
16
16
  * 显式 operator **没有**额外射程:一封信是收信人的,不是运维治理面(与停驻门「operator 可决任意行」刻意不同,决断口按族分臂)。
17
17
  * · **只有人能答的卡才进审批席**(core `PEER_HELD_REVIEW_CAUSES`:`mode-mismatch` / `no-mode-asserted` / `invalid-mode-attestation` —— 权限档
18
18
  * 对等判扣下的信;CC 只对这几个成因开审阅框与到期计时,cli309.js:36111–36116 的句子逐条:`managed-setting` / `repo-setting` / `explicit-setting`
@@ -41,10 +41,6 @@
41
41
  */
42
42
  import { type PeerHeldEntry, type PeerHeldSettleOutcome } from "@sema-agent/core";
43
43
  import { type PeerInboundLane } from "./org-delivery-gate.js";
44
- /**
45
- * 调用方 principal 的信箱 scope —— core 写侧 `taskScope = spec.principal ?? "default"` 的同一条读法(无主 ⇒ `"default"`)。列 / 决都按它找队列。
46
- */
47
- export declare function peerBoxScope(principal: string | null | undefined): string;
48
44
  /** 决断口 id 的坐标(id 是它的唯一拼法,见 {@link peerInboundIdOf})。 */
49
45
  export interface PeerInboundCoord {
50
46
  readonly sessionId: string;
@@ -3,9 +3,6 @@ import { PEER_INBOUND_LANES, boxOfLane, laneOfBox } from "./org-delivery-gate.js
3
3
  import { PEER_INBOUND_ID_PREFIX } from "./approval-queue-kind.js";
4
4
  import { recordFailOpen } from "./observability/fail-open.js";
5
5
  import { seatText } from "./trace/exit-seat.js";
6
- export function peerBoxScope(principal) {
7
- return principal ?? "default";
8
- }
9
6
  export function peerInboundIdOf(c) {
10
7
  return `${PEER_INBOUND_ID_PREFIX}${c.sessionId}:${c.lane}:${c.seq}`;
11
8
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * **一个调用方交来的权限模式词,本部署收不收、收成什么** —— 唯一的受理判官(规则一处、三只属主各一处、序一处)。
2
+ * **一个进入 run 的权限模式词(调用方交来的,或停驻 run 的存储体被重放时体上的),本部署收不收、收成什么** —— 唯一的受理判官(规则一处、三只属主各一处、序一处)。
3
3
  *
4
4
  * ## 一条规则,三只属主,本模块只排序
5
5
  * ① 本任务 settings 的 kill-switch 折叠(`task-settings.ts` `foldDisableAutoMode`:`disableAutoMode` 在 ⇒ `auto` 折 `default`);
@@ -8,12 +8,30 @@
8
8
  * ①② 是**入口一步**({@link entryStep}:改词、不拒),③ 是**托管一步**(只拒、不改词)。顺序承重的只有「② 在 ③ 之前」(地板把 bypass 钳成 plan 之后就不开写面,不拒);
9
9
  * ① 只动 `auto`、③ 只看开写面的词,两者互不相交。
10
10
  *
11
- * ## 两口的调用形(同一组函数、同一个序;同一个词 × 同一份部署事实 × 同一份任务 settings ⇒ 同一个结果,钉 `test/run-permission-mode-admission.test.ts` 的两口表)
11
+ * ## 三口的调用形(同一组函数、同一个序;提交口与切档口:同一个词 × 同一份部署事实 × 同一份任务 settings ⇒ 同一个结果,钉 `test/run-permission-mode-admission.test.ts` 的两口表;
12
+ * 续跑口与它们只差地板的出口词一个事实,钉 `test/entry-permission-mode.test.ts` 的续跑口表)
12
13
  * · 在飞 run 的切档口(`http/routes/run-permission-mode.ts`:`POST /v1/runs/:id/permission-mode` 的 `mode`,本任务 settings 读存储体)调整只判官 {@link admitPermissionModeWord}。
13
14
  * · 提交口(`POST /v1/tasks|/v1/tasks/stream|/v1/runs` 的词 = 顶层 `permissionMode` ?? bundle `settings.permissions.defaultMode`)把两步落在**上一版就在的两处**:
14
15
  * 入口一步在受理门盖章({@link stampSubmissionEntryMode},`http/admission.ts`:值形门之后、装配之前 —— 首跑装配与 `buildCheckpointCtx` 读的都是盖过章的体);
15
16
  * 托管一步是装配期那一处判(`boot/resolve-spec.ts`,对盖过章的词调同一只 ③;提交腿与续跑重放腿同经)。不把整只判官提前到受理门:那会让托管拒抢在
16
- * 属主 / 会话 / 执行上下文与装配期其余值判之前答(拒序变了 —— 钉同一文件的拒序组)。续跑重放不再过 ②:地板是**入口**事实,热改只影响之后提交的任务。
17
+ * 属主 / 会话 / 执行上下文与装配期其余值判之前答(拒序变了 —— 钉同一文件的拒序组)。
18
+ * · 续跑口(一条 HTTP 车道停驻 run 的存储体被重放之前;`http/resume-legs.ts` 的 `replayCtxOf`,五个续跑读点同调;leader 车道的行按那条车道「一 run 一读」照写下的体重放,
19
+ * 规则在 `replayCtxOf` 头注)调 {@link stampReplayEntryMode}:体上的词过与提交口**同一只**入口一步
20
+ * ({@link bodyEntry}:同一只折叠、同一只 `entryPermissionMode`、地板读同一份**活** config)、盖同一只章 —— 提交之后运维把 `AUTONOMY` 改严,或体是更早的写者留下的请求原词,
21
+ * 重放照**现**地板钳;被钳 = 钳到地板放行(与提交口同形,不拒、不新增码),生效词由这条腿的 `wiring_manifest.permissionMode` 说;钳过的体随 CAS 赢点写回,
22
+ * 存储体仍是这条 run 模式词唯一的源。与提交口只差一个**事实**(不是续跑专用臂,见下一节):体**顶层** `permissionMode` 上写着的是这块地板的出口词({@link floorExitWords})⇒ 原样读。
23
+ * 托管一步照旧在装配期判。
24
+ *
25
+ * ## 为什么地板的出口词在续跑口原样读
26
+ * 存储体上的模式词有三个写者:提交口(盖过地板)、切档口(盖过地板)、`plan_review` 的批准 / 编辑写回(`http/resume-legs.ts` `drivePlanReview`:把计划带出 plan,
27
+ * 写 `PLAN_APPROVE_PERMISSION_MODES_AFTER` 里的词 —— 这就是 plan 地板的出口:一个人批了这份计划)。提交口与切档口收的是**调用方**的词,出口词照钳(没有人批过计划);
28
+ * 续跑口读的是**存储体**的词,出口词是批准写下的 ⇒ 对它也钳 = 每一次挂起→续跑都把批过的计划拉回 plan(上一版撤掉按段重钳模式词的那条理由,`runtime-governance.ts`
29
+ * `autonomyOverrides` 顶注)。出口词以外的词(`bypassPermissions` / `auto` / `dontAsk` / 缺席)在这块地板下没有任何写者写得出来 —— 只能是更松的地板(改严之前)
30
+ * 或更早的写者(7.121.x 及以前的请求原词)留下的 ⇒ 照现地板钳。出口写者只写**顶层** `permissionMode`(`drivePlanReview` 的写回;bundle 的 `defaultMode` 不动),
31
+ * 所以出口判读的是顶层那一格的**原字**(严格读):只在 bundle 拼法里的词、经本任务 kill-switch 折出来的词(顶层 `auto` × `disableAutoMode` ⇒ `default`)、
32
+ * 词表外的串(重放的宽读把它折成 `default`)都不是出口写下的,照钳。
33
+ * ⚠️ 射程如实:顶层的出口词本身读不出是谁写的 —— 改严之前提交的顶层 `default` / `acceptEdits`、7.121.x 及以前的同字原词与批准写回逐字相同,原样重放
34
+ * (MIGRATION 如实写);要分开它们得给行补一个「谁写的」事实,本模块不猜。
17
35
  *
18
36
  * ## 为什么切档口必须经这只判官(而不是靠引擎的 deny 位)
19
37
  * 引擎在根腿上只认两只模式拒位(`RuntimeCaps.bypassPermissions` / `RuntimeCaps.autoMode`,本仓由 center 与本地 kill-switch 写);上面三问
@@ -55,8 +73,8 @@ export type PermissionModeAdmission = {
55
73
  export declare function admitPermissionModeWord(word: PermissionMode | undefined, facts: PermissionModeAdmissionFacts): PermissionModeAdmission;
56
74
  /**
57
75
  * 提交口的入口一步(`http/admission.ts` 唯一调用;直调装配腿的测试夹具也调它 —— 少了这一步的体是部署上到不了的体):
58
- * 体上的词(顶层 `permissionMode` ?? bundle `settings.permissions.defaultMode`)过 {@link entryStep},本任务的 settings = 体上那一份;
59
- * 入口词盖进体(`task-settings.ts` `stampEntryPermissionMode`:地板在场时写顶层 `permissionMode`,缺席原物)。不拒 —— 托管一步在装配期判(见顶注「两口的调用形」)。
76
+ * 体上的词过 {@link bodyEntry};入口词盖进体(`task-settings.ts` `stampEntryPermissionMode`:地板在场时写顶层 `permissionMode`,缺席原物)。
77
+ * 不拒 —— 托管一步在装配期判(见顶注「三口的调用形」)。
60
78
  */
61
79
  export declare function stampSubmissionEntryMode<B extends {
62
80
  permissionMode?: unknown;
@@ -65,4 +83,22 @@ export declare function stampSubmissionEntryMode<B extends {
65
83
  readonly body: B;
66
84
  readonly entry: EntryPermissionMode;
67
85
  };
86
+ /**
87
+ * 一块入口地板的**出口词** —— 存储体上由地板自己的出口写下的词(续跑口原样读它们,理由见顶注「为什么地板的出口词在续跑口原样读」)。
88
+ * 闭集穷尽 switch:引擎的模式词表加一词 ⇒ 编译红,逼着回答「这个词当地板时靠什么出口」。`plan` 的出口是人审(`plan_review` 的批准 / 编辑把计划带出 plan,
89
+ * 写回的词表 = `task-settings.ts` `PLAN_APPROVE_PERMISSION_MODES_AFTER`,那张表是唯一的源);其余五词今天不是任何档的地板(`runtime-governance.ts` `autonomyModeFloor`
90
+ * 只答 `plan` 或缺席),当地板也没有出口 ⇒ 空(失效关闭:比它松的词照钳)。
91
+ */
92
+ export declare function floorExitWords(floor: PermissionMode): readonly PermissionMode[];
93
+ /**
94
+ * 续跑口的入口一步:一条停驻 run 的存储体被重放之前,体上的词过 {@link bodyEntry}(与提交口同一只,地板读此刻的活 config),入口词盖进体
95
+ * (同一只 `stampEntryPermissionMode`);体顶层 `permissionMode` 的原字是 `bodyEntry` 读到的那块地板的出口词({@link floorExitWords})⇒ 体**原物**交回
96
+ * (出口写者只写顶层;那个词已经过了地板)。
97
+ * 地板缺席 ⇒ 体原物(零字节变化)。不拒:钳到地板放行,与提交口同形(见顶注「三口的调用形」)。
98
+ * 唯一调用点:`http/resume-legs.ts` 的 `replayCtxOf`(五个续跑读点同调;之后的存体预判、重建、plan_review 的模式词门与驱动段、CAS 赢点的写回读的都是它交回的那一份)。
99
+ */
100
+ export declare function stampReplayEntryMode<B extends {
101
+ permissionMode?: unknown;
102
+ settings?: unknown;
103
+ }>(body: B, deployment: PermissionModeAdmissionFacts["deployment"]): B;
68
104
  //# sourceMappingURL=permission-mode-admission.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { autonomyModeFloor, entryPermissionMode } from "./runtime-governance.js";
2
2
  import { HOST_POSTURE_REFUSED_CODE, permissionModeWriteFaceRefusal } from "./hosted-posture.js";
3
- import { foldDisableAutoMode, parseTaskSettings, requestedPermissionMode, stampEntryPermissionMode } from "./task-settings.js";
3
+ import { PLAN_APPROVE_PERMISSION_MODES_AFTER, coercePermissionModeStrict, foldDisableAutoMode, parseTaskSettings, requestedPermissionMode, stampEntryPermissionMode } from "./task-settings.js";
4
4
  export const PERMISSION_MODE_ADMISSION_FOLDS = ["entry_floor", "disable_auto_mode"];
5
5
  function entryStep(word, facts) {
6
6
  const folded = foldDisableAutoMode(word, facts.settings);
@@ -15,9 +15,35 @@ export function admitPermissionModeWord(word, facts) {
15
15
  return { kind: "refused", status: 422, code: HOST_POSTURE_REFUSED_CODE, message: why };
16
16
  return { kind: "admitted", requested: word, admitted: entry.effective, entry, ...(foldedBy !== undefined ? { foldedBy } : {}) };
17
17
  }
18
- export function stampSubmissionEntryMode(body, deployment) {
18
+ function bodyEntry(body, deployment) {
19
19
  const settings = parseTaskSettings(body.settings).settings;
20
- const { entry } = entryStep(requestedPermissionMode(body, settings), { deployment, settings });
20
+ return entryStep(requestedPermissionMode(body, settings), { deployment, settings }).entry;
21
+ }
22
+ export function stampSubmissionEntryMode(body, deployment) {
23
+ const entry = bodyEntry(body, deployment);
21
24
  return { body: stampEntryPermissionMode(body, entry), entry };
22
25
  }
26
+ export function floorExitWords(floor) {
27
+ switch (floor) {
28
+ case "plan":
29
+ return PLAN_APPROVE_PERMISSION_MODES_AFTER;
30
+ case "default":
31
+ case "acceptEdits":
32
+ case "auto":
33
+ case "bypassPermissions":
34
+ case "dontAsk":
35
+ return [];
36
+ default: {
37
+ const unhandled = floor;
38
+ throw new Error(`entry floor "${String(unhandled)}" has no exit arm`);
39
+ }
40
+ }
41
+ }
42
+ export function stampReplayEntryMode(body, deployment) {
43
+ const entry = bodyEntry(body, deployment);
44
+ const top = coercePermissionModeStrict(body.permissionMode);
45
+ if (entry.floor !== undefined && top !== undefined && floorExitWords(entry.floor).includes(top))
46
+ return body;
47
+ return stampEntryPermissionMode(body, entry);
48
+ }
23
49
  //# sourceMappingURL=permission-mode-admission.js.map
@@ -133,8 +133,10 @@ export declare class SqlAdoptionLogStore implements AdoptionLogStore {
133
133
  constructor(db: SqlDriver);
134
134
  private q;
135
135
  /** 池级只读执行面(phase 0 的 preflight 扫描用——它**零写**,不需要也不该占一条事务连接)。
136
- * 写路径一律走 {@link rebindTransaction};把驱动整个暴露出去会让调用方能绕开 phase CAS。 */
136
+ * 写路径一律走 {@link rebindTransaction};把驱动整个暴露出去会让调用方能绕开 phase CAS。
137
+ * 交出的是**封过**的执行面(只一个 `query`,出店口:库错只带原码 + 稳定句),不是驱动本身。 */
137
138
  get reader(): SqlExec;
139
+ private sealedReader;
138
140
  /**
139
141
  * 发起腿的 **insert-or-fetch 原子形**。返回读回的行(可能是别人插的)+ 是否本次插入。
140
142
  * 调用方据 `row.toPrincipal !== toPrincipal` 判「同 from 异 to」⇒ typed 409。
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
4
4
  import { acquireAdvisoryLockWithin, releaseAdvisoryLock } from "./ensure-schema-lock-policy.js";
5
5
  import { ensureIndexes } from "./ensure-index.js";
6
+ import { sealStoreClass, sealStoreObject } from "./store-boundary.js";
6
7
  export const ADOPTION_LOG_TABLE = "adoption_log";
7
8
  export const ADOPTION_PHASE = {
8
9
  INTENT: 2,
@@ -105,8 +106,9 @@ export class SqlAdoptionLogStore {
105
106
  return this.db.dialect === "mysql" ? mysql : pg;
106
107
  }
107
108
  get reader() {
108
- return this.db;
109
+ return (this.sealedReader ??= sealStoreObject({ query: (sql, params) => this.db.query(sql, params) }, "sql-adoption-log-reader", { query: "exit" }));
109
110
  }
111
+ sealedReader;
110
112
  async ensureAdoption(row) {
111
113
  const emptyLegs = "[]";
112
114
  const res = await this.db.query(this.q(`INSERT IGNORE INTO ${ADOPTION_LOG_TABLE} (${COLS}) VALUES (?, ?, ?, ?, 'in_flight', ?, ?, NULL, NULL, NULL, ?, ?)`, `INSERT INTO ${ADOPTION_LOG_TABLE} (${COLS}) VALUES ($1, $2, $3, $4, 'in_flight', $5, $6, NULL, NULL, NULL, $7, $8) ON CONFLICT (from_principal) DO NOTHING`), [row.adoptionId, row.fromPrincipal, row.toPrincipal, ADOPTION_PHASE.INTENT, emptyLegs, emptyLegs, row.nowMs, row.nowMs]);
@@ -216,6 +218,10 @@ export class SqlAdoptionLogStore {
216
218
  }
217
219
  }
218
220
  }
221
+ sealStoreClass(SqlAdoptionLogStore, "sql-adoption-log-store", {
222
+ ensureAdoption: "exit", getById: "exit", getByFrom: "exit", listInFlight: "exit", rebindOn: "exit", sweepOn: "exit", advancePhase: "exit", putConfigs: "exit",
223
+ finalizeAdopted: "exit", finalizeRejected: "exit", withLock: "exit",
224
+ });
219
225
  export class TiDBAdoptionLogStore extends SqlAdoptionLogStore {
220
226
  constructor(pool) {
221
227
  super(mysqlDriver(pool));
@@ -2,7 +2,7 @@ import { type ExecutionLane } from "../execution-lane-caps.js";
2
2
  import { type LocalAskLaneBackfillRefusal } from "./one-time-migrations.js";
3
3
  import type { AskState } from "../approval-ask-machine.js";
4
4
  import type { SettleCause } from "../approval-deny-reasons.js";
5
- import type { AskCallCoordinate, AskRow, AskTerminalClaimIntent, AskTerminalClaimOpts, AskTerminalClaimOutcome, AskTransitionPatch, ApprovalAskStore, BatchRow, BindGateInput, BindResult, DecideAskInput, DecideResult, EnsureAskResult, ExpireResult, NewAskRow } from "./approval-ask-store-sql.js";
5
+ import type { AskCallCoordinate, AskDecisionRecord, AskRow, AskTerminalClaimIntent, AskTerminalClaimOpts, AskTerminalClaimOutcome, AskTransitionPatch, ApprovalAskStore, BatchRow, BindGateInput, BindResult, DecideAskInput, DecideResult, EnsureAskResult, ExpireResult, NewAskRow, RecordParkedDecisionResult } from "./approval-ask-store-sql.js";
6
6
  /** 数据根下的目录名。**按店命名**,与兄弟 File 店同规(`approval-nonces` / `approval-ask-audit` /
7
7
  * `approval-exemptions` / `resume-anchors` / `checkpoint-ctx` / …)—— 首版写的是族名 `approvals`,而那
8
8
  * 正是读者会去找**持久 park 行**的地方(它们其实在 `checkpoint-ctx`),且持久数据面的名字按硬 breaking
@@ -24,7 +24,7 @@ export declare const ASK_JOURNAL_COMPACT_GROWTH = 2;
24
24
  * 账本的**动词闭集**([ref] 形)。运行期的词表判定与编译期的穷尽 `switch` 读同一份:
25
25
  * 往这里加一个词,{@link FileApprovalAskStore.replayOp} 的 switch 立刻编译红(漏一口不可能发布)。
26
26
  */
27
- declare const JOURNAL_OPS: readonly ["ensureAsk", "transitionAsk", "decideAsk", "claimTerminal", "claimTerminalRefuse", "expireAsk", "bindBatch", "abortBatch", "deferReconcile", "resolveProvisional", "deleteBySession", "reapSettledAsks", "reapEmptyBatches"];
27
+ declare const JOURNAL_OPS: readonly ["ensureAsk", "transitionAsk", "decideAsk", "recordParkedDecision", "claimTerminal", "claimTerminalRefuse", "expireAsk", "bindBatch", "abortBatch", "deferReconcile", "resolveProvisional", "deleteBySession", "reapSettledAsks", "reapEmptyBatches"];
28
28
  type JournalOpWord = (typeof JOURNAL_OPS)[number];
29
29
  /** 运行期词表(上面的数组)与类型面动词维({@link JournalCall})的**双向**对表 —— 任一边加词而另一边
30
30
  * 没跟上,这个别名解析成 `never`,{@link JOURNAL_OP_TABLE_GATE} 的赋值当场编译红。 */
@@ -59,6 +59,12 @@ type JournalCall = {
59
59
  askId: string;
60
60
  batchId: string;
61
61
  decision: DecideAskInput;
62
+ }
63
+ /** 停驻形的人决落行。更老的读者不认这个动词 ⇒ 拒启并点名出路(与留存两动词同一条降级纪律)。 */
64
+ | {
65
+ op: "recordParkedDecision";
66
+ askId: string;
67
+ record: AskDecisionRecord;
62
68
  } | {
63
69
  op: "claimTerminal";
64
70
  askId: string;
@@ -202,6 +208,8 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
202
208
  * (整本账的信封号另算 —— 见顶注「信封版本」)。
203
209
  */
204
210
  decideAsk(askId: string, batchId: string, owner: string | null, decision: DecideAskInput): Promise<DecideResult>;
211
+ /** 停驻形的人决落行 —— 出域的一次不落账本(与 {@link decideAsk} 同一条 [ref] 理由),其余落账 → 内核。 */
212
+ recordParkedDecision(askId: string, owner: string | null, record: AskDecisionRecord): Promise<RecordParkedDecisionResult>;
205
213
  claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
206
214
  expireAsk(askId: string, batchId: string): Promise<ExpireResult>;
207
215
  bindBatch(batchId: string, askId: string, gate: BindGateInput): Promise<BindResult>;
@@ -20,6 +20,7 @@ const JOURNAL_OPS = [
20
20
  "ensureAsk",
21
21
  "transitionAsk",
22
22
  "decideAsk",
23
+ "recordParkedDecision",
23
24
  "claimTerminal",
24
25
  "claimTerminalRefuse",
25
26
  "expireAsk",
@@ -280,6 +281,8 @@ export class FileApprovalAskStore {
280
281
  return this.core.transitionAsk(rec.askId, rec.from, rec.to, rec.patch);
281
282
  case "decideAsk":
282
283
  return this.core.decideAsk(rec.askId, rec.batchId, this.core.peekAsk(rec.askId)?.owner ?? null, rec.decision);
284
+ case "recordParkedDecision":
285
+ return this.core.recordParkedDecision(rec.askId, this.core.peekAsk(rec.askId)?.owner ?? null, rec.record);
283
286
  case "claimTerminal":
284
287
  return this.core.claimTerminal(rec.askId, rec.batchId, rec.intent, rec.cause !== undefined ? { cause: rec.cause } : undefined);
285
288
  case "claimTerminalRefuse":
@@ -318,6 +321,11 @@ export class FileApprovalAskStore {
318
321
  return this.core.decideAsk(askId, batchId, owner, journalled);
319
322
  return this.write({ op: "decideAsk", askId, batchId, decision: journalled }, (c) => this.core.decideAsk(c.askId, c.batchId, owner, c.decision));
320
323
  }
324
+ async recordParkedDecision(askId, owner, record) {
325
+ if (!askRowCarriesOwner(this.core.peekAsk(askId), owner))
326
+ return this.core.recordParkedDecision(askId, owner, record);
327
+ return this.write({ op: "recordParkedDecision", askId, record }, (c) => this.core.recordParkedDecision(c.askId, owner, c.record));
328
+ }
321
329
  async claimTerminal(askId, batchId, intent, opts) {
322
330
  switch (intent) {
323
331
  case "expire":