@sema-agent/server 7.142.0 → 7.144.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 (301) hide show
  1. package/MIGRATION.md +608 -0
  2. package/USAGE.md +63 -36
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +7 -5
  5. package/dist/approval-ask-audit-store.js +12 -6
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +155 -39
  9. package/dist/approval-card.js +111 -31
  10. package/dist/approval-queue-kind.d.ts +27 -0
  11. package/dist/approval-queue-kind.js +6 -0
  12. package/dist/approval-reconciler.d.ts +8 -8
  13. package/dist/approval-reconciler.js +3 -3
  14. package/dist/audit.js +4 -2
  15. package/dist/boot/budget-tracing.d.ts +1 -1
  16. package/dist/boot/execution-env.js +1 -1
  17. package/dist/boot/governance-seams.d.ts +26 -41
  18. package/dist/boot/governance-seams.js +61 -28
  19. package/dist/boot/late-binds.d.ts +7 -4
  20. package/dist/boot/late-binds.js +3 -1
  21. package/dist/boot/leader.js +2 -4
  22. package/dist/boot/memory-consolidation.d.ts +0 -8
  23. package/dist/boot/memory-consolidation.js +3 -3
  24. package/dist/boot/reapers.js +5 -4
  25. package/dist/boot/resolve-spec.d.ts +1 -1
  26. package/dist/boot/resolve-spec.js +8 -8
  27. package/dist/boot/runner-deps.d.ts +14 -6
  28. package/dist/boot/runner-deps.js +6 -17
  29. package/dist/boot/shutdown.d.ts +9 -0
  30. package/dist/boot/shutdown.js +18 -2
  31. package/dist/boot/stage-01-config.js +2 -0
  32. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  33. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  34. package/dist/boot/stage-03-store-backend.js +1 -2
  35. package/dist/boot/stage-04-stores.d.ts +5 -2
  36. package/dist/boot/stage-04-stores.js +3 -5
  37. package/dist/boot/stage-05-execution-env.d.ts +5 -2
  38. package/dist/boot/stage-06-runners.d.ts +5 -2
  39. package/dist/boot/stage-06-runners.js +1 -3
  40. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  41. package/dist/boot/stage-07-capability-layer.js +6 -2
  42. package/dist/boot/stage-08-reapers.d.ts +5 -2
  43. package/dist/boot/stage-09-leader.d.ts +5 -2
  44. package/dist/boot/stage-10-http-server.d.ts +5 -2
  45. package/dist/boot/stage-10-http-server.js +4 -1
  46. package/dist/boot/stores.d.ts +5 -2
  47. package/dist/boot/stores.js +7 -7
  48. package/dist/boot/web-admin.d.ts +5 -1
  49. package/dist/boot/web-admin.js +31 -19
  50. package/dist/capabilities/hands-lane.d.ts +9 -39
  51. package/dist/capabilities/hands-lane.js +8 -13
  52. package/dist/capabilities/mcp-probe.d.ts +3 -2
  53. package/dist/capabilities/scenarios.d.ts +23 -17
  54. package/dist/capabilities/scenarios.js +2 -2
  55. package/dist/capabilities/skills.d.ts +1 -17
  56. package/dist/capabilities/skills.js +0 -8
  57. package/dist/capability-facts.d.ts +8 -8
  58. package/dist/capability-facts.js +4 -4
  59. package/dist/card-void.d.ts +6 -9
  60. package/dist/config-catalog.js +1 -1
  61. package/dist/config-d-migration.d.ts +2 -0
  62. package/dist/config-dir-trust.js +58 -29
  63. package/dist/config-invariants.d.ts +7 -1
  64. package/dist/config-invariants.js +7 -0
  65. package/dist/config-provider.d.ts +10 -0
  66. package/dist/config-provider.js +15 -4
  67. package/dist/config-root-migration.d.ts +13 -0
  68. package/dist/config-root-migration.js +99 -0
  69. package/dist/config-types.d.ts +5 -5
  70. package/dist/config.d.ts +4 -3
  71. package/dist/config.js +1 -1
  72. package/dist/delegated-session-owner.d.ts +35 -151
  73. package/dist/delegated-session-owner.js +1 -83
  74. package/dist/device-ws-protocol.d.ts +1 -0
  75. package/dist/device-ws-protocol.js +2 -1
  76. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  77. package/dist/env-model-table-gate.d.ts +53 -0
  78. package/dist/env-model-table-gate.js +53 -0
  79. package/dist/execution-placement.d.ts +7 -4
  80. package/dist/fleet/fleet-bus.d.ts +28 -52
  81. package/dist/fleet/fleet-bus.js +35 -100
  82. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  83. package/dist/fleet/fleet-reconciler.js +1 -1
  84. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  85. package/dist/fleet/subagent-tail-bus.js +15 -28
  86. package/dist/hooks/classic-stations.d.ts +3 -1
  87. package/dist/hooks/classic-stations.js +2 -0
  88. package/dist/hooks/hook-runner.js +28 -7
  89. package/dist/host-decision.d.ts +54 -20
  90. package/dist/host-decision.js +42 -14
  91. package/dist/http/admission.d.ts +1 -1
  92. package/dist/http/admission.js +7 -3
  93. package/dist/http/approval-sweeps.js +11 -8
  94. package/dist/http/caller-trust.d.ts +1 -1
  95. package/dist/http/durable-run-leg.js +2 -2
  96. package/dist/http/engine-refusal.d.ts +16 -0
  97. package/dist/http/engine-refusal.js +17 -0
  98. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  99. package/dist/http/memory-compliance-failure-reply.js +4 -6
  100. package/dist/http/owner-access.d.ts +11 -0
  101. package/dist/http/owner-access.js +13 -0
  102. package/dist/http/principal-gate.d.ts +1 -1
  103. package/dist/http/refusal-body.d.ts +7 -3
  104. package/dist/http/refusal-body.js +1 -0
  105. package/dist/http/resume-legs.d.ts +21 -7
  106. package/dist/http/resume-legs.js +82 -53
  107. package/dist/http/resume-restatement.d.ts +39 -0
  108. package/dist/http/resume-restatement.js +25 -0
  109. package/dist/http/route-ctx.d.ts +35 -10
  110. package/dist/http/route-ctx.js +1 -1
  111. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  112. package/dist/http/routes/approvals-assistant.js +143 -31
  113. package/dist/http/routes/capabilities.js +11 -2
  114. package/dist/http/routes/memory-policy.d.ts +2 -2
  115. package/dist/http/routes/memory-policy.js +1 -1
  116. package/dist/http/routes/notify-wake.js +23 -25
  117. package/dist/http/routes/rules.js +48 -13
  118. package/dist/http/routes/runs.js +9 -8
  119. package/dist/http/routes/session-memory-sync.d.ts +34 -6
  120. package/dist/http/routes/session-memory-sync.js +27 -10
  121. package/dist/http/routes/session-sync.js +13 -20
  122. package/dist/http/routes/sessions.d.ts +2 -2
  123. package/dist/http/routes/sessions.js +22 -23
  124. package/dist/http/routes/tasks.js +27 -49
  125. package/dist/http/server.d.ts +13 -7
  126. package/dist/http/server.js +2 -2
  127. package/dist/http/stored-body-replay.js +15 -4
  128. package/dist/http/wire-types.d.ts +17 -7
  129. package/dist/index.d.ts +1 -0
  130. package/dist/index.js +1 -0
  131. package/dist/lane-home-probe.d.ts +15 -0
  132. package/dist/lane-home-probe.js +4 -0
  133. package/dist/leader/leader.js +5 -2
  134. package/dist/leader/wire.d.ts +7 -1
  135. package/dist/leader/wire.js +6 -5
  136. package/dist/leg-facts.d.ts +37 -0
  137. package/dist/leg-facts.js +15 -0
  138. package/dist/memory-bundle-engine.d.ts +1 -4
  139. package/dist/memory-bundle-engine.js +2 -2
  140. package/dist/memory-operator-faces.d.ts +30 -66
  141. package/dist/memory-operator-faces.js +17 -88
  142. package/dist/memory-scope-stores.d.ts +38 -40
  143. package/dist/memory-scope-stores.js +51 -36
  144. package/dist/memory-scope.d.ts +0 -9
  145. package/dist/memory-scope.js +0 -9
  146. package/dist/memory-sync-client.js +14 -3
  147. package/dist/memory-sync.d.ts +20 -4
  148. package/dist/memory-sync.js +23 -4
  149. package/dist/model-plane-face.d.ts +13 -7
  150. package/dist/model-plane-face.js +2 -1
  151. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  152. package/dist/observability/corrupt-read-seat.js +1 -2
  153. package/dist/observability/fail-open.d.ts +30 -10
  154. package/dist/observability/fail-open.js +27 -10
  155. package/dist/observability/run-terminal.d.ts +1 -1
  156. package/dist/observability/run-terminal.js +6 -2
  157. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  158. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  159. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  160. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  161. package/dist/orchestration/workflow-park-decision.js +5 -3
  162. package/dist/org-delivery-gate.d.ts +11 -0
  163. package/dist/org-delivery-gate.js +19 -1
  164. package/dist/parked-decide.d.ts +13 -9
  165. package/dist/parked-decide.js +7 -7
  166. package/dist/parked-revive-surface.d.ts +6 -7
  167. package/dist/parked-revive-surface.js +1 -1
  168. package/dist/peer-inbound-face.d.ts +122 -0
  169. package/dist/peer-inbound-face.js +123 -0
  170. package/dist/permission-rule-list.d.ts +13 -9
  171. package/dist/permission-rule-list.js +7 -3
  172. package/dist/plugins/approval-ask-store-file.js +18 -6
  173. package/dist/plugins/approval-ask-store-memory.js +8 -6
  174. package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
  175. package/dist/plugins/approval-ask-store-sql.js +30 -11
  176. package/dist/plugins/caching-session-store.d.ts +0 -3
  177. package/dist/plugins/caching-session-store.js +0 -3
  178. package/dist/plugins/checkpoint-store-sql.d.ts +45 -38
  179. package/dist/plugins/checkpoint-store-sql.js +108 -45
  180. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  181. package/dist/plugins/file-history-store-sql.js +24 -19
  182. package/dist/plugins/file-run-store.d.ts +29 -0
  183. package/dist/plugins/file-run-store.js +97 -8
  184. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  185. package/dist/plugins/fork-routing-session-store.js +6 -0
  186. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  187. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  188. package/dist/plugins/local-checkpoint-store.d.ts +6 -8
  189. package/dist/plugins/local-checkpoint-store.js +5 -4
  190. package/dist/plugins/local-session-store.d.ts +69 -30
  191. package/dist/plugins/local-session-store.js +133 -57
  192. package/dist/plugins/local-session-window.d.ts +4 -2
  193. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  194. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  195. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  196. package/dist/plugins/mailbox-store-sql.js +76 -7
  197. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  198. package/dist/plugins/memory-sync-store-pg.js +21 -0
  199. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  200. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  201. package/dist/plugins/one-time-migrations.d.ts +44 -14
  202. package/dist/plugins/one-time-migrations.js +118 -15
  203. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  204. package/dist/plugins/org-delivery-quota-store.js +2 -12
  205. package/dist/plugins/park-commit-watch.d.ts +1 -1
  206. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  207. package/dist/plugins/permission-rule-store-file.js +15 -2
  208. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  209. package/dist/plugins/permission-rule-store-sql.js +35 -2
  210. package/dist/plugins/pg-session-storage.d.ts +1 -9
  211. package/dist/plugins/pg-session-storage.js +2 -19
  212. package/dist/plugins/remote-env-device.d.ts +8 -11
  213. package/dist/plugins/remote-env-device.js +6 -3
  214. package/dist/plugins/remote-env-host.d.ts +8 -4
  215. package/dist/plugins/remote-env-host.js +7 -9
  216. package/dist/plugins/remote-env-k8s.d.ts +37 -24
  217. package/dist/plugins/remote-env-k8s.js +50 -17
  218. package/dist/plugins/remote-shell.js +12 -2
  219. package/dist/plugins/retention-store-sql.d.ts +2 -2
  220. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  221. package/dist/plugins/sandbox-read-floor.js +2 -7
  222. package/dist/plugins/schema-consistency.d.ts +5 -1
  223. package/dist/plugins/session-fork.d.ts +1 -1
  224. package/dist/plugins/session-ownership.d.ts +9 -0
  225. package/dist/plugins/session-ownership.js +6 -0
  226. package/dist/plugins/session-store.d.ts +13 -16
  227. package/dist/plugins/session-store.js +9 -10
  228. package/dist/plugins/sql-errors.d.ts +10 -1
  229. package/dist/plugins/sql-errors.js +13 -0
  230. package/dist/plugins/sql-json-column.d.ts +24 -2
  231. package/dist/plugins/sql-json-column.js +38 -3
  232. package/dist/plugins/store-backend.d.ts +23 -28
  233. package/dist/plugins/store-backend.js +12 -3
  234. package/dist/plugins/store-contracts.d.ts +29 -5
  235. package/dist/plugins/store-contracts.js +4 -1
  236. package/dist/plugins/task-attachment-store.d.ts +2 -2
  237. package/dist/plugins/task-attachment-store.js +37 -52
  238. package/dist/plugins/tidb-session-store.d.ts +2 -14
  239. package/dist/plugins/tidb-session-store.js +2 -20
  240. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  241. package/dist/plugins/tool-result-store-sql.js +23 -2
  242. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  243. package/dist/plugins/wiring-section-codec.js +55 -0
  244. package/dist/plugins/workspace-fill.d.ts +1 -1
  245. package/dist/rule-entry-list.d.ts +3 -2
  246. package/dist/rule-entry-list.js +5 -1
  247. package/dist/rules-consent.d.ts +28 -5
  248. package/dist/rules-consent.js +40 -28
  249. package/dist/run-local.js +11 -6
  250. package/dist/runs.d.ts +12 -8
  251. package/dist/runs.js +10 -7
  252. package/dist/runtime-governance.d.ts +1 -1
  253. package/dist/security.d.ts +0 -4
  254. package/dist/server-secret-env.d.ts +4 -0
  255. package/dist/session-error-wire.d.ts +34 -23
  256. package/dist/session-error-wire.js +8 -3
  257. package/dist/session-incarnation.d.ts +3 -3
  258. package/dist/session-notify-door.d.ts +106 -0
  259. package/dist/session-notify-door.js +97 -0
  260. package/dist/session-sync.d.ts +9 -14
  261. package/dist/session-sync.js +5 -5
  262. package/dist/session-workspace.d.ts +3 -13
  263. package/dist/session-workspace.js +0 -9
  264. package/dist/standing-answer-grant.d.ts +5 -0
  265. package/dist/standing-answer-grant.js +10 -0
  266. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  267. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  268. package/dist/task-mcp.d.ts +1 -1
  269. package/dist/task-settings.d.ts +11 -8
  270. package/dist/task-settings.js +31 -11
  271. package/dist/tool-approval.d.ts +55 -35
  272. package/dist/tool-approval.js +112 -74
  273. package/dist/trace/core-keyset-guard.d.ts +12 -9
  274. package/dist/trace/engine-notice-wire.d.ts +1 -1
  275. package/dist/trace/engine-notice-wire.js +4 -2
  276. package/dist/trace/exit-seat.d.ts +7 -1
  277. package/dist/trace/exit-seat.js +1 -0
  278. package/dist/trace/ledger-sink.d.ts +2 -2
  279. package/dist/trace/ledger-sink.js +12 -4
  280. package/dist/trace/model-segment.d.ts +74 -25
  281. package/dist/trace/model-segment.js +141 -32
  282. package/dist/trace/project.d.ts +15 -6
  283. package/dist/trace/project.js +12 -4
  284. package/dist/trace/redact.d.ts +72 -9
  285. package/dist/trace/redact.js +1509 -471
  286. package/dist/trace/self-minted-wire.d.ts +598 -47
  287. package/dist/trace/self-minted-wire.js +190 -20
  288. package/dist/trace/wire-projection-faces.d.ts +1 -1
  289. package/dist/trace/wire-projection-faces.js +1 -1
  290. package/dist/verifier-pause-card.d.ts +33 -5
  291. package/dist/verifier-pause-card.js +15 -10
  292. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  293. package/dist/web-admin-client/apply-effective.js +90 -10
  294. package/dist/workspace-space.d.ts +19 -3
  295. package/dist/workspace-space.js +8 -4
  296. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  297. package/package.json +3 -2
  298. package/wire-changes/server@7.143.0.json +1167 -0
  299. package/wire-changes/server@7.144.0.json +291 -0
  300. package/dist/session-records-skipped-notice.d.ts +0 -21
  301. package/dist/session-records-skipped-notice.js +0 -27
@@ -5,6 +5,7 @@ import { type RepoReadClient } from "./repo-tools.js";
5
5
  import { type LoadedSkill, type RunSkillEntry, type ScenarioSkillEntry, type SkillSource } from "./skills.js";
6
6
  import { type ScenarioHands } from "./hands-lane.js";
7
7
  import type { DelegationRootWitness } from "../delegated-session-owner.js";
8
+ import type { BackgroundCompletionSinkFor } from "../session-notify-door.js";
8
9
  /**
9
10
  * Scenario routing — a deployment serves many scenarios from one image. Each request's `scenario`
10
11
  * selects a pre-wired capability bundle: tools (incl. subagent roster) + its prompt seats + skills.
@@ -83,11 +84,11 @@ export interface ScenarioContext {
83
84
  */
84
85
  sessionId?: string;
85
86
  /**
86
- * [ref] / [ref]:**委派树根的见证**(停驻挂点的属主来源)—— 根会话 id + 调用方判断那一刻读到的根行住客(`delegated-session-owner.ts` 的 `DelegationRootWitness`)。
87
- * 由**每一个**接了停驻挂点的取用方显式交出(一条读法,没有回落):请求腿 = 本请求经鉴权解析的会话与受理时那一读(`boot/resolve-spec.ts`);`/decide` 赎回面 = 行上的
88
- * `rootSessionId` 与赎回腿写口放行的那一位住客(`parked-revive-surface.ts`,交不交座都交)。它**只**喂停驻挂点,不挂任何会话级设施(那是 {@link sessionId} 的事,
89
- * 判据是「这只 bundle 是不是在那只会话上取用的」,与属主来源是两个独立判据)。缺席 = 没有根可交(boot 期探针、run-local、行上没有根)⇒ 律给哨兵。
90
- * 挂点在停驻那一刻再读一次根行:住客不是见证的那一位(删了、同 id 被重新登记)⇒ 停驻否决 —— 不按新住客认领。
87
+ * [ref]:**委派树根的见证** —— 根会话 id + 调用方判断那一刻读到的根行住客(`delegated-session-owner.ts` 的 `DelegationRootWitness`)。
88
+ * 由取用方显式交出(一条读法,没有回落):请求腿 = 本请求经鉴权解析的会话与受理时那一读(`boot/resolve-spec.ts`);`/decide` 赎回面 = 行上的
89
+ * `rootSessionId` 与赎回腿写口放行的那一位住客(`parked-revive-surface.ts`,交不交座都交)。它只喂 Runner 之外的后台子代完成 relay(会话通知门的目标住客:属主与化身),
90
+ * 不挂任何会话级设施(那是 {@link sessionId} 的事,判据是「这只 bundle 是不是在那只会话上取用的」)。缺席 = 没有根可交(boot 期探针、run-local、行上没有根)⇒ 不接 relay。
91
+ * 停驻挂点不读它(core 7.62.0 起挂点只比对引擎交来的委派树属主;根见证在停驻挂点的读法随 [ref] P-5 退役)。
91
92
  */
92
93
  delegationRoot?: DelegationRootWitness;
93
94
  }
@@ -141,22 +142,27 @@ export interface ScenarioDeps {
141
142
  * pre-153 行为(子代 SAFETY ask 照旧 deny+continue,零回归)。
142
143
  * - `checkpointStore` 必须与 `RunnerDeps.checkpointStore`(主 runner 的 per-task spec 面)**同实例**
143
144
  * ——子代 suspend 在那里铸 token,异实例 = park 全静默失效(core JSDoc 钉过症状,组装区注释同步)。
144
- * - `ensureChildSessionDurable` = park 提交前的 capability 挂点(core 签名 `(sessionId) => Promise<void>`)
145
- * ——resolve = 该子代 session 已从任意进程可读,reject/缺席 = veto(子代按 pre-153 settle failed,
146
- * checkpoint 被 expire)。**本车保守实现**:只在子代 session 已经真正路由到宿主 durable 会话店
147
- * **且真有内容**(`getLeafId() !== null`——排除 F-7 同族的空壳 claim-create 行,那种行 acquire
148
- * 命中但零内容,是`register()`对任意自报 sessionId 的副作用,不是真正 durable 的会话)时 resolve
149
- * (=fork 续聊的常见形),其余一律 reject——不做「主动迁移私店会话进 durable 店」这一步
150
- * (ForkRoutingSessionStore 的路由表是私有状态,无迁移 API;主动迁移是更大的活,candidate 留给
151
- * 后续按需再做)。
145
+ * - `ensureChildSessionDurable` = park 提交前的 capability 挂点(core 签名 `(sessionId, given: { owner }) => Promise<void>`,下面那个字段)
146
+ * ——resolve = 该子代 session 已从任意进程可读且属主是引擎交来的那一位,reject/缺席 = veto(子代按 pre-153 settle failed,
147
+ * checkpoint 被 expire)。实现只验不搬(`plugins/session-store.ts` 的 `attestChildSessionDurable`):宿主店里在、有真内容
148
+ * (`getLeafId() !== null`——排除 F-7 同族的空壳 claim-create 行)、属主相等,才 resolve。
152
149
  */
153
150
  checkpointStore?: CheckpointStore;
154
151
  /**
155
- * [ref]:比 core 挂点多一个参数 —— `root` = **委派树根的见证,由取用这只 bundle 的调用方交出**({@link ScenarioContext.delegationRoot};缺席 = 没有根可交)。
156
- * core 的挂点只给子代 id,停驻写下 / 定夺的宿主行属主却要按委派继承律取委派树根的属主;场景工厂把见证绑进交给 core 的那只闭包,同一棵树里任何深度的后台子代
157
- * 停驻都经它(子代沿用父的委派工具实例)。
152
+ * 停驻挂点 —— 签名就是 core 的(`(sessionId, given: { owner }) => Promise<void>`,core 7.62.0 [ref]):`given.owner` = 引擎判的委派树属主,挂点只比对宿主行属主与它
153
+ * (`plugins/session-store.ts` 的 `attestChildSessionDurable`)。场景工厂原样交给委派工具,不绑任何调用方见证(委派树根见证闭包随 [ref] P-5 退役)。
158
154
  */
159
- ensureChildSessionDurable?: (sessionId: string, root: DelegationRootWitness | undefined) => Promise<void>;
155
+ ensureChildSessionDurable?: (sessionId: string, given: {
156
+ readonly owner: string | null;
157
+ }) => Promise<void>;
158
+ /**
159
+ * **Runner 之外的后台子代完成**交给哪里 —— 按委派树根见证({@link ScenarioContext.delegationRoot})造委派工具挂载期的 `background.notify`。
160
+ * 停驻赎回腿的复活、操作员 `handle.resume` 都不是哪一次 run 内的调用,没有调用车道;引擎只在能解出一只完成 sink(调用 ?? 挂载)时才起后台续跑,
161
+ * 解不出 ⇒ 拒(`revive.no_background`)。sink = 委派树根会话的会话通知门(`session-notify-door.ts` 的 `createBackgroundCompletionRelay`;律与地址、
162
+ * 竞态、条目形都写在那里)。Runner 之内的调用照旧走调用车道,这只 sink 不被调。缺席(没有门的装配:run-local)⇒ 不接 ——
163
+ * 不接一只空 sink(接了 = 引擎起续跑、完成没人收)。
164
+ */
165
+ backgroundCompletionSinkFor?: BackgroundCompletionSinkFor;
160
166
  /**
161
167
  * [ref]([ref] F2 症状 → [ref] 定谳):**会话级**任务清单店解析器,属主 =
162
168
  * `boot/task-list-lane.ts` 的 {@link import("../boot/task-list-lane.js").createTaskListLane}
@@ -38,7 +38,6 @@ export function buildScenarios(deps) {
38
38
  const webSearch = requestWebSearchBackend(deps.requirePrincipal !== true
39
39
  ? judgeRequestWebSearch(req?.settings?.webSearch)
40
40
  : { kind: "absent" }, deps.webSearch, deps.logger);
41
- const ensureChildSessionDurable = deps.ensureChildSessionDurable;
42
41
  const delegationRoot = ctx?.delegationRoot;
43
42
  return {
44
43
  tools: [
@@ -58,7 +57,8 @@ export function buildScenarios(deps) {
58
57
  scope: "default",
59
58
  ...(deps.backgroundAgentStore ? { agentStore: deps.backgroundAgentStore } : {}),
60
59
  ...(deps.checkpointStore ? { checkpointStore: deps.checkpointStore } : {}),
61
- ...(ensureChildSessionDurable !== undefined ? { ensureChildSessionDurable: (childSessionId) => ensureChildSessionDurable(childSessionId, delegationRoot) } : {}),
60
+ ...(deps.ensureChildSessionDurable !== undefined ? { ensureChildSessionDurable: deps.ensureChildSessionDurable } : {}),
61
+ ...(deps.backgroundCompletionSinkFor !== undefined && delegationRoot !== undefined ? { notify: deps.backgroundCompletionSinkFor(delegationRoot) } : {}),
62
62
  },
63
63
  ...createSubagentExtraTools(deps.subagentExtraTools),
64
64
  ...(deps.logger
@@ -1,4 +1,4 @@
1
- import type { SkillSpec, TaskSpec } from "@sema-agent/core";
1
+ import type { SkillSpec } from "@sema-agent/core";
2
2
  import type { RemoteExecProvider } from "../execution-lane-caps.js";
3
3
  import { type Logger } from "../observability/logger.js";
4
4
  /**
@@ -105,22 +105,6 @@ export type ScenarioSkillEntry = SkillListEntry<SkillSource>;
105
105
  export type RunSkillSource = SkillSource | "request";
106
106
  /** run 级名册的一条(`wiring_manifest.skills[]`)。铸点唯一:`capabilities/scenarios.ts` 的 `buildRunSkills`。 */
107
107
  export type RunSkillEntry = SkillListEntry<RunSkillSource>;
108
- /**
109
- * run 级技能名册在 TaskSpec 上的**显式字段键** —— 与执行车道字段(`hands-lane.ts` 的 `HANDS_LANE_FIELD`,[ref])同一形:symbol 键随
110
- * spread / rest 解构走(续跑腿的 `taskConfig` 就是 rest 解构出来的),对上游 core 的键面不可见(JSON / 结构化克隆跳过 symbol 键)。
111
- * 铸点唯一 = `boot/resolve-spec.ts` 的 resolveSpec 出口({@link stampRunSkills});读点唯一 = {@link runSkillsOf}(每条投 `wiring_manifest` 的腿)。
112
- */
113
- export declare const RUN_SKILLS_FIELD: unique symbol;
114
- /**
115
- * 铸名册(**单一属主**:只许 resolveSpec 的出口调用)。字段 `enumerable`(复制得走)、`writable:false` + `configurable:false`(同一只 spec 被第二次铸 ⇒
116
- * 当场 TypeError,两个写者是漂移的成因)。名册冻结一份拷贝(spec 与腿不共享可变引用)。
117
- */
118
- export declare function stampRunSkills<T extends TaskSpec>(spec: T, roster: readonly RunSkillEntry[]): T;
119
- /**
120
- * 读名册。`undefined` = 这只 spec 不是本仓装配出口产的(嵌入方自写 `resolveSpec` / 测试桩)⇒ 腿上没有关于技能的陈述(段缺席)。
121
- * 不是保护型量(缺席只少一段披露,不放宽任何东西)⇒ 缺席照常跑,与车道字段的「缺席即拒」不同。入参收 `object`:续跑腿手里是 rest 解构出来的 taskConfig。
122
- */
123
- export declare function runSkillsOf(spec: object): readonly RunSkillEntry[] | undefined;
124
108
  /** 一个技能范围的卡上清单 —— 与 {@link skillsForScenario} 同一只过滤、同一个序,只是投影不同。 */
125
109
  export declare function scenarioSkillEntries(loaded: readonly LoadedSkill[], scope: string): ScenarioSkillEntry[];
126
110
  /** {@link parseFrontmatter} 的三种读数(闭集):`absent` = 第一行不是围栏,没有 frontmatter(正当的全局技能)· `block` = 围栏收好了的块 · `unclosed` = 第一行是围栏而块没收好(读不了 —— 调用方拒载)。 */
@@ -127,14 +127,6 @@ function skillsInScope(loaded, scope) {
127
127
  export function skillsForScenario(loaded, scope) {
128
128
  return skillsInScope(loaded, scope).map((k) => k.spec);
129
129
  }
130
- export const RUN_SKILLS_FIELD = Symbol("sema-server.runSkills");
131
- export function stampRunSkills(spec, roster) {
132
- Object.defineProperty(spec, RUN_SKILLS_FIELD, { value: Object.freeze(roster.map((e) => Object.freeze({ ...e }))), enumerable: true, writable: false, configurable: false });
133
- return spec;
134
- }
135
- export function runSkillsOf(spec) {
136
- return spec[RUN_SKILLS_FIELD];
137
- }
138
130
  export function scenarioSkillEntries(loaded, scope) {
139
131
  return skillsInScope(loaded, scope).map((k) => ({ name: k.spec.name, description: k.spec.description, source: k.source }));
140
132
  }
@@ -21,9 +21,8 @@
21
21
  import type { FlatServiceDeps } from "./http/server.js";
22
22
  import type { ServiceConfig } from "./config.js";
23
23
  import type { OwnerAwareSessionStore } from "./security.js";
24
- import type { ServiceFileHistoryStore } from "./plugins/store-backend.js";
24
+ import type { ServiceBlobExchange, ServiceFileHistoryStore } from "./plugins/store-backend.js";
25
25
  import { resolveStreamApprovalGate, type ApprovalFrameFamily } from "./tool-approval.js";
26
- import { type SyncBlobHistoryStore } from "./session-sync.js";
27
26
  import { type WorkspaceSyncCapability } from "./workspace-space.js";
28
27
  import type { WorkspaceCacheCapability } from "./workspace-cache.js";
29
28
  /** why 闭集:词 → 给运维看的一句义(诊断 / 测试读它;wire 上的拒句由各门自写)。 */
@@ -47,7 +46,7 @@ export declare const CAPABILITY_FACT_WHY: {
47
46
  readonly session_export_absent: "the session store has no entry export faces (exportEntries / exportEntriesStream)";
48
47
  readonly session_import_staging_absent: "the session store has no staged-import face (beginImportStaging)";
49
48
  readonly policy_purge_absent: "the session policy store has no deleteBySession purge verb";
50
- readonly sync_blob_faces_absent: "the file-history store has no blob transfer faces (SQL backends only)";
49
+ readonly blob_exchange_absent: "the file-history store carries no blob exchange (FileHistoryStore.blobExchange — SQL backends only)";
51
50
  readonly device_hub_absent: "the device lane hub is not wired";
52
51
  readonly device_store_absent: "the device store is not wired";
53
52
  readonly device_enrollment_absent: "device enrollment is not wired";
@@ -193,10 +192,11 @@ export declare function sessionSync(d: Pick<Deps, "backend" | "sessionStorage" |
193
192
  sessionStorage: SyncSessionStore;
194
193
  historyStore: ServiceFileHistoryStore;
195
194
  }>;
196
- /** 会话同步的**文件历史半族**:对话半族 ∧ 文件历史店有 blob 搬运面(只有 SQL 两只店有)。「店有没有 blob 两面」只在 `syncBlobFacesOf` 判一次(marker 门,不做名字探测),
197
- * blob 取值口 `blobGetterOf` 读的是同一只 —— 本事实为真 ⟺ 取值口通到 `historyStore.getBlob`。 */
198
- export declare function sessionSyncFileHistory(d: Pick<Deps, "backend" | "sessionStorage" | "fileHistoryStore" | "sessionPolicyStore">): Fact<SessionSyncWhy | "sync_blob_faces_absent", {
199
- historyStore: SyncBlobHistoryStore;
195
+ /** 会话同步的**文件历史半族**:对话半族 ∧ 文件历史店带 blob 交换组(只有 SQL 两只店有)。「店能不能交换 blob」只在 `blobExchangeOf` 判一次(core 判官),
196
+ * blob 取值口 `blobGetterOf` 读的是同一只 —— 本事实为真 ⟺ 取值口通到 `blobExchange.get`;判过的组随事实交出,路由的 GET / PUT / 在场预检只读它。 */
197
+ export declare function sessionSyncFileHistory(d: Pick<Deps, "backend" | "sessionStorage" | "fileHistoryStore" | "sessionPolicyStore">): Fact<SessionSyncWhy | "blob_exchange_absent", {
198
+ historyStore: ServiceFileHistoryStore;
199
+ blobExchange: ServiceBlobExchange;
200
200
  }>;
201
201
  /** 持久审批底座(checkpoint 店)在场 —— `/v1/approvals/*` 与 `/v1/assistant/*` 整条车道、会话 wake 都站在它上面。 */
202
202
  export declare function checkpointPark(d: Pick<Deps, "checkpointStore">): Fact<"checkpoint_store_absent", {
@@ -280,7 +280,7 @@ export declare function memoryExport(d: Pick<Deps, "memoryExport">): Fact<"memor
280
280
  read: Store<"memoryExport">;
281
281
  }>;
282
282
  /**
283
- * 中心侧同步口(`POST /v1/memory/sync/:scope`):DB 记忆平面的同步执行位在场 ∧ 本中心声明了同步身份(`MEMORY_SYNC_SERVE_IDENTITY`)。
283
+ * 中心侧同步口(`POST /v2/memory/sync/:scope`):DB 记忆平面的同步执行位在场 ∧ 本中心声明了同步身份(`MEMORY_SYNC_SERVE_IDENTITY`)。
284
284
  * 见证 = 执行位 + 那份声明 —— 能力位 `memorySyncServe` 的 `{ identity }` 逐字投影它,入站 `peer` 与它按串比:一处声明,两个读者读同一个值。
285
285
  */
286
286
  export declare function memorySyncServe(d: Pick<Deps, "memorySync" | "config">): Fact<"memory_db_plane_absent" | "memory_sync_identity_undeclared", {
@@ -4,7 +4,7 @@ import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
4
4
  import { presentedApprovalFrameFamily, resolveStreamApprovalGate } from "./tool-approval.js";
5
5
  import { selfOrchestrationDenial } from "./task-workflow.js";
6
6
  import { centerEntitlementSourceWired } from "./runtime-caps-resolver.js";
7
- import { overwriteWipeIncapable, syncBlobFacesOf } from "./session-sync.js";
7
+ import { blobExchangeOf, overwriteWipeIncapable } from "./session-sync.js";
8
8
  import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, workspaceSnapshotTimeoutOf } from "./workspace-space.js";
9
9
  import { headReadOf } from "./session-incarnation.js";
10
10
  export const CAPABILITY_FACT_WHY = {
@@ -27,7 +27,7 @@ export const CAPABILITY_FACT_WHY = {
27
27
  session_export_absent: "the session store has no entry export faces (exportEntries / exportEntriesStream)",
28
28
  session_import_staging_absent: "the session store has no staged-import face (beginImportStaging)",
29
29
  policy_purge_absent: "the session policy store has no deleteBySession purge verb",
30
- sync_blob_faces_absent: "the file-history store has no blob transfer faces (SQL backends only)",
30
+ blob_exchange_absent: "the file-history store carries no blob exchange (FileHistoryStore.blobExchange — SQL backends only)",
31
31
  device_hub_absent: "the device lane hub is not wired",
32
32
  device_store_absent: "the device store is not wired",
33
33
  device_enrollment_absent: "device enrollment is not wired",
@@ -190,8 +190,8 @@ export function sessionSyncFileHistory(d) {
190
190
  const sync = sessionSync(d);
191
191
  if (!sync.available)
192
192
  return sync;
193
- const faces = syncBlobFacesOf(sync.historyStore);
194
- return faces !== undefined ? yes({ historyStore: faces }) : no("sync_blob_faces_absent");
193
+ const blobExchange = blobExchangeOf(sync.historyStore);
194
+ return blobExchange !== undefined ? yes({ historyStore: sync.historyStore, blobExchange }) : no("blob_exchange_absent");
195
195
  }
196
196
  export function checkpointPark(d) {
197
197
  return d.checkpointStore !== undefined ? yes({ checkpointStore: d.checkpointStore }) : no("checkpoint_store_absent");
@@ -1,18 +1,15 @@
1
1
  /**
2
- * **结算时作废一张本服务兑现不了的卡** —— 全仓「结算作废」的唯一动词:读卡 → 卡仍 pending ∧ 卡在调用方说的那只会话上 ⇒
2
+ * **结算时作废一张本 run 自己会话上的、本服务兑现不了的卡** —— 读卡 → 卡仍 pending ∧ 卡在调用方说的那只会话上 ⇒
3
3
  * 按卡**自己的** scope 走 checkpoint 店的 `expire`(与引擎的本进程仲裁器、续跑的 resolve 同一只 CAS,恰一个赢家)。不新增任何持久态。
4
4
  *
5
- * 规则只有一条:**本服务兑现不了的卡不留在待决面上**。两个产地读这一只动词,各自只答「手里是哪一张卡、它该在哪只会话上」:
6
- * · 验证器自己的停驻(`verifier-pause-card.ts`):卡在验证器现铸的会话上,本服务的续跑链连不回它;
7
- * · 停驻没落定就以失败收场的腿(`runs.ts` 的 `voidHeldParkCard`:后台腿与续跑腿):卡在本 run 的会话上,但兑现它的那条 run 已经
8
- * `failed`、认领已放 —— 卡的命运先于认领释放(调用方先调本动词、再放认领)。
5
+ * 规则只有一条:**本服务兑现不了的卡不留在待决面上**。产地 = 停驻没落定就以失败收场的腿(`runs.ts` 的 `voidHeldParkCard`:后台腿与
6
+ * 续跑腿):卡在本 run 的会话上,但兑现它的那条 run 已经 `failed`、认领已放 —— 卡的命运先于认领释放(调用方先调本动词、再放认领)。
7
+ * 验证器自己的停驻卡不走这里:它由引擎的 `Runner.voidVerifierPause` 作废(引擎读卡上的组合腿判它是不是验证器的卡,
8
+ * `verifier-pause-card.ts`);那只动词只收验证器的卡,本 run 自己会话上的卡按引擎的读法仍走店级 `expire`。
9
9
  *
10
10
  * 不抛:作废没做成(店读 / 作废抛错、卡的会话对不上、没接店)⇒ 卡回到「留在待决面上、由审批兜底收割收走」的形,按产地登记的 tag
11
11
  * 响亮留痕(计数 + 探针行 + 一次 warn),结算照常往下走。卡已不 pending / 已不在 ⇒ 什么都不做(CAS 输给了别的结算者,结局相同)。
12
- *
13
- * ## 🚧 过渡(引擎动词到货即改调)
14
- * 店级 `expire` 只作废卡,**不释放**引擎为这张卡钉住的会话(引擎在停驻时 pin 会话,只在续跑终局 / 续跑失败臂 unpin):
15
- * 被作废的卡的会话在本副本内存里一直常驻到进程重启。引擎给出「作废停驻卡并释放其会话」的动词之后,本动词改调它(两个产地一字不改)。
12
+ * 店级 `expire` 只作废卡,不动引擎在停驻时给这只会话打的钉(钉在续跑终局 / 续跑失败臂放)。
16
13
  */
17
14
  import type { CheckpointToken } from "@sema-agent/core";
18
15
  import { type FailOpenTag } from "./observability/fail-open.js";
@@ -337,7 +337,7 @@ export const CONFIG_CATALOG = [
337
337
  return compat.reasoningEffortLevels ?? null;
338
338
  },
339
339
  }),
340
- r("MODEL_ROUTER_ID", "modelPlane", "string", "leader 路由分类模型覆写", { derivedDefaultNote: "缺席 ⇒ MODEL_CHEAP_ID,再退 default", danger: BILL }),
340
+ r("MODEL_ROUTER_ID", "modelPlane", "string", "leader 路由分类模型覆写", { derivedDefaultNote: "缺席 ⇒ 在服务表里解 classifier 角色(classifier → summarize → default;env cheap 槽绑 summarize,采纳的目录绑它自己的)", danger: BILL }),
341
341
  r("MODEL_THINKING_FORMAT", "modelPlane", "enum", "本网关收「开/关思考」的拼法(上 core OpenAICompletionsCompat.thinkingFormat;缺省开思考的 vLLM/Qwen 网关只有 qwen-chat-template 关得掉;anthropic 腿不铸此键;未知词拒启)", {
342
342
  enumValues: [...THINKING_FORMATS],
343
343
  derivedDefaultNote: "缺席 ⇒ 不铸此键,core 按 baseUrl/模型 id 自行推断(默认 openai 的 reasoning_effort)",
@@ -28,6 +28,8 @@ export interface ConfigDirMigrationLog {
28
28
  * · `noop` / `no-documents` / 目录不在 ⇒ 不出声(没有东西可迁);
29
29
  * · `refused` ⇒ warn `config_dir_migration_refused`(一字未写;逐条点名迁移 id、坐标与契约的拒句 —— 读侧随后对这些旧形照旧响亮拒);
30
30
  * · `unreadable` / 调用抛 ⇒ warn `config_dir_migration_failed`(一字未写或写到一半;原因 —— 坏文件的句子与读腿同一只判官)。
31
+ * 契约的 `unreadable` 不带成因,本仓分不出两类:① 读腿同一拍也会拒的坏文本(空 / 坏 JSON / 不是对象 / 嵌得过深〔根下 199 层〕:同一只文本判官,读腿随后另有一行 `config_candidate_rejected`,同一件事两行);
32
+ * ② 只有迁移读不了的(目录列不了、悬空符号链接 —— 读腿接受,这一行是唯一的信号)。静默 ① 就会连 ② 一起吞,所以两类同一行;契约交出成因后 ① 改为只留读腿那一行(待契约)。
31
33
  * · `pending` 只在契约的 `check: true` 下出现,本仓从不传 ⇒ 撞上即是接线错,响亮抛。
32
34
  */
33
35
  export declare function logConfigDirMigration(log: ConfigDirMigrationLog, m: ConfigDirMigration): void;
@@ -1,5 +1,6 @@
1
1
  import { lstat, readFile, readdir, readlink, realpath, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { CONFIG_ROOT_DOCUMENTS } from "@sema-agent/settings-schema";
3
4
  import { LOCAL_READER_DOCUMENTS } from "./config-d-migration.js";
4
5
  import { recordFailOpen } from "./observability/fail-open.js";
5
6
  import { HOST_PLATFORM } from "./plugins/host-platform.js";
@@ -244,35 +245,44 @@ async function confirmUnreached(lexical) {
244
245
  return { kind: "unresolvable", path: lexical };
245
246
  }
246
247
  const documentFiles = () => LOCAL_READER_DOCUMENTS.map((d) => `${d}.json`);
247
- async function reachDir(lexical, ctx) {
248
+ const rootDocumentFiles = () => CONFIG_ROOT_DOCUMENTS.map((d) => `${d}.json`);
249
+ async function reach(lexical, want, ctx) {
248
250
  const top = await lstatStep("/");
249
251
  if (!isStats(top))
250
252
  return "finding" in top ? top.finding : undefined;
251
253
  const rootVerdict = await judgeComponent("/", top, ctx);
252
254
  if (typeof rootVerdict !== "string")
253
255
  return rootVerdict;
254
- const step = await walk({ start: "/", stats: top, open: rootVerdict === "open", hops: 0 }, lexical.split("/"), "dir", ctx);
256
+ const step = await walk({ start: "/", stats: top, open: rootVerdict === "open", hops: 0 }, lexical.split("/"), want, ctx);
255
257
  if ("finding" in step)
256
258
  return step.finding;
257
259
  return step.reached ? step : confirmUnreached(lexical);
258
260
  }
259
- async function judgePosix(configDir, ctx) {
260
- const dir = await reachDir(configDir, ctx);
261
- if (dir === undefined || "kind" in dir)
262
- return dir;
263
- for (const file of documentFiles()) {
264
- const doc = await walk({ start: dir.physical, stats: dir.stats, open: dir.open, hops: dir.hops }, [file], "file", ctx);
261
+ async function judgeDocumentsIn(at, lexical, files, ctx) {
262
+ for (const file of files) {
263
+ const doc = await walk({ start: at.physical, stats: at.stats, open: at.open, hops: at.hops }, [file], "file", ctx);
265
264
  if ("finding" in doc)
266
265
  return doc.finding;
267
266
  if (!doc.reached) {
268
- const unseen = await confirmUnreached(path.join(configDir, file));
267
+ const unseen = await confirmUnreached(path.join(lexical, file));
269
268
  if (unseen !== undefined)
270
269
  return unseen;
271
270
  }
272
271
  }
273
272
  return undefined;
274
273
  }
275
- async function judgeWin32(configDir) {
274
+ async function judgePosix(root, configDir, ctx) {
275
+ for (const file of rootDocumentFiles()) {
276
+ const doc = await reach(path.join(root, file), "file", ctx);
277
+ if (doc !== undefined && "kind" in doc)
278
+ return doc;
279
+ }
280
+ const dir = await reach(configDir, "dir", ctx);
281
+ if (dir === undefined || "kind" in dir)
282
+ return dir;
283
+ return judgeDocumentsIn(dir, configDir, documentFiles(), ctx);
284
+ }
285
+ async function judgeWin32(root, configDir) {
276
286
  const resolveStep = async (p) => {
277
287
  try {
278
288
  return await realpath(p);
@@ -286,26 +296,44 @@ async function judgeWin32(configDir) {
286
296
  return { kind: "unreadable", path: p, errno: code ?? String(e) };
287
297
  }
288
298
  };
289
- const dir = await resolveStep(configDir);
290
- if (dir === undefined)
299
+ const judgeDocs = async (dir, files) => {
300
+ let present = 0;
301
+ for (const file of files) {
302
+ const doc = await resolveStep(path.join(dir, file));
303
+ if (doc === undefined)
304
+ continue;
305
+ if (typeof doc !== "string")
306
+ return doc;
307
+ const st = await stat(doc);
308
+ if (!st.isFile())
309
+ return { kind: "not-regular", path: doc, type: typeOf(st) };
310
+ if (st.size > CONFIG_DOCUMENT_MAX_BYTES)
311
+ return { kind: "too-large", path: doc, bytes: st.size };
312
+ present += 1;
313
+ }
314
+ return present;
315
+ };
316
+ const rootAt = await resolveStep(root);
317
+ if (rootAt === undefined)
291
318
  return undefined;
292
- if (typeof dir !== "string")
293
- return dir;
294
- if (!(await stat(dir)).isDirectory())
319
+ if (typeof rootAt !== "string")
320
+ return rootAt;
321
+ if (!(await stat(rootAt)).isDirectory())
295
322
  return undefined;
296
- for (const file of documentFiles()) {
297
- const doc = await resolveStep(path.join(dir, file));
298
- if (doc === undefined)
299
- continue;
300
- if (typeof doc !== "string")
301
- return doc;
302
- const st = await stat(doc);
303
- if (!st.isFile())
304
- return { kind: "not-regular", path: doc, type: typeOf(st) };
305
- if (st.size > CONFIG_DOCUMENT_MAX_BYTES)
306
- return { kind: "too-large", path: doc, bytes: st.size };
323
+ const rootDocs = await judgeDocs(rootAt, rootDocumentFiles());
324
+ if (typeof rootDocs !== "number")
325
+ return rootDocs;
326
+ const dir = await resolveStep(path.join(rootAt, path.basename(configDir)));
327
+ if (dir !== undefined && typeof dir !== "string")
328
+ return dir;
329
+ const dirIsDir = dir !== undefined && (await stat(dir)).isDirectory();
330
+ if (dirIsDir) {
331
+ const docs = await judgeDocs(dir, documentFiles());
332
+ if (typeof docs !== "number")
333
+ return docs;
307
334
  }
308
- recordFailOpen(WIN32_UNVERIFIED_TAG, `config.d=${dir}`);
335
+ if (dirIsDir || rootDocs > 0)
336
+ recordFailOpen(WIN32_UNVERIFIED_TAG, dirIsDir ? `config.d=${dir}` : `config root=${rootAt}`);
309
337
  return undefined;
310
338
  }
311
339
  export function localConfigDirOf(root) {
@@ -314,14 +342,15 @@ export function localConfigDirOf(root) {
314
342
  export async function judgeLocalConfigDir(input) {
315
343
  const host = input.host ?? processHost();
316
344
  const configDir = localConfigDirOf(input.root);
345
+ const root = path.dirname(configDir);
317
346
  if (host.platform === "win32")
318
- return judgeWin32(configDir);
347
+ return judgeWin32(root, configDir);
319
348
  const owners = new Set([0]);
320
349
  if (host.euid !== undefined)
321
350
  owners.add(host.euid);
322
351
  if (input.declaredOwnerUid !== undefined)
323
352
  owners.add(input.declaredOwnerUid);
324
- return judgePosix(configDir, { owners, mountinfo: host.mountinfo ?? processMountinfo });
353
+ return judgePosix(root, configDir, { owners, mountinfo: host.mountinfo ?? processMountinfo });
325
354
  }
326
355
  const octal = (mode) => `0${(mode & 0o7777).toString(8).padStart(3, "0")}`;
327
356
  export function buildConfigDirUntrustedSentence(f, ctx) {
@@ -26,6 +26,7 @@
26
26
  * 不引 config.ts,故 config.ts / runs.ts 都可以安全引它。
27
27
  */
28
28
  import type { ConfigProviderWord, ServiceConfigFlat } from "./config-types.js";
29
+ import { type EnvTableReplacementInput } from "./env-model-table-gate.js";
29
30
  /**
30
31
  * 活体心跳周期(ms)——`runs.ts` 的 `HEARTBEAT_MS` 就是它(那边 re-export,单源在此)。
31
32
  *
@@ -34,7 +35,7 @@ import type { ConfigProviderWord, ServiceConfigFlat } from "./config-types.js";
34
35
  */
35
36
  export declare const LIVENESS_HEARTBEAT_MS = 30000;
36
37
  /** 闭集:新增一条不变量必须在这里加词(拼错 = 编译红,而不是运行期悄悄不判)。 */
37
- export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "operator-single-user" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "anthropic-credential-exclusive" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "require-execution-lane" | "center-one-identity" | "workflow-park-decision-domain" | "remote-config-provider-url" | "retention-lane-complete";
38
+ export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "operator-single-user" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "anthropic-credential-exclusive" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "env-table-replacement-acknowledged" | "require-execution-lane" | "center-one-identity" | "workflow-park-decision-domain" | "remote-config-provider-url" | "retention-lane-complete";
38
39
  /** 表读的键集(取自平铺配置型 ⇒ 键名/类型永不与真配置漂)。 */
39
40
  export type CrossInvariantView = Readonly<Pick<ServiceConfigFlat, "runStaleSec" | "authToken" | "authTokens" | "allowUnauthedWrites" | "imageBakes" | "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals" | "operatorSingleUser" | "sensitiveWritePatterns" | "leaderEnabled" | "selfOrchestrationEnabled" | "workflowRunStoreBackend" | "remoteExec" | "requireExecutionLane" | "dbBackend" | "tidb" | "pg" | "envModel" | "gatewayBaseUrl" | "gatewayFallbackUrls" | "anthropic" | "models" | "modelRefusals" | "tiers" | "authBridgeIssuer" | "webAdmin" | "configProvider" | "retentionPolicy" | "retentionSweep">>;
40
41
  export interface CrossInvariantInput {
@@ -42,6 +43,11 @@ export interface CrossInvariantInput {
42
43
  readonly live: CrossInvariantView;
43
44
  /** 候选(stage 期 = 现役 + 本批 staged 赋值)。 */
44
45
  readonly next: CrossInvariantView;
46
+ /**
47
+ * 一份**模型面候选**随身带的、不在配置键里的事实(它从哪条配置腿来、本地腿上配置根的 env 模型表决定在同一拍读作什么、引擎铸的 env 表)——
48
+ * 只有模型面 stage 带(它是装模型表的唯一写口);全表扫描(限额批的候选视图)不装模型表、不带。读它的不变量:`env-table-replacement-acknowledged`。
49
+ */
50
+ readonly candidate?: EnvTableReplacementInput;
45
51
  }
46
52
  export interface CrossInvariant {
47
53
  readonly id: CrossInvariantId;
@@ -4,6 +4,7 @@ import { modelRouteUrlRefusal } from "./model-route-url.js";
4
4
  import { anthropicCredentialRefusal } from "./model-route-gap.js";
5
5
  import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
6
6
  import { defaultModelRefusal, refusedNameMintedRefusal } from "./model-entry-refusal.js";
7
+ import { judgeEnvTableReplacement } from "./env-model-table-gate.js";
7
8
  import { WEB_ADMIN_ENV, httpUrlFlaws } from "@sema-agent/settings-schema";
8
9
  import { authBridgeIssuerOf } from "./auth-bridge.js";
9
10
  import { buildDisplaySafeUrl } from "./trace/redact.js";
@@ -318,6 +319,12 @@ export const CONFIG_CROSS_INVARIANTS = [
318
319
  bootAnchor: { file: "src/boot/stage-02-web-admin-pull.ts", marker: "CROSS-INVARIANT:refused-names-not-minted" },
319
320
  check: ({ live, next }) => (next.models === live.models ? undefined : refusedNameMintedRefusal(next)),
320
321
  },
322
+ {
323
+ id: "env-table-replacement-acknowledged",
324
+ keys: ["models"],
325
+ restartReason: "env-model-table",
326
+ check: ({ candidate }) => (candidate === undefined ? undefined : judgeEnvTableReplacement(candidate)?.reason),
327
+ },
321
328
  {
322
329
  id: "center-one-identity",
323
330
  keys: ["authBridgeIssuer", "webAdmin"],
@@ -1,5 +1,7 @@
1
1
  import { type DroppedConfigEntry, type UnreadConfigFile, type UnreadConfigKey } from "./web-admin-client/read-warnings.js";
2
2
  import { FileConfigStore } from "@sema-agent/settings-schema/node";
3
+ import { type EnvModelsDecision } from "@sema-agent/settings-schema";
4
+ import { type CatalogSource } from "./env-model-table-gate.js";
3
5
  import { type ConfigDocumentRefusal, type EffectiveConfig as AgentConfigEffective, type EffectiveReadWarning } from "@sema-agent/settings-schema";
4
6
  import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, type EffectiveConfig } from "./web-admin-client/facade.js";
5
7
  import type { CenterEndpoint } from "./web-admin-client/center-request.js";
@@ -28,6 +30,8 @@ import type { ConfigProviderWord } from "./config-types.js";
28
30
  * (与域判形失败同一条回落链、同一行拒判);缺席 = 没有。远端腿:wire 上只有墓碑一种,那一键不进 effective、其余域照读、版本照读;本地腿:契约的单读口
29
31
  * `FileConfigStore.read()` 交 `refused` ⇒ 被拒文档 = 那一读的拒单原样,什么域内容都没装配 —— 候选是空的 effective(被整拒,不会被应用),版本 = 那一读的契约版本(按文档内容寻址)。 */
30
32
  /** `unreadKeys`:本地读腿读了那份文件、却结构上不读其中一键(`prompts.json` 的 `workers`)—— 同 `unreadFiles`,不进候选门,调用方一键一行 `config_unread_key`。 */
33
+ /** `catalogSource`:这份候选**从哪条配置腿来**(必填;`env-model-table-gate.ts`)—— 本地腿另带配置根上 env 模型表决定在**同一拍**的读数(契约的唯一读者
34
+ * `readEnvModelsDecision`;本地 etag 并入同一份读数的身份)。调用方原样交给 `applyEffective` / `logEffectiveDiff`,模型面 stage 的本地腿确认闸只读它。 */
31
35
  export type FetchEffectiveResult = {
32
36
  effective: EffectiveConfig;
33
37
  etag?: string;
@@ -41,6 +45,7 @@ export type FetchEffectiveResult = {
41
45
  refusedDocuments?: ConfigDocumentRefusal[];
42
46
  readWarnings: readonly EffectiveReadWarning[];
43
47
  credentialIdentity?: string;
48
+ catalogSource: CatalogSource;
44
49
  } | null;
45
50
  /**
46
51
  * 引号段 = **一条规则**:先过 `redactSecrets`;文本里没有引号字符(`"` / `'`)⇒ 原样返回;有 ⇒ 从**第一个**引号到**最后一个**引号(含两端,
@@ -161,6 +166,11 @@ export declare class RemoteConfigProvider implements ConfigProvider {
161
166
  */
162
167
  /** 本地读腿对契约文件店只有一种读法 —— 单读口 `read()`(一次读整棵 `config.d`,交一个结局)。注入缝(测试)按这一形给;严读 / 容错档位不在这条腿的类型里。 */
163
168
  export type LocalConfigReader = Pick<FileConfigStore, "read">;
169
+ /**
170
+ * 本地配置源的 etag(纯字符串,唯一拼法):契约文件店的内容版本 ⊕ 配置根上决定读数的身份(`envModelsDecisionTag`:`absent` / `d:<digest>` / `io:<errno>`)。
171
+ * 只改决定、不改目录 ⇒ etag 也变(下一拍重判);运行记录不进 etag(只在起服读)。
172
+ */
173
+ export declare function buildLocalConfigEtag(version: number, envModels: EnvModelsDecision): string;
164
174
  export declare class LocalConfigProvider implements ConfigProvider {
165
175
  #private;
166
176
  readonly kind: "local";
@@ -1,7 +1,9 @@
1
1
  import { readMcpSourceLabel } from "./mcp-source-label.js";
2
2
  import { createHmac, randomBytes } from "node:crypto";
3
3
  import { splitFoldedReadWarnings, withDroppedCatalogEntries } from "./web-admin-client/read-warnings.js";
4
- import { FileConfigStore } from "@sema-agent/settings-schema/node";
4
+ import { FileConfigStore, readEnvModelsDecision } from "@sema-agent/settings-schema/node";
5
+ import { envModelsDecisionTag } from "@sema-agent/settings-schema";
6
+ import { REMOTE_CATALOG_SOURCE } from "./env-model-table-gate.js";
5
7
  import { assertLocalConfigDirTrusted } from "./config-dir-trust.js";
6
8
  import { danglingRefs, emptyEffective, findSkillContent, promptArtifactFor, renderRefIssue, skillContentHash, } from "@sema-agent/settings-schema";
7
9
  import { redactSecrets } from "./trace/redact.js";
@@ -52,10 +54,13 @@ export class RemoteConfigProvider {
52
54
  async fetchEffective(etag) {
53
55
  const fn = this.deps.fetchEffective ?? remoteFetchEffective;
54
56
  const r = await fn(this.cc, etag, undefined, this.cc.worker);
55
- if (r === null || (r.domainErrors === undefined && r.droppedEntries === undefined && r.refusedDocuments === undefined))
56
- return r;
57
+ if (r === null)
58
+ return null;
59
+ if (r.domainErrors === undefined && r.droppedEntries === undefined && r.refusedDocuments === undefined)
60
+ return { ...r, catalogSource: REMOTE_CATALOG_SOURCE };
57
61
  return {
58
62
  ...r,
63
+ catalogSource: REMOTE_CATALOG_SOURCE,
59
64
  ...(r.domainErrors !== undefined ? { domainErrors: r.domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })) } : {}),
60
65
  ...(r.droppedEntries !== undefined ? { droppedEntries: r.droppedEntries.map(redactDroppedEntry) } : {}),
61
66
  ...(r.refusedDocuments !== undefined ? { refusedDocuments: r.refusedDocuments.map(redactRefusedDocument) } : {}),
@@ -73,6 +78,9 @@ export class RemoteConfigProvider {
73
78
  }
74
79
  }
75
80
  }
81
+ export function buildLocalConfigEtag(version, envModels) {
82
+ return `${String(version)}+${envModelsDecisionTag(envModels)}`;
83
+ }
76
84
  export class LocalConfigProvider {
77
85
  kind = "local";
78
86
  skillBodyCacheDir = undefined;
@@ -88,7 +96,9 @@ export class LocalConfigProvider {
88
96
  async fetchEffective(etag) {
89
97
  await assertLocalConfigDirTrusted({ root: this.root, declaredOwnerUid: this.#declaredOwnerUid });
90
98
  const read = await this.#store.read();
91
- const tag = String(read.kind === "refused" ? read.version : read.effective.version);
99
+ const envModels = await readEnvModelsDecision(this.root);
100
+ const catalogSource = { kind: "local", root: this.root, envModels };
101
+ const tag = buildLocalConfigEtag(read.kind === "refused" ? read.version : read.effective.version, envModels);
92
102
  if (etag !== undefined && etag === tag)
93
103
  return null;
94
104
  const readWarnings = [...read.warnings];
@@ -98,6 +108,7 @@ export class LocalConfigProvider {
98
108
  ...faces,
99
109
  etag: tag,
100
110
  readWarnings,
111
+ catalogSource,
101
112
  ...(folded.unreadFiles.length > 0 ? { unreadFiles: folded.unreadFiles } : {}),
102
113
  };
103
114
  }
@@ -0,0 +1,13 @@
1
+ import { type EnvModelsMigration } from "@sema-agent/settings-schema/node";
2
+ import type { ConfigDirMigrationLog } from "./config-d-migration.js";
3
+ /** 一次起服的输入:配置根、目标引擎的 env(契约只读两槽、只用来点名被顶替的 env 模型)、env 表是不是占位(严格名册布防 —— 与模型面闸同一只谓词)。 */
4
+ export interface EnvModelsMigrationInput {
5
+ readonly root: string;
6
+ readonly env: Readonly<Record<string, string | undefined>>;
7
+ readonly envTableIsPlaceholder: boolean;
8
+ }
9
+ /** 契约的迁移体调一次(本仓不改写它的判序,只调度)。 */
10
+ export declare function runEnvModelsMigration(input: EnvModelsMigrationInput): Promise<EnvModelsMigration>;
11
+ /** 结局的**唯一**说法(两个入口同调)。 */
12
+ export declare function logEnvModelsMigration(log: ConfigDirMigrationLog, input: EnvModelsMigrationInput, m: EnvModelsMigration): void;
13
+ //# sourceMappingURL=config-root-migration.d.ts.map