@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
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * ## 病灶(为什么要有这一只)
5
5
  * 问账行 `PARKED` 是终态(`approval-ask-machine.ts`:`PARKED: []`):赎回腿的单赢者是 **checkpoint 的 CAS**,取消腿动的也是 checkpoint(先 `expire` 门、再驱 run 终局),
6
- * 两条腿都不写 ask 行。于是「这张卡的门还在不在」这件事只有 checkpoint 店知道;读口若只看行,就会在卡被答过 / run 被取消之后继续说 `parked` 并指路去决定,
6
+ * 两条腿都不改 ask 行的态(赎回受理之后只把决定记在行上,行仍 `PARKED`;门被引擎重开了行也不知道)。于是「这张卡的门还在不在」这件事只有 checkpoint 店知道;读口若只看行,就会在卡被答过 / run 被取消之后继续说 `parked` 并指路去决定,
7
7
  * 而应答口对同一张卡早已答 `409 approval_stale`。同一件事两个读法 —— 本模块把它收成一个:读的是同一个问题、同一个来源。
8
8
  *
9
9
  * ## 判官
@@ -1,5 +1,6 @@
1
1
  import { VerifiedIdentity, type AuthBridgeErrorCode } from "@sema-agent/settings-schema/api/auth-bridge";
2
2
  import { type TokenScopeRefusal } from "@sema-agent/settings-schema/api/scopes";
3
+ import type { ServiceConfig } from "./config-types.js";
3
4
  import type { Logger } from "./observability/logger.js";
4
5
  import type { Metrics } from "./observability/metrics.js";
