@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,15 +1,6 @@
1
- /**
2
- * Restart-to-apply CHANGE detection — the structured `RestartSignal` an orchestrator consumes (GET /health →
3
- * `restart`), computed against the process's BOOT config (not presence, else a refresh would re-fire the same
4
- * diff and loop-restart). Also owns the model-plane deferral predicates (`planeHasActiveTiers`/
5
- * `modelPlaneChanged`) main.ts uses to decide whether a hot-apply is safe under a tier-frozen Runner. Split out
6
- * of `facade.ts` (design/158 A13, internal-lossless — the facade re-exports every symbol below unchanged).
7
- */
8
1
  import { resolveActiveTiers } from "@sema-agent/registry-core";
9
2
  import { RUNTIME_GATE_KEYS, HOT_RUNTIME_GATE_KEYS, runtimeGatePresent, resolveDefaultModelName } from "./apply-effective.js";
10
3
  const RESTART_SLICES = ["skills", "mcp", "a2a", "scenarios", "runtime-gates", "models-tiers", "degrade-route", "read-face"];
11
- /** Canonical, key-sorted JSON (array order preserved) so two semantically-equal effective slices fingerprint
12
- * identically regardless of object key order from the center serializer. */
13
4
  function stableStringify(v) {
14
5
  if (v === null || typeof v !== "object")
15
6
  return JSON.stringify(v) ?? "null";
@@ -21,43 +12,15 @@ function stableStringify(v) {
21
12
  .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)
22
13
  .join(",")}}`;
23
14
  }
24
- /** The comparable value of one restart-to-apply slice (undefined config / unpublished slice → null, so
25
- * "center started/stopped publishing skills" is itself a detectable change). Skills/MCP/scenarios fingerprint
26
- * ONLY the ENABLED entries — the runtime consumers (applyCenterSkills / resolveMcpServers / centerScenarios) skip
27
- * `enabled:false`, so a disabled entry's content has zero runtime effect and must NOT trigger a restart.
28
- * Every enable/disable FLIP still changes the filtered set → still triggers, correctly. */
29
15
  function enabledOnly(rows) {
30
16
  if (!rows)
31
17
  return null;
32
18
  return rows.filter((r) => r.enabled !== false);
33
19
  }
34
- /** One catalog entry, reduced to the fields whose change is genuinely restart-to-apply. Consumers of a
35
- * boot-frozen catalog entry read almost every field on the FULLY-DERIVED Model (`toModel`) — id → body.model,
36
- * baseUrl → the endpoint, provider → which brain, maxTokens/compat/extraBody/input → the request body,
37
- * apiKeyEnv/sealedApiKey → the credential — so we fingerprint the WHOLE entry minus the three provably HOT
38
- * fields:
39
- * - `cost` — refreshed by `mutateInPlace(pricing, buildPricing(config.models))`, and core prefers
40
- * `deps.pricing[model.id]` over the (possibly frozen) `Model.cost`; `toModel` always emits
41
- * a cost object, so every center-lane model has a live pricing entry.
42
- * - `quotaWeight` — refreshed by `mutateInPlace(config.modelQuotaWeights, …)`, read live per burn.
43
- * - `enabled` — `enabledOnly` already filtered on it, so among the entries that reach here it is
44
- * `true`-or-absent: two encodings of ONE state. Keeping it would let a center serializer
45
- * that starts/stops emitting the explicit `true` manufacture a restart out of nothing.
46
- * Every real enable/disable FLIP changes the filtered SET, so it still signals.
47
- * A DENY-list (not an allow-list) on purpose: a catalog field added later rides the fingerprint automatically,
48
- * so the failure mode of drift is a spurious restart, never a silently missed one.
49
- *
50
- * Shared by BOTH catalog-entry fingerprints (`degrade-route`, `models-tiers` + `modelPlaneChanged`) since the
51
- * 复审 2026-07-29 留档: models-tiers had kept all three, so on a tiers-active deployment a pure re-pricing /
52
- * weight tweak / serializer writing `enabled:true` rolling-restarted the fleet. The two fingerprints must
53
- * subtract the SAME set — under active tiers `modelPlaneChanged` defers the plane and the refresh loop then
54
- * FORCE-pushes the `models-tiers` reason for a deferred candidate (boot/config-center.ts R15/R22), so
55
- * subtracting in only one of them is either no-op (restart still fires) or incoherent (applied yet signalled). */
56
20
  function catalogEntryFingerprint(entry) {
57
21
  const { cost: _cost, quotaWeight: _quotaWeight, enabled: _enabled, ...route } = entry;
58
22
  return route;
59
23
  }
60
- /** The enabled entries of a model plane, each reduced by `catalogEntryFingerprint` (null = no plane published). */
61
24
  function planeEntriesFingerprint(rows) {
62
25
  const enabled = enabledOnly(rows);
63
26
  return enabled ? enabled.map((m) => catalogEntryFingerprint(m)) : null;
@@ -71,9 +34,6 @@ function restartSliceValue(eff, slice, ctx) {
71
34
  case "mcp":
72
35
  return enabledOnly(eff.mcp?.servers);
73
36
  case "a2a":
74
- // 与 mcp 逐字同形:只指纹 ENABLED 条目(`resolveA2aPeers` 跳过 `enabled:false`,所以被禁条目的
75
- // 内容零运行期效果),整条记录进指纹(url/cardUrl/headerRefs/principalHeader/allowSkills/scenarios
76
- // 全是运行期相关字段;DENY-list 都不需要——本域没有 cost/quotaWeight 那种热字段)。
77
37
  return enabledOnly(eff.a2a?.peers);
78
38
  case "scenarios":
79
39
  return enabledOnly(eff.scenarios?.scenarios);
@@ -82,38 +42,21 @@ function restartSliceValue(eff, slice, ctx) {
82
42
  if (!rt)
83
43
  return null;
84
44
  const present = {};
85
- // #322 批1:限额五闸已转热(stage-limits 双腿写入)⇒ **不再进指纹**。留在里面的话,一次纯限额发布
86
- // 会让整片 fleet 为一个已经生效的变更滚动重启一遍——而重启后的姿态与重启前逐字相同(read-face 的
87
- // env-held 剔除同族论证)。剩下的 approvalRequire 仍是 boot-baked,归批2。
88
45
  for (const k of RUNTIME_GATE_KEYS)
89
46
  if (!HOT_RUNTIME_GATE_KEYS.has(k) && runtimeGatePresent(rt, k))
90
47
  present[k] = rt[k];
91
48
  return Object.keys(present).length ? present : null;
92
49
  }
93
50
  case "models-tiers": {
94
- // Fingerprint the models+tiers pair ONLY when this side resolves an active tier table (the Runner-copy
95
- // decoupling only exists under tiers). Tier-less side → null: both sides tier-less never signals (hot-apply
96
- // is real there); a tiers on/off FLIP is itself a detectable change (null vs object) — correctly signals,
97
- // because the flip only takes effect at Runner construction.
98
51
  const active = eff.models ? resolveActiveTiers(eff.models) : undefined;
99
52
  if (!active || Object.keys(active).length === 0)
100
53
  return null;
101
54
  const enabled = enabledOnly(eff.models?.models);
102
- // codex R8: the resolved DEFAULT rides the fingerprint too — the Runner's frozen copy carries its own
103
- // boot-time `default` entry, so a refresh that ONLY moves the default source (models.default /
104
- // roles.default / tier binding) re-points config.models.default while spec.model="default" keeps routing
105
- // the stale Runner entry. SAME resolver as applyEffective (resolveDefaultModelName, single source);
106
- // dangling candidates fall through silently here — applyEffective owns the warn.
107
55
  const names = new Set((enabled ?? []).map((m) => m.name));
108
56
  const def = resolveDefaultModelName(eff, (n) => names.has(n), enabled?.[0]?.name ?? "");
109
- // Entries reduced by the shared deny-list (cost/quotaWeight/enabled are hot — see catalogEntryFingerprint).
110
57
  return { models: planeEntriesFingerprint(eff.models?.models), tiers: active, default: def.name };
111
58
  }
112
59
  case "read-face": {
113
- // 指纹的是**真会 boot-bake 的那部分**:域里被本机 env 占住的成员剔除(codex finding 2 —— 旧实现
114
- // 指纹原始域,于是 center 改一个本机永远用不上的键也会让整片 fleet 滚动重启一遍,而重启后姿态
115
- // 逐字不变)。域缺席、或全部成员都被 env 占住 ⇒ null:两者对**本机**是同一件事(这条腿不存在),
116
- // 于是「组织开始/停止发布 READ 面」在真会生效的那些键上仍然可检测。
117
60
  const d = eff.readFace;
118
61
  if (!d)
119
62
  return null;
@@ -124,30 +67,18 @@ function restartSliceValue(eff, slice, ctx) {
124
67
  case "degrade-route": {
125
68
  const to = ctx?.reactiveDegradeTo;
126
69
  if (to === undefined)
127
- return null; // lane off — no degrading brain exists to go stale
128
- // Present↔absent is itself a change: with no enabled entry named `to`, createBrain composes NO
129
- // degrading shell at all, so the flip only takes effect at the next boot.
70
+ return null;
130
71
  const target = (enabledOnly(eff.models?.models) ?? []).find((m) => m.name === to);
131
72
  return target ? catalogEntryFingerprint(target) : null;
132
73
  }
133
74
  }
134
75
  }
135
- /** Restart-to-apply slices that DIFFER between this process's BOOT config and a freshly-pulled one. Empty =
136
- * nothing baked-at-boot changed (only hot-apply slices moved, or nothing) → no restart needed. An orchestrator
137
- * can auto rolling-restart on a non-empty result WITHOUT a restart loop, because the comparison is always
138
- * against boot (a refresh that re-fires the same diff is idempotent, not a fresh trigger). */
139
76
  export function restartReasons(boot, current, ctx) {
140
77
  return RESTART_SLICES.filter((s) => {
141
78
  const b = restartSliceValue(boot, s, ctx);
142
79
  const c = restartSliceValue(current, s, ctx);
143
80
  if (stableStringify(b) === stableStringify(c))
144
81
  return false;
145
- // #324:撤销台账腿在场时,mcp 片的**纯删除向**不签 —— 删除已被台账即时承接(core 每次 dispatch 前
146
- // 探 isRevoked),签了就是为一个已生效的变更滚动重启 fleet。判据=**保序严格子序列**(codex F3,
147
- // confirmed:旧判据用 Set 成员测,[A,B]→[B,A] 纯重排两侧指纹不等、每条又都在 boot 集 ⇒ 被误豁免,
148
- // 而服务器顺序是 mount 语义〔工具串接序〕,重排只能 restart 生效;删除+重排混合同理——存留条目的
149
- // 相对顺序必须与 boot 逐字一致才是「纯删除」)。任何新增/改形/重排照签;域整体出现/消失(null↔数组)
150
- // 不是删除向,保守照签。ctx 键缺席 = 席没接线 ⇒ 旧语义照签(fail-closed,#157)。
151
82
  if (s === "mcp" && ctx?.mcpRevocationLive === true && Array.isArray(b) && Array.isArray(c) && c.length < b.length) {
152
83
  const bootFps = b.map(stableStringify);
153
84
  let bi = 0;
@@ -163,18 +94,6 @@ export function restartReasons(boot, current, ctx) {
163
94
  return true;
164
95
  });
165
96
  }
166
- /** codex R10 (models-tiers 窗收口): TRUE when the MODEL PLANE (enabled models + active tier table + resolved
167
- * default) of `next` differs from the last-APPLIED plane `prev`. Under a tier-frozen Runner (tiers non-empty at
168
- * construction → private expanded copy) main.ts DEFERS the whole plane mutation until restart when this is true —
169
- * admission (/v1/models, the catalog gates) and the Runner then stay on ONE generation through the restart
170
- * window, so a same-key retarget can never silently route/bill the stale Model object. `prev === undefined`
171
- * (env-boot plane, no EffectiveConfig to compare) is conservatively "changed" — one deferred adoption + restart
172
- * converges. Deliberately NOT gated on tier activity of either side (unlike the restart slice): a frozen Runner
173
- * goes stale on ANY plane change, including a tiers-off flip. */
174
- /** codex R12: TRUE when this candidate resolves an ACTIVE tier table. Used by main.ts's defer predicate — a
175
- * tier-less-booted Runner never re-runs expandTiers, so hot-applying an ARRIVING tier table splits admission
176
- * (tier words pass the expanded gate) from execution (core throws "Unknown model ref"). Hot-apply of the model
177
- * plane is safe only when BOTH the constructed generation and the candidate are tier-less. */
178
97
  export function planeHasActiveTiers(eff) {
179
98
  const active = eff.models ? resolveActiveTiers(eff.models) : undefined;
180
99
  return active !== undefined && Object.keys(active).length > 0;
@@ -187,9 +106,6 @@ export function modelPlaneChanged(prev, next) {
187
106
  const active = e.models ? (resolveActiveTiers(e.models) ?? null) : null;
188
107
  const names = new Set((enabled ?? []).map((m) => m.name));
189
108
  const def = resolveDefaultModelName(e, (n) => names.has(n), enabled?.[0]?.name ?? "");
190
- // Same reduction as the models-tiers slice — the two MUST subtract the same hot fields (see
191
- // catalogEntryFingerprint): a deferred candidate force-pushes that slice's reason, so any divergence
192
- // either re-manufactures the restart we just subtracted or signals a plane that was applied hot.
193
109
  return stableStringify({ models: planeEntriesFingerprint(e.models?.models), tiers: active, default: def.name });
194
110
  };
195
111
  return fp(prev) !== fp(next);
@@ -28,7 +28,7 @@ export declare function resolveMcpServers(mcp: {
28
28
  servers: CenterMcpServer[];
29
29
  }, logger?: Logger): ScopedMcpServer[];
30
30
  /** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
31
- * #273 alias 窗:标签写**旧场景名**的条目同样命中(`scenarioConfigKeys`,同 `skillsForScenario`)。 */
31
+ * #342:alias 窗撤销,单键命中( `skillsForScenario`)。 */
32
32
  export declare function mcpForScenario(servers: ScopedMcpServer[] | undefined, scenario: string): McpServerSpec[] | undefined;
33
33
  /**
34
34
  * DESIGN-269 §2.1 —— resolve center A2A peers (`/effective.a2a`) into core `A2aServerSpec`s + the scenarios
@@ -1,30 +1,7 @@
1
- /**
2
- * Small consumption lanes that all project a center manifest domain onto a per-request runtime shape:
3
- * skills application (`applyCenterSkills` — merge center skills OVER the image baseline, content-addressed
4
- * fetch+verify+cache), MCP server resolution (`resolveMcpServers`/`mcpForScenario` — env-NAME ref
5
- * resolution + per-scenario filtering) and — since DESIGN-269 车1 — A2A peer resolution
6
- * (`resolveA2aPeers`/`a2aForScenario`, the same two rules on the agent-to-agent lane; it lives HERE rather
7
- * than in a new file because it shares `resolveRefs`, and a secret-boundary rule with two implementations
8
- * is a rule with one that will drift). Split out of `facade.ts` (design/158 A13, internal-lossless —
9
- * the facade re-exports every symbol below unchanged).
10
- */
11
1
  import { skillContentHash } from "@sema-agent/registry-core";
12
- import { scenarioConfigKeys } from "../capabilities/scenario-alias.js";
13
2
  import { fetchSkillContent } from "./http-client.js";
14
3
  import { SHA256_HEX_RE } from "../digest-form.js";
15
- /**
16
- * Merge center skills OVER the image baseline (design/41-sibling B1; `sema-registry docs/MCP-SKILLS.md`). Load order =
17
- * `loadSkills(SKILLS_DIR)` baseline → center skills lazily fetched by hash and overlaid BY NAME (center
18
- * wins on a clash → can override a built-in; guarded center-side by publisher RBAC + the publish gate).
19
- * `enabled:false` is skipped. Content is verified + cached by hash (same hash fetched once). A skill whose
20
- * content fetch/verify fails is skipped (the baseline version, if any, stays) — never blocks startup.
21
- * Restart-to-apply: this runs at boot, before `buildScenarios` bakes skills into scenarios.
22
- */
23
- export async function applyCenterSkills(baseline, manifest, baseUrl, token, logger, fetchImpl = fetch,
24
- /** LKG 姊妹面(clay 拍 2026-07-18):skill 正文的 content-addressed 盘缓存目录。读序=内存→盘(验
25
- * hash,坏文件忽略)→中心(成功后 best-effort 回写)。让 LKG-boot 在中心不可达时不再于正文拉取上
26
- * 挂数秒(live 实测黑洞 +5.3s/skill 批),顺带省正常 refresh 的重复拉取。undefined=无盘缓存(原行为)。 */
27
- diskCacheDir) {
4
+ export async function applyCenterSkills(baseline, manifest, baseUrl, token, logger, fetchImpl = fetch, diskCacheDir) {
28
5
  const { promises: fsp } = await import("node:fs");
29
6
  const { join: joinPath } = await import("node:path");
30
7
  const diskRead = async (hash) => {
@@ -32,21 +9,17 @@ diskCacheDir) {
32
9
  return undefined;
33
10
  const hex = hash.replace(/^sha256:/, "");
34
11
  if (!SHA256_HEX_RE.test(hex))
35
- return undefined; // hash 形不对=不碰盘(路径安全)
12
+ return undefined;
36
13
  try {
37
14
  const text = await fsp.readFile(joinPath(diskCacheDir, hex), "utf8");
38
15
  if (skillContentHash(text) === hash)
39
- return text; // content-addressed:必验,坏文件=undefined 走中心
16
+ return text;
40
17
  logger?.warn("sema_registry_skill_cache_corrupt", { hash, note: "on-disk body fails its own hash — ignored, refetching from center" });
41
18
  }
42
19
  catch {
43
- /* absent/unreadable → center */
44
20
  }
45
21
  return undefined;
46
22
  };
47
- // F5(LKG 复审三轮):写盘 promises 收集、函数返回前 await——fire-and-forget 会让 persistLkgDurable
48
- // 的「先落正文再发信号」序失效(await 只等到 fetch,rename 还在飞,窗口内重启=离线 boot 缺 skill)。
49
- // 单写失败仍 warn 软降级(收集的是 catch 过的 promise,Promise.all 不会炸)。
50
23
  const pendingWrites = [];
51
24
  const diskWrite = (hash, content) => {
52
25
  if (!diskCacheDir)
@@ -60,8 +33,8 @@ diskCacheDir) {
60
33
  })().catch((err) => logger?.warn("sema_registry_skill_cache_write_failed", { hash, err: String(err) })));
61
34
  };
62
35
  const byName = new Map(baseline.map((s) => [s.spec.name, s]));
63
- const contentCache = new Map(); // content-addressed: same hash fetched once
64
- const seenNames = new Set(); // duplicate names WITHIN the center manifest collapse last-wins — warn so a bad publish is diagnosable
36
+ const contentCache = new Map();
37
+ const seenNames = new Set();
65
38
  for (const m of manifest.skills ?? []) {
66
39
  if (m.enabled === false)
67
40
  continue;
@@ -74,7 +47,7 @@ diskCacheDir) {
74
47
  content = await diskRead(m.contentHash);
75
48
  if (content === undefined) {
76
49
  content = await fetchSkillContent(baseUrl, token, m.contentHash, fetchImpl);
77
- diskWrite(m.contentHash, content); // best-effort 回写(content-addressed,幂等)
50
+ diskWrite(m.contentHash, content);
78
51
  }
79
52
  contentCache.set(m.contentHash, content);
80
53
  const overrodeBuiltin = byName.has(m.name);
@@ -85,16 +58,9 @@ diskCacheDir) {
85
58
  logger?.warn("sema_registry_skill_failed", { name: m.name, hash: m.contentHash, err: String(err), note: "keeping baseline if any; skipping center version" });
86
59
  }
87
60
  }
88
- await Promise.all(pendingWrites); // F5:返回=正文已 durable(或已 warn),调用方的落盘序才成立
61
+ await Promise.all(pendingWrites);
89
62
  return [...byName.values()];
90
63
  }
91
- /** Resolve env-NAME refs (target-key → ENV_NAME) to real values from THIS service's env. Returns null when
92
- * ANY referenced env var is unset → the caller skips the whole server (fail-safe: never wire a half-credentialed
93
- * endpoint). Mirrors the missing-apiKeyEnv handling for models (the secret boundary is unchanged).
94
- *
95
- * `event`/`skipNote` are per-lane so an operator reading logs sees WHICH lane dropped an entry (MCP server vs
96
- * A2A peer) — the resolution rule itself is one function on purpose (two copies of a secret-boundary rule is
97
- * how one of them drifts open). */
98
64
  function resolveRefs(refs, server, kind, logger, event = "sema_registry_mcp_env_missing", skipNote = "skipping this MCP server — referenced env var is unset in this service") {
99
65
  if (!refs)
100
66
  return {};
@@ -109,20 +75,12 @@ function resolveRefs(refs, server, kind, logger, event = "sema_registry_mcp_env_
109
75
  }
110
76
  return out;
111
77
  }
112
- /**
113
- * Resolve center MCP servers (`/effective.mcp`) into core `McpServerSpec`s + the scenarios each applies to.
114
- * env-NAME refs → real values from THIS service's env (secret boundary unchanged); a server with any missing
115
- * ref is skipped (`resolveRefs` → null). `enabled:false` skipped. Restart-to-apply (filtered per scenario in
116
- * `resolveSpec` via `mcpForScenario`).
117
- */
118
78
  export function resolveMcpServers(mcp, logger) {
119
79
  const out = [];
120
80
  for (const s of mcp.servers ?? []) {
121
81
  if (s.enabled === false)
122
82
  continue;
123
83
  const allow = s.allowTools && s.allowTools.length > 0 ? { allowTools: s.allowTools } : {};
124
- // E23 (shell-host contract): carry the per-server inbound-elicitation opt-in onto McpServerSpec.elicitation. Only when
125
- // explicitly true (default OFF / fail-closed) — core advertises the capability only when this AND onElicit hold.
126
84
  const elicit = s.elicitation === true ? { elicitation: true } : {};
127
85
  if (s.transport.kind === "stdio") {
128
86
  const env = resolveRefs(s.transport.envRefs, s.name, "env", logger);
@@ -147,32 +105,12 @@ export function resolveMcpServers(mcp, logger) {
147
105
  logger?.info("sema_registry_mcp", { servers: out.map((s) => s.spec.name) });
148
106
  return out;
149
107
  }
150
- /** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
151
- * #273 alias 窗:标签写**旧场景名**的条目同样命中(`scenarioConfigKeys`,同 `skillsForScenario`)。 */
152
108
  export function mcpForScenario(servers, scenario) {
153
109
  if (!servers || servers.length === 0)
154
110
  return undefined;
155
- const keys = scenarioConfigKeys(scenario);
156
- const hit = servers.filter((s) => s.scenarios.length === 0 || keys.some((n) => s.scenarios.includes(n))).map((s) => s.spec);
111
+ const hit = servers.filter((s) => s.scenarios.length === 0 || s.scenarios.includes(scenario)).map((s) => s.spec);
157
112
  return hit.length > 0 ? hit : undefined;
158
113
  }
159
- /**
160
- * DESIGN-269 §2.1 —— resolve center A2A peers (`/effective.a2a`) into core `A2aServerSpec`s + the scenarios
161
- * each applies to. Exact sibling of {@link resolveMcpServers}, same three rules:
162
- * · header-NAME refs → real values from THIS service's env (the secret boundary is unchanged — the center
163
- * never holds the bearer). A peer with ANY missing ref is SKIPPED wholesale (`resolveRefs` → null):
164
- * dialing a remote AGENT with half the credentials is the wrong failure direction.
165
- * · `enabled:false` skipped.
166
- * · restart-to-apply (filtered per scenario in `resolveSpec` via {@link a2aForScenario}).
167
- *
168
- * ⚠️ **No `toolAxes` on the center face, deliberately** (this is where the A2A lane STOPS mirroring MCP):
169
- * core's ruling is that a caller override is the ONLY trusted way down from the fail-closed
170
- * `egress:true`+`effect:"write"` default, and it names the CALLER as that trust root. An operator lowering
171
- * an axis in center config would be vouching, on behalf of every future task, that a REMOTE agent's skill
172
- * only reads — a claim the operator cannot actually verify and which core's own doc-comment reserves for
173
- * the caller. Absent face > a face that quietly launders trust. (`allowSkills` is the operator's real
174
- * knob here: narrow WHAT mounts, never re-label what it does.)
175
- */
176
114
  export function resolveA2aPeers(a2a, logger) {
177
115
  const out = [];
178
116
  for (const p of a2a.peers ?? []) {
@@ -197,15 +135,10 @@ export function resolveA2aPeers(a2a, logger) {
197
135
  logger?.info("sema_registry_a2a", { peers: out.map((p) => p.spec.name) });
198
136
  return out;
199
137
  }
200
- /** The A2A peers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
201
- * Twin of {@link mcpForScenario} — `undefined` (not `[]`) is load-bearing: an empty array would put an
202
- * `a2a: []` key on the spec, i.e. "this deployment declares an empty peer set", where absence means
203
- * "this leg does not exist here". */
204
138
  export function a2aForScenario(peers, scenario) {
205
139
  if (!peers || peers.length === 0)
206
140
  return undefined;
207
- const keys = scenarioConfigKeys(scenario); // #273 alias 窗:旧场景名标签同样命中( mcpForScenario)
208
- const hit = peers.filter((p) => p.scenarios.length === 0 || keys.some((n) => p.scenarios.includes(n))).map((p) => p.spec);
141
+ const hit = peers.filter((p) => p.scenarios.length === 0 || p.scenarios.includes(scenario)).map((p) => p.spec);
209
142
  return hit.length > 0 ? hit : undefined;
210
143
  }
211
144
  //# sourceMappingURL=skills-mcp.js.map
@@ -5,18 +5,12 @@ const INFRA_RATE_KEYS = ["toolCallMicroUsd", "sandboxSecMicroUsd", "egressGbMicr
5
5
  const okNum = (v) => typeof v === "number" && Number.isFinite(v);
6
6
  const atLeast = (key, v, min) => okNum(v) && v >= min ? { ok: true, value: v } : { ok: false, reason: `${key} must be a finite number >= ${min}` };
7
7
  const positive = (key, v) => okNum(v) && v > 0 ? { ok: true, value: v } : { ok: false, reason: `${key} must be a finite number > 0` };
8
- /** 逐键判据。**坏值一律拒整批**(不夹取、不丢弃)——见文件头注纪律 3。 */
9
8
  const VALIDATORS = {
10
- rateLimitPerMin: (v) => atLeast("rateLimitPerMin", v, 0), // 0 = 关断哨兵
11
- maxPrincipalCostUsd: (v) => atLeast("maxPrincipalCostUsd", v, 0), // 0 = 无配额
12
- costQuotaWindowSec: (v) => positive("costQuotaWindowSec", v), // core 拿它做除数
9
+ rateLimitPerMin: (v) => atLeast("rateLimitPerMin", v, 0),
10
+ maxPrincipalCostUsd: (v) => atLeast("maxPrincipalCostUsd", v, 0),
11
+ costQuotaWindowSec: (v) => positive("costQuotaWindowSec", v),
13
12
  maxTaskCostUsd: (v) => atLeast("maxTaskCostUsd", v, 0),
14
13
  maxTaskTokens: (v) => atLeast("maxTaskTokens", v, 0),
15
- // 🔴 codex R1-F2(真 finding,红先钉在 batch1-codex-r1.test.ts):**部分发布必须按基线补齐**。
16
- // 修前这里从**空对象**重建,于是只发 `toolCallMicroUsd` 的一次发布会把 sandbox/egress 费率变成
17
- // undefined —— 而消费端(routes/runs.ts 的 supervisorCost)把它们当 0 算,结果是**静默漏计费**:
18
- // 账面看起来正常,只是少了两条轴。同一条 finding 的另一半:未知成员(拼错)修前被**忽略**,
19
- // 极端情形下提交一个空费率对象;现在拼错=整批响亮拒(一个拼错的费率名等于「这条轴没在计费」)。
20
14
  infraCostRates: (v, baseline) => {
21
15
  if (v === null || typeof v !== "object" || Array.isArray(v))
22
16
  return { ok: false, reason: "infraCostRates must be an object of non-negative micro-USD rates" };
@@ -29,7 +23,7 @@ const VALIDATORS = {
29
23
  for (const k of INFRA_RATE_KEYS) {
30
24
  const raw = src[k];
31
25
  if (raw === undefined) {
32
- out[k] = base[k] ?? 0; // 缺席成员 = 本域不管这一条轴 ⇒ 回 env 基线(非粘),**不是**清零
26
+ out[k] = base[k] ?? 0;
33
27
  continue;
34
28
  }
35
29
  if (!okNum(raw) || raw < 0)
@@ -38,7 +32,7 @@ const VALIDATORS = {
38
32
  }
39
33
  return { ok: true, value: out };
40
34
  },
41
- attachmentOrphanGraceMs: (v) => atLeast("attachmentOrphanGraceMs", v, 0), // 0 = 关掉孤儿清扫腿
35
+ attachmentOrphanGraceMs: (v) => atLeast("attachmentOrphanGraceMs", v, 0),
42
36
  workspaceFileMaxBytes: (v) => positive("workspaceFileMaxBytes", v),
43
37
  attachmentMaxBytes: (v) => atLeast("attachmentMaxBytes", v, LIMITS_HTTP_FLOORS.attachmentMaxBytes),
44
38
  attachmentMimeAllowlist: (v) => {
@@ -51,10 +45,9 @@ const VALIDATORS = {
51
45
  },
52
46
  attachmentUnboundTtlMs: (v) => atLeast("attachmentUnboundTtlMs", v, LIMITS_HTTP_FLOORS.attachmentUnboundTtlMs),
53
47
  drainGraceMs: (v) => atLeast("drainGraceMs", v, LIMITS_HTTP_FLOORS.drainGraceMs),
54
- runStaleSec: (v) => positive("runStaleSec", v), // 与心跳的关系由跨键不变量表判(单源)
55
- toolResultTtlSec: (v) => positive("toolResultTtlSec", v), // 0 ⇒ cutoff=now ⇒ 一拍收光所有卸载结果
48
+ runStaleSec: (v) => positive("runStaleSec", v),
49
+ toolResultTtlSec: (v) => positive("toolResultTtlSec", v),
56
50
  };
57
- /** env 派生基线(**首次** stage 时快照;boot 序 = loadConfig → applyEffective,此刻 config 尚是纯 env)。 */
58
51
  const limitsEnvBaseline = new WeakMap();
59
52
  function baselineOf(config) {
60
53
  const known = limitsEnvBaseline.get(config);
@@ -67,21 +60,13 @@ function baselineOf(config) {
67
60
  return snap;
68
61
  }
69
62
  const same = (a, b) => (typeof a === "object" || typeof b === "object" ? JSON.stringify(a ?? null) === JSON.stringify(b ?? null) : a === b);
70
- /** 候选视图 = 现役 + 本批 staged 赋值(跨键不变量判的就是这个「如果落地会怎样」)。 */
71
63
  function candidateView(config, assignments) {
72
64
  const view = { ...config };
73
- // 写口窄化到「本批键 → unknown」的可选记录:值的真类型已在 VALIDATORS 里逐键判过,这里只是把判过的值
74
- // 放回同名槽位(窄 `as`,不是 `as unknown as` —— ServiceConfig 本就可赋给这个形)。
75
65
  const writable = view;
76
66
  for (const [k, v] of assignments)
77
67
  writable[k] = v;
78
68
  return view;
79
69
  }
80
- /**
81
- * STAGE:算出本世代 limitsHttp 组要 COMMIT 的全部赋值(纯计算,零变异)。
82
- *
83
- * 返回 `rejected` 时 `assignments`/`deletions` 恒为空 —— 整批拒的物理保证在返回值形上,不靠调用方自律。
84
- */
85
70
  export function stageLimits(config, eff, logger) {
86
71
  const baseline = baselineOf(config);
87
72
  const legacy = eff.legacyGates ?? {};
@@ -91,10 +76,6 @@ export function stageLimits(config, eff, logger) {
91
76
  const deferred = [];
92
77
  const badKeys = [];
93
78
  let reason;
94
- // 🔴 codex R1-F1(真 finding,红先):`limits` 是 **open-world wire 数据**(无 schema 保证),所以
95
- // 容器本身必须先判形。修前只对它做可选索引 ⇒ `"bad"` / 数组 / null 让**每个键都看起来缺席** ⇒ 全体
96
- // 回 env 底,而且该世代被当作合法提交:中心刚把 rate/cost 门从 0 打开的部署上,一次畸形发布就把保护
97
- // 一声不响地关回去了。方向与本域其余判据一致 —— 读不懂就更严不更松(整批拒,旧值继续服务)。
98
79
  const rawLimits = eff.limits;
99
80
  if (rawLimits !== undefined && (rawLimits === null || typeof rawLimits !== "object" || Array.isArray(rawLimits))) {
100
81
  const rejected = { keys: [...BATCH1_LIMIT_KEYS], reason: `the published limits domain must be a JSON object — got ${rawLimits === null ? "null" : Array.isArray(rawLimits) ? "array" : typeof rawLimits}` };
@@ -106,9 +87,6 @@ export function stageLimits(config, eff, logger) {
106
87
  return { assignments: [], deletions: [], changed: [], deferred: [], rejected };
107
88
  }
108
89
  const limits = rawLimits;
109
- // LANDING-7.38 接线件④:limits 域是 open-world(registry-core 0.19.0 承运未知键并点名),但**键表
110
- // 属主在这里**——发布了、本批却不消费的键(拼错/超前批次)至少要 warn,否则「registry-core 点了名、
111
- // 部署里没人听」= 配置写了不生效的静默形。DEFERRED_LIMIT_KEYS(如 costQuotaWindowSec)不算未知。
112
90
  if (limits !== undefined) {
113
91
  const known = new Set([...BATCH1_LIMIT_KEYS, ...DEFERRED_LIMIT_KEYS.map((d) => d.key)]);
114
92
  const unconsumed = Object.keys(limits).filter((k) => !known.has(k));
@@ -121,12 +99,10 @@ export function stageLimits(config, eff, logger) {
121
99
  }
122
100
  }
123
101
  for (const key of BATCH1_LIMIT_KEYS) {
124
- // 🔴 用**存在性**判新位优先级,不用 `??`(codex R1-F1 的另一半):`{rateLimitPerMin: null}` 在 `??`
125
- // 下会当成「缺席」而回底/落旧位,一个显式的坏值就这样变成了静默撤回。存在即判 ⇒ null 走坏值拒。
126
102
  const published = limits !== undefined && Object.prototype.hasOwnProperty.call(limits, key) ? limits[key] : legacy[key];
127
103
  let next;
128
104
  if (published === undefined) {
129
- next = baseline[key]; // 非粘:center 不管这一键 ⇒ 回 env 底
105
+ next = baseline[key];
130
106
  }
131
107
  else {
132
108
  const verdict = VALIDATORS[key](published, baseline[key]);
@@ -140,8 +116,6 @@ export function stageLimits(config, eff, logger) {
140
116
  const current = config[key];
141
117
  if (same(current, next))
142
118
  continue;
143
- // 🔴 codex R2-F2:**显式 deferred 的键不落地** —— 值已验过(坏值照样整批拒),但它的变更只能重启生效,
144
- // 所以这里逐键 defer(不是整批拒:同批的热键该生效还得生效)+ 铸因进世代账 + 一条响亮 warn。
145
119
  const deferral = DEFERRED_LIMIT_KEYS.find((d) => d.key === key);
146
120
  if (deferral) {
147
121
  deferred.push({ key, reason: deferral.restartReason });
@@ -162,7 +136,6 @@ export function stageLimits(config, eff, logger) {
162
136
  });
163
137
  return { assignments: [], deletions: [], changed: [], deferred: [], rejected };
164
138
  }
165
- // 跨键不变量:本批任一键触发**全表**校验(design/322 §2 A′,codex F2)。
166
139
  const verdict = checkCrossInvariants({ live: config, next: candidateView(config, assignments) });
167
140
  if (!verdict.ok) {
168
141
  const involved = changed.length > 0 ? changed : BATCH1_LIMIT_KEYS.slice();
@@ -1,15 +1,7 @@
1
1
  import { assertGuardPatternsUsable } from "./deployment-governance.js";
2
- /**
3
- * 活体心跳周期(ms)——`runs.ts` 的 `HEARTBEAT_MS` 就是它(那边 re-export,单源在此)。
4
- *
5
- * 为什么常量住在不变量文件里:它唯一的**判据用途**是「陈旧窗必须大于心跳」这条不变量,而该不变量现在
6
- * 有 boot 与 stage 两个消费面。放在这里,两面读的是同一个数,`runs.ts` 的心跳环也读同一个数。
7
- */
8
2
  export const LIVENESS_HEARTBEAT_MS = 30_000;
9
3
  const anyServiceAuth = (v) => Boolean(v.authToken) || Object.keys(v.authTokens ?? {}).length > 0;
10
- /** 与 `resolveBindHost` 逐字同判:无鉴权写面 + 零凭据 ⇒ boot 期把监听地址收窄到 loopback。 */
11
4
  const mustNarrowBind = (v) => Boolean(v.allowUnauthedWrites) && !anyServiceAuth(v);
12
- /** 直连门的合取项(config.ts:2788 的同一组);任一项翻转 = 门姿态变更。 */
13
5
  const directDoorConjuncts = (v) => Boolean(v.directApprovalDoor) && Boolean(v.durableApproval) && v.approvalHmacKeys.length > 0 && v.principalJwtPubkeys.length > 0 && Boolean(v.principalJwtIss) && Boolean(v.principalJwtAud);
14
6
  export const CONFIG_CROSS_INVARIANTS = [
15
7
  {
@@ -22,9 +14,6 @@ export const CONFIG_CROSS_INVARIANTS = [
22
14
  : undefined,
23
15
  },
24
16
  {
25
- // 转换型:boot 期算一次的绑址收窄(resolveBindHost → server.listen)在运行期无法重算,所以任何
26
- // **让收窄条件从不成立变成成立**的热切都是谎:写门确实收紧了,监听面却还在原来的接口上。
27
- // 反向(窄→宽,例如加了凭据)不拒:那是收紧姿态,已绑的 loopback 只会更严。
28
17
  id: "bind-posture",
29
18
  keys: ["authToken", "authTokens", "allowUnauthedWrites"],
30
19
  restartReason: "bind-posture",
@@ -42,8 +31,6 @@ export const CONFIG_CROSS_INVARIANTS = [
42
31
  : undefined,
43
32
  },
44
33
  {
45
- // 状态 + 转换两面都判:半开的直连门(boot 会拒启的那些组合)绝不许经热路径造出来;门**姿态**
46
- // (激活/未激活)的翻转同样只许重启 —— 消费面(/health directDoorActive、鉴权腿)在 boot 期定形。
47
34
  id: "direct-door-anchors",
48
35
  keys: ["directApprovalDoor", "durableApproval", "approvalHmacKeys", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud", "requirePrincipal", "operatorPrincipals"],
49
36
  restartReason: "direct-door",
@@ -83,8 +70,6 @@ export const CONFIG_CROSS_INVARIANTS = [
83
70
  : undefined,
84
71
  },
85
72
  {
86
- // bake 门是 build-host-RCE 面,空 operator 名单让 isOperator 变 true-for-all = world-writable RCE。
87
- // 与上一条同族但**判据不同**(那条判多租户 durable 审批的读面),故各自登记、各自锚。
88
73
  id: "bake-operator-principals",
89
74
  keys: ["imageBakes", "operatorPrincipals"],
90
75
  restartReason: "operator-principals",
@@ -127,7 +112,6 @@ export const CONFIG_CROSS_INVARIANTS = [
127
112
  : undefined,
128
113
  },
129
114
  ];
130
- /** 全表校验(**第一条不过即拒**:候选是整批的,报第一个成因足够行动,继续跑其余条目只会淹掉它)。 */
131
115
  export function checkCrossInvariants(input) {
132
116
  for (const inv of CONFIG_CROSS_INVARIANTS) {
133
117
  const reason = inv.check(input);
@@ -1,44 +1,16 @@
1
- /**
2
- * LKG 落盘(clay 拍,2026-07-18)——干净配置候选的本机持久化,boot 的第三级来源:
3
- * 中心(预算窗内)> 盘上 LKG > env 兜底。
4
- *
5
- * 为什么存在(1.222.0 发版复审的残余环,clay 重裁量证据):boot 抢答窗让慢中心不再阻塞启动,但
6
- * skills/mcp/scenarios/runtime-gates 四个 restart-to-apply 面是 boot 烤进进程的——配置迟到只能发
7
- * restartRequired 信号。center 发布了这些面 + 中心**持续**慢于预算 + orchestrator 自动重启 ⇒ 每个
8
- * 新进程都为真缺失的面发一次信号 = 反复重启环。盘上 LKG 让下一进程 boot 直读上一进程验证过的干净
9
- * 候选(全部面进 buildScenarios),到货后与之比较通常零差异 ⇒ 无信号无环;中心恢复自然收敛。
10
- * 白拿的第二收益:重启秒起且配置齐全(不再有「起服后几秒 roster pending」窗口)。
11
- *
12
- * 安全边界:config plane 恒只携带 env-NAME 引用(apiKeyEnv/tokenEnv/…),真实秘密从不入 effective
13
- * ——落盘物按同一纪律无秘密;文件 0600 + 原子写(tmp+rename)。**只存干净候选**(候选门通过的:
14
- * boot clean / refresh LKG advance / deferred clean),所以读侧只需防「文件坏形/被改」:JSON+信封
15
- * 判形+prompts 面复判(validatePromptsDomain,与候选门同函数),任何不对 ⇒ 忽略文件走 env(warn
16
- * 点名,绝不让坏缓存挡启动)。
17
- *
18
- * 范围:**remote lane only**——local lane 的 FileConfigStore 本来就是盘上即时读,无此需要。
19
- * 文件按 worker 键控(`config-lkg-<worker|global>.json`),同宿主多 worker 不互踩。
20
- */
21
1
  import { promises as fs } from "node:fs";
22
2
  import { homedir } from "node:os";
23
3
  import { join, dirname } from "node:path";
24
4
  import { randomBytes } from "node:crypto";
25
5
  import { validatePromptsDomain } from "./prompts-domain-validate.js";
26
- /** skill 正文的 content-addressed 盘缓存目录(LKG 的姊妹面):LKG 只含 skills MANIFEST(正文按
27
- * hash 另拉)——黑洞中心下 LKG-boot 会在 skill 正文拉取上再挂数秒(live 实测 +5.3s)。正文按
28
- * `<dir>/<sha-hex>` 落盘,读侧**必验 hash**(content-addressed 的全部意义;坏文件=忽略重拉)。 */
29
6
  export function defaultSkillCacheDir() {
30
7
  return join(process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema"), "skill-cache");
31
8
  }
32
- /** 默认路径(scheduler store 同门:SEMA_CONFIG_DIR → ~/.sema)。`CONFIG_LKG_PATH` 覆写;
33
- * `CONFIG_LKG_ENABLED=false` 关闭(读写双关)。🪦 正名前的 `CONFIG_LKG_DISABLED` 自 server 3.0.0 起是
34
- * **fail-loud 墓碑**——设了(任何值)即拒启并指路新名,不再被读取。 */
35
9
  export function defaultLkgPath(worker) {
36
10
  const dir = process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema");
37
11
  const key = (worker ?? "global").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64);
38
12
  return join(dir, `config-lkg-${key}.json`);
39
13
  }
40
- /** 原子落盘一个干净候选(tmp+rename,0600)。BEST-EFFORT:失败=一条 warn 的素材(调用方发),绝不
41
- * 影响在飞的应用链——LKG 缓存丢失的代价只是下次冷启动回到 env 兜底。 */
42
14
  export async function saveLkg(path, worker, effective, etag) {
43
15
  const body = { formatVersion: 1, savedAt: new Date().toISOString(), worker: worker ?? "global", ...(etag !== undefined ? { etag } : {}), effective };
44
16
  const tmp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
@@ -46,10 +18,6 @@ export async function saveLkg(path, worker, effective, etag) {
46
18
  await fs.writeFile(tmp, JSON.stringify(body), { mode: 0o600 });
47
19
  await fs.rename(tmp, path);
48
20
  }
49
- /**
50
- * 读盘上 LKG。返回 undefined 的所有情形都「安静地回 env 兜底」(调用方 warn 点名 reason):
51
- * 文件缺席/JSON 坏/信封形不对/worker 不匹配(防同名文件跨 worker 串包)/prompts 面复判失败。
52
- */
53
21
  export async function loadLkg(path, worker) {
54
22
  let text;
55
23
  try {
@@ -76,18 +44,9 @@ export async function loadLkg(path, worker) {
76
44
  return { reason: "bad effective" };
77
45
  if (p.etag !== undefined && typeof p.etag !== "string")
78
46
  return { reason: "bad etag" };
79
- // 浅域判形(LKG 复审 F4):applyEffective 是 in-place mutate——半程炸=混态。载荷信任面与 live 远端
80
- // 相同(config plane、本机文件=operator 权限),这里防的是**跨版本 schema 漂移/文件被改**的结构性坏形:
81
- // 各域存在时必须是对象、其主数组存在时必须是数组、version 必须是数;更深的语义仍归 apply 层
82
- // (live 载荷同待遇——LKG 不引入第二套 schema)。
83
47
  const eff = p.effective;
84
48
  if (typeof eff.version !== "number")
85
49
  return { reason: "bad effective.version" };
86
- // codex R24(真存量 bug):teams 的真形=信封 `{ teams: CenterTeam[] }`(EffectiveConfig 声明形,
87
- // applyEffective 消费 `eff.teams?.teams`)——旧判形要求裸数组,任何带 teams 的候选保存成功、下次 boot
88
- // 被拒("bad effective.teams")→ env 兜底 → 慢中心下 defer 再签,正是交棒门要消的环。teams 改与其他域
89
- // 同姿势:对象 + 主数组存在时必须是数组。(旧夹具 `teams: []` 曾遮住此形;裸数组从未被真实 save 产出,
90
- // 拒掉无害:unusable → env 兜底 → 下个干净候选重写。)
91
50
  for (const [domain, arrayKey] of [["models", "models"], ["teams", "teams"], ["skills", "skills"], ["mcp", "servers"], ["scenarios", "scenarios"], ["runtime", null]]) {
92
51
  const v = eff[domain];
93
52
  if (v === undefined)
@@ -100,7 +59,6 @@ export async function loadLkg(path, worker) {
100
59
  return { reason: `bad effective.${domain}.${arrayKey}` };
101
60
  }
102
61
  }
103
- // prompts 面复判(候选门同函数):坏 prompts 若放行,boot 采用后每个任务 prepare 都会炸。
104
62
  const promptsRaw = p.effective.prompts;
105
63
  if (promptsRaw !== undefined && !validatePromptsDomain(promptsRaw).ok)
106
64
  return { reason: "prompts face fails re-validation" };