@sema-agent/server 7.137.0 → 7.139.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 (328) hide show
  1. package/MIGRATION.md +600 -142
  2. package/README.md +7 -3
  3. package/README.zh-CN.md +6 -3
  4. package/USAGE.md +132 -41
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/wire.d.ts +3 -3
  8. package/dist/approval-card.d.ts +23 -16
  9. package/dist/approval-card.js +1 -0
  10. package/dist/audit.js +1 -0
  11. package/dist/background-agent-name.d.ts +5 -0
  12. package/dist/background-agent-name.js +5 -0
  13. package/dist/bench/s1/arms.d.ts +1 -1
  14. package/dist/bench/s1/arms.js +1 -3
  15. package/dist/bench/s1/run-firm.js +2 -2
  16. package/dist/boot/budget-tracing.d.ts +1 -12
  17. package/dist/boot/deployment-mods.d.ts +46 -16
  18. package/dist/boot/deployment-mods.js +82 -6
  19. package/dist/boot/device-lane.d.ts +8 -3
  20. package/dist/boot/device-lane.js +7 -2
  21. package/dist/boot/execution-env.d.ts +4 -2
  22. package/dist/boot/execution-env.js +16 -24
  23. package/dist/boot/execution-lane.d.ts +1 -1
  24. package/dist/boot/governance-seams.d.ts +12 -18
  25. package/dist/boot/governance-seams.js +10 -24
  26. package/dist/boot/leader.d.ts +3 -0
  27. package/dist/boot/leader.js +1 -0
  28. package/dist/boot/memory-consolidation.js +2 -2
  29. package/dist/boot/org-delivery.js +15 -2
  30. package/dist/boot/resolve-spec.d.ts +4 -1
  31. package/dist/boot/resolve-spec.js +29 -19
  32. package/dist/boot/retention-lane.d.ts +14 -8
  33. package/dist/boot/retention-lane.js +2 -8
  34. package/dist/boot/runner-deps.d.ts +10 -4
  35. package/dist/boot/runner-deps.js +2 -0
  36. package/dist/boot/session-faces.d.ts +0 -3
  37. package/dist/boot/session-faces.js +15 -8
  38. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -12
  40. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  41. package/dist/boot/stage-05-execution-env.js +8 -5
  42. package/dist/boot/stage-06-runners.d.ts +3 -12
  43. package/dist/boot/stage-06-runners.js +5 -3
  44. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  45. package/dist/boot/stage-07-capability-layer.js +2 -2
  46. package/dist/boot/stage-08-reapers.d.ts +3 -12
  47. package/dist/boot/stage-09-leader.d.ts +3 -12
  48. package/dist/boot/stage-09-leader.js +2 -3
  49. package/dist/boot/stage-10-http-server.d.ts +3 -12
  50. package/dist/boot/stage-10-http-server.js +5 -3
  51. package/dist/boot/stores.js +4 -4
  52. package/dist/boot/web-admin.d.ts +5 -2
  53. package/dist/boot/web-admin.js +5 -5
  54. package/dist/brain.js +5 -5
  55. package/dist/budget.d.ts +17 -15
  56. package/dist/budget.js +2 -2
  57. package/dist/capabilities/mcp-probe.d.ts +36 -11
  58. package/dist/capabilities/mcp-probe.js +44 -5
  59. package/dist/capabilities/scenarios.d.ts +1 -1
  60. package/dist/capability-facts.d.ts +21 -7
  61. package/dist/capability-facts.js +13 -3
  62. package/dist/checkpoint-identity.d.ts +13 -6
  63. package/dist/config-catalog.js +14 -8
  64. package/dist/config-invariants.d.ts +23 -3
  65. package/dist/config-invariants.js +42 -0
  66. package/dist/config-provider.d.ts +19 -24
  67. package/dist/config-provider.js +27 -12
  68. package/dist/config-types.d.ts +25 -12
  69. package/dist/config.d.ts +33 -0
  70. package/dist/config.js +52 -20
  71. package/dist/delegated-session-owner.d.ts +2 -2
  72. package/dist/deployment-shape.d.ts +7 -7
  73. package/dist/device-enrollment.d.ts +41 -2
  74. package/dist/device-enrollment.js +27 -12
  75. package/dist/device-store.d.ts +58 -85
  76. package/dist/device-store.js +19 -49
  77. package/dist/device-ws-hub.js +2 -1
  78. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  79. package/dist/exec-cut.d.ts +60 -0
  80. package/dist/exec-cut.js +36 -0
  81. package/dist/execution-lane-caps.d.ts +19 -0
  82. package/dist/execution-lane-caps.js +10 -0
  83. package/dist/execution-placement.d.ts +259 -0
  84. package/dist/execution-placement.js +199 -0
  85. package/dist/field-conflict.d.ts +3 -2
  86. package/dist/field-conflict.js +1 -0
  87. package/dist/gate-settlement.d.ts +71 -0
  88. package/dist/gate-settlement.js +40 -0
  89. package/dist/hooks/classic-stations.d.ts +1 -0
  90. package/dist/hooks/classic-stations.js +1 -0
  91. package/dist/hooks/command-spawn.d.ts +2 -2
  92. package/dist/hooks/hook-runner.js +14 -14
  93. package/dist/hosted-posture.d.ts +2 -2
  94. package/dist/http/admission.js +10 -19
  95. package/dist/http/checkpoint-ctx.d.ts +21 -12
  96. package/dist/http/checkpoint-ctx.js +4 -2
  97. package/dist/http/claimed-run.d.ts +1 -1
  98. package/dist/http/dispatch.d.ts +4 -0
  99. package/dist/http/dispatch.js +3 -0
  100. package/dist/http/durable-run-leg.js +12 -6
  101. package/dist/http/execution-context.d.ts +218 -0
  102. package/dist/http/execution-context.js +330 -0
  103. package/dist/http/memory-compliance-failure-reply.js +3 -0
  104. package/dist/http/resume-legs.d.ts +5 -11
  105. package/dist/http/resume-legs.js +179 -104
  106. package/dist/http/route-ctx.d.ts +44 -59
  107. package/dist/http/route-ctx.js +2 -5
  108. package/dist/http/routes/a2a-serve.js +10 -4
  109. package/dist/http/routes/agents-roster.d.ts +1 -1
  110. package/dist/http/routes/agents-roster.js +5 -1
  111. package/dist/http/routes/approvals-assistant.js +6 -6
  112. package/dist/http/routes/background.d.ts +29 -1
  113. package/dist/http/routes/background.js +29 -2
  114. package/dist/http/routes/capabilities.js +12 -10
  115. package/dist/http/routes/devices.d.ts +13 -19
  116. package/dist/http/routes/devices.js +21 -26
  117. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  118. package/dist/http/routes/mcp-reconnect.js +7 -5
  119. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  120. package/dist/http/routes/memory-origin.d.ts +1 -1
  121. package/dist/http/routes/rules.js +3 -3
  122. package/dist/http/routes/runs.js +11 -7
  123. package/dist/http/routes/session-sync.js +110 -15
  124. package/dist/http/routes/sessions.d.ts +6 -0
  125. package/dist/http/routes/sessions.js +119 -13
  126. package/dist/http/routes/side-query.d.ts +12 -4
  127. package/dist/http/routes/side-query.js +6 -8
  128. package/dist/http/routes/tasks.js +800 -782
  129. package/dist/http/server.d.ts +61 -39
  130. package/dist/http/server.js +11 -0
  131. package/dist/http/session-inherit-env.d.ts +3 -4
  132. package/dist/http/session-inherit-env.js +1 -1
  133. package/dist/http/wire-types.d.ts +22 -18
  134. package/dist/identity-source.d.ts +1 -1
  135. package/dist/index.d.ts +3 -0
  136. package/dist/index.js +1 -0
  137. package/dist/leader/wire.d.ts +7 -0
  138. package/dist/leader/wire.js +5 -4
  139. package/dist/mcp-assertion-header.d.ts +51 -40
  140. package/dist/mcp-assertion-header.js +10 -11
  141. package/dist/mcp-authorization-face.d.ts +27 -0
  142. package/dist/mcp-authorization-face.js +15 -0
  143. package/dist/memory-operator-faces.d.ts +13 -4
  144. package/dist/memory-operator-faces.js +1 -17
  145. package/dist/memory-sync-client.d.ts +53 -14
  146. package/dist/memory-sync-client.js +187 -92
  147. package/dist/memory-sync.d.ts +1 -1
  148. package/dist/memory-sync.js +3 -2
  149. package/dist/model-route-gap.d.ts +6 -22
  150. package/dist/model-route-gap.js +7 -5
  151. package/dist/mods-declaration.d.ts +110 -0
  152. package/dist/mods-declaration.js +109 -0
  153. package/dist/observability/cost-taxonomy.d.ts +2 -2
  154. package/dist/observability/err-text.d.ts +5 -0
  155. package/dist/observability/err-text.js +9 -0
  156. package/dist/observability/fail-open.d.ts +17 -1
  157. package/dist/observability/fail-open.js +17 -1
  158. package/dist/observability/run-terminal.js +0 -1
  159. package/dist/observability/secret-env-scrub.d.ts +1 -1
  160. package/dist/observability/secret-env-scrub.js +1 -1
  161. package/dist/orchestration/workflow-notify-journal.js +4 -4
  162. package/dist/org-memory-admission.d.ts +4 -3
  163. package/dist/org-memory-admission.js +6 -6
  164. package/dist/parked-decide.d.ts +49 -11
  165. package/dist/parked-decide.js +25 -26
  166. package/dist/parked-revive-surface.d.ts +12 -15
  167. package/dist/parked-revive-surface.js +3 -6
  168. package/dist/peer-seat.d.ts +156 -0
  169. package/dist/peer-seat.js +273 -0
  170. package/dist/permission-rule-vocab.d.ts +7 -7
  171. package/dist/permission-rule-vocab.js +3 -3
  172. package/dist/plain-string-record.d.ts +38 -0
  173. package/dist/plain-string-record.js +38 -0
  174. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  175. package/dist/plugins/approval-ask-store-file.js +11 -0
  176. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  177. package/dist/plugins/approval-ask-store-memory.js +12 -0
  178. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  179. package/dist/plugins/approval-ask-store-sql.js +30 -12
  180. package/dist/plugins/background-agent-store-sql.js +6 -3
  181. package/dist/plugins/caching-session-store.d.ts +11 -2
  182. package/dist/plugins/caching-session-store.js +15 -3
  183. package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
  184. package/dist/plugins/checkpoint-store-sql.js +13 -15
  185. package/dist/plugins/device-store-sql.d.ts +25 -51
  186. package/dist/plugins/device-store-sql.js +49 -103
  187. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  188. package/dist/plugins/execution-placement-sql.js +223 -0
  189. package/dist/plugins/file-history-store-sql.d.ts +7 -6
  190. package/dist/plugins/file-history-store-sql.js +8 -7
  191. package/dist/plugins/file-run-store.d.ts +6 -1
  192. package/dist/plugins/file-run-store.js +4 -0
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  194. package/dist/plugins/fork-routing-session-store.js +16 -0
  195. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  196. package/dist/plugins/k8s-pod-reaper.js +3 -3
  197. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  198. package/dist/plugins/local-checkpoint-store.js +0 -1
  199. package/dist/plugins/local-session-store.d.ts +183 -45
  200. package/dist/plugins/local-session-store.js +412 -105
  201. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  202. package/dist/plugins/mailbox-store-sql.js +103 -15
  203. package/dist/plugins/memory-key-guards.d.ts +4 -0
  204. package/dist/plugins/memory-key-guards.js +1 -0
  205. package/dist/plugins/memory-run-store.d.ts +1 -1
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +117 -6
  209. package/dist/plugins/one-time-migrations.js +383 -14
  210. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
  212. package/dist/plugins/permission-rule-store-sql.js +34 -29
  213. package/dist/plugins/pg-pool.d.ts +7 -3
  214. package/dist/plugins/pg-session-storage.d.ts +41 -22
  215. package/dist/plugins/pg-session-storage.js +136 -55
  216. package/dist/plugins/remote-env-adb.d.ts +22 -4
  217. package/dist/plugins/remote-env-adb.js +70 -62
  218. package/dist/plugins/remote-env-device.d.ts +32 -12
  219. package/dist/plugins/remote-env-device.js +18 -17
  220. package/dist/plugins/remote-env-e2b.d.ts +19 -3
  221. package/dist/plugins/remote-env-e2b.js +41 -42
  222. package/dist/plugins/remote-env-file-error.d.ts +2 -0
  223. package/dist/plugins/remote-env-file-error.js +1 -1
  224. package/dist/plugins/remote-env-host.d.ts +23 -8
  225. package/dist/plugins/remote-env-host.js +66 -70
  226. package/dist/plugins/remote-env-k8s.d.ts +13 -16
  227. package/dist/plugins/remote-env-k8s.js +61 -54
  228. package/dist/plugins/remote-env-local-docker.d.ts +30 -5
  229. package/dist/plugins/remote-env-local-docker.js +46 -59
  230. package/dist/plugins/remote-env-ssh.d.ts +18 -4
  231. package/dist/plugins/remote-env-ssh.js +72 -56
  232. package/dist/plugins/remote-shell.d.ts +100 -3
  233. package/dist/plugins/remote-shell.js +166 -2
  234. package/dist/plugins/retention-store-sql.d.ts +23 -8
  235. package/dist/plugins/retention-store-sql.js +13 -1
  236. package/dist/plugins/run-store-sql.d.ts +13 -3
  237. package/dist/plugins/run-store-sql.js +5 -0
  238. package/dist/plugins/schema-consistency.d.ts +15 -5
  239. package/dist/plugins/schema-consistency.js +14 -8
  240. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  241. package/dist/plugins/schema-drift-sentence.js +38 -0
  242. package/dist/plugins/session-fork.d.ts +63 -12
  243. package/dist/plugins/session-fork.js +38 -21
  244. package/dist/plugins/session-ownership.d.ts +7 -0
  245. package/dist/plugins/session-ownership.js +5 -0
  246. package/dist/plugins/session-row-sql.d.ts +22 -0
  247. package/dist/plugins/session-row-sql.js +81 -0
  248. package/dist/plugins/session-store.d.ts +1 -1
  249. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  250. package/dist/plugins/session-workspace-cell.js +38 -0
  251. package/dist/plugins/store-backend.d.ts +1 -1
  252. package/dist/plugins/store-backend.js +26 -14
  253. package/dist/plugins/store-contracts.d.ts +25 -5
  254. package/dist/plugins/tidb-pool.d.ts +12 -7
  255. package/dist/plugins/tidb-pool.js +15 -3
  256. package/dist/plugins/tidb-session-store.d.ts +74 -28
  257. package/dist/plugins/tidb-session-store.js +132 -53
  258. package/dist/plugins/tool-result-store-sql.d.ts +16 -8
  259. package/dist/plugins/tool-result-store-sql.js +26 -17
  260. package/dist/plugins/worktree-isolation.d.ts +3 -2
  261. package/dist/plugins/worktree-isolation.js +1 -7
  262. package/dist/project-identity.d.ts +1 -14
  263. package/dist/project-identity.js +0 -4
  264. package/dist/project-memory.d.ts +9 -15
  265. package/dist/project-memory.js +5 -4
  266. package/dist/request-key-closure.js +1 -1
  267. package/dist/request-value-closure.d.ts +6 -7
  268. package/dist/request-value-closure.js +1 -3
  269. package/dist/rules-consent.js +6 -4
  270. package/dist/run-local.d.ts +6 -0
  271. package/dist/run-local.js +36 -26
  272. package/dist/runs.d.ts +9 -2
  273. package/dist/runs.js +9 -2
  274. package/dist/sandbox-git-plane.d.ts +2 -2
  275. package/dist/security.d.ts +38 -12
  276. package/dist/security.js +8 -2
  277. package/dist/server-secret-env.d.ts +15 -8
  278. package/dist/server-secret-env.js +4 -2
  279. package/dist/session-children-retire.d.ts +27 -9
  280. package/dist/session-children-retire.js +9 -5
  281. package/dist/session-incarnation.d.ts +19 -10
  282. package/dist/session-incarnation.js +5 -3
  283. package/dist/session-row.d.ts +114 -5
  284. package/dist/session-row.js +55 -5
  285. package/dist/session-titler.d.ts +2 -2
  286. package/dist/session-workspace.d.ts +130 -0
  287. package/dist/session-workspace.js +83 -0
  288. package/dist/spec-fields.d.ts +1 -1
  289. package/dist/standing-answer-grant.d.ts +2 -13
  290. package/dist/standing-answer-grant.js +0 -7
  291. package/dist/task-a2a.d.ts +44 -14
  292. package/dist/task-a2a.js +46 -20
  293. package/dist/task-cwd.d.ts +6 -1
  294. package/dist/task-mcp.d.ts +33 -20
  295. package/dist/task-mcp.js +46 -22
  296. package/dist/tool-approval.d.ts +38 -8
  297. package/dist/tool-approval.js +57 -13
  298. package/dist/trace/core-keyset-guard.d.ts +33 -18
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +2 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +3 -0
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/project.d.ts +26 -7
  306. package/dist/trace/project.js +30 -3
  307. package/dist/trace/redact.js +42 -13
  308. package/dist/trace/self-minted-wire.d.ts +240 -12
  309. package/dist/trace/self-minted-wire.js +100 -4
  310. package/dist/trace/wire-projection-faces.d.ts +6 -6
  311. package/dist/trace/wire-projection-faces.js +6 -5
  312. package/dist/url-refusal.d.ts +1 -1
  313. package/dist/user-authored-text.d.ts +2 -2
  314. package/dist/user-authored-text.js +16 -1
  315. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  316. package/dist/web-admin-client/apply-effective.js +20 -10
  317. package/dist/web-admin-client/center-request.d.ts +3 -2
  318. package/dist/web-admin-client/center-request.js +2 -3
  319. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  320. package/dist/web-admin-client/skills-mcp.js +32 -18
  321. package/dist/web-admin-client/types.d.ts +11 -0
  322. package/package.json +3 -3
  323. package/wire-changes/server@7.138.0.json +446 -0
  324. package/wire-changes/server@7.139.0.json +348 -0
  325. package/dist/http/device-placement.d.ts +0 -175
  326. package/dist/http/device-placement.js +0 -145
  327. package/dist/http/mcp-remote-text.d.ts +0 -5
  328. package/dist/http/mcp-remote-text.js +0 -7