5
6
  export interface WebAdminJwtVerifierOptions {
@@ -58,6 +59,25 @@ type ReportedOutcome = {
58
59
  * (调用方按验证失败处置:不设身份 ⇒ 门答 generic 401)。验证失败的结局验证器自己已记过账,这里只答「不装」。
59
60
  */
60
61
  export declare function admitBridgeIdentity(outcome: ReportedOutcome, sinks: BridgeSinks): BridgeIdentity | undefined;
62
+ /**
63
+ * [ref] —— 「本 worker 把登录令牌绑在哪个 scope」的**唯一读口**(判官 {@link ssoForeignScope} 与起服行 `auth_bridge_enabled` 同读)。
64
+ * · fleet 成员(`FLEET_GENERATION` 在场 —— 「是不是 fleet 形」在本仓的那一个读数,配置目录行「缺席 ⇔ 非 fleet 成员」)⇒ `config.fleetScope`:
65
+ * env `FLEET_SCOPE` 的唯一读数(缺席 = 契约缺省 scope),排空口的身份比对读的就是这一个值 —— 一个事实一个读数,不另读 env。
66
+ * 编排器给每台受管引擎同拍渲成员代际与 `FLEET_SCOPE`;成员却缺 `FLEET_SCOPE` ⇒ 绑在缺省 scope(偏多拒,与排空口同读,不读成「不绑」)。
67
+ * · 非成员(手接 / 壳起的个人引擎)⇒ `undefined` = 不绑:令牌的 scope 就是它的用户自己的空间,准入不比对(本仓不为这一形猜一个空间)。
68
+ */
69
+ export declare function ssoBoundScopeOf(config: Pick<ServiceConfig, "fleetGeneration" | "fleetScope">): string | undefined;
70
+ /**
71
+ * [ref] —— **sso 准入的 scope 相等判**(唯一判官;唯一调用点 = `http/dispatch.ts` 的入口预认证)。入参是**已装**的桥身份({@link admitBridgeIdentity} 交出的,
72
+ * scope 已由 `readTokenScope` 读定、恒在)。本 worker 绑了 scope 而令牌的 scope 不是它 ⇒ 拒,交回两只 scope(只进运维日志;HTTP 体不带本 worker 的 scope ——
73
+ * 那是另一个空间的名字);不绑或相等 ⇒ `undefined`(装)。逐字比较,不规范化(两边都是 scope id 形;不同就是不同)。
74
+ */
75
+ export declare function ssoForeignScope(identity: {
76
+ readonly scope: string;
77
+ }, config: Pick<ServiceConfig, "fleetGeneration" | "fleetScope">): {
78
+ readonly tokenScope: string;
79
+ readonly boundScope: string;
80
+ } | undefined;
61
81
  /**
62
82
  * The live object `createWebAdminJwtVerifier` produces — a single `verify` entry point over the process-level JWKS cache, and the issuer it verifies for.
63
83
  * This is also the type of the injected seat (`ServiceDeps.webAdminJwtVerifier`): an embedder's verifier states which issuer it verifies, so `createHttpServer`
@@ -23,6 +23,15 @@ export function admitBridgeIdentity(outcome, sinks) {
23
23
  }
24
24
  return { ...rest, scope: scoped.scope, ...(inc.data !== undefined ? { principalInc: inc.data } : {}) };
25
25
  }
26
+ export function ssoBoundScopeOf(config) {
27
+ return config.fleetGeneration === undefined ? undefined : config.fleetScope;
28
+ }
29
+ export function ssoForeignScope(identity, config) {
30
+ const boundScope = ssoBoundScopeOf(config);
31
+ if (boundScope === undefined || identity.scope === boundScope)
32
+ return undefined;
33
+ return { tokenScope: identity.scope, boundScope };
34
+ }
26
35
  export const DEFAULT_JWKS_TTL_SEC = 15 * 60;
27
36
  export const JWKS_FETCH_TIMEOUT_MS = 10_000;
28
37
  const MAX_JWKS_BYTES = 256 * 1024;
@@ -42,7 +42,7 @@ export interface SessionHardReap {
42
42
  * `no_pgid`(未确认)、linux ⇒ `killed`;已收割 ⇒ `already_gone`。受据按观察答([ref] 车PA 删了「已硬收过」闩:组信号任一成员收到即
43
43
  * 成功,闩在部分送达时把活着读成没了),本函数也不为「让硬面先打」挪任何一拍。
44
44
  *
45
- * `scope` = 会话行属主 `owner ?? "default"`(与 core 注册铸值 `spec.principal ?? "default"` 同源 —— 缺席 scope 在 core 的
45
+ * `scope` = 会话的引擎租户分区 `sessionTenantScope`(唯一换算 `engineScopeOfOwner`,与 core 注册铸值同源 —— 缺席 scope 在 core 的
46
46
  * `canAccess` 上 fail-closed、一行都收不到,[ref] 钉过这一病)。
47
47
  *
48
48
  * 两处调用(见文件头 ①),都 `await`:E21 删除级联 `includeRetained:false`(显式活服务照留);停止口照传请求体。应答在硬面之后发,
@@ -16,7 +16,8 @@ interface RunnerEnv {
16
16
  }
17
17
  /**
18
18
  * bake-runner 的 **env 契约**(本进程只读这些;成文另见 USAGE.md §9.7):
19
- * · 必填:`IMAGE_API_BASE`(image-api 根,尾斜杠去掉)、`BAKE_RUNNER_TOKEN`(claim / heartbeat 的运维主体 bearer;不进日志、不进子进程)。
19
+ * · 必填:`IMAGE_API_BASE`(image-api 根,尾斜杠去掉)、`BAKE_RUNNER_TOKEN`(claim / heartbeat 的运维主体 bearer;不进日志、不进子进程 —— 它是本包的自有凭据,
20
+ * 登记在 `config-catalog.ts` `BAKE_RUNNER_CREDENTIAL_ENV`,起服第一步按名与按值向引擎声明:任何子进程组都拿不到它与它的同值副本,`BAKE_ENV_ALLOW` 点到它 ⇒ 拒启)。
20
21
  * · `BAKE_RUNNER_ID`(缺省 `bake-runner-<HOSTNAME>`)、`RECIPE_REF`(缺省 `HEAD` = 用现成 checkout)、`RECIPE_DIR`(缺省 `/opt/recipes`)、
21
22
  * `BUILD_SH_PATH`(缺省 `e2b-template/dev-sandbox/build.sh`)、`BAKE_DATA_DIR`(缺省 `/data`)、`LOG_LEVEL`(缺省 info)。
22
23
  * · 数值四键(`parseNumOrFailNonNegative`,非数 / 负数拒启):`BAKE_HEARTBEAT_MS`(30000)、`BAKE_IDLE_POLL_MS`(5000)、
@@ -24,7 +25,8 @@ interface RunnerEnv {
24
25
  * · `BAKE_ENV_ALLOW=NAME,NAME`(7.103.0,[ref] 残余):本进程 spawn 的每一只子进程组(build.sh 与 git fetch / docker push /
25
26
  * docker image inspect / df)的 env 缺省 = 本进程 env 剥密(runner 自己的 `BAKE_RUNNER_TOKEN` 在内);名单点到的键从本进程 env **原样**
26
27
  * 放回(部署方显式承担 —— 例如配方脚本真要一枚 `*_PASSWORD` 做 registry 登录)。与服务侧 `LSP_ENV_ALLOW` **同一只**解析器
27
- * (`config.ts` `parseEnvNameAllowlist`)与同一只放行谓词:形错(空段 / 尾逗号 / 非变量名形 / 点名任一只名单型旋钮)⇒ **拒启**,拒句点名
28
+ * (`config.ts` `parseEnvNameAllowlist`)与同一只放行谓词:形错(空段 / 尾逗号 / 非变量名形 / 点名任一只名单型旋钮 / 点名本包自有凭据 —— 名是,
29
+ * 或值是,runner 自己的 `BAKE_RUNNER_TOKEN` 在内)⇒ **拒启**,拒句点名
28
30
  * 旋钮与段号、不回显值;本进程环境里没有的名 ⇒ 起服 `config_env_entry_rejected` 只报序号、不放行;旋钮原文本身按凭据类,不进子进程。
29
31
  * 没有多租户姿态(构建宿主守护进程没有主体 / 租户);生效名单见起服行 `bake_runner_start.envAllow`(只键名)。
30
32
  * ⚠️ 这不是服务配置目录里的键(目录 `BAKE_*` 行是服务侧 bake API 的旋钮),服务进程不读它。
@@ -7,9 +7,8 @@ export interface BindHostConfig {
7
7
  authToken?: string;
8
8
  authTokens?: Record<string, string>;
9
9
  }
10
- /** 这台部署配了任何一把 service 凭据吗(`SERVICE_AUTH_TOKEN` / `SERVICE_AUTH_TOKENS`)。 */
11
- export declare function anyServiceCredential(config: Pick<BindHostConfig, "authToken" | "authTokens">): boolean;
12
- /** 无鉴权写面(`ALLOW_UNAUTHED_WRITES`)+ 零凭据 ⇒ 起服把监听地址收窄到 loopback(见 {@link resolveBindHost})。 */
10
+ /** 无鉴权写面(`ALLOW_UNAUTHED_WRITES`)+ 全局凭据门不武装(没配 service 凭据,`hasServiceAuth`)⇒ 起服把监听地址收窄到 loopback(见 {@link resolveBindHost})。
11
+ * 逃生口开着时写门不拒,而门不武装 ⇒ 能连上这个口的都能写 —— 鉴权桥在不在不改这一句(验不过 / 不带令牌的调用方照样过)。 */
13
12
  export declare function bindMustNarrow(config: Pick<BindHostConfig, "allowUnauthedWrites" | "authToken" | "authTokens">): boolean;
14
13
  /**
15
14
  * 这台进程的监听地址(绑址旋钮 + 无鉴权自收窄)。`undefined` = Node 缺省全接口。
package/dist/bind-host.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import { isIP } from "node:net";
2
2
  import { canonicalAddress } from "./net-address.js";
3
- export function anyServiceCredential(config) {
4
- return Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
5
- }
3
+ import { hasServiceAuth } from "./identity-source.js";
6
4
  export function bindMustNarrow(config) {
7
- return Boolean(config.allowUnauthedWrites) && !anyServiceCredential(config);
5
+ return Boolean(config.allowUnauthedWrites) && !hasServiceAuth(config);
8
6
  }
9
7
  export function resolveBindHost(config) {
10
8
  const mustNarrow = bindMustNarrow(config);
@@ -34,7 +34,7 @@ export function createBudgetAndTracing(ctx) {
34
34
  const tracer = createTracer(metrics, costQuota, modelUsageTracker, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1, promptManifestTracker);
35
35
  const sideQueryAccounting = createSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1);
36
36
  const toolResultStore = backend?.toolResult ? backend.toolResult() : undefined;
37
- const sessionPolicyStore = backend ? backend.sessionPolicy(logger) : undefined;
37
+ const sessionPolicyStore = backend ? backend.sessionPolicy() : undefined;
38
38
  const { store: fileHistoryStore, erasure: fileHistoryErasure } = assembleFileHistory(backend);
39
39
  return {
40
40
  brain, degradeRouteWatch, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker,
@@ -245,6 +245,9 @@ export interface EngineLeaseExitReport {
245
245
  * · `tickMs` = 自退检查的采样拍。自退发生在**生效窗**满之后、至多再晚一拍(顶注 8);生效窗恒 ≥ `lingerMs`(`lingerMs` 是各窗里最短的一个),所以触点比 `lingerMs` 密 ⇒ 在任何状态下都先于自退。
246
246
  * 读数**不预言**自退时刻:父还活着时生效的是 owned 窗,自退只会更晚。
247
247
  * · `lane`([ref])= 此刻的车道(与自退判据同一只派生函数,每次现读)。
248
+ * · `attached`([ref])= 本副本此刻**打开着的 SSE 流**条数 —— 与自退判据第一合取(`attachedStreams() === 0`)读**同一只**读口(`deps.attachedStreams()`),每次现读,不是采样拍的缓存。
249
+ * 哪些算、哪些不算写在契约 `/health` 节计法表的「附着」一列(与 `inflightBreakdown` 的「总数 / 阻断」两列同一张表):订阅流与流式提交腿算,后台腿、无流的同步腿、leader 腿不算。
250
+ * 它是**附着流条数,不是活跃调用方**:纯轮询壳(零 SSE)与毫秒级请求的下采样读到 0 合法 —— 判「这台引擎此刻有没有人在用」要连 `inflight` 与最近触点一起读(顶注 3),不能拿 `attached === 0` 当「没人」。
248
251
  *
249
252
  * ## 一源
250
253
  * 与租约腿自己的行为同一处铸出:`engine_lease_armed` 日志、自退判据用的窗、拍频推导(`tickMs = min(15s, 最短窗)`)和本读数读的是同一组数 ——
@@ -255,6 +258,8 @@ export interface EngineLeaseFacts {
255
258
  readonly tickMs: number;
256
259
  /** [ref]:此刻的车道。 */
257
260
  readonly lane: EngineLane;
261
+ /** [ref]:此刻打开着的 SSE 附着流条数(整数,≥ 0;语义与读口见上「报的是什么」一条 —— 附着流条数,不是活跃调用方)。 */
262
+ readonly attached: number;
258
263
  /** [ref] 首窗格({@link EngineLeaseFirstAttachFacts}):候首触窗有限(owned 窗有限)才在场 —— 永不到期的窗不上 wire(同上一段那条律)。 */
259
264
  readonly firstAttach?: EngineLeaseFirstAttachFacts;
260
265
  /** [ref] 交接格({@link EngineHandoverFacts}):只在 handover 车道在场。 */
@@ -184,6 +184,7 @@ export function createEngineLeaseTracker(deps) {
184
184
  lingerMs: shortestLingerMs,
185
185
  tickMs,
186
186
  lane: laneNow().lane,
187
+ attached: deps.attachedStreams(),
187
188
  ...(firstAttach !== undefined ? { firstAttach } : {}),
188
189
  ...(handover !== undefined ? { handover } : {}),
189
190
  });
@@ -19,6 +19,7 @@ import { type LocalDockerCapacity } from "../plugins/local-docker-capacity.js";
19
19
  import * as facts from "../capability-facts.js";
20
20
  import { type TaskAttachmentStore } from "../plugins/task-attachment-store.js";
21
21
  import type { TaskInheritEnv } from "../request-value-closure.js";
22
+ import { type SessionBindings, type SessionBindingSeat } from "../session-bindings.js";
22
23
  import { type HeadRead } from "../session-incarnation.js";
23
24
  import type { SpaceReclaimer } from "../workspace-space.js";
24
25
  import { type SessionResidentCounts } from "../session-resident-background.js";
@@ -43,20 +44,11 @@ import { type SessionResidentCounts } from "../session-resident-background.js";
43
44
  * 那一个副本上发生。跨副本的陈值靠「读面必经本副本的一次提交 ⇒ 必过本副本的围栏」兜住 —— 这条推理成立
44
45
  * 是因为 run 在受理它的那个副本上执行(ARCHITECTURE 副本模型)。device lane 的部署形本身也是单副本(§4.6)。
45
46
  */
46
- export interface SessionScopedRegistries {
47
+ export interface SessionScopedRegistries extends SessionBindingSeat {
47
48
  readonly perSessionCwd: Map<string, string>;
48
49
  readonly perSessionShellEnv: Map<string, Record<string, string>>;
49
- /** [ref]:host shell 的继承策略(`inheritEnv` 请求键)。只读面给 host 工厂包装;写只经 {@link setSessionInheritEnv}。 */
50
+ /** [ref]:host shell 的继承策略(`inheritEnv` 请求键)。只读面给 host 工厂包装。 */
50
51
  readonly perSessionInheritEnv: ReadonlyMap<string, TaskInheritEnv>;
51
- setSessionCwd(sid: string, cwd: string): void;
52
- setSessionShellEnv(sid: string, env: Record<string, string>): void;
53
- /**
54
- * [ref] —— 第三只 per-session 绑定(与 cwd / shellEnv 同一只 LRU 权威、同一条围栏、同一条删除)。
55
- * `undefined` = **清掉**这一格(不是「不动」):`inheritEnv` 是「本条请求要什么」,缺席的请求要的是缺省清洗 ——
56
- * 与 cwd / shellEnv 的「缺席 = 留着上一条的值」刻意不同(那两位由壳每条请求重发,粘住无害;这一位粘住 = 替一个没说话的
57
- * 请求放宽凭据面)。
58
- */
59
- setSessionInheritEnv(sid: string, policy: TaskInheritEnv | undefined): void;
60
52
  /** 本进程见过的会话 → 属主(围栏表本身,只读视图;`null` = 匿名)。**会话驻留后台行的枚举源**:`session-resident-background.ts` 逐会话问注册表
61
53
  * (core 的访问元组要带 scope,没有进程总数的读口),读的就是这一张 —— 每次提交写一笔、`dropSession` 清一笔、LRU 有界,不另立第二张会话名册。 */
62
54
  readonly sessionOwners: ReadonlyMap<string, string | null>;
@@ -178,9 +170,11 @@ export declare function createExecutionEnv(ctx: ExecutionEnvCtx): {
178
170
  perTaskImage: PerTaskImageRegistry;
179
171
  sessionEnvSelection: SessionEnvironmentSelection;
180
172
  perSessionCwd: Map<string, string>;
181
- setSessionCwd: (sid: string, cwd: string) => void;
182
- setSessionShellEnv: (sid: string, env: Record<string, string>) => void;
183
- setSessionInheritEnv: (sid: string, policy: TaskInheritEnv | undefined) => void;
173
+ sessionBindings: {
174
+ commitSessionBindings: (sessionId: string, bindings: SessionBindings) => import("../session-bindings.js").SessionBindingReceipt;
175
+ withdrawSessionBindings: (receipt: import("../session-bindings.js").SessionBindingReceipt) => boolean;
176
+ settleSessionBindings: (receipt: import("../session-bindings.js").SessionBindingReceipt) => boolean;
177
+ };
184
178
  fenceSessionOwner: (sid: string, owner: string | null, incarnation?: string) => void;
185
179
  dropSessionScoped: (sid: string) => void;
186
180
  dropSessionScopedOf: (sid: string, incarnation: string) => boolean;
@@ -32,6 +32,7 @@ import { reapOrphanWorktrees, withWorktreeIsolation } from "../plugins/worktree-
32
32
  import { derivePkgSourceEnv } from "../sandbox-pkg-source.js";
33
33
  import { effectiveHostWorkspace } from "../task-cwd.js";
34
34
  import { taskInheritEnvRefusal } from "../hosted-posture.js";
35
+ import { bindingsOf } from "../session-bindings.js";
35
36
  import { sessionFaceOfHead } from "../session-incarnation.js";
36
37
  import { isCodeToken } from "../terminal.js";
37
38
  import { createCacheMissMemory } from "../workspace-cache.js";
@@ -61,16 +62,40 @@ function sandboxLaneEnvMerged(lane) {
61
62
  return Object.keys(merged).length > 0 ? merged : undefined;
62
63
  }
63
64
  const MAX_CWD_SESSIONS = 4096;
65
+ function withWrites(cells, writes) {
66
+ return bindingsOf(writes.cwd ?? cells.cwd, writes.shellEnv !== undefined ? { ...writes.shellEnv } : cells.shellEnv, writes.inheritEnv);
67
+ }
68
+ function restoreCell(map, sid, value) {
69
+ if (value === undefined)
70
+ map.delete(sid);
71
+ else
72
+ map.set(sid, value);
73
+ }
64
74
  export function createSessionScopedRegistries(maxEntries = MAX_CWD_SESSIONS) {
65
75
  const perSessionCwd = new Map();
66
76
  const perSessionShellEnv = new Map();
67
77
  const perSessionInheritEnv = new Map();
68
78
  const owners = new Map();
69
79
  const occupants = new Map();
80
+ const logs = new Map();
81
+ const cellsOf = (sid) => bindingsOf(perSessionCwd.get(sid), perSessionShellEnv.get(sid), perSessionInheritEnv.get(sid));
82
+ const materialize = (sid, log) => {
83
+ const cells = log.entries.reduce((acc, e) => withWrites(acc, e.writes), log.base);
84
+ restoreCell(perSessionCwd, sid, cells.cwd);
85
+ restoreCell(perSessionShellEnv, sid, cells.shellEnv);
86
+ restoreCell(perSessionInheritEnv, sid, cells.inheritEnv);
87
+ };
88
+ const compact = (sid, log) => {
89
+ while (log.entries[0]?.settled === true)
90
+ log.base = withWrites(log.base, log.entries.shift().writes);
91
+ if (log.entries.length === 0)
92
+ logs.delete(sid);
93
+ };
70
94
  const dropSession = (sid) => {
71
95
  perSessionCwd.delete(sid);
72
96
  perSessionShellEnv.delete(sid);
73
97
  perSessionInheritEnv.delete(sid);
98
+ logs.delete(sid);
74
99
  owners.delete(sid);
75
100
  occupants.delete(sid);
76
101
  };
@@ -85,22 +110,34 @@ export function createSessionScopedRegistries(maxEntries = MAX_CWD_SESSIONS) {
85
110
  perSessionCwd,
86
111
  perSessionShellEnv,
87
112
  sessionOwners: owners,
88
- setSessionCwd: (sid, cwd) => {
89
- perSessionCwd.set(sid, cwd);
113
+ perSessionInheritEnv,
114
+ commitSessionBindings: (sid, bindings) => {
115
+ const log = logs.get(sid) ?? { base: cellsOf(sid), entries: [] };
116
+ logs.set(sid, log);
117
+ const token = Symbol(sid);
118
+ log.entries.push({ token, writes: { ...bindings, ...(bindings.shellEnv !== undefined ? { shellEnv: { ...bindings.shellEnv } } : {}) }, settled: false });
119
+ materialize(sid, log);
90
120
  touch(sid, undefined);
121
+ return { sessionId: sid, token };
91
122
  },
92
- setSessionShellEnv: (sid, env) => {
93
- perSessionShellEnv.set(sid, env);
94
- touch(sid, undefined);
123
+ withdrawSessionBindings: (receipt) => {
124
+ const log = logs.get(receipt.sessionId);
125
+ const at = log?.entries.findIndex((e) => e.token === receipt.token) ?? -1;
126
+ if (log === undefined || at < 0)
127
+ return false;
128
+ log.entries.splice(at, 1);
129
+ materialize(receipt.sessionId, log);
130
+ compact(receipt.sessionId, log);
131
+ return true;
95
132
  },
96
- perSessionInheritEnv,
97
- setSessionInheritEnv: (sid, policy) => {
98
- if (policy === undefined) {
99
- perSessionInheritEnv.delete(sid);
100
- return;
101
- }
102
- perSessionInheritEnv.set(sid, policy);
103
- touch(sid, undefined);
133
+ settleSessionBindings: (receipt) => {
134
+ const log = logs.get(receipt.sessionId);
135
+ const entry = log?.entries.find((e) => e.token === receipt.token);
136
+ if (log === undefined || entry === undefined)
137
+ return false;
138
+ entry.settled = true;
139
+ compact(receipt.sessionId, log);
140
+ return true;
104
141
  },
105
142
  fenceSessionOwner: (sid, owner, incarnation) => {
106
143
  const ownerChanged = owners.has(sid) && owners.get(sid) !== owner;
@@ -113,6 +150,7 @@ export function createSessionScopedRegistries(maxEntries = MAX_CWD_SESSIONS) {
113
150
  perSessionCwd.delete(sid);
114
151
  perSessionShellEnv.delete(sid);
115
152
  perSessionInheritEnv.delete(sid);
153
+ logs.delete(sid);
116
154
  }
117
155
  touch(sid, owner);
118
156
  },
@@ -250,9 +288,7 @@ export function createExecutionEnv(ctx) {
250
288
  const perTaskImage = new PerTaskImageRegistry();
251
289
  const sessionEnvSelection = new SessionEnvironmentSelection();
252
290
  const sessionScoped = createSessionScopedRegistries();
253
- const { perSessionCwd, setSessionCwd } = sessionScoped;
254
- const { perSessionShellEnv, setSessionShellEnv } = sessionScoped;
255
- const { perSessionInheritEnv, setSessionInheritEnv } = sessionScoped;
291
+ const { perSessionCwd, perSessionShellEnv, perSessionInheritEnv } = sessionScoped;
256
292
  const lspManager = createLspManager(config, (event, fields) => logger.info(event, fields));
257
293
  if (lspManager)
258
294
  logger.info("lsp_enabled", { provider: config.remoteExec.provider });
@@ -533,7 +569,8 @@ export function createExecutionEnv(ctx) {
533
569
  },
534
570
  });
535
571
  return {
536
- perTaskImage, sessionEnvSelection, perSessionCwd, setSessionCwd, setSessionShellEnv, setSessionInheritEnv,
572
+ perTaskImage, sessionEnvSelection, perSessionCwd,
573
+ sessionBindings: { commitSessionBindings: sessionScoped.commitSessionBindings, withdrawSessionBindings: sessionScoped.withdrawSessionBindings, settleSessionBindings: sessionScoped.settleSessionBindings },
537
574
  fenceSessionOwner: sessionScoped.fenceSessionOwner,
538
575
  dropSessionScoped: sessionScoped.dropSession,
539
576
  dropSessionScopedOf: sessionScoped.dropSessionOf,
@@ -1,7 +1,7 @@
1
1
  import { loadRemoteExec } from "@sema-agent/settings-schema/node";
2
2
  import { assertLocalConfigDirTrusted } from "../config-dir-trust.js";
3
3
  import { crossInvariant } from "../config-invariants.js";
4
- import { entryDeclarableRemoteExec } from "../config.js";
4
+ import { entryDeclarableRemoteExec, FILE_LANE_CELL_ENV, logConfigDiagnostics, parseHostLaneEnv, parseLocalDockerLaneEnv, } from "../config.js";
5
5
  import { ALL_EXECUTION_LANES, buildReadDenyFloorUnkeptNote, laneIsolated, laneWiresExecutionEnv } from "../execution-lane-caps.js";
6
6
  import { installReadDenyFloorDeployment } from "../read-deny-floor-notice.js";
7
7
  import { buildReadFacePostureNotice, settleDerivedReadFace } from "../read-face-posture.js";
@@ -27,6 +27,29 @@ function fileLaneUnresolvableRefusal(entry, lane, envReading, root) {
27
27
  `(file-source name→value resolution is not wired yet), and ${envReading}. The file's declaration ends the lane selection: no other declaration may stand in for it. ` +
28
28
  `Refusing before any task starts: set REMOTE_EXEC=${lane} with that lane's required env, or change the file to the lane you mean.`);
29
29
  }
30
+ function envReadingOfFileLane(minted, read, logger) {
31
+ if (minted !== undefined)
32
+ return minted;
33
+ const reading = read();
34
+ logConfigDiagnostics(logger);
35
+ return reading;
36
+ }
37
+ function discloseFileCellOverrides(lane, envReading, fileCells, logger, root) {
38
+ const cellEnvKeys = FILE_LANE_CELL_ENV[lane];
39
+ const keys = Object.entries(cellEnvKeys)
40
+ .filter(([cell]) => envReading[cell] !== undefined && fileCells[cell] !== undefined && envReading[cell] !== fileCells[cell])
41
+ .map(([, envKey]) => envKey);
42
+ if (keys.length === 0)
43
+ return;
44
+ logger.warn("remote_exec_file_overrides", {
45
+ provider: lane,
46
+ root,
47
+ keys,
48
+ note: `config.d/remote-exec.json (CONFIG_PROVIDER=local, file-wins) gives a different value than env for ${keys.join(", ")} on the "${lane}" lane — the file's value is in effect and ` +
49
+ `the env value for ${keys.length === 1 ? "that key is" : "those keys are"} INERT. The lane's other env parameters are read as usual. ` +
50
+ `Make the two agree: unset the env key if the file's value is the intended one, or change the file's cell (an optional cell can be removed from the file; "image" is mandatory there).`,
51
+ });
52
+ }
30
53
  export async function composeExecutionLane(loaded, ctx) {
31
54
  if (ctx.configSource === "local")
32
55
  await applyRemoteExecFile(loaded, ctx);
@@ -127,25 +150,29 @@ async function applyRemoteExecFile(config, ctx) {
127
150
  };
128
151
  const drives = (lane) => ctx.entry.runs.includes(lane);
129
152
  if (fileRx.provider === "host" && drives("host")) {
130
- config.remoteExec = {
131
- provider: "host",
153
+ const envReading = envReadingOfFileLane(config.remoteExec?.provider === "host" ? config.remoteExec : undefined, parseHostLaneEnv, logger);
154
+ const fileCells = {
132
155
  ...(fileRx.workdir ? { workspaceBase: fileRx.workdir } : {}),
133
156
  ...(fileRx.commandTimeoutMs != null ? { commandTimeoutMs: fileRx.commandTimeoutMs } : {}),
134
157
  };
158
+ config.remoteExec = { provider: "host", ...envReading, ...fileCells };
135
159
  disclosePreemption("host");
160
+ discloseFileCellOverrides("host", envReading, fileCells, logger, localRoot);
136
161
  logger.info("remote_exec_from_file", { provider: "host", root: localRoot });
137
162
  return;
138
163
  }
139
164
  if (fileRx.provider === "local-docker" && drives("local-docker")) {
140
- config.remoteExec = {
141
- provider: "local-docker",
165
+ const envReading = envReadingOfFileLane(config.remoteExec?.provider === "local-docker" ? config.remoteExec : undefined, () => parseLocalDockerLaneEnv(config.requirePrincipal !== true), logger);
166
+ const fileCells = {
142
167
  image: fileRx.image,
143
168
  ...(fileRx.mountPath ? { mountPath: fileRx.mountPath } : fileRx.workdir ? { mountPath: fileRx.workdir } : {}),
144
169
  ...(fileRx.memory ? { memory: fileRx.memory } : {}),
145
170
  ...(fileRx.cpus != null ? { cpus: fileRx.cpus } : {}),
146
171
  ...(fileRx.network ? { network: fileRx.network } : {}),
147
172
  };
173
+ config.remoteExec = { provider: "local-docker", ...envReading, ...fileCells };
148
174
  disclosePreemption("local-docker");
175
+ discloseFileCellOverrides("local-docker", envReading, fileCells, logger, localRoot);
149
176
  logger.info("remote_exec_from_file", { provider: "local-docker", root: localRoot, image: fileRx.image });
150
177
  return;
151
178
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 晚绑席([ref] 十段切分的**唯一**跨段可变面)—— 五只 cell,读点在前段、写点在后段。
2
+ * 晚绑席([ref] 十段切分的**唯一**跨段可变面)—— 六只 cell,读点在前段、写点在后段。
3
3
  *
4
4
  * 为什么是模块级 live binding 而不是 ctx 字段:读点的原文是**裸标识符**(`() => runStore`、
5
5
  * `() => parkedAskRedeem`),而 ctx 字段是**快照** —— 把读点改成 `ctx.runStore` 就是正文改写,
@@ -9,9 +9,10 @@
9
9
  * · `parkedAskRedeem` / `runDenySweep` —— 它们的**声明行本身**就是 `let`(逐字搬进本文件,含缩进),
10
10
  * 赋值点(server 造出来之后那两处)也随之搬进 `bindServerLateSeats` —— 跨模块给 import 赋值是非法的,
11
11
  * 所以 cell 与它的写点必须同居一个模块;
12
- * · `sessionNotifyDoor` —— 同上一形(server 的产物,写点在 `bindServerLateSeats`)。
12
+ * · `sessionNotifyDoor` / `runEndedHandoff` —— 同上一形(server 的产物,写点在 `bindServerLateSeats`);
13
+ * · `sessionAgeOut` —— 会话老化腿(`boot/session-age-out.ts`):读点在第 8 段的维护拍,它要的删除协调器在第 9 段铸 ⇒ 写点 `bindSessionAgeOut` 在第 9 段协调器之后。
13
14
  *
14
- * 时序与修前逐字相同:五只 cell 的读点全是 `() => x` 取值闭包(boot 期不求值,最早的求值发生在
15
+ * 时序与修前逐字相同:六只 cell 的读点全是 `() => x` 取值闭包(boot 期不求值,最早的求值发生在
15
16
  * listen 之后的请求/tick 上),写点仍在各自原来的那一拍。未绑窗内取值得到 `undefined` —— 这正是
16
17
  * 各消费座既有的 fail-closed 臂(device 限速座:`gate()` 取不到就抛,见 boot/device-lane.ts:68)。
17
18
  */
@@ -27,7 +28,10 @@ declare let rateLimiter: RateGate | undefined;
27
28
  declare let parkedAskRedeem: ParkedAskRedeem | undefined;
28
29
  declare let runDenySweep: ((now: number) => Promise<void>) | undefined;
29
30
  declare let sessionNotifyDoor: SessionNotifyDoor | undefined;
30
- export { runStore, rateLimiter, parkedAskRedeem, runDenySweep, sessionNotifyDoor };
31
+ declare let runEndedHandoff: ((runId: string) => Promise<void>) | undefined;
32
+ declare let sessionAgeOut: (() => Promise<unknown>) | undefined;
33
+ export { runStore, rateLimiter, parkedAskRedeem, runDenySweep, sessionNotifyDoor, runEndedHandoff, sessionAgeOut };
34
+ export declare function bindSessionAgeOut(v: (() => Promise<unknown>) | undefined): void;
31
35
  export declare function bindRunStore(v: ReturnType<StoreBackend["run"]> | undefined): void;
32
36
  export declare function bindRateLimiter(v: RateGate): void;
33
37
  export declare function bindServerLateSeats(ctx: {
@@ -3,7 +3,12 @@ let rateLimiter;
3
3
  let parkedAskRedeem;
4
4
  let runDenySweep;
5
5
  let sessionNotifyDoor;
6
- export { runStore, rateLimiter, parkedAskRedeem, runDenySweep, sessionNotifyDoor };
6
+ let runEndedHandoff;
7
+ let sessionAgeOut;
8
+ export { runStore, rateLimiter, parkedAskRedeem, runDenySweep, sessionNotifyDoor, runEndedHandoff, sessionAgeOut };
9
+ export function bindSessionAgeOut(v) {
10
+ sessionAgeOut = v;
11
+ }
7
12
  export function bindRunStore(v) {
8
13
  runStore = v;
9
14
  }
@@ -14,6 +19,7 @@ export function bindServerLateSeats(ctx) {
14
19
  const { config, logger, server } = ctx;
15
20
  runDenySweep = server.denyExpiredApprovals;
16
21
  sessionNotifyDoor = server.sessionNotifyDoor;
22
+ runEndedHandoff = server.handOverRunEndedDecisions;
17
23
  if (!config.directDoorActive)
18
24
  parkedAskRedeem = server.redeemParkedAsk;
19
25
  else
@@ -326,8 +326,9 @@ export declare function judgeConsolidationValve(input: ConsolidationValveInput):
326
326
  export declare function buildConsolidationValveAudit(input: {
327
327
  /** 阀门判决({@link resolveConsolidationValve} 的 `enabled` + `source`)。 */
328
328
  valve: Pick<ConsolidationValve, "enabled" | "source">;
329
- /** 这台部署配了任何一把 service 凭证吗(`config.authToken` 或 `config.authTokens` 非空)。 */
330
- hasServiceAuth: boolean;
329
+ /** 这台部署接没接任何一类过凭据门的来源(service 凭据 / 鉴权桥;部署形表行 `credentialGateSource`,写门律 5 在部署上的投影)。
330
+ * 没接 ⇒ 没有一次请求过得了写门(逃生口之外)。 */
331
+ credentialGateSource: boolean;
331
332
  /** 本地开发的显式逃生口(`ALLOW_UNAUTHED_WRITES=true`)。 */
332
333
  allowUnauthedWrites: boolean;
333
334
  }): {
@@ -282,17 +282,17 @@ export function buildConsolidationValveAudit(input) {
282
282
  if (!input.valve.enabled || input.valve.source !== "explicit")
283
283
  return { warnings: [] };
284
284
  const warnings = [];
285
- if (!input.hasServiceAuth && !input.allowUnauthedWrites) {
285
+ if (!input.credentialGateSource && !input.allowUnauthedWrites) {
286
286
  warnings.push({
287
287
  tag: "memory_consolidation_run_credential_gated",
288
- detail: "MEMORY_CONSOLIDATION_DRIVER=on armed the consolidation valve, but this worker has NO service credential " +
289
- "(SERVICE_AUTH_TOKEN / per-system tokens are unset) and ALLOW_UNAUTHED_WRITES is not on. POST " +
288
+ detail: "MEMORY_CONSOLIDATION_DRIVER=on armed the consolidation valve, but this worker accepts NO credential on its write face " +
289
+ "(SERVICE_AUTH_TOKEN / per-system tokens are unset and no SSO bridge verifies registry sign-in tokens) and ALLOW_UNAUTHED_WRITES is not on. POST " +
290
290
  "/v1/admin/memory/consolidation/run runs the model, so it rides the billable-submit gate and is refused " +
291
- "with 503 auth.service_token_required BEFORE it reaches the route — while capabilities." +
291
+ "with 503 auth.service_token_required BEFORE it reaches the route for every caller — while capabilities." +
292
292
  "memoryConsolidationDriver still reports true (a family-wide gap this worker shares with adoption / memory " +
293
293
  "bundle / memory compliance; it is documented, not silently accepted). GET /v1/admin/memory/consolidation " +
294
- "is unaffected (it runs no model). Set SERVICE_AUTH_TOKEN, or set ALLOW_UNAUTHED_WRITES=true for local " +
295
- "development only.",
294
+ "is unaffected (it runs no model). Set SERVICE_AUTH_TOKEN (or configure the SSO bridge, AUTH_BRIDGE_ISSUER, so verified " +
295
+ "sign-in tokens pass), or set ALLOW_UNAUTHED_WRITES=true for local development only.",
296
296
  });
297
297
  }
298
298
  return { warnings };
@@ -28,6 +28,7 @@ import type { ToolApprovalCoordinator } from "../tool-approval.js";
28
28
  import type { ParkCommitWatch } from "../plugins/park-commit-watch.js";
29
29
  import { type SandboxReaper } from "../plugins/sandbox-reaper.js";
30
30
  import { type AuditRotationPort } from "../approval-ask-retention.js";
31
+ import type { HolderPullTally } from "../orchestration/workflow-park-holder.js";
31
32
  /** [ref] / [ref]§五(sema-comms `audits/failopen-governance-176.md` §7 "Z 类 1 条"):default consecutive-failure
32
33
  * count before a reaper leg's swallowed error gets a `warn` line. The ~15 `.catch(() => undefined)` sites
33
34
  * below are correct in DIRECTION (a hiccuping periodic sweep must never crash the whole tick loop — one bad
@@ -103,6 +104,8 @@ export interface ReapersCtx {
103
104
  * `TOOL_APPROVAL_ENABLED=false` 的部署恒 undefined ⇒ 收敛器照常收敛,只是不发通知帧(壳侧靠
104
105
  * 重连 preamble 对账,见 approval-card.ts 的 `ApprovalRevokeFrame` 顶注)。 */
105
106
  toolApproval: ToolApprovalCoordinator | undefined;
107
+ /** workflow 停驻卡持有进程的拉取腿(`orchestration/workflow-park-holder.ts`;第 8 段按决定店 / 执行器视图在场装配)。缺席 = 没有 workflow 停驻车道 ⇒ 本腿零调用。 */
108
+ workflowParkHolderPull?: () => Promise<HolderPullTally>;
106
109
  /** [ref]:规则店的**保留期口**。`PERMISSION_RULES_ENABLED=false` 或后端没实装 ⇒ undefined
107
110
  * ⇒ 本腿根本不注册(零扫描),与它的同族旋钮腿同姿势。
108
111
  *
@@ -122,6 +125,8 @@ export interface ReapersCtx {
122
125
  approvalAskAudit: AuditRotationPort | undefined;
123
126
  /** 晚绑(server 造出来才有)——见文件头「位置即契约」①。 */
124
127
  getRunDenySweep: () => ((now: number) => Promise<void>) | undefined;
128
+ /** 会话老化腿(晚绑:它要的删除协调器在第 9 段铸,`late-binds.ts` 的 `sessionAgeOut`)。缺席 = 这台部署记不了会话种类 / 还没绑 ⇒ 本腿零调用。 */
129
+ getSessionAgeOut?: () => (() => Promise<unknown>) | undefined;
125
130
  /** 本车道的沙箱回收座(`plugins/sandbox-reaper.ts`;第 5 段与 run 工厂同一份车道配置铸):k8s 车道 = 沙箱 Pod reaper(`plugins/k8s-pod-reaper.ts`),local-docker 车道 = 无主容器 reaper
126
131
  * (`plugins/local-docker-reaper.ts`)。缺席 = 别的车道 ⇒ 本腿零调用。一次部署一条执行车道 ⇒ 一只座、一个节拍。 */
127
132
  sandboxReaper?: SandboxReaper;
@@ -86,7 +86,7 @@ export function createPlacementAwareReapSessions(deps) {
86
86
  };
87
87
  }
88
88
  export function startReapers(ctx) {
89
- const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, parkCommits, rateLimiter, costQuota, storeSweep, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, sqlWorkflowRunStore, workflowNotifyJournal, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, userInputLedger, approvalAskAudit, sandboxReaper, } = ctx;
89
+ const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, parkCommits, rateLimiter, costQuota, storeSweep, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, sqlWorkflowRunStore, workflowNotifyJournal, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, getSessionAgeOut, userInputLedger, approvalAskAudit, sandboxReaper, workflowParkHolderPull, } = ctx;
90
90
  const leg = (p) => {
91
91
  if (p !== undefined)
92
92
  trackMaintenanceLeg(p);
@@ -119,6 +119,7 @@ export function startReapers(ctx) {
119
119
  const approvalNonceReapExpiredGuard = createThrottledReaperCatch("approval_nonce_reap_expired", logger);
120
120
  const runsFailSuspendedExpiredCheckpointGuard = createThrottledReaperCatch("runs_fail_suspended_expired_checkpoint", logger);
121
121
  const runDenySweepGuard = createThrottledReaperCatch("run_deny_sweep", logger);
122
+ const sessionAgeOutGuard = createThrottledReaperCatch("session_age_out", logger);
122
123
  const runsReapSuspendedGuard = createThrottledReaperCatch("runs_reap_suspended", logger);
123
124
  const checkpointReapCtxGuard = createThrottledReaperCatch("checkpoint_reap_ctx", logger);
124
125
  const storeSweepGuards = {
@@ -158,6 +159,8 @@ export function startReapers(ctx) {
158
159
  const approvalReconcileGuard = createThrottledReaperCatch("approval_reconcile", logger);
159
160
  const approvalSuspendedPollGuard = createThrottledReaperCatch("approval_suspended_poll", logger);
160
161
  let approvalSuspendedPollInFlight = false;
162
+ const workflowParkHolderPullGuard = createThrottledReaperCatch("workflow_park_holder_pull", logger);
163
+ let workflowParkHolderPullInFlight = false;
161
164
  const permissionRuleReapGuard = createThrottledReaperCatch("permission_rule_reap_expired", logger);
162
165
  let permissionRuleReapInFlight = false;
163
166
  let approvalReconcileInFlight = false;
@@ -265,6 +268,8 @@ export function startReapers(ctx) {
265
268
  leg(getRunDenySweep()?.(Date.now())
266
269
  .then(() => runDenySweepGuard.onSuccess(), runDenySweepGuard.onError));
267
270
  }
271
+ leg(getSessionAgeOut?.()?.()
272
+ .then(() => sessionAgeOutGuard.onSuccess(), sessionAgeOutGuard.onError));
268
273
  leg(approvalNonceStore
269
274
  ?.reapExpired()
270
275
  .then(reapCount("approval_nonces_reaped_total", {}))
@@ -360,6 +365,12 @@ export function startReapers(ctx) {
360
365
  .then(() => approvalSuspendedPollGuard.onSuccess(), approvalSuspendedPollGuard.onError)
361
366
  .finally(() => (approvalSuspendedPollInFlight = false)));
362
367
  }
368
+ if (workflowParkHolderPull && !workflowParkHolderPullInFlight) {
369
+ workflowParkHolderPullInFlight = true;
370
+ leg(workflowParkHolderPull()
371
+ .then(() => workflowParkHolderPullGuard.onSuccess(), workflowParkHolderPullGuard.onError)
372
+ .finally(() => (workflowParkHolderPullInFlight = false)));
373
+ }
363
374
  if (!permissionRuleReapInFlight) {
364
375
  permissionRuleReapInFlight = true;
365
376
  leg(permissionRuleStores
@@ -33,9 +33,9 @@ export interface ResolveSpecCtx {
33
33
  /** 活引用(registry 热应用换引用)—— 见文件头「位置即契约」。 */
34
34
  getKeyResolver: () => ReturnType<typeof createKeyResolver>;
35
35
  taskAttachmentStore: TaskAttachmentStore | undefined;
36
- perSessionCwd: Map<string, string>;
37
- setSessionCwd: (sid: string, cwd: string) => void;
38
- setSessionShellEnv: (sid: string, env: Record<string, string>) => void;
36
+ /** 会话登记簿的工作目录那一格(**只读**:这条腿没带工作目录时它是会话上已登记的那一枚)。会话绑定(cwd / shellEnv / inheritEnv)本装配器只判、只算
37
+ * (`LegFacts.sessionBindings`),写在路由层 run 认领之后的唯一提交点(`session-bindings.ts` 的 `commitLegBindings`)—— 律见那只文件头注。 */
38
+ perSessionCwd: ReadonlyMap<string, string>;
39
39
  /** 两张 per-session 登记簿的**租户围栏**(codex R1-F1):每次提交对拍本会话的主人,换主即清。
40
40
  * 论证(为什么 device lane 把这条从「不需要」变成「必需」)逐字见 `createSessionScopedRegistries` 头注。 */
41
41
  fenceSessionOwner: (sid: string, owner: string | null, incarnation?: string) => void;