@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,10 +1,3 @@
1
- /**
2
- * EffectiveConfig application — mutate the boot `ServiceConfig` OVER with a pulled `EffectiveConfig`
3
- * (models/roles/tiers/projects `applyEffective`, the 6 runtime gates `applyRuntimeGates`, the per-request-hot
4
- * governance baton `applyRuntimeHot`) plus their shared helpers (`toModel`, `mutateInPlace`,
5
- * `resolveDefaultModelName`, `logEffectiveDiff`, `runtimeHasActiveGate`). Split out of `facade.ts`
6
- * (design/158 A13, internal-lossless — the facade re-exports every symbol below unchanged).
7
- */
8
1
  import { isThinkingLevel } from "@sema-agent/core";
9
2
  import { resolveActiveTiers, SEALED_BOX_ALG } from "@sema-agent/registry-core";
10
3
  import { sealedKeyPoison } from "../sealed-key.js";
@@ -15,11 +8,8 @@ import { registerTeams } from "../capabilities/team.js";
15
8
  import { stageLimits } from "./stage-limits.js";
16
9
  import { BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
17
10
  const asModelThinking = (v) => (v && isThinkingLevel(v) && v !== "off" ? v : undefined);
18
- /** design/123: accept only a positive finite roster charsPerToken (fail-safe to env default / core 4). */
19
11
  const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
20
12
  function toModel(m, envDefaults = {}) {
21
- // design/111 L2②: a center model MAY declare defaultThinking/reasoningEffortLevels; absent → inherit the env-derived
22
- // model's values (BL-8 — same class as reasoning/contextWindow/maxTokens, else a roster silently drops the env default).
23
13
  const thinkingDefault = asModelThinking(m.defaultThinking) ?? envDefaults.defaultThinking;
24
14
  const effortLevels = (m.reasoningEffortLevels ? m.reasoningEffortLevels.map(asModelThinking).filter((x) => x !== undefined) : undefined) ?? envDefaults.reasoningEffortLevels ?? [];
25
15
  const model = {
@@ -27,102 +17,35 @@ function toModel(m, envDefaults = {}) {
27
17
  name: m.name,
28
18
  provider: m.provider,
29
19
  api: m.api,
30
- // 实机 /model 切换 404 定谳: a catalog model MAY carry its own gateway URL — the
31
- // hardcoded "" made core's brain fall back to the boot-env endpoint, so cross-endpoint `body.model`
32
- // selection hit the WRONG gateway (a model id routed to a gateway that does not serve it → 404).
33
- // Absent still = "" (brain env default;
34
- // byte-compat for single-gateway rosters). core brain honors model.baseUrl; keys stay per-model via apiKeyEnv.
35
20
  baseUrl: m.baseUrl ?? "",
36
- // BL-8 extended (systematic-audit): fall back to the ENV-derived model's reasoning, not Boolean(undefined)=false —
37
- // else a center roster that omits `reasoning` SILENTLY disables it even though the env lane defaults it ON
38
- // (config.ts MODEL_REASONING="true"). A center model MAY set false to override. Same class as contextWindow/maxTokens.
39
21
  reasoning: m.reasoning !== undefined ? Boolean(m.reasoning) : (envDefaults.reasoning ?? true),
40
22
  input: m.vision ? ["text", "image"] : ["text"],
41
- // BL-8: fall back to the ENV-derived model's value (MODEL_CONTEXT_WINDOW / MODEL_MAX_TOKENS), not a
42
- // hardcoded literal — else a center roster that replaces the env model SILENTLY clamps maxTokens to 4096
43
- // (the prod review-gw runs deepseek at 32000), shrinking every response. A center model MAY still set its
44
- // own value to override. (ROBUSTNESS-BACKLOG:28 / BL-8.)
45
23
  contextWindow: m.contextWindow ?? envDefaults.contextWindow ?? 262144,
46
24
  maxTokens: m.maxTokens ?? envDefaults.maxTokens ?? 4096,
47
- // design/123 D2 (BL-8 class): center override ?? env default (MODEL_CHARS_PER_TOKEN) ?? omit = core 4.
48
- // Positive-finite guard mirrors the env lane — a bad roster value must not zero structural estimates.
49
25
  ...(charsPerTokenOf(m.charsPerToken) !== undefined || envDefaults.charsPerToken !== undefined
50
26
  ? { charsPerToken: charsPerTokenOf(m.charsPerToken) ?? envDefaults.charsPerToken }
51
27
  : {}),
52
28
  cost: m.cost
53
29
  ? { input: m.cost.input, output: m.cost.output, cacheRead: m.cost.cacheRead ?? 0, cacheWrite: m.cost.cacheWrite ?? 0 }
54
30
  : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
55
- // Explicit cache family by provider (core 1.23) — same as the env path in config.ts. Anthropic brain
56
- // reports input EXCLUDING cached; the openai gateway INCLUDES it (design/18).
57
31
  params: { ...(m.tier ? { tier: m.tier } : {}), promptCacheFamily: m.provider === "anthropic" ? "input-excludes-cached" : "input-includes-cached" },
58
- // design/111 L2②: per-model default thinking + endpoint-accepted tiers (center override ?? env default, BL-8).
59
32
  ...(thinkingDefault ? { defaultThinking: thinkingDefault } : {}),
60
33
  ...(effortLevels.length > 0 && m.provider !== "anthropic" ? { compat: { reasoningEffortLevels: effortLevels } } : {}),
61
- // Generation tuning (1.60) carried verbatim — without this a center roster that replaces the env model
62
- // would silently drop the degenerate-looping prevention (freq/pres penalties). Absent → unchanged.
63
34
  ...(m.extraBody ? { extraBody: m.extraBody } : {}),
64
- // Per-model behavior-slot sentences ([998]⑤c consumer half) — verbatim, same posture as extraBody.
65
35
  ...(Array.isArray(m.promptGuidance) && m.promptGuidance.length > 0 ? { promptGuidance: m.promptGuidance.map(String) } : {}),
66
36
  };
67
- // [818]①/A-065 P2-6 1M dual-window: an explicit roster `autoCompactTokens` (structural, like tier
68
- // passthroughs) wins; else the model-id table (sonnet-5 family ∧ >=1M ⇒ 967000 — CC ships that value
69
- // per-model, not per window width; see applyAutoCompactWindow header); else absent. A non-sonnet 1M roster
70
- // model gets NO implicit value — roster culture is explicit declaration. Same helper as the env lane.
71
37
  const explicitAct = m.autoCompactTokens;
72
38
  applyAutoCompactWindow(model, typeof explicitAct === "number" ? explicitAct : undefined);
73
39
  return model;
74
40
  }
75
- /** Apply center config OVER the env-derived config (mutates `config`). Empty domains keep env defaults. */
76
- /** Replace a record's contents IN PLACE (clear keys, copy source) so holders of the SAME reference see the
77
- * change. This is what makes models/roles/pricing HOT-apply: core's Runner reads
78
- * `this.deps.models/roles/pricing` LIVE per-task and `/v1/models` reads `config.models` — both share the
79
- * reference captured at boot, so mutating it (vs reassigning) updates both with no split, no Runner rebuild. */
80
41
  export function mutateInPlace(target, source) {
81
42
  for (const k of Object.keys(target))
82
43
  delete target[k];
83
44
  Object.assign(target, source);
84
45
  }
85
- /** 每个 config 对象上一次 COMMIT 的世代号([2283]③ CAS 的 live 端)。WeakMap——config 回收即回收;
86
- * version 0(未发布/本地一次性 lane)不参与登记。 */
87
46
  const appliedGeneration = new WeakMap();
88
- /** [2311]/[2314] 裁①:env 派生 roles 基线(首次进入模型面 apply 时快照,冻结拷贝)。roles 的 file/center
89
- * lane 语义=**逐键 override,env 基线为底,非粘**——上游(registry-core resolveEffectiveForWorker)本就是
90
- * 逐键 merge 链;整表替换会让低信任写者(center 编辑面)静默抹掉 operator 的 env 部署契约(MODEL_VERIFIER
91
- * 反自评等),且旧守卫 `length>0` 粘住的不是 env 底而是**上一个 center 世代**(applyRuntimeHot 头注
92
- * NON-STICKY 论证同族,roles 曾漏在纪律外——cli [2311] 实测「用户撤销被静默吞掉」即此)。 */
93
47
  const envRolesBaseline = new WeakMap();
94
- /**
95
- * #233 / A-002.8 —— 无法解读的名单条目的**代表元**。
96
- *
97
- * 为什么需要它(codex 复审 R1 真 finding):把坏条目**丢掉**会让 `[7, null]` 这种「在场但一条都读不
98
- * 懂」的名单塌成**空数组**,而空数组在消费端的语义恰恰是「治理关闭 = 全放行」—— 一个表达"我要限制"
99
- * 的配置,因为写错了类型,反而把门整个打开。方向必须反过来:名单在场 = operator 意图限制;读不懂的
100
- * 条目 ⇒ 换成一个**永不可能是 catalog name** 的代表元(前导 NUL,registry-core 的 name 词法不可能产
101
- * 出),于是它在归一时解不到任何目录条目 ⇒ 谁都准不了。
102
- *
103
- * 后果是**有界的**:被拒的只有「用户逐 turn 显式点模型」这一件事;`default` / roles / 档位表照常,
104
- * 任务照跑。写错配置的代价是"点不了模型"而不是"服务不可用",且 warn 当场点名成因。
105
- * 🔴 只活在内存判定面:不进 wire(`/v1/models` 只发布尔)、不进日志(warn 打的是原始坏值的类型标记)。
106
- */
107
48
  export const UNPARSABLE_AT_MODEL_ALLOWLIST_ENTRY = "\u0000<unparsable-at-model-allowlist-entry>";
108
- /**
109
- * #233 / A-002.8 —— `models.atModelAllowlist` 的 STAGE 侧归一(纯函数,可抛的事都不做)。
110
- *
111
- * 语义(registry-core `ModelsDomain.atModelAllowlist` 的 JSDoc 同源):**缺席/空 = 全放行**。返回值恒是
112
- * 一个新数组,调用点每次 apply 整表重算 ⇒ **非粘**。
113
- *
114
- * ⚠️ 入参刻意收 `unknown`:`EffectiveConfig` 把它声明成 `string[]`,但 **local lane 的 eff 来自
115
- * `FileConfigStore` 的原始 JSON**(不是 zod 输出),类型在那条腿上不是运行时保证。
116
- *
117
- * 坏形处置(不静默,三条各有一句;共同的方向 = **在场即意图限制,读不懂就更严不更松**):
118
- * - 字段在场但**不是数组** ⇒ 响亮 warn + 落**一条代表元**(见上)= 用户逐 turn 选择全拒。刻意不按
119
- * "缺席"处置:那会把一个写坏了的**限制**声明翻译成"不限制",正是治理轴上最不该有的失败方向。
120
- * 也刻意不整代拒绝:那会把整个模型面(models/roles/tiers/密钥)一起拖下水,爆炸半径不成比例。
121
- * - 数组里的**非串/空串**成员 ⇒ **逐条换成代表元**(不是丢弃)+ warn:名单只会更窄,不会更宽。
122
- * - 成员名**不在本世代启用目录里**(悬空 ref)⇒ warn(照 `sema_registry_default_dangling` 先例)。
123
- * 保留原串:悬空成员在消费侧本就归一不到任何条目;留声必须有——名单整条悬空时"谁都点不了"是
124
- * fail-closed 的真后果,运维要能一眼看见成因。
125
- */
126
49
  function stageAtModelAllowlist(raw, models, logger) {
127
50
  if (raw === undefined || raw === null)
128
51
  return [];
@@ -160,24 +83,12 @@ function stageAtModelAllowlist(raw, models, logger) {
160
83
  }
161
84
  export function applyEffective(config, eff, logger, opts = {}) {
162
85
  const staged = stageEffective(config, eff, logger, opts);
163
- // #325(test [4809]):CAS 的序轴=**收货序数**(opts.generation,config-center 收货处铸的进程内
164
- // 单调 ordinal),不再是 source version——local 腿的 version 是内容哈希(registry-core file-store
165
- // getVersion(),上游只承诺「同 files⇒同 number」,无序承诺),按它比序会把哈希更小的合法新代
166
- // 永久静默拒掉。generation 缺席(单元测试/一次性 lane)退回 version:既有单调 version 语义不变。
167
86
  const gen = opts.generation ?? staged.version;
168
87
  const live = appliedGeneration.get(config);
169
88
  if (staged.version > 0 && live !== undefined && gen < live) {
170
- // [2283]③:两次拉取的 staging 并发/乱序完成时,慢的旧世代不得整体覆盖快的新世代——每次都是
171
- // 「整世代」,但方向反了。拒绝必须留痕(C6);caller 的 etag 纪律本就只在 apply 成功后推进,
172
- // 下一拍拉到的自然是更新的世代。等版本重放(etag 未推进的幂等重放,同 ordinal)照常放行。
173
89
  logger?.warn("sema_registry_stale_generation_refused", { staged: staged.version, stagedOrdinal: gen, live, note: "an older staged generation must not overwrite a newer committed one — refused whole; next poll replays" });
174
90
  return false;
175
91
  }
176
- // core 5.49 `Runner.swapModels` 换装腿([4775]①,defer 臂退役):caller 传入 swapPlane 时,staged
177
- // 模型面在 COMMIT 之前先喂给全部 boot Runner 原子换代——swap 自己 expandTiers 验证候选,抛=原代
178
- // byte-identical 不动 ⇒ 这里按候选整拒处理(与 CAS 拒同一 false 臂:活配置零触碰、etag 不进、
179
- // 下一拍原样重判,中心修好即自愈)。先 swap 后 commit 的次序是硬约束:反过来 commit 先落,swap 再
180
- // 抛,admission(config.models)已在新代而执行(Runner 私有目录)还在旧代——正是 R10 封掉的分裂窗。
181
92
  if (staged.modelPlane !== undefined && opts.swapPlane !== undefined) {
182
93
  try {
183
94
  opts.swapPlane({ models: staged.modelPlane.models, tiers: staged.modelPlane.activeTiers });
@@ -193,9 +104,7 @@ export function applyEffective(config, eff, logger, opts = {}) {
193
104
  }
194
105
  commitStaged(config, staged);
195
106
  if (staged.version > 0)
196
- appliedGeneration.set(config, gen); // #325:CAS 的 live 端=收货序数
197
- // ── post-commit notifications(全部世代描述性通知在最后一笔赋值之后,[2283]②)──
198
- // #322 世代账:apply 报告也是「世代描述性通知」⇒ 同样在 commit 之后发(commit 段零回调纪律不破)。
107
+ appliedGeneration.set(config, gen);
199
108
  opts.report?.({
200
109
  version: staged.version,
201
110
  ...(opts.generation !== undefined ? { ordinal: opts.generation } : {}),
@@ -224,34 +133,22 @@ export function applyEffective(config, eff, logger, opts = {}) {
224
133
  logger?.info("sema_registry_projects", { projects: Object.keys(staged.projects) });
225
134
  return true;
226
135
  }
227
- /** COMMIT 段:纯赋值,零 await/零回调/零发射(源码钉守着——[2283]①②)。mutateInPlace/整对象重赋值/
228
- * registry 表整体换装(registerTeams/registerCollabWorkflows 皆为过滤+swap 的赋值形,无回调面)。 */
229
136
  function commitStaged(config, staged) {
230
137
  const mp = staged.modelPlane;
231
138
  if (mp) {
232
- config.modelApiKeyEnv = mp.modelApiKeyEnv; // reassign ok — keyResolver is rebuilt from it on refresh (main.ts)
233
- config.modelApiKeys = mp.modelApiKeys; // plaintext values — memory-only; poison markers ride the same map
139
+ config.modelApiKeyEnv = mp.modelApiKeyEnv;
140
+ config.modelApiKeys = mp.modelApiKeys;
234
141
  mutateInPlace(config.modelQuotaWeights, mp.quotaWeights);
235
- mutateInPlace(config.models, mp.models); // IN PLACE: keep the ref the Runner + /v1/models share (hot-apply)
142
+ mutateInPlace(config.models, mp.models);
236
143
  config.model = config.models.default;
237
- mutateInPlace(config.roles, mp.roles); // IN PLACE: Runner reads this.deps.roles live([2314] 裁①:staged roles 已是 env 底+center 逐键 merge 的整表,无条件落——旧 `length>0` 守卫=空表粘住上一 center 世代)
144
+ mutateInPlace(config.roles, mp.roles);
238
145
  mutateInPlace(config.tiers, mp.activeTiers);
239
- // #233:整表重赋值(不是 mutateInPlace)—— 本键的全部消费点(resolveSpec / side-query / GET /v1/models /
240
- // 未知模型 400 的 available)都是**逐请求**从 `config` 上读,没有任何一处在 boot 期捕获这个数组本身
241
- // (与 `config.modelApiKeyEnv` 的 "reassign ok" 同判据)。非粘由 stage 侧整表重算保证。
242
- // 🔴 刻意与整个模型面**同生共死**(在 `if (mp)` 里,不在外面):名单说的是"这本目录里哪些可点",
243
- // 所以它必须与它描述的那本目录同世代。plane 被跳过时(deferModelPlane 的 tier-frozen 窗口 /
244
- // 本世代零启用模型 ⇒ 目录仍是上一世代或 env 底),名单跟着一起停在同一世代——把新名单配到旧目录上
245
- // 才是真正的错配(按新名字判旧目录 = 全员悬空 = 谁都点不了)。
246
146
  config.atModelAllowlist = mp.atModelAllowlist;
247
147
  }
248
148
  if (staged.gateAssignments) {
249
149
  for (const [k, v] of staged.gateAssignments)
250
150
  config[k] = v;
251
151
  }
252
- // #322 批1:limitsHttp 组的整批赋值(stage 已保证「要么全在、要么全空」)。缺席基线的键**删**而不是
253
- // 写 undefined —— 本仓的 presence-as-semantics 判定(`"k" in config`)只打平铺面,写 undefined 会让
254
- // 「center 撤键回默认」看起来像「显式设成 undefined」。
255
152
  for (const [k, v] of staged.limits.assignments)
256
153
  config[k] = v;
257
154
  for (const k of staged.limits.deletions)
@@ -265,47 +162,23 @@ function commitStaged(config, staged) {
265
162
  registerTeams(tp.teams);
266
163
  registerCollabWorkflows(tp.workflows);
267
164
  }
268
- mutateInPlace(config.projects, staged.projects); // IN PLACE: keep the ref per-request consumers captured at boot
269
- // 世代登记在 applyEffective 内做(#325:登记的是收货序数 gen,不是 source version)。
165
+ mutateInPlace(config.projects, staged.projects);
270
166
  }
271
- /** STAGE 段:一切计算/校验/解封/投影(可抛;抛=候选整体拒绝,活配置零触碰)。内容判定性 warn/error
272
- * (描述 eff 真伪,与是否 commit 无关)在此发;世代描述性 info 只装载荷,post-commit 发。 */
273
167
  function stageEffective(config, eff, logger, opts) {
274
- // version 0 / empty effective = the config-center has nothing for us yet — typically CONFIG_PUBLISH_MODE
275
- // is ON but nothing has been published. We do NOT wipe: models/roles fall back to env (the enabled>0
276
- // guard below), teams to BUILTIN_TEAMS (registerTeams resets to built-ins). Warn once at boot so the
277
- // operator knows their config isn't live (publish a version, or disable the publish gate). See the
278
- // config-center's ORCHESTRATOR-REVIEW §④ publish-gate contract.
279
168
  if (!opts.teamsOnly && !eff.version) {
280
169
  logger?.warn("sema_registry_unpublished", {
281
170
  version: eff.version,
282
171
  note: "empty/unpublished effective config — using env + built-in fallback; publish a config or unset CONFIG_PUBLISH_MODE",
283
172
  });
284
173
  }
285
- // models/roles HOT-apply on BOTH boot and refresh (TOC "save = effective"). We MUTATE
286
- // config.models/roles IN PLACE (mutateInPlace) — NOT reassign — so the live Runner (reads `this.deps.models/
287
- // roles` per-task by reference) and `/v1/models` (reads config.models) share the one updated object with NO
288
- // split, no Runner rebuild, no core change. Caller (main.ts refresh) additionally refreshes `pricing` (same
289
- // ref) + rebuilds `keyResolver`. (Was startup-only when reassignment split the Runner's ref from config.)
290
174
  const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
291
175
  let modelPlane;
292
- // codex R10: `deferModelPlane` skips the WHOLE model plane (models/roles/tiers + per-model keys) — main.ts
293
- // sets it when the Runner is tier-frozen (private expanded copy) and the plane changed: hot-applying would
294
- // split admission (/v1/models, catalog gates) from the Runner's frozen generation, letting a same-key retarget
295
- // silently route/bill the stale Model object through the restart window. Deferred = live plane stays on the
296
- // Runner's generation; the models-tiers restart slice signals the orchestrator; the restarted process applies
297
- // the new plane at boot BEFORE Runner construction. Caller logs the deferral (never silent).
298
176
  if (enabled.length > 0 && opts.deferModelPlane !== true) {
299
177
  const models = {};
300
178
  const modelApiKeyEnv = {};
301
- const modelApiKeys = {}; // sealed-box 解封明文(内存-only,绝不日志/落盘)或毒丸标记(解封失败,见下)
302
- // Capture the env-derived model's generation bounds BEFORE the loop overwrites config.model (line below):
303
- // a center model that omits maxTokens/contextWindow inherits the operator's env values, not a literal (BL-8).
179
+ const modelApiKeys = {};
304
180
  const envDefaults = { maxTokens: config.model.maxTokens, contextWindow: config.model.contextWindow, reasoning: config.model.reasoning, charsPerToken: config.model.charsPerToken, defaultThinking: config.model.defaultThinking, reasoningEffortLevels: config.model.compat?.reasoningEffortLevels };
305
181
  for (const m of enabled) {
306
- // Guard (since 1.128.0): a registry baseUrl now DIRECTS model traffic (incl. that model's
307
- // Authorization key) — a registry editor is a lower-trust writer than the env, so a non-http(s)/unparsable
308
- // value is REJECTED back to "" (boot-env gateway) and every honored override is logged (observable routing).
309
182
  const rawBaseUrl = m.baseUrl;
310
183
  let safeUrl = rawBaseUrl;
311
184
  if (rawBaseUrl) {
@@ -325,22 +198,6 @@ function stageEffective(config, eff, logger, opts) {
325
198
  }
326
199
  }
327
200
  models[m.name] = toModel(safeUrl === rawBaseUrl ? m : { ...m, baseUrl: safeUrl }, envDefaults);
328
- // Sealed-box custody (registry-core 0.10.5): a model MAY carry `sealedApiKey` — the
329
- // api key pasted in the web UI and sealed BROWSER-SIDE against this host's X25519 public key; the
330
- // center stores only the ciphertext. We unseal with the private key registered under the EXACT
331
- // `publicKeyId` (no try-every-key scanning — the contract selects precisely) and inject the
332
- // plaintext into the in-memory per-model key pipeline (config.modelApiKeys → getApiKeyAndHeaders).
333
- // Mutual exclusion (contract): sealed OUTRANKS apiKeyEnv when both are set — and STRICTLY so: a
334
- // sealed key that fails to unseal (missing private key / bad base64 / MAC failure) does NOT fall
335
- // back to the env-NAME reference. Falling back would turn "the key I pasted isn't live" into a
336
- // silent misroute onto some stale env key; we fail LOUD (warn). And since a merely-ABSENT map
337
- // entry is indistinguishable from "no per-model key configured" — which with a gateway
338
- // MODEL_API_KEY deployed meant the model's tasks silently burned the shared account — a failed
339
- // unseal plants a POISON PILL in the map instead of injecting nothing: the key resolver turns it
340
- // into a task-level SealedKeyPoisonedError on every brain call (the gateway key is NEVER a silent
341
- // fallback for a broken sealed key) until the operator fixes custody and a later apply delivers a
342
- // decryptable ciphertext (the rebuild below makes recovery automatic). 🔴 Neither the plaintext
343
- // NOR the ciphertext ever reaches a log line or the poison object (write-only discipline).
344
201
  if (m.sealedApiKey) {
345
202
  const s = m.sealedApiKey;
346
203
  if (s.alg !== SEALED_BOX_ALG) {
@@ -365,25 +222,16 @@ function stageEffective(config, eff, logger, opts) {
365
222
  }
366
223
  }
367
224
  }
368
- // Per-model key: center declares the env var NAME (never the secret) holding this model's upstream
369
- // key. Drives the spec's getApiKeyAndHeaders so each model authenticates with its own account; the
370
- // value is resolved from process.env in THIS service (secrets stay in env, design/15). A model
371
- // without apiKeyEnv (or whose env var is unset) falls back to the gateway key — purely additive.
372
- // (`else if` on sealedApiKey ABSENCE: a sealed model never consults the env reference — see above.)
373
225
  else if (m.apiKeyEnv) {
374
226
  if (process.env[m.apiKeyEnv])
375
227
  modelApiKeyEnv[m.name] = m.apiKeyEnv;
376
228
  else
377
229
  logger?.warn("sema_registry_model_key_env_missing", { model: m.name, apiKeyEnv: m.apiKeyEnv, hint: "env var not set in this service — model falls back to the gateway key" });
378
230
  }
379
- // A center model whose provider has no matching brain in THIS service's env will mis-route:
380
- // createRoutingBrain falls back to the gateway brain. The anthropic route needs ANTHROPIC_API_KEY.
381
231
  if (m.provider === "anthropic" && !config.anthropic) {
382
232
  logger?.warn("sema_registry_model_no_brain", { model: m.name, provider: m.provider, hint: "set ANTHROPIC_API_KEY in this service's env, else it mis-routes to the gateway brain" });
383
233
  }
384
234
  }
385
- // weight-at-burn 源:quotaWeight 按 name+id 双键索引(tracer 的 brain.call e.model=模型 id,
386
- // 目录键=name——双键免猜);非法值(≤0/NaN)按缺省 1 丢弃。IN PLACE 与 models 同批 hot-apply。
387
235
  const quotaWeights = {};
388
236
  for (const m of enabled) {
389
237
  const qw = typeof m.quotaWeight === "number" && Number.isFinite(m.quotaWeight) && m.quotaWeight > 0 ? m.quotaWeight : undefined;
@@ -393,8 +241,6 @@ function stageEffective(config, eff, logger, opts) {
393
241
  quotaWeights[m.id] = qw;
394
242
  }
395
243
  }
396
- // [2311]/[2314] 裁①:首次进入模型面 apply 时快照 env 派生基线(此刻 config.roles 尚未被任何 center
397
- // 世代写过——boot 序=loadConfig→applyEffective;deferModelPlane 跳过的 apply 不进本分支,不污染)。
398
244
  if (!envRolesBaseline.has(config))
399
245
  envRolesBaseline.set(config, { ...config.roles });
400
246
  const centerRoles = {};
@@ -404,25 +250,10 @@ function stageEffective(config, eff, logger, opts) {
404
250
  else if ("select" in tgt)
405
251
  centerRoles[role] = { select: tgt.select };
406
252
  }
407
- // 逐键 override(env 底)+ 非粘:每次 apply 从基线整表重算——center 撤键/发空表 = 该键/整表回 env 底,
408
- // 上一世代绝不驻留;center 同名键盖 env(operator 两边都配时 center 编辑面可见可审)。
409
253
  const roles = { ...envRolesBaseline.get(config), ...centerRoles };
410
- // registry-core 0.9.0: tier groups → the engine's single active tier table. `resolveActiveTiers`
411
- // is registry-core's ONE resolution point (active group → its bound tiers; no active / dangling / all-unbound
412
- // → undefined = INERT). Values are catalog NAME strings (open, not enum'd); dangling bindings were already
413
- // culled center-side. mutateInPlace keeps the reference RunnerDeps.tiers holds — the expansion itself happens
414
- // at Runner CONSTRUCTION (core 1.265 expandTiers), so a refresh-time change is restart-to-apply like models.
415
- // [2373]B-6:EffectiveConfig.models 已声明 tierGroups/activeTierGroup(types.ts)——旧「CenterModel
416
- // doesn't type them」注随宽铸一起退役;resolveActiveTiers 收 registry-core 形,窄化一步即可。
417
254
  const activeTiers = resolveActiveTiers(eff.models) ?? {};
418
- // [865]①(生产实锤:面板上选了 A 模型,实际跑的是目录里的另一个)显式默认解析——enabled[0] 会被任何改变 pool 顺序的写
419
- // 静默翻转,default 必须消费 wire 里已有的显式意图。解析单源 = resolveDefaultModelName(dry-run 的
420
- // wouldDefaultModel 共用同一只,报数与真 apply 永不撕裂——codex M2)。
421
255
  const picked = resolveDefaultModelName(eff, (n) => models[n] !== undefined, enabled[0].name, (source, name) => logger?.warn("sema_registry_default_dangling", { source, name, hint: "explicit default names a model that is not in the enabled catalog — falling to the next source" }));
422
256
  models.default = models[picked.name];
423
- // #233 / A-002.8 `@`-mention 允许名单:与 roles 同族的**非粘整表重算**——每次 modelPlane apply 都从
424
- // 本世代的 eff 重算,center 撤名单/发空表 ⇒ 本表清空 ⇒ 回「全放行」。粘写法(`length>0` 才写)会让
425
- // 上一个 center 世代的名单常驻 = **收不回的治理锁**(roles 那条 [2314] 裁①同族病,不重犯)。
426
257
  const atModelAllowlist = stageAtModelAllowlist(eff.models?.atModelAllowlist, models, logger);
427
258
  modelPlane = {
428
259
  models,
@@ -435,47 +266,21 @@ function stageEffective(config, eff, logger, opts) {
435
266
  infoLine: { count: enabled.length, default: models.default.id, defaultSource: picked.source, roles: Object.keys(roles), tiers: Object.keys(activeTiers), atModelAllowlist: atModelAllowlist.length, sealedKeys: Object.values(modelApiKeys).filter((v) => typeof v === "string").length, sealedPoisoned: Object.values(modelApiKeys).filter((v) => typeof v !== "string").length },
436
267
  };
437
268
  }
438
- // Runtime governance/limit gates (phase-2): STARTUP only (restart-to-apply) — the live RateLimiter / CostQuota
439
- // / approval gate are built from `config` AFTER this in main.ts, so mutating it here before they're constructed
440
- // is what takes effect; a refresh (teamsOnly) must NOT touch them (the running gates hold their boot values).
441
- // governance 切新位:治理三件优先读 governance 域(真值),runtime 旧位(双写镜像)fallback——
442
- // 双写期两处逐键相等语义不变;center 撤双写后 governance 即唯一来源。限额残余(rateLimit/cost 五件)仍在 runtime。
443
269
  const gatesView = eff.governance ? { ...eff.runtime, ...eff.governance } : eff.runtime;
444
270
  const gates = opts.teamsOnly ? undefined : stageRuntimeGates(gatesView, logger);
445
- // #322 批1:limitsHttp 组是**热**的 ⇒ 与 hot 二件同姿势,boot 与 refresh 双腿都算(不在 teamsOnly 门内)。
446
- // 五个既有闸键因此从 `stageRuntimeGates` 的 boot-only 车道**移出**(RUNTIME_GATE_KEYS 里剩 approvalRequire),
447
- // 单一属主:一个键只能有一个写者,否则 boot 一次 refresh 一次的两条腿迟早给出不同答案。
448
- // 🔴 `limits` 按**键存在性**透传,不按真值(codex R1-F1 同族):`null`/`0`/`""` 这些畸形值必须原样送到
449
- // stage 的判形门上去,truthy 过滤会把它们折成「域缺席」= 静默回 env 底,正是那条 finding 要堵的形。
450
271
  const limits = stageLimits(config, { version: typeof eff.version === "number" ? eff.version : 0, ...("limits" in eff ? { limits: eff.limits } : {}), legacyGates: legacyGateValues(eff.runtime) }, logger);
451
- // Runtime governance "second baton" (center §10): autonomy + commandPolicy are per-request HOT (read live in
452
- // resolveSpec), NOT baked into boot middleware → apply on BOTH boot and refresh (outside the teamsOnly guard) so
453
- // they hot-reload. No restart-to-apply signal (they take effect on the next task without a restart).
454
272
  const hot = stageRuntimeHot(config, gatesView, logger);
455
- // Teams → registry (hot-reloadable; center overrides/extends the built-ins).
456
- // codex R11: teams + collab workflows carry MODEL REFERENCES (member.model / workflow model args) — when the
457
- // model plane is deferred (tier-frozen Runner, see the deferModelPlane guard above) these faces must defer
458
- // WITH it, or a candidate that atomically adds/retargets a model AND updates a team/workflow to reference it
459
- // publishes a mixed generation: the new template goes live while config.models/the Runner stay old (a new ref
460
- // fails as unknown; a same-name retarget silently executes stale). One catalog generation = one visibility.
461
273
  let teamsPlane;
462
274
  if (opts.deferModelPlane !== true) {
463
275
  const teams = {};
464
276
  for (const t of (eff.teams?.teams ?? []).filter((t) => t.enabled !== false)) {
465
277
  teams[t.name] = {
466
278
  name: t.name,
467
- // F8: honor member.model (core TeamMember.model supports it) — was silently dropped, leaving the
468
- // member to fall back to the "team" role instead of the operator's chosen model.
469
279
  members: t.members.map((mm) => ({ role: mm.role, ...(mm.model ? { model: mm.model } : {}), modelRole: mm.modelRole, systemPrompt: mm.systemPrompt })),
470
280
  rounds: t.rounds,
471
281
  synthesizer: t.synthesizer ? { role: t.synthesizer.role, modelRole: t.synthesizer.modelRole, systemPrompt: t.synthesizer.systemPrompt } : undefined,
472
282
  };
473
283
  }
474
- // collab → named-workflow projection(切片 1.5,design/140 统一解;切片① 的
475
- // TeamTemplate 投影已整体替换——纪律「别留双路径降级」)。可投影子集翻译成命名 workflow 注册条目
476
- // (执行体=core 内置 team-discussion 脚本,center 模板=defaultArgs 合并链第二级;键=collab id,shell
477
- // `/team`/LLM 经 Workflow({name}) 调用);子集外整条跳过+结构化上报,绝不静默降级。HOT:boot+refresh
478
- // 双腿整表替换(非粘——center 停发即空表,内置 workflow 不受影响)。register* 在 commit 段成对换装。
479
284
  const projected = projectCollabToWorkflows(eff.collab?.templates);
480
285
  const notices = [];
481
286
  if (Object.keys(teams).length > 0)
@@ -488,12 +293,6 @@ function stageEffective(config, eff, logger, opts) {
488
293
  notices.push({ level: "info", msg: "sema_registry_collab_notes", fields: { notes: projected.notes } });
489
294
  teamsPlane = { teams, workflows: projected.workflows, notices };
490
295
  }
491
- // 142-S4 projects 域(registry-core 0.10.0):center 项目登记簿 → config.projects(键=
492
- // projectId)。消费是 per-request 查表(memoryScope 派生 + defaultScopes 种子,security.ts/main.ts),
493
- // 所以 boot+refresh 双腿都 mutateInPlace 热应用(整表替换,非粘——center 停发即清空);env lane 恒空。
494
- // [2373]B-8:裸 Record 臂删——registry-core 0.13.0 ProjectsConfig 只有包裹形 `{projects:{...}}`
495
- // (types.d.ts:3599,且 :12231 钉进 EffectiveConfig),裸形无 producer(与 Q15 declared 双形容忍同族)。
496
- // 防御保留:非 object 条目跳过,字段逐个验型(wire verbatim,不信任形状)。
497
296
  const projDomain = eff.projects;
498
297
  const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain) &&
499
298
  "projects" in projDomain && typeof projDomain.projects === "object"
@@ -502,7 +301,7 @@ function stageEffective(config, eff, logger, opts) {
502
301
  const projects = {};
503
302
  for (const [pid, reg] of Object.entries(rawProjects)) {
504
303
  if (!reg || typeof reg !== "object" || Array.isArray(reg))
505
- continue; // defensive: non-object entry → skip
304
+ continue;
506
305
  const r = reg;
507
306
  projects[pid] = {
508
307
  ...(typeof r.displayName === "string" ? { displayName: r.displayName } : {}),
@@ -520,38 +319,8 @@ function stageEffective(config, eff, logger, opts) {
520
319
  projects,
521
320
  };
522
321
  }
523
- /**
524
- * Apply the center `runtime` governance/limit gates OVER the env-derived config (mutates `config`).
525
- *
526
- * UNIFORM presence semantics across all 6 gates (center `f95438a` adopted this from our sentinel flag —
527
- * SERVICE-INTEGRATION「Runtime 治理/限额闸」): all six fields are center `.optional()`, so
528
- * `undefined` = center isn't managing it → KEEP env;
529
- * present (any value, INCLUDING an explicit 0 / [] = "publish the gate OFF") → OVERRIDE env.
530
- * (Earlier center had `rateLimitPerMin`/`approvalRequire` as `.default(0/[])`, which would have let a default
531
- * silently disable an env-set gate — we flagged it, center made all six optional, so a plain presence rule is
532
- * now both correct and uniform. No sentinel special-casing.)
533
- *
534
- * Only guard: `costQuotaWindowSec` must be `> 0` (a 0 window is nonsensical — core divides by it). Center's
535
- * schema is `.positive()` so it can't send 0; this is belt-and-suspenders.
536
- *
537
- * Per-worker overrides ride the per-worker `/effective?worker=` response (center merges WorkerSpec.runtime over
538
- * the fleet default server-side), so this single "effective beats env" rule already honors per-worker > fleet.
539
- */
540
- /** The 6 runtime governance/limit gates, in ONE place (council DRY): the consume loop, the restart-to-apply
541
- * signal, and the dry-run diff all derive from this list, so adding a 7th gate is a single edit and can't
542
- * silently miss `runtimeHasActiveGate` (which would break the restart-to-apply warning). All six share keys
543
- * across `EffectiveConfig["runtime"]` and `ServiceConfig`. Exported (not in the original single-file form) so
544
- * the sibling restart-signal module can share the identical key list + presence rule. */
545
322
  export const RUNTIME_GATE_KEYS = ["rateLimitPerMin", "approvalRequire", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
546
- /**
547
- * #322 批1:六闸里**已转热**的五个(限额族)——它们的属主是 `stage-limits.ts`(boot+refresh 双腿),
548
- * 所以 boot-only 的闸车道不再写它们,`runtime-gates` 重启片也不再为它们铸重启理由(键是热的却还要求重启
549
- * = 白白滚动重启整片 fleet)。剩下的 `approvalRequire` 仍是 restart-to-apply,归批2。
550
- */
551
323
  export const HOT_RUNTIME_GATE_KEYS = new Set(BATCH1_LIMIT_KEYS);
552
- /** Uniform presence rule (center f95438a): a gate is "managed" when the field is present — any number (incl. 0),
553
- * or an array for approvalRequire. Sole guard: costQuotaWindowSec must be > 0 (a 0 window is nonsensical — core
554
- * divides by it; center's `.positive()` already enforces, belt-and-suspenders here). */
555
324
  export function runtimeGatePresent(rt, key) {
556
325
  const v = rt[key];
557
326
  if (key === "approvalRequire")
@@ -560,42 +329,17 @@ export function runtimeGatePresent(rt, key) {
560
329
  return typeof v === "number" && v > 0;
561
330
  return typeof v === "number";
562
331
  }
563
- /**
564
- * A1:中心下发的 `approvalRequire` 名单**按 env 腿同款判据**验过再落配。此前只验 `Array.isArray` 就逐字
565
- * 落配——含退役工具名(`bash`/`Task`/`KillShell`)或 pre-prefix/不完整 MCP 名(`figma__x`/`mcp__figma`)时
566
- * 该名单项永不匹配任何现役工具,审批门静默失守,而同一拼法从 env 腿(`APPROVAL_REQUIRE`)进来是 boot 拒启
567
- * ——两腿判据不对称,写配置的人无从知道自己踩的是哪一条。判据单点=config.ts `findUnmatchableToolNames`。
568
- *
569
- * 处置形照本文件既有的域级拒绝先例(`commandPolicy` 非法 ⇒ fail-loud + 保留前值):常驻进程不能因为一次
570
- * 坏发布 crash,所以**只拒这一个域**(其余五闸照常应用)、保留前值、留一条响亮 warn。「半应用一个被静默
571
- * 削弱的门」才是要堵的那种失败。
572
- *
573
- * 顺带 C1-2:数组项 trim + 去空串,与 env csv 归一(`csv()` 的 `.map(trim).filter(Boolean)`)一致——
574
- * 否则 `" Bash "` 从中心腿进来会是个永不匹配的名字,正是本条要消灭的那类静默失效。
575
- *
576
- * ⚠️ codex R1(#186 同批复审):**形错必须拒域,不能折成 `[]`**。旧实现先按 `typeof === "string"` 过滤再验
577
- * 判据,于是 `[123]`(非串项)与 `"Bash"`(整个不是数组)双双归约成空名单、判据自然干净 ⇒ 被当作「运维
578
- * 显式把闸关掉」落配,把前值换成空门且**零告警**。方向与 A1 相反(A1 是门空转,这条是门蒸发),但同属本域
579
- * 要堵的失败,且在安全轴上是静默 fail-open。判据分家:**空串/纯空白仍丢弃**(C1-2 的 env csv 归一,env 腿
580
- * 产不出非串项),**非串项 / 非数组=形错 ⇒ 拒整域保前值**;显式 `[]` 依旧是合法的「闸关掉」。
581
- */
582
332
  function stageApprovalRequire(raw, logger) {
583
- // `kept` 逐臂说真话:内容臂的后果是「门空转」,形错臂的后果是「门蒸发」——两句不能混用。
584
333
  const refuse = (detail, names, kept) => {
585
334
  logger?.warn("sema_registry_approval_require_invalid", { detail, names: [...names], kept });
586
335
  return {};
587
336
  };
588
337
  const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
589
338
  const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
590
- // 形错门(codex R1),先于内容判据:此处**不能**静默归一,归一的结果恰好是「空门」。
591
- // 非数组一支是**纵深防御**:唯一调用点先过 `runtimeGatePresent`(判 `Array.isArray`),非数组在那里就按
592
- // 「缺席」跳过、保前值——已是 fail-safe。本函数签名收 `unknown`,所以自己也把这一形收口,免得 present
593
- // 判据日后放宽时非数组悄悄掉进下面的归一腿(测试两支都钉了)。
594
339
  if (!Array.isArray(raw)) {
595
340
  return refuse(`published runtime.approvalRequire must be an ARRAY of whole tool names — got ${raw === null ? "null" : typeof raw}. ` +
596
341
  `Refusing the domain instead of coercing it: an empty list means "no approval gate", so coercing a malformed value would SILENTLY remove the gate.`, [], KEPT_WOULD_REMOVE_GATE);
597
342
  }
598
- // 逐项报**下标 + 实际类型**(运维要能在发布件里定位到那一项;只报个数无法行动)。
599
343
  const nonString = raw
600
344
  .map((n, i) => (typeof n === "string" ? undefined : { i, t: n === null ? "null" : typeof n }))
601
345
  .filter((e) => e !== undefined);
@@ -604,7 +348,6 @@ function stageApprovalRequire(raw, logger) {
604
348
  `(${nonString.map((e) => `[${e.i}]:${e.t}`).join(", ")}) — every entry must be a whole tool name string. ` +
605
349
  `Refusing the domain instead of dropping them: dropping every entry would SILENTLY remove the approval gate.`, nonString.map((e) => `[${e.i}]`), KEPT_WOULD_REMOVE_GATE);
606
350
  }
607
- // wire verbatim ⇒ 空串/纯空白按 env csv 归一丢弃(C1-2);元素形状已在上面的形错门收口。
608
351
  const names = raw.filter((n) => typeof n === "string").map((n) => n.trim()).filter((n) => n.length > 0);
609
352
  const bad = findUnmatchableToolNames(names);
610
353
  if (bad.length > 0) {
@@ -612,8 +355,6 @@ function stageApprovalRequire(raw, logger) {
612
355
  }
613
356
  return { value: names };
614
357
  }
615
- /** [2283] stage 半场:六闸的赋值清单(纯计算,零变异)。commit 段照单赋值,通知载荷 post-commit 发。
616
- * A1 的名单校验是**内容判定**(描述 eff 的真伪,与是否 commit 无关)⇒ 与 commandPolicy 同,stage 期发 warn。 */
617
358
  function stageRuntimeGates(rt, logger) {
618
359
  if (!rt)
619
360
  return { assignments: [] };
@@ -621,24 +362,22 @@ function stageRuntimeGates(rt, logger) {
621
362
  const applied = {};
622
363
  for (const key of RUNTIME_GATE_KEYS) {
623
364
  if (HOT_RUNTIME_GATE_KEYS.has(key))
624
- continue; // #322 批1:属主已移交 stage-limits(热腿),一个键只许一个写者
365
+ continue;
625
366
  if (!runtimeGatePresent(rt, key))
626
- continue; // undefined / sentinel → keep env
367
+ continue;
627
368
  if (key === "approvalRequire") {
628
369
  const staged = stageApprovalRequire(rt[key], logger);
629
370
  if (staged.value === undefined)
630
- continue; // 判据未过 ⇒ 只拒该域,保留前值(其余闸不受牵连)
371
+ continue;
631
372
  assignments.push([key, staged.value]);
632
373
  applied[key] = staged.value;
633
374
  continue;
634
375
  }
635
- assignments.push([key, rt[key]]); // present (incl. explicit 0/[]) → override
376
+ assignments.push([key, rt[key]]);
636
377
  applied[key] = rt[key];
637
378
  }
638
379
  return { assignments, ...(Object.keys(applied).length > 0 ? { info: applied } : {}) };
639
380
  }
640
- /** #322:旧位(`runtime`)里**已过 presence 判据**的限额闸值。判据属主恒是 `runtimeGatePresent`
641
- * —— stage-limits 不复刻它,只消费它的产物(两位判据不同的理由见 stage-limits 的 `LimitsSource` 注)。 */
642
381
  function legacyGateValues(rt) {
643
382
  const out = {};
644
383
  if (!rt)
@@ -649,10 +388,6 @@ function legacyGateValues(rt) {
649
388
  return out;
650
389
  }
651
390
  export function applyRuntimeGates(config, rt, logger) {
652
- // 独立调用面的兼容壳(测试/外部):stage → 就地赋值 → 通知。applyEffective 不走这里——它把
653
- // assignments 并进自己的 commit 段以保住整世代原子性([2283]②)。
654
- // #322:限额五闸的属主已移交热腿,所以本壳**同样**要过 stage-limits —— 否则一个仍叫 applyRuntimeGates
655
- // 的函数会静默漏掉六个键里的五个(比改名更坏的那种沉默)。
656
391
  const s = stageRuntimeGates(rt, logger);
657
392
  for (const [k, v] of s.assignments)
658
393
  config[k] = v;
@@ -665,37 +400,13 @@ export function applyRuntimeGates(config, rt, logger) {
665
400
  if (Object.keys(info).length > 0)
666
401
  logger?.info("sema_registry_runtime", info);
667
402
  }
668
- /**
669
- * Apply the runtime governance "second baton" (center §10): `autonomy` + `commandPolicy`. UNLIKE the 6 gates in
670
- * {@link applyRuntimeGates}, these are per-request HOT (read live in main.ts `resolveSpec` via
671
- * `applyRuntimeGovernance`), so they apply on BOTH boot and refresh (NOT via RUNTIME_GATE_KEYS / restart-to-apply).
672
- *
673
- * 🔴 NON-STICKY (differs from the restart-to-apply gates on purpose — adversarial-review HIGH): a hot field that
674
- * center STOPS publishing must REVERT to the env baseline, not keep the last center value. The restart-to-apply
675
- * gates can be sticky because a refresh never touches them (the running middleware holds boot values until a
676
- * restart re-reads env+center); a HOT field has no such reset, so "absent ⇒ keep" would silently freeze a stale
677
- * center override (e.g. center published `auto`, then un-published it — the gate would stay OFF forever). So we
678
- * recompute every call as `present ? center : envBaseline`. The env baseline = `AUTONOMY` (re-derived, env is
679
- * immutable at runtime) for autonomy; `undefined` (no env scalar source) for commandPolicy.
680
- *
681
- * 🔴 commandPolicy is VALIDATED here (adversarial-review HIGH): an invalid command (glob/path/operator — which
682
- * core's EXACT-name matcher would silently never match → a hole) is rejected FAIL-LOUD and the prior good policy
683
- * is KEPT (a broken publish never half-applies a silently-weakened gate).
684
- */
685
- /** [2283] stage 半场:hot 二件的赋值决定(计算+校验;`sema_registry_commandpolicy_invalid` 是内容判定,
686
- * stage 期发——它描述 eff 的真伪,与是否 commit 无关)。对比基准=stage 时刻的 config 现值:stage 与
687
- * commit 同一同步 tick,期间无人能改 config(单线程),对比不失效。 */
688
403
  function stageRuntimeHot(config, rt, logger) {
689
404
  const infoLine = {};
690
- // autonomy: present ⇒ center value; absent ⇒ revert to the env baseline (never a stale center override).
691
405
  const envAutonomy = parseAutonomy(process.env.AUTONOMY);
692
406
  const autonomyNext = rt?.autonomy !== undefined ? rt.autonomy : envAutonomy;
693
407
  const setAutonomy = config.autonomy !== autonomyNext;
694
408
  if (setAutonomy)
695
409
  infoLine.autonomy = autonomyNext ?? "(env-baseline)";
696
- // commandPolicy: present+valid ⇒ apply; present+invalid ⇒ fail-loud + keep prior; absent ⇒ revert to baseline
697
- // (undefined — no env scalar source for structured command rules). Only re-set + log on an ACTUAL change
698
- // (deep-equal compare) — refresh runs every ~60s and the policy is usually unchanged; logging every tick = noise.
699
410
  let setCommandPolicy = false;
700
411
  let commandPolicyNext;
701
412
  if (rt?.commandPolicy !== undefined) {
@@ -706,18 +417,17 @@ function stageRuntimeHot(config, rt, logger) {
706
417
  else if (JSON.stringify(config.commandPolicy) !== JSON.stringify(rt.commandPolicy)) {
707
418
  setCommandPolicy = true;
708
419
  commandPolicyNext = rt.commandPolicy;
709
- infoLine.commandPolicy = rt.commandPolicy.length; // log the COUNT, not the rules (avoid leaking on every refresh)
420
+ infoLine.commandPolicy = rt.commandPolicy.length;
710
421
  }
711
422
  }
712
423
  else if (config.commandPolicy !== undefined) {
713
424
  setCommandPolicy = true;
714
- commandPolicyNext = undefined; // center stopped managing → revert to baseline (no env source)
425
+ commandPolicyNext = undefined;
715
426
  infoLine.commandPolicy = "(env-baseline)";
716
427
  }
717
428
  return { setAutonomy, autonomyNext, setCommandPolicy, commandPolicyNext, infoLine };
718
429
  }
719
430
  export function applyRuntimeHot(config, rt, logger) {
720
- // 独立调用面的兼容壳(测试/外部)——applyEffective 不走这里(同 applyRuntimeGates 的注)。
721
431
  const s = stageRuntimeHot(config, rt, logger);
722
432
  if (s.setAutonomy)
723
433
  config.autonomy = s.autonomyNext;
@@ -726,22 +436,7 @@ export function applyRuntimeHot(config, rt, logger) {
726
436
  if (Object.keys(s.infoLine).length > 0)
727
437
  logger?.info("sema_registry_runtime_hot", s.infoLine);
728
438
  }
729
- /** [865]①/H3:activeTierGroup 档绑定当默认时的档梯,与 core 单一语义源逐字对齐(core roles.js:
730
- * `ROLE_TIER_DEFAULTS.default = "pro"` + `resolveTier` 从本档位置**只向低档**扫 DEFAULT_TIER_ORDER
731
- * [ultra,max,pro,flash,lite] ⇒ pro→flash→lite,绝不向上抓 max/ultra——引擎运行时对 "default" 角色就是
732
- * 这么解析的,applyEffective 的 default 若用别的梯子会与真路由再度撕裂,codex H3)。 */
733
439
  const DEFAULT_ROLE_TIER_LADDER = ["pro", "flash", "lite"];
734
- /**
735
- * [865]① 显式默认解析——applyEffective 与 dry-run(logEffectiveDiff.wouldDefaultModel)共用的单源。优先级
736
- * (显式源指向不在目录/未启用的名 ⇒ onDangling 回调后落下一级,绝不静默指错模型):
737
- * 1) eff.models.default 结构键 —— ✅ 既得路径([874] registry-core 0.10.11 落 `default?: string` schema
738
- * 键+per-worker /effective 直通;本地 lane 由 config-provider mapToServiceEffective 直通)。写面悬空 ref
739
- * 由 registry-core superRefine fail-loud;存量/未经 schema 路径仍走本函数的 warn 降级(双层姿势与
740
- * activeTierGroup 同构)。(1.200-1.202 期间此级恒空=当时如实的前行 seam,codex H2 注记已兑现。)
741
- * 2) roles.default 的显式 {model} 绑定(现有 wire 真源)。
742
- * 3) activeTierGroup 档绑定,按 {@link DEFAULT_ROLE_TIER_LADDER}(= core 对 "default" 角色的档语义)。
743
- * 4) fallback(enabled[0]——原兜底,唯一不触发 onDangling 的落点)。
744
- */
745
440
  export function resolveDefaultModelName(eff, has, fallback, onDangling) {
746
441
  const activeTiers = resolveActiveTiers(eff.models) ?? {};
747
442
  const structural = eff.models?.default;
@@ -760,11 +455,6 @@ export function resolveDefaultModelName(eff, has, fallback, onDangling) {
760
455
  }
761
456
  return { name: fallback, source: "enabled[0]" };
762
457
  }
763
- /**
764
- * Read-only comparison (SEMA_REGISTRY_DRY_RUN): log what the center config WOULD change vs the current
765
- * (env-derived) config, WITHOUT applying it. The safe-rollout step the config-center recommends — verify
766
- * the center's models/roles/teams match (or intentionally differ from) the env baseline before going live.
767
- */
768
458
  export function logEffectiveDiff(config, eff, logger) {
769
459
  const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
770
460
  const centerModels = enabled.map((m) => ({ name: m.name, id: m.id, provider: m.provider, apiKeyEnv: m.apiKeyEnv ?? null, tier: m.tier ?? null }));
@@ -774,9 +464,7 @@ export function logEffectiveDiff(config, eff, logger) {
774
464
  logger?.warn("sema_registry_dry_run", {
775
465
  note: "DRY RUN — registry config NOT applied; unset SEMA_REGISTRY_DRY_RUN to go live",
776
466
  version: eff.version,
777
- wouldOverrideModels: enabled.length > 0, // empty/unpublished center → env baseline kept (no override)
778
- // [865]①/codex M2:与 applyEffective 同一只解析器——dry-run 的上线前核验必须报真 apply 会选的默认,
779
- // 报 enabled[0] 会让核验对显式默认(roles.default/tier 档)配置给出错误结论。
467
+ wouldOverrideModels: enabled.length > 0,
780
468
  wouldDefaultModel: enabled.length > 0 ? resolveDefaultModelName(eff, (n) => enabled.some((m) => m.name === n), enabled[0].name).name : null,
781
469
  currentDefaultModel: config.model.id,
782
470
  models: { center: centerModels, envDerived: envModels },
@@ -785,14 +473,11 @@ export function logEffectiveDiff(config, eff, logger) {
785
473
  skills: { centerWouldLoad: (eff.skills?.skills ?? []).filter((s) => s.enabled !== false).map((s) => s.name) },
786
474
  mcp: { centerWouldRegister: (eff.mcp?.servers ?? []).filter((s) => s.enabled !== false).map((s) => s.name) },
787
475
  runtime: {
788
- // What the gates would become if applied (present center value overrides; absent → kept env shown as current).
789
476
  center: eff.runtime ?? null,
790
477
  current: Object.fromEntries(RUNTIME_GATE_KEYS.map((k) => [k, config[k]])),
791
478
  },
792
479
  });
793
480
  }
794
- /** True if the center `runtime` carries any managed gate (a PRESENT field) — i.e. a refresh that changed it
795
- * needs a restart to take effect (restart-to-apply). Same key list + presence rule as applyRuntimeGates. */
796
481
  export function runtimeHasActiveGate(rt) {
797
482
  if (!rt)
798
483
  return false;