package/dist/config.js CHANGED
@@ -9,13 +9,14 @@ import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
9
9
  import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
10
10
  import { buildRuleListJudgeOptions } from "./permission-rule-list.js";
11
11
  import { isV2ScopeKey, memoryReplicationCoverage, replicatesProjectLayersOf } from "./memory-scope.js";
12
+ import { memorySyncPeerOf } from "./memory-sync-client.js";
12
13
  import { assertProjectLayerOwnable } from "./project-identity.js";
13
14
  import { projectLayerOwnerOf } from "./project-layer-key.js";
14
15
  import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
15
16
  import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
16
17
  import { CONFIG_PROVIDER_WORDS, RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, fleetScopeOf, DEVICE_DELIVERY_TIMEOUT_BOUNDS, DEVICE_EXEC_TIMEOUT_BOUNDS, inWholeNumberBounds, wholeNumberBoundsRule } from "./config-types.js";
17
18
  import { DEVICE_HELLO_ACK_BOUNDS } from "./device-ws-protocol.js";
18
- import { FLEET_GENERATION_ENV, FLEET_SCOPE_ENV, SERVING_LEASE_TTL_FLOOR_MS, isCanonicalGeneration } from "@sema-agent/settings-schema/fleet";
19
+ import { FLEET_ADVERTISE_ADDRESS_ENV, FLEET_GENERATION_ENV, FLEET_SCOPE_ENV, SERVING_LEASE_TTL_FLOOR_MS, isCanonicalGeneration } from "@sema-agent/settings-schema/fleet";
19
20
  import { ScopeId } from "@sema-agent/settings-schema/api/scopes";
20
21
  import { centerLaneConfigured, pathBaseUrlRefusal } from "./web-admin-client/center-request.js";
21
22
  import { resolveLocalDataRoot } from "./local-data-root.js";
@@ -26,7 +27,7 @@ import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat,
26
27
  import {} from "./posture-source.js";
27
28
  import { COST_QUOTA_WINDOW_SEC_RANGE, costQuotaWindowSecInRange } from "./observability/cost-quota.js";
28
29
  import { NAME_TABLE_BEHAVIOR, TOOL_NAME_ROSTER, describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
29
- import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, centerIdentityRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal, operatorSingleUserRefusal, workflowParkDecisionDomainRefusal } from "./config-invariants.js";
30
+ import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, centerIdentityRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal, operatorSingleUserRefusal, remoteConfigProviderUrlRefusal, retentionLaneRefusal, workflowParkDecisionDomainRefusal } from "./config-invariants.js";
30
31
  import { declaredSemaConfigDir } from "./sema-config-dir.js";
31
32
  import { lkgPathOverrideOf } from "./config-lkg.js";
32
33
  import { K8S_WORKSPACE_MOUNT_DEFAULT, WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE, parseWorkspaceSnapshotExclude, parseWorkspaceSnapshotPrefix } from "./workspace-space.js";
@@ -35,7 +36,7 @@ import { laneLivenessOf } from "./exec-bounds.js";
35
36
  import { SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS } from "./sandbox-egress-policy.js";
36
37
  import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE_MOUNT_RESERVED, WORKSPACE_CACHE_SIZE_DEFAULT, WORKSPACE_CACHE_WORDS, isK8sQuantity, mountsOverlap, reservedMountCollision } from "./workspace-cache.js";
37
38
  import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
38
- import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, laneRequiredEnv } from "./execution-lane-caps.js";
39
+ import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, laneMissingEnv } from "./execution-lane-caps.js";
39
40
  import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
40
41
  import { AUTONOMY_MODES } from "./runtime-governance.js";
41
42
  import { MAX_NAMED_INERT_ENTRIES, allowLayerEntryCountIssue, describeInertRuleEntries, inertRuleEntriesOf, inertRuleEntryGuidance, ruleEntryListShapeOk } from "./rule-entry-list.js";
@@ -47,7 +48,7 @@ import { modelRouteUrlRefusal } from "./model-route-url.js";
47
48
  import { httpUrlFlaws } from "@sema-agent/settings-schema";
48
49
  import { buildHttpUrlRefusal, buildRefusalShownUrl } from "./url-refusal.js";
49
50
  import { customPkgSourceFromEnv } from "./sandbox-pkg-source.js";
50
- import { anthropicCredentialRefusal, csvOf, envLaneTable, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
51
+ import { anthropicCredentialRefusal, csvOf, declaredCredential, envLaneTable, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
51
52
  import { displayListEntry } from "./list-entry-display.js";
52
53
  import { echoCallerText } from "./caller-text-echo.js";
53
54
  import { buildDisplaySafeUrl, redactSecrets } from "./trace/redact.js";
@@ -55,6 +56,7 @@ import { parseConfigLocalOwnerUid } from "./config-dir-trust.js";
55
56
  import { allowlistedEnv } from "./server-secret-env.js";
56
57
  import { ENV_NAME_ALLOWLIST_KNOBS, ENV_NAME_ALLOWLIST_KNOB_NAMES, describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
57
58
  import { APPROVAL_ASK_RETENTION_DEFAULT_MS, APPROVAL_ASK_RETENTION_FLOOR_MS } from "./approval-ask-retention.js";
59
+ import { parseModsDeclaration } from "./mods-declaration.js";
58
60
  export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
59
61
  function csv(name) {
60
62
  return csvOf(process.env[name]);
@@ -194,7 +196,7 @@ function buildExecLaneNotice(remoteExec) {
194
196
  };
195
197
  }
196
198
  const ENV_VALUE_ECHO_CHARS = 120;
197
- function parseConfigProvider() {
199
+ export function parseConfigProvider() {
198
200
  const raw = process.env.CONFIG_PROVIDER;
199
201
  if (raw === undefined || raw === "")
200
202
  return undefined;
@@ -508,11 +510,32 @@ function parseNameListEnv(name, raw, opts) {
508
510
  }
509
511
  const ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
510
512
  const ENV_NAME_ALLOWLIST_WITHHELD = "<withheld: entries are not echoed — a value pasted in place of a name can pass the name check>";
511
- function ownCredentialPutBackProblem(name) {
513
+ const OWN_CREDENTIAL_HANDOFF = {
514
+ "host-put-back": { where: "a put-back list hands the named variable back, as written, to the child the engine withheld it from", whom: "that child" },
515
+ "sandbox-grant": { where: "a sandbox list puts the named variable, as written, into a sandbox the model can read", whom: "the sandbox" },
516
+ };
517
+ function ownCredentialNameProblem(name, channel) {
512
518
  const engine = inheritEnvPolicyProblem([name]);
513
- return engine === undefined
514
- ? undefined
515
- : `this deployment's own credential — a put-back list hands the named variable back, as written, to the child the engine withheld it from (the engine refuses it on any allow list: ${engine})`;
519
+ if (engine === undefined)
520
+ return undefined;
521
+ const { where, whom } = OWN_CREDENTIAL_HANDOFF[channel];
522
+ return `this deployment's own credential — ${where}; give ${whom} a credential of its own (another name and another value) and list that name instead (the engine refuses it on any allow list: ${engine})`;
523
+ }
524
+ const NAMES_IN_FORCE = {
525
+ LSP_ENV_ALLOW: (c) => c.lspEnvAllow,
526
+ E2B_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "e2b" ? Object.keys(c.remoteExec.sandboxEnv ?? {}) : []),
527
+ K8S_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "k8s" ? Object.keys(c.remoteExec.sandboxEnv ?? {}) : []),
528
+ DOCKER_SANDBOX_ENV: (c) => (c.remoteExec?.provider === "local-docker" ? Object.keys(c.remoteExec.env ?? {}) : []),
529
+ };
530
+ const SERVER_ENV_NAME_ALLOWLIST_KNOBS = Object.keys(NAMES_IN_FORCE);
531
+ export function sandboxEnvNamesInForce(seats) {
532
+ return SERVER_ENV_NAME_ALLOWLIST_KNOBS.filter((knob) => ENV_NAME_ALLOWLIST_KNOBS[knob].channel === "sandbox-grant").flatMap((knob) => NAMES_IN_FORCE[knob](seats));
533
+ }
534
+ export function ownCredentialHitsInForce(seats) {
535
+ return SERVER_ENV_NAME_ALLOWLIST_KNOBS.flatMap((knob) => NAMES_IN_FORCE[knob](seats).flatMap((name) => {
536
+ const problem = ownCredentialNameProblem(name, ENV_NAME_ALLOWLIST_KNOBS[knob].channel);
537
+ return problem === undefined ? [] : [{ knob, name, problem }];
538
+ }));
516
539
  }
517
540
  export function parseEnvNameAllowlist(knob, singleUserTurnkey) {
518
541
  const raw = process.env[knob];
@@ -527,9 +550,7 @@ export function parseEnvNameAllowlist(knob, singleUserTurnkey) {
527
550
  ? 'not an environment-variable name (letters, digits and "_", not starting with a digit)'
528
551
  : ENV_NAME_ALLOWLIST_KNOB_NAMES.has(seg.toUpperCase())
529
552
  ? `the name of a name-list knob (${[...ENV_NAME_ALLOWLIST_KNOB_NAMES].join(" / ")}: their raw text is withheld from every child process and sandbox; naming one would put that text back)`
530
- : spec.channel === "host-put-back"
531
- ? ownCredentialPutBackProblem(seg)
532
- : undefined;
553
+ : ownCredentialNameProblem(seg, spec.channel);
533
554
  if (problem === undefined)
534
555
  return;
535
556
  throw new Error(`env ${knob}: entry #${i + 1} of ${segments.length} is ${problem}` +
@@ -1103,7 +1124,7 @@ function parseModelDomain() {
1103
1124
  }
1104
1125
  const models = envLaneTable(modelId, model, cheapModel);
1105
1126
  const gatewayApiKey = route.gatewayApiKey;
1106
- if (gatewayBaseUrl === undefined && gatewayApiKey !== undefined && gatewayApiKey !== "") {
1127
+ if (gatewayBaseUrl === undefined && declaredCredential(gatewayApiKey) !== undefined) {
1107
1128
  CONFIG_NOTICES.push({
1108
1129
  level: "warn",
1109
1130
  event: "model_gateway_key_unpaired",
@@ -1703,6 +1724,7 @@ function parseMemoryDomain(ctx) {
1703
1724
  if (memorySyncToken === undefined) {
1704
1725
  throw new Error("MEMORY_SYNC_URL is set but MEMORY_SYNC_TOKEN is not — refusing to start half-configured (the central sync face requires a bearer token)");
1705
1726
  }
1727
+ memorySyncPeerOf(memorySyncUrl);
1706
1728
  if (memorySyncScopeRaw === undefined && memoryScope !== undefined && !isV2ScopeKey(memoryScope)) {
1707
1729
  throw new Error(`MEMORY_SYNC_URL is set but MEMORY_SYNC_SCOPE is not set and MEMORY_SCOPE (${JSON.stringify(memoryScope)}) is not a typed scope key — the sync leg replicates exactly ONE scope by name ` +
1708
1730
  "(core syncMemoryScope takes the entries whose scope === the sync scope and POSTs them under that same name), so deriving `user:<MEMORY_SCOPE>` would name a scope this deployment never writes and nothing would ever be synced (S-964). " +
@@ -1901,10 +1923,10 @@ function parseOrchestrationDomain(ctx) {
1901
1923
  throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")}. ` +
1902
1924
  `An unrecognized value used to fall through to the host/in-process lane silently; refusing to start instead (#157).`);
1903
1925
  }
1904
- const missing = laneRequiredEnv(word).filter((name) => !process.env[name]);
1926
+ const missing = laneMissingEnv(word, process.env);
1905
1927
  if (missing.length > 0) {
1906
- throw new Error(`REMOTE_EXEC=${lane} but required env ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} missing/empty — ` +
1907
- `refusing to start rather than silently downgrading to the host/in-process lane (#157; unset REMOTE_EXEC if you meant no sandbox lane).`);
1928
+ throw new Error(`REMOTE_EXEC=${lane} but required env ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} unset, empty or whitespace-only — it counts as absent: ` +
1929
+ `refusing to start rather than silently downgrading to the host/in-process lane (#157; set ${missing.length === 1 ? "it" : "them"}, or unset REMOTE_EXEC if you meant no sandbox lane).`);
1908
1930
  }
1909
1931
  }
1910
1932
  }
@@ -1950,7 +1972,7 @@ function parseOrchestrationDomain(ctx) {
1950
1972
  orphanReclaim: boolEnv("E2B_ORPHAN_RECLAIM", true),
1951
1973
  }
1952
1974
  :
1953
- process.env.REMOTE_EXEC === "k8s" && process.env.K8S_SANDBOX_IMAGE
1975
+ process.env.REMOTE_EXEC === "k8s" && process.env.K8S_SANDBOX_IMAGE && process.env.K8S_NAMESPACE
1954
1976
  ? {
1955
1977
  provider: "k8s",
1956
1978
  image: process.env.K8S_SANDBOX_IMAGE,
@@ -1958,7 +1980,7 @@ function parseOrchestrationDomain(ctx) {
1958
1980
  ...(process.env.K8S_TOKEN ? { token: process.env.K8S_TOKEN } : {}),
1959
1981
  ...(process.env.K8S_CA_CERT_B64 ? { caCert: Buffer.from(process.env.K8S_CA_CERT_B64, "base64").toString("utf8") } : {}),
1960
1982
  ...(boolEnv("K8S_INSECURE_TLS", false) ? { insecureTls: true } : {}),
1961
- ...(process.env.K8S_NAMESPACE ? { namespace: process.env.K8S_NAMESPACE } : {}),
1983
+ namespace: process.env.K8S_NAMESPACE,
1962
1984
  ...(process.env.K8S_RUNTIME_CLASS != null ? { runtimeClass: process.env.K8S_RUNTIME_CLASS } : {}),
1963
1985
  ...(process.env.K8S_MOUNT_PATH ? { mountPath: process.env.K8S_MOUNT_PATH } : {}),
1964
1986
  ...((n) => (n !== undefined ? { timeoutMs: n } : {}))(optNumEnvStrict("K8S_TIMEOUT_MS")),
@@ -2228,6 +2250,7 @@ function parseIntegrationsDomain(ctx) {
2228
2250
  gitApiKind: parseGitApiKind(process.env.GIT_API_KIND, process.env.GIT_API_BASEURL),
2229
2251
  defaultScenario: env("DEFAULT_SCENARIO", "code"),
2230
2252
  skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
2253
+ modsDeclaration: parseModsDeclaration(process.env.MODS_ROOT, process.env.MODS_PACKAGES),
2231
2254
  centerCredentialRenewLeadMs: parseCenterCredentialRenewLeadMs(process.env.CENTER_CREDENTIAL_RENEW_LEAD_MS),
2232
2255
  webAdmin,
2233
2256
  fleetLeaseFailClosed: parseFleetLeaseFailClosed(process.env.FLEET_LEASE_FAIL_CLOSED, DEPLOYMENT_SHAPE_PREDICATES.multiTenant.holds({ requirePrincipal })),
@@ -2273,9 +2296,10 @@ function parseFleetScope(raw) {
2273
2296
  return fleetScopeOf(raw);
2274
2297
  }
2275
2298
  function parseFleetAdvertiseAddress(raw) {
2299
+ const name = "FLEET_ADVERTISE_ADDRESS";
2276
2300
  if (raw === undefined || raw === "")
2277
2301
  return {};
2278
- const refusal = pathBaseUrlRefusal("env FLEET_ADVERTISE_ADDRESS", raw);
2302
+ const refusal = pathBaseUrlRefusal(`env ${name}`, raw);
2279
2303
  if (refusal !== undefined) {
2280
2304
  throw fleetEnvRefusal("advertise_address_invalid", `${refusal} — it must be an http(s) base URL such as "http://10.0.0.5:8090": the fleet center rejects any other shape on every announce. ` +
2281
2305
  `Unset it if this engine does not announce itself.`);
@@ -2460,7 +2484,7 @@ const LIMITS_HTTP_GROUP_KEYS = [
2460
2484
  const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
2461
2485
  const INTEGRATIONS_GROUP_KEYS = [
2462
2486
  "pluginsAllowHosts", "mcpServers", "a2aPeers", "a2aServe", "configBootFetchBudgetMs", "gitApiBaseUrl", "gitApiToken", "gitApiKind", "defaultScenario",
2463
- "skillsDir", "centerCredentialRenewLeadMs", "webAdmin", "fleetLeaseFailClosed", "fleetGeneration", "fleetScope", "fleetAdvertiseAddress", "fleetLeaseOutageMaxMs", "configProvider", "configLocalDir", "configLocalOwnerUid",
2487
+ "skillsDir", "modsDeclaration", "centerCredentialRenewLeadMs", "webAdmin", "fleetLeaseFailClosed", "fleetGeneration", "fleetScope", "fleetAdvertiseAddress", "fleetLeaseOutageMaxMs", "configProvider", "configLocalDir", "configLocalOwnerUid",
2464
2488
  ];
2465
2489
  export const CONFIG_GROUP_KEYS = {
2466
2490
  store: STORE_GROUP_KEYS,
@@ -2599,6 +2623,14 @@ export function loadConfig() {
2599
2623
  if (centerIdentity !== undefined) {
2600
2624
  throw new Error(centerIdentity);
2601
2625
  }
2626
+ const remoteSourceRefusal = remoteConfigProviderUrlRefusal({ configProvider: integrations.configProvider, webAdmin: integrations.webAdmin });
2627
+ if (remoteSourceRefusal !== undefined) {
2628
+ throw new Error(remoteSourceRefusal);
2629
+ }
2630
+ const retentionRefusal = retentionLaneRefusal({ retentionPolicy: auth.retentionPolicy, retentionSweep: auth.retentionSweep });
2631
+ if (retentionRefusal !== undefined) {
2632
+ throw new Error(retentionRefusal);
2633
+ }
2602
2634
  const { directApprovalDoor, durableApproval: dgDurable, approvalHmacKeys: dgHmacKeys } = approval;
2603
2635
  const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, operatorSingleUser, authTokens } = auth;
2604
2636
  const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
@@ -38,7 +38,7 @@
38
38
  * M23 回填了无主的父)⇒ 写下的仍是判断时的读数 —— 按律如此;那一行若因此无主,照上一节处置。
39
39
  * · **停驻挂点**(`plugins/session-store.ts` 的 `ensureChildSessionDurableWithPromotion`;快路 + 晋升):来源 = **调用方交出的委派树根,只有这一只**,连同
40
40
  * 调用方判断那一刻读到的根行住客({@link DelegationRootWitness};见证值,判断用,不进 CAS):请求腿 = 本请求经鉴权解析的会话,住客 = 受理时那一读
41
- * (`boot/resolve-spec.ts`);`/decide` 的赎回腿 = 行上的 `rootSessionId`,住客 = 赎回腿 claim 前后两读的那一位(`parked-revive-surface.ts`,每一行都交)。
41
+ * (`boot/resolve-spec.ts`);`/decide` 的赎回腿 = 行上的 `rootSessionId`,住客 = 赎回腿写口放行的那一位(认领之前解析器那一读;`parked-revive-surface.ts`,每一行都交)。
42
42
  * 挂点先读一次根行:它在而住客不是见证的那一位 ⇒ 根被删、同 id 换了住客 ⇒ **拒**(不按新住客晋升 / 认领,{@link assertRootStillWitnessed});它不在 ⇒ 律给哨兵
43
43
  * (根真没了)。不回落父:core 的挂点 `ensureChildSessionDurable(sessionId)` 只交子代 id,父 id 在这一写点拿不到;两条候选读法都读不到东西 ——
44
44
  * ① 读 `session_meta(子代).placement_parent_session_id`:晋升的拷贝分支恰恰只在子代**没有** placed 行时才走(有 placed 行 ⇒ 快路),这一列按构造为空;
@@ -134,7 +134,7 @@ export interface PlacedRowWriter {
134
134
  export declare function createPlacedSessionRow(sessionId: string, placement: PlacementSources, io: PlacedRowWriter, log: OrphanOwnerLog): Promise<void>;
135
135
  /**
136
136
  * 停驻挂点的**见证**:调用方交出的委派树根 + 调用方判断那一刻读到的根行住客(化身;`null` = 那一读里根行不在)。请求腿 = 鉴权解析的会话与受理时那一读;
137
- * 赎回腿 = 行上的根与 claim 前后两读的那一位住客。
137
+ * 赎回腿 = 行上的根与写口放行的那一位住客(认领之前解析器那一读)。
138
138
  */
139
139
  export interface DelegationRootWitness {
140
140
  readonly sessionId: string;
@@ -92,24 +92,24 @@ export declare const DEPLOYMENT_SHAPE_PREDICATES: Readonly<{
92
92
  * 签发方配置是身份验签轴、不算租户证据)—— 两个问题、一根轴、一个读口;「鉴权必需」与「多租形」拆成两个声明时只改这一行的判官。
93
93
  */
94
94
  multiTenant: DeploymentShapePredicate<"principal">;
95
- /** 读能力表 `callerCwd` 位(host = 单主才开;device = 路径落在发起者自己的设备上,归属由 `device_session` 绑定验过,不要求单主)。 */
96
- callerCwd: DeploymentShapePredicate<"principal" | "lane">;
95
+ /** 读能力表 `callerCwd` 位(host = 单主才开;device = 路径落在发起者自己的设备上,归属由根会话行执行格上的设备绑定验过,不要求单主)。 */
96
+ callerCwd: DeploymentShapePredicate<"lane" | "principal">;
97
97
  /** 调用方给的宿主绝对路径(附加目录 / 项目上下文 / 本机 SendUserFile / 指令文件探针)只在「手就在本 worker 的盘上 ∧ 单主」时指向调用方自己的东西。 */
98
- callerOwnsWorkerDisk: DeploymentShapePredicate<"principal" | "lane">;
98
+ callerOwnsWorkerDisk: DeploymentShapePredicate<"lane" | "principal">;
99
99
  /** wire `executionLane.toolsOnThisHost` 与出厂 / 中心技能的 `baseDir`:只问文件系统同一性,与租户无关(出厂内容每个租户拿到同一份)。 */
100
100
  toolsOnThisHost: DeploymentShapePredicate<"lane">;
101
101
  /** `REMOTE_EXEC` 未设的进程内形(没有手)且单主:进程级消费者(hooks 命令、项目上下文本机读、卡批规则 root)看到的就是引擎自己的进程 cwd / env。 */
102
- inProcessSingleUser: DeploymentShapePredicate<"principal" | "lane">;
102
+ inProcessSingleUser: DeploymentShapePredicate<"lane" | "principal">;
103
103
  /** 任务层替部署声明宿主姿态(bypassPermissions 开写面 / host shell 继承整份环境)的射程:device 车道上跨租户宿主穿越结构性不存在(clay 裁定③)。 */
104
- taskHostPostureDeclarations: DeploymentShapePredicate<"principal" | "lane">;
104
+ taskHostPostureDeclarations: DeploymentShapePredicate<"lane" | "principal">;
105
105
  /** 写门走 [ref] 代理裁决(文件平面不在 worker 本机);认不出的词 ⇒ 走代理(代理 slot 空 ⇒ 写门 fail-closed)。 */
106
106
  sandboxPathAdjudication: DeploymentShapePredicate<"lane">;
107
107
  /** 读能力表 `sandboxSendUserFile` 位(e2b / k8s 每任务一只沙箱 ⇒ 任何租户;ssh 共享一台真机 ⇒ 只单主)。 */
108
- sandboxSendUserFile: DeploymentShapePredicate<"principal" | "lane">;
108
+ sandboxSendUserFile: DeploymentShapePredicate<"lane" | "principal">;
109
109
  /** 读能力表 `remoteScratchpad` 位(`/tmp/scratchpad/<sessionId>` 远端约定)。 */
110
110
  remoteScratchpad: DeploymentShapePredicate<"lane">;
111
111
  /** 读能力表 `tenantSeparation` 位(值来自 settings-schema 车道词表正本);唯一读者 = 跨域不变量 `multi-tenant-shared-lane`。 */
112
- laneSeparatesTenants: DeploymentShapePredicate<"principal" | "lane">;
112
+ laneSeparatesTenants: DeploymentShapePredicate<"lane" | "principal">;
113
113
  /**
114
114
  * [ref] —— **VM 级**隔离强度(独立内核:e2b 的 Firecracker microVM;k8s 只在 Kata / gVisor 运行时类上)。与能力表 `isolation` 位
115
115
  * (OS 级边界,容器即算;wire `executionLane.isolated` 的属主 `laneIsolated`)是两个问题,所以不叫「isolated」。读者:A 值路由。
@@ -18,12 +18,21 @@
18
18
  * 上是**同一个** `device.enrollment_invalid`;设备读面的「不存在」与「不是你的」是同一个
19
19
  * `not_found.device`。细分只进审计。
20
20
  *
21
+ * ── 绑定从哪读(一只读)─────────────────────────────────────────────────────────────────────────────
22
+ * 根会话 ↔ 设备的绑定是根会话行上这一位住客的执行格 writer(device, D)(`execution-placement.ts` 头注)。准入链**不另读**:
23
+ * 读只有会话店的执行席 `seat.read(root)` 一只({@link DeviceEnrollment.readPlacement},装配点把会话店的执行席交进来 ——
24
+ * device 车道只在 SQL 后端 ⇒ 席恒在,缺 ⇒ 装配期响亮抛);{@link DeviceEnrollment.admitSession} 判的是调用方交进来的**那一读**,
25
+ * 于是提交意图「判已绑 / 未绑」与它记下的住客出自同一次行读。「这一格放在哪台设备上」只有一种读法 `boundDeviceOf`。
26
+ *
21
27
  * ── 吊销在途语义(§8-R4)──────────────────────────────────────────────────────────────────────────
22
28
  * `revoked` 是终态:①绑定的 session 后续提交 = `device.revoked`(admitSession 步 2,**先于** presence
23
29
  * 判定);②吊销同事务把连接租约作废并把 `conn_generation` 推进一格 ⇒ 在途连接的每一次带世代谓词的写
24
30
  * 都会影响 0 行,设备侧因此立刻失权(§4.6 步 2);③重新 enrollment = 新 deviceId,不复活旧行。
25
31
  */
26
- import { type DeviceErrorCode, type DeviceOwner, type DeviceRejectCode, type DeviceEnrollTokenRow, type DeviceRow, type DeviceSessionRow, type DeviceStore, type NewDeviceInput } from "./device-store.js";
32
+ import { type DeviceErrorCode, type DeviceOwner, type DeviceRejectCode, type DeviceEnrollTokenRow, type DeviceRow, type DeviceStore, type NewDeviceInput } from "./device-store.js";
33
+ import { type ExecutionDeviceCell, type ExecutionPlacementReading, type ExecutionPlacementSeat } from "./execution-placement.js";
34
+ import type { ExecutionLane } from "./execution-lane-caps.js";
35
+ import type { SessionFace } from "./session-incarnation.js";
27
36
  /** typed 拒绝 —— 端点层只负责把它翻成 HTTP(status 已在体内,不许调用方另算)。 */
28
37
  export interface DeviceRejection {
29
38
  /** [ref]:含两个借用位(`limit.rate_exceeded` 429 / `draining` 503);enrollment 本文件只铸前者,
@@ -68,10 +77,34 @@ export interface EnrollDeviceValue {
68
77
  }
69
78
  export interface AdmitSessionInput {
70
79
  rootSessionId: string;
80
+ /** 调用方读的**那一读**(会话店执行席 `seat.read(rootSessionId)` 的答案,经 {@link DeviceEnrollment.readPlacement})—— 准入链只判它,不另读。 */
81
+ placement: ExecutionPlacementReading | undefined;
71
82
  requester: DeviceOwner;
72
83
  /** 提交体携带的 deviceId(§4.3.2:仅根任务携;缺席 = 沿用绑定)。 */
73
84
  requestedDeviceId?: string;
74
85
  }
86
+ /**
87
+ * 首绑({@link DeviceEnrollment.bindPlacement})的结果:成立(`device` = 落下的格)、设备闭集的 typed 拒(异设备 `device.claim_conflict` /
88
+ * 设备 404 / 已吊销),或会话行那一侧的三种结局 —— 住客不是判过的那一位 / 行不在(`gone`)、写者在别处(`mirror`)、格在别的车道上(`tree_bound`)。
89
+ * 后三者不在设备闭集里(它们说的是会话,不是设备),由提交点(`http/execution-context.ts` 的 `commitExecutionContext`)各答各的码。
90
+ */
91
+ export type BindPlacementOutcome = {
92
+ readonly ok: true;
93
+ readonly device: ExecutionDeviceCell;
94
+ } | {
95
+ readonly ok: false;
96
+ readonly reject: DeviceRejection;
97
+ } | {
98
+ readonly ok: false;
99
+ readonly placement: "gone";
100
+ } | {
101
+ readonly ok: false;
102
+ readonly placement: "mirror";
103
+ } | {
104
+ readonly ok: false;
105
+ readonly placement: "tree_bound";
106
+ readonly boundLane: ExecutionLane;
107
+ };
75
108
  export interface AdmitSessionValue {
76
109
  deviceId: string;
77
110
  device: DeviceRow;
@@ -110,11 +143,15 @@ export interface DeviceEnrollment {
110
143
  /** §7-T5 五件的第 ①②③ 件的**写入侧**:绑定复合身份 + TTL + 一次性摘要落库(明文一现)。 */
111
144
  issueEnrollToken(input: IssueEnrollTokenRequest): Promise<DeviceOutcome<IssuedEnrollToken>>;
112
145
  enroll(input: EnrollDeviceInput): Promise<DeviceOutcome<EnrollDeviceValue>>;
146
+ /** 绑定的唯一读:会话店执行席的 `read(root)`(一次行读:住客 + 格;行不在 ⇒ `undefined`;读故障抛,不吞成「未绑」)。 */
147
+ readPlacement(rootSessionId: string): Promise<ExecutionPlacementReading | undefined>;
148
+ /** 首绑(认领之后的初始放置写):按意图那一读的住客定址,设备店一只事务。 */
113
149
  bindPlacement(input: {
114
150
  rootSessionId: string;
151
+ occupant: SessionFace;
115
152
  deviceId: string;
116
153
  requester: DeviceOwner;
117
- }): Promise<DeviceOutcome<DeviceSessionRow>>;
154
+ }): Promise<BindPlacementOutcome>;
118
155
  admitSession(input: AdmitSessionInput): Promise<DeviceOutcome<AdmitSessionValue>>;
119
156
  revoke(input: {
120
157
  deviceId: string;
@@ -125,6 +162,8 @@ export interface DeviceEnrollment {
125
162
  }
126
163
  export interface DeviceEnrollmentDeps {
127
164
  store: DeviceStore;
165
+ /** 会话店的执行席(绑定的唯一载体与唯一读;装配点交会话店的 `executionPlacement`)。 */
166
+ seat: ExecutionPlacementSeat;
128
167
  /** 缺席 = 不限速(端点车必须传;见文件头注 ⑤)。 */
129
168
  rateLimiter?: DeviceEnrollRateLimiter;
130
169
  /** 测试可注入确定性铸造;默认 `mintDeviceId()`。 */
@@ -1,4 +1,5 @@
1
1
  import { DEVICE_ERROR_CODES, DEVICE_ERROR_STATUS, DEVICE_RATE_LIMIT_CODE, assertDeviceIdShape, assertDeviceOwnerShape, assertDevicePubkeyShape, assertEnrollTokenHashShape, assertRootSessionIdShape, hashEnrollToken, mintDeviceId, mintEnrollToken, } from "./device-store.js";
2
+ import { boundDeviceOf } from "./execution-placement.js";
2
3
  export const DEFAULT_ENROLL_TOKEN_TTL_SEC = 900;
3
4
  export function buildDeviceRejection(errorCode, message) {
4
5
  return { code: errorCode, status: DEVICE_ERROR_STATUS[errorCode], message };
@@ -13,7 +14,7 @@ function rejectNotFound() {
13
14
  return reject(DEVICE_ERROR_CODES.NOT_FOUND, "device not found");
14
15
  }
15
16
  export function createDeviceEnrollment(deps) {
16
- const { store } = deps;
17
+ const { store, seat } = deps;
17
18
  const mintId = deps.mintId ?? mintDeviceId;
18
19
  async function rateGate(key) {
19
20
  if (!deps.rateLimiter)
@@ -87,21 +88,35 @@ export function createDeviceEnrollment(deps) {
87
88
  return rejectEnrollment();
88
89
  }
89
90
  }
91
+ async function readPlacement(rootSessionId) {
92
+ assertRootSessionIdShape(rootSessionId);
93
+ return seat.read(rootSessionId);
94
+ }
90
95
  async function bindPlacement(input) {
91
96
  assertRootSessionIdShape(input.rootSessionId);
92
97
  assertDeviceIdShape(input.deviceId);
93
98
  assertDeviceOwnerShape(input.requester);
94
- const result = await store.bindSession({ rootSessionId: input.rootSessionId, deviceId: input.deviceId, owner: input.requester });
99
+ const result = await store.placeDeviceExecution({ root: input.rootSessionId, occupant: input.occupant, deviceId: input.deviceId, owner: input.requester });
95
100
  switch (result.outcome) {
96
101
  case "bound":
97
- case "idempotent":
98
- return { ok: true, value: result.row };
99
- case "conflict":
102
+ case "same":
103
+ return { ok: true, device: result.device };
104
+ case "device_conflict":
100
105
  return reject(DEVICE_ERROR_CODES.CLAIM_CONFLICT, "this session is already bound to a different device");
101
106
  case "device_not_found":
102
107
  return rejectNotFound();
103
108
  case "device_revoked":
104
109
  return reject(DEVICE_ERROR_CODES.REVOKED, "device has been revoked");
110
+ case "gone":
111
+ return { ok: false, placement: "gone" };
112
+ case "mirror":
113
+ return { ok: false, placement: "mirror" };
114
+ case "tree_bound":
115
+ return { ok: false, placement: "tree_bound", boundLane: result.boundLane };
116
+ default: {
117
+ const unhandled = result;
118
+ throw new Error(`bindPlacement: unknown store outcome ${JSON.stringify(unhandled)}`);
119
+ }
105
120
  }
106
121
  }
107
122
  async function admitSession(input) {
@@ -109,22 +124,22 @@ export function createDeviceEnrollment(deps) {
109
124
  assertDeviceOwnerShape(input.requester);
110
125
  if (input.requestedDeviceId !== undefined)
111
126
  assertDeviceIdShape(input.requestedDeviceId);
112
- const binding = await store.getSessionBinding(input.rootSessionId);
113
- if (!binding)
127
+ const bound = input.placement === undefined ? undefined : boundDeviceOf(input.placement.cell);
128
+ if (!bound)
114
129
  return reject(DEVICE_ERROR_CODES.NOT_BOUND, "this session has no device binding");
115
- if (binding.owner.tenant !== input.requester.tenant || binding.owner.subject !== input.requester.subject)
130
+ if (bound.owner.tenant !== input.requester.tenant || bound.owner.subject !== input.requester.subject)
116
131
  return rejectNotFound();
117
- if (input.requestedDeviceId !== undefined && input.requestedDeviceId !== binding.deviceId) {
132
+ if (input.requestedDeviceId !== undefined && input.requestedDeviceId !== bound.id) {
118
133
  return reject(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, "the requested device is not the one bound to this session");
119
134
  }
120
- const view = await store.readDeviceForAdmission(binding.deviceId, input.requester);
135
+ const view = await store.readDeviceForAdmission(bound.id, input.requester);
121
136
  if (!view)
122
137
  return rejectNotFound();
123
138
  if (view.row.status === "revoked")
124
139
  return reject(DEVICE_ERROR_CODES.REVOKED, "device has been revoked");
125
140
  if (!view.online)
126
141
  return reject(DEVICE_ERROR_CODES.OFFLINE, "device is not connected");
127
- return { ok: true, value: { deviceId: binding.deviceId, device: view.row, connGeneration: view.row.connGeneration } };
142
+ return { ok: true, value: { deviceId: bound.id, device: view.row, connGeneration: view.row.connGeneration } };
128
143
  }
129
144
  async function revoke(input) {
130
145
  assertDeviceIdShape(input.deviceId);
@@ -138,7 +153,7 @@ export function createDeviceEnrollment(deps) {
138
153
  return rejectNotFound();
139
154
  }
140
155
  }
141
- return { issueEnrollToken, enroll, bindPlacement, admitSession, revoke };
156
+ return { issueEnrollToken, enroll, readPlacement, bindPlacement, admitSession, revoke };
142
157
  }
143
158
  export function createFixedWindowRateLimiter(opts) {
144
159
  const now = opts.now ?? (() => Date.now());