@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
package/dist/main.js CHANGED
@@ -9,7 +9,7 @@ import { subagentSendUserFileExtraTools } from "./capabilities/send-user-file-to
9
9
  import { brainSummary } from "./brain.js";
10
10
  import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, numEnv } from "./config.js";
11
11
  import { cardRuleScopeRoot } from "./task-cwd.js";
12
- import { isPricingConfigured } from "./budget.js"; // 缝合审 M3:capabilities.pricingConfigured 的单一真源判据
12
+ import { isPricingConfigured } from "./budget.js";
13
13
  import { drainNumEnvWarnings } from "./plugins/remote-shell.js";
14
14
  import { ensureChildSessionDurableWithPromotion } from "./plugins/session-store.js";
15
15
  import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
@@ -45,13 +45,13 @@ import { createConfigCenterRuntime } from "./boot/config-center.js";
45
45
  import { createResolveSpec } from "./boot/resolve-spec.js";
46
46
  import { createParkedReviveInheritedGate } from "./boot/parked-revive-gate.js";
47
47
  import { startReapers } from "./boot/reapers.js";
48
- import { assertRetentionLaneWirable, startRetentionLane, RETENTION_LEASE_TTL_FACTOR } from "./boot/retention-lane.js"; // #270 车2
48
+ import { assertRetentionLaneWirable, startRetentionLane, RETENTION_LEASE_TTL_FACTOR } from "./boot/retention-lane.js";
49
49
  import { startFleetReconciler } from "./fleet/fleet-reconciler.js";
50
50
  import { openStores } from "./boot/stores.js";
51
51
  import { runAdoptionBootScan } from "./boot/adoption.js";
52
52
  import { auditDormantPermissionRules } from "./boot/permission-rules-audit.js";
53
53
  import { createBudgetAndTracing } from "./boot/budget-tracing.js";
54
- import { createLimitSync } from "./boot/limit-sync.js"; // #322 批1:限额座的换代口(恒构造 + 关断哨兵)
54
+ import { createLimitSync } from "./boot/limit-sync.js";
55
55
  import { createSideQueryLane } from "./boot/side-query-lane.js";
56
56
  import { createWebFetchSummarizeLane } from "./boot/webfetch-summarize-lane.js";
57
57
  import { createRuleConsentLane } from "./rules-consent.js";
@@ -67,8 +67,6 @@ import { createSessionFaces } from "./boot/session-faces.js";
67
67
  import { createLeaderFace } from "./boot/leader.js";
68
68
  import { assertStaticWiringConsistent } from "./http/routes/diagnostics.js";
69
69
  import { installShutdownHandlers } from "./boot/shutdown.js";
70
- /** Service version for GET /v1/capabilities — read from the package.json shipped next to src/ in the
71
- * image (Dockerfile COPYs it). Best-effort: an unreadable file reports "unknown", never crashes boot. */
72
70
  function serviceVersion() {
73
71
  try {
74
72
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
@@ -78,93 +76,30 @@ function serviceVersion() {
78
76
  return "unknown";
79
77
  }
80
78
  }
81
- /**
82
- * Service entrypoint. **装配次序表**(design/158 A10)。
83
- *
84
- * 各 boot 段已按内聚下沉到 `src/boot/*.ts` 叶子(纯搬运,逐字);本函数保留的是**顺序本身**——
85
- * 谁先谁后是行为(env 读取时序、诊断排空点、信号注册时点、工厂装饰顺序),每个叶子的文件头
86
- * 都写明了它那条「位置即契约」。当前次序:
87
- *
88
- * loadConfig → logger/metrics → 诊断排空(logConfigDiagnostics + drainNumEnvWarnings)
89
- * → boot/config-center(sealed-key/provider/prompt-epoch 店/effective 拉取/LKG 兜底/tolerant apply)
90
- * → boot/config-center.applyLocalRemoteExec(local 形 config.d/remote-exec.json 覆写)
91
- * → boot/stores(StoreBackend + memory + 四个 durable 店 + sessionStore + 拒启断言)
92
- * → boot/budget-tracing(brain/pricing/quota/tracer)
93
- * → boot/execution-env(remote-exec 工厂链 + LSP)
94
- * → toolTracer/deploymentHooks
95
- * → boot/workflow-orchestration → boot/coordinators → boot/runtime-caps
96
- * → boot/runner-deps → new Runner → hook LLM/agent → subRunner
97
- * → runStore/anchors/approval 面 → skills → boot/config-center.applyCenterCapabilities
98
- * (center skills/plugins/boot-LKG/mcp)→ buildScenarios → .applyCenterScenarios
99
- * → boot/reapers → OTLP → boot/config-center.startRefreshLoop → .initKeyResolver
100
- * → boot/session-faces → boot/leader → ServiceDeps 七组 → boot/resolve-spec
101
- * → createHttpServer → listen → fleet client → boot/shutdown(必须最后:信号注册时点即契约)
102
- *
103
- * A10 尾刀(config-center):`ccEtag/effective/latestEffective/appliedPlaneEff/planeDeferredNoHandoff/
104
- * pendingRestart/bootSkillStale/keyResolver/centerPrompts/…` 这组跨段可变状态,已整体搬进
105
- * `boot/config-center.ts` 那只工厂的**闭包**(仍是 `let`,每一处读写的文本与次序逐字未改 ——
106
- * codex R10–R38 的重启风暴判据全部按原样保留);main 只经窄口读:`getKeyResolver/getCenterPrompts/
107
- * modelReady/restartState/planeDeferredState/promptSource`,与本目录既有的晚绑 getter 惯例同款。
108
- *
109
- * Wires the three planes:
110
- * Brain = external LLM gateway (createBrain)
111
- * Session = pluggable store (TiDB durable event-log center, or in-memory)
112
- * Hands = tools/MCP injected per request in resolveSpec (deployment-owned)
113
- * Persistence (L1 sessions + L2 memory) lives in the external TiDB center; this container is stateless.
114
- */
115
79
  async function main() {
116
80
  const config = loadConfig();
117
- // ── CROSS-INVARIANT-REGION:begin ────────────────────────────────────────────────────────────────
118
- // Liveness invariant: the running instance refreshes updated_at every HEARTBEAT_MS; the reaper fails
119
- // a run whose updated_at is older than runStaleSec. If the stale window were ≤ the heartbeat, the
120
- // reaper would race a perfectly alive run. Fail fast on a misconfiguration instead of reaping live work.
121
- // #322:判据本体已搬进 `config-invariants.ts` 的单源表 —— 热路径(config-center stage)跑的是**同一条**,
122
- // 所以一个过小的 runStaleSec 既不能从 env 起服,也不能从中心热发布进来(禁手抄第二份判据)。
123
81
  if (config.runStaleSec * 1000 <= HEARTBEAT_MS) {
124
- // CROSS-INVARIANT:run-stale-heartbeat
125
82
  throw new Error(`REAP_RUN_STALE_SEC (${config.runStaleSec}s) must exceed the ${HEARTBEAT_MS / 1000}s liveness heartbeat`);
126
83
  }
127
- // ── CROSS-INVARIANT-REGION:end ──────────────────────────────────────────────────────────────────
128
84
  const logger = createLogger(config.logLevel);
129
85
  const metrics = createMetrics();
130
- // #157 fail-open 治理:把 logger/metrics 接到进程级 failOpen 记录器上(见 observability/fail-open.ts)。
131
- // 排在这里而不是更晚:兜底臂遍布装配链,越早接上,越少的 fail-open 只剩探针行没有结构化痕迹。
132
- // 装配前已累计的计数与已喊过的 tag 由 install 继承,不重置也不重喊。
133
86
  installFailOpenRecorder({ logger, metrics });
134
- // A-033.3(#256):同一处接上密钥形 env 剥除的留痕面(core `scrubSecretEnv` 的 findings 收集器半场)。
135
- // 同因排在这里:两条剥除腿(host shell 继承 env / project-memory 的 git 子进程)都在没有观测句柄的深处。
136
87
  installSecretEnvScrubRecorder({ logger, metrics });
137
- // S20 (SILENT-FALLBACK P1) + [792]④ boot notices: config parsing runs before the logger exists — drain both
138
- // pre-logger collectors via the shared helper (F10: run-local uses the same one, so entrypoints can't drift).
139
88
  logConfigDiagnostics(logger);
140
- // design/158 B4 交接小件:plugins 数值旋钮(HOST_BG_*/K8S_BG_*/E2B_*,模块级 import 时读)的非法
141
- // 字面量告警——通道在 remote-shell.ts 自带(不造 plugins→config 依赖边),与上面同一 boot 诊断点排空。
142
89
  for (const w of drainNumEnvWarnings())
143
90
  logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw });
144
- // M1(接入审计 2026-07-08):core verify 契约=verifier 必须异于 implementer(去相关性是 🔴 部署契约)。
145
- // verifier 角色落 "default" 时对抗验证者=实现者自评自批——比无验证更糟(假安全感)。曝光而非静默:
146
- // 单模型部署配 MODEL_VERIFIER 指异构模型即消警(typo/不在 catalog 同样落到这里)。
147
91
  if ((config.roles?.verifier ?? "default") === "default") {
148
92
  logger.warn("verify_decorrelation_unavailable", {
149
93
  hint: "verifier role = default (= implementer) — adversarial verification grades its own work; set MODEL_VERIFIER=<catalog model id> to a heterogeneous model",
150
94
  ...(process.env.MODEL_VERIFIER ? { rejected: process.env.MODEL_VERIFIER, reason: "not in catalog" } : {}),
151
95
  });
152
96
  }
153
- // S24: redaction rewrites by pattern; LOW: web-search non-array payloads by provider.
154
97
  setRedactionObserver((pattern, n) => metrics.inc("redactions_applied_total", { pattern }, n));
155
98
  setWebSearchBadPayloadObserver((provider) => {
156
99
  metrics.inc("web_search_bad_payload_total", { provider });
157
100
  logger.warn("web_search_non_array_payload", { provider });
158
101
  });
159
- // 🔴 ONE local root for BOTH the config provider (models/skills/mcp) AND the remoteExec loader (avoids a
160
- // split-brain — createConfigProvider defaulted to process.cwd() while loadRemoteExec used
161
- // AGENT_DATA_DIR, so config.d and remote-exec.json could resolve to DIFFERENT directories). run-local already
162
- // computes this once; main.ts now does too. Shared with the config-center runtime and every boot leaf below.
163
102
  const localRoot = config.configLocalDir ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
164
- // 缝合审 M6:上面这条论证合并了 config provider 与 remoteExec 两个根,**漏了第三个** —— store 的
165
- // 数据根另有一条解析(config.ts 的 `localDataRoot`,它吃 `LOCAL_DATA_ROOT` 而这里不吃)。只设
166
- // LOCAL_DATA_ROOT 的部署因此数据在一处、config.d/remote-exec.json 在另一处,而且**一声不响**。
167
- // 不改解析(改了现有部署的 config.d 会当场失踪),改成说出来 + 给合一旋钮。
168
103
  const rootsSplit = splitLocalRoots({
169
104
  localDataRoot: config.localDataRoot,
170
105
  configLocalDir: config.configLocalDir,
@@ -177,49 +112,17 @@ async function main() {
177
112
  note: "LOCAL_DATA_ROOT moved the local backend's DATA root, but config.d / remote-exec.json are still read from the config root (LOCAL_DATA_ROOT is not consulted there). If you meant to move both, set CONFIG_LOCAL_DIR to the same directory.",
178
113
  });
179
114
  }
180
- // design/158 A10 尾刀:config-center 的 boot 半场(sealed-key 托管 → provider → prompt-epoch 店 →
181
- // effective 拉取 / LKG 兜底 / tolerant apply)搬到 src/boot/config-center.ts。该段与 refresh 循环共享的
182
- // 12 个跨段可变 `let` 现在是那只工厂的闭包私有字段(每一处读写的文本与次序逐字保留,见该文件头注);
183
- // main 只经窄口读:getKeyResolver/getCenterPrompts/modelReady/restartState/planeDeferredState/promptSource。
184
- // ⚠️ 位置即契约:必须在 openStores 之前 —— models/roles/teams 要在 Runner/持久层建起来之前应用进 config。
185
115
  const configCenter = await createConfigCenterRuntime({ config, logger, metrics, localRoot });
186
- // design/158 A10 尾刀:local 形的 config.d/remote-exec.json 覆写搬到 boot/config-center.ts(逐字)。
187
- // ⚠️ 位置即契约:就地改写 config.remoteExec,必须早于 createExecutionEnv 这个消费点。
188
116
  await configCenter.applyLocalRemoteExec();
189
- // design/158 A10:持久层装配搬到 src/boot/stores.ts(逐字)。⚠️ 该段就地归一 `config.sessionBackend`
190
- // 且承载三条 fail-loud 拒启断言 —— 位置即契约,理由见该文件头注。
191
117
  const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, } = await openStores({ config, logger, metrics, localRoot });
192
- // design/183 I6(server 同族):**每副本 boot 必查**收编日志的在飞行 —— 续跑或响亮留痕,禁静默跳过。
193
- // 位置:store 开完之后(要 backend)、任何路由装配之前(半迁移状态绝不带进服务期)。判据与「为什么
194
- // 是续跑而不是拒启」见 boot/adoption.ts 顶注。
195
- // #264 v2-c:治理携出 bundle 两面(POST /v1/memory/{export,import})的引擎接线。**一次**构造、两口共用
196
- // —— 与 orgMemoryDirectory / sharedMemoryStore 同姿势(一个进程对同一件事只有一个实例)。
197
- // 引擎为什么在这里现构、以及「这两条路径是店级的、不读挂载树」的亲读依据,全文见 memory-bundle-engine.ts。
198
- // 缺席(记忆引擎未接线)⇒ 两口 501 `capability.memory_engine_required`。
199
118
  const memoryBundleFaces = memoryEngine ? createMemoryBundleFaces(memoryEngine) : undefined;
200
119
  const adoptionBoot = await runAdoptionBootScan({ backend, logger });
201
120
  if (adoptionBoot.scanned > 0 || adoptionBoot.error !== undefined) {
202
121
  logger.info("adoption_boot_scan", { ...adoptionBoot });
203
122
  }
204
- // design/158 A10:计费/追踪/预算装配搬到 src/boot/budget-tracing.ts(逐字;tracer 与 side-query 同 sink 实例的
205
- // 「同段构造」契约见该文件头注)。
206
123
  const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileSnapshotStore, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
207
- // #154 车二:持久化权限规则店(core 5.18.0 design/179 + 5.22.0 design/182)。三面一束 —— 规则桶
208
- // provider 上 `RunnerDeps.permissionRuleStore`(引擎据它铸 ruleSuggestions + 把 manifest 的
209
- // `permissionRules.storeWired` 报成真),审批记录 + 导入票喂同意车道(HTTP 两口的属主)。
210
- // 缺席(local 车道 / 无 backend)⇒ 诚实缺席:storeWired:false、帧上零候选、两口 501。
211
- // 🔴 总开关在最前(codex round7 [high] 一):关 ⇒ 整条车道根本不装配(店/车道/帧键/两口一起消失)。
212
124
  const permissionRuleStores = config.permissionRulesEnabled && backend ? backend.permissionRule() : undefined;
213
125
  const ruleConsent = permissionRuleStores ? createRuleConsentLane(permissionRuleStores) : undefined;
214
- // 🔴 A-010.10(验真后修):**旋钮开着却上不了场 ⇒ 打一行**,与同族旋钮 `STREAM_APPROVAL_ENABLED` 的
215
- // `stream_approval_disabled` 一行(`boot/coordinators.ts`)对称。
216
- // 此前这一格全程静默:`PERMISSION_RULES_ENABLED=true` 的部署若没有 backend、或 backend 没实装规则店,
217
- // 车道就悄悄整条不装配 —— 运维看到的是「设了旋钮,可 `/v1/rules` 恒 501、卡上恒无候选」,而启动日志
218
- // 里一个字都没有。判据逐字照那条先例:①**info 不是 warn**(这是合法部署形,不是配置错误);
219
- // ②旋钮本来就没开的部署**不打**(显式关掉的人自己知道为什么,一行噪音零信息);
220
- // ③原因写成机读位(哪一项不成立),不是一句人话。
221
- // 与紧随其后的 `auditDormantPermissionRules` 是两件事、互不遮蔽:那条说的是「车道上了场,并且库里有
222
- // 既有桶被默认 ON 唤醒」,这条说的是「车道压根没上场」——两者的合取恒假,永远只会打其中一行。
223
126
  if (config.permissionRulesEnabled && permissionRuleStores === undefined) {
224
127
  logger.info("permission_rules_lane_unavailable", {
225
128
  knob: "PERMISSION_RULES_ENABLED",
@@ -228,107 +131,38 @@ async function main() {
228
131
  note: "PERMISSION_RULES_ENABLED is on but no permission-rule store is wired — /v1/rules/* answer 501, approval cards carry no rule suggestions, and respond's persistRule is refused with rule_lane_unavailable",
229
132
  });
230
133
  }
231
- // #203 §3(设计稿 v2 F4 残余):默认 ON 会把**既有**规则桶一并唤醒 —— 在启动日志里把它说出来。
232
- // 三条判据(店缺席 / 零桶 / 运维显式表过态 ⇒ 都不打行)与失败方向(数不出来只 warn,绝不拒启、
233
- // 也绝不编一个 0)逐字见 `boot/permission-rules-audit.ts`。
234
134
  await auditDormantPermissionRules({ stores: permissionRuleStores, logger, explicit: config.permissionRulesEnabledExplicit });
235
- // design/158 A10:执行环境装配搬到 src/boot/execution-env.ts(逐字)。
236
- // ⚠️ 工厂装饰顺序=行为(scratchpad → worktree → SendUserFile 登记 → 附件物化最外层),见该文件头注。
237
135
  const { perTaskImage, sessionEnvSelection, perSessionCwd, setSessionCwd, setSessionShellEnv, executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager, } = createExecutionEnv({ config, logger, metrics, taskAttachmentStore });
238
- // prove-before-scale stopgap: a no-op tool tracer (logs every tool call incl. resumed legs that
239
- // /events hides) so we can diagnose single-agent coding behaviour. Off unless TOOL_TRACE=true.
240
136
  const toolTracer = config.toolTrace ? createToolTracer(logger) : undefined;
241
137
  if (toolTracer)
242
138
  logger.info("tool_trace_enabled", {});
243
- // Goal B4 (2026-07-08): the ALWAYS-ON gate-deny meter (`permission_denied_total{source}`) — an
244
- // operational/security signal that must not sit behind the TOOL_TRACE diagnostic switch. Composed
245
- // UNDER the tracer (deployment slot order: meter first, tracer second — composeHooks double-runs the
246
- // void permissionDenied slot); this composite is what every hooks site below uses as the deployment
247
- // baseline, so a task-hooks fold (resolveSpec) shadows neither.
248
139
  const deploymentHooks = toolTracer ? composeHooks(createPermissionDeniedMeter(metrics), toolTracer) : createPermissionDeniedMeter(metrics);
249
- // design/158 A10:workflow 编排面搬到 src/boot/workflow-orchestration.ts(逐字;构造次序契约见该文件头注)。
250
140
  const { sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion, workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink, workflowRecoverOpts, workflowAgentRegistry, subagentSteerRegistry, } = createWorkflowOrchestration({ config, logger, metrics, localRoot, backend, getRunStore: () => runStore });
251
- // #329:PARKED 行迟到决议的赎回席 —— **晚绑** holder(席本体是 `createHttpServer` 的产物,而协调器段
252
- // 按其自身的次序契约必须排在它之前;同款先例 = 下方 reaper 的 `runDenySweep`)。接线在 server 造出来
253
- // 之后一行,那里同时是**直连门**的 fail-closed 判定点(理由写在赋值处)。
254
141
  let parkedAskRedeem;
255
- // design/158 A10:活体协调器 + SendUserFile 工具面搬到 src/boot/coordinators.ts(逐字;durableEnabled 的
256
- // 「必须早于 runnerDeps 求值」次序契约见该文件头注)。
257
142
  const { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec } = createLiveCoordinators({
258
143
  config, logger, backend, sendUserFileTaskEnvs, ruleConsent,
259
144
  getParkedRedeem: () => parkedAskRedeem,
260
- // #295(F-1,[4512] 修向 (b')):卡批「不再询问」的 project root——语义单点见 task-cwd.ts
261
- // `cardRuleScopeRoot` 顶注(session 注册 cwd ?? in-process 单用户形 process.cwd();其余 undefined)。
262
145
  ruleScopeRootFor: (sessionId) => cardRuleScopeRoot(sessionId !== undefined ? perSessionCwd.get(sessionId) : undefined, config),
263
146
  });
264
- // design/158 A10:per-principal caps 段搬到 src/boot/runtime-caps.ts(逐字)。
265
147
  const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver } = createRuntimeCaps({ config, logger });
266
- // design/170 件A(#148 件3③):org 记忆准入装配(目录源三态选择+C12 能力探测,坏配置在此拒启动)。
267
148
  const orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
268
- // design/177 —— org 共享记忆库(memory_list/memory_read + `/v1/shared-memory/*` 只读面)。
269
- // 🔴 铸它的合取式是**唯一**的挂载条件,模型面与 HTTP 面共用:
270
- // ① SQL 供给面在场(`backend.sharedMemoryStore` —— local 车道诚实缺席,理由在 store-backend.ts);
271
- // ② org 折叠面在场(目录源)—— 没有成员性判据的共享读面只能全放或全拒,两个都比"这个面不存在"差。
272
- // deployment-origin scope 只在**无租户边界**的部署里授予:多租户下一条部署级声明会同时授予每一个
273
- // principal,那是跨租户读而不是配置便利(org-memory.ts 的 N2 同判)。择净在这一行,授权模块只忠实使用。
274
149
  const sharedMemoryStore = orgMemoryAdmission.directory !== undefined
275
150
  ? backend?.sharedMemoryStore?.(createSharedMemoryScopeAuthorizer({
276
151
  directory: orgMemoryAdmission.directory,
277
- // 🔴 A-057.40:递的是**取值口**不是值 —— `config.projects` 由 config-center 就地热应用,
278
- // boot 期快照会让新登记的 org 库变成终局式空集、让已撤销的 scope 继续被放行(后者是
279
- // fail-open,且 projects 不在 RESTART_SLICES 里 ⇒ /health 一句提示都没有)。多租户择净
280
- // 仍留在这一行(那是部署形态判据,boot 期定死,不随热应用变)。
281
152
  deploymentScopes: config.requirePrincipal === true ? () => [] : orgMemoryAdmission.deploymentMemoryScopesLive,
282
153
  }))
283
154
  : undefined;
284
155
  if (sharedMemoryStore)
285
156
  logger.info("shared_memory_stores_enabled", { backend: backend?.kind });
286
- // 🔴 codex 复审(#196 finding-2b):`toolResultStore` 在**无 backend** 形下是 undefined,而 core 的
287
- // Runner 构造函数会在缺席时**每只各自私建**一份 `RunnerSharedToolResultStore`(runtask.js
288
- // `if (!this.deps.toolResultStore)`)。本部署有多只 Runner(主 / subRunner / hookAgent / #196 的两只无手
289
- // 孪生),私建 ⇒ 每只一个独立店 ⇒ A 车道 offload 出去的 `ReadToolResult` 引用在 B 车道解引用不到
290
- // (fork 子任务跨 runner 读父的 offload 是既有可达形,#196 的车道分家又多了一条)。这里显式铸**一份**
291
- // 共享兜底(参数与 core 私建那只逐字相同:`RunnerSharedToolResultStore` 是 `InMemoryToolResultStore`
292
- // 的空子类,cap 64M chars),只喂 Runner 侧。
293
- // ⚠️ 刻意**不动** `toolResultStore` 本身:那个键的「present ⇔ durable 后端在场」语义还管着 E21 purge、
294
- // reaper 清扫、leader 装配与 /health 的接线自述(改它会让无 backend 的部署自述成 "shared(sql)" = 谎)。
295
157
  const runnerOffloadStore = toolResultStore ?? new InMemoryToolResultStore({ maxTotalChars: 64_000_000 });
296
- // 交接件⑤:commit 尾注署名座**算一次**,主/sub 两只 Runner 经共享基座同源。
297
- // [931]① clay 拍(core 1.300 BREAKING:缺省不署 Co-Authored-By,署名=产品身份资产归部署):
298
- // branded 形态(local provider = Sema 产品线,scenarios brandIdentity 同判据)commit 尾注接 Sema 署名;
299
- // 非 brand 部署维持 core 新缺省(不署)。判据逐字不变,变的只是它现在**也**喂给 subRunner ——
300
- // 一个被委派的子代提交进的是同一个仓、代表同一个部署,它的 commit 少一行 trailer 没有理由。
301
158
  const commitHands = config.configProvider === "local" ? { commitCoAuthor: "Sema <noreply@vivi-ai.com>" } : undefined;
302
- // checkpointStore 构造(只依赖 backend/config——顺序安全;DURABLE_APPROVAL 仍是唯一门,ALL three
303
- // backends carry checkpoint(),local = core FileCheckpointStore + service half)。四个消费点同一实例:
304
- // 主 runner 的 per-task spec 面(resolveSpec durableEnabled 分支)、scenarioDeps(subagent spawn 面
305
- // forwardDurableApproval)、subRunner deps(子代执行面 suspendAsk——[1584] 接线,裁定见 subRunner 构造
306
- // 内注),以及紧随其下的 design/170 件D 留存能力校验(它要读这只店的 `retention` 声明位)。
307
- // ⚠️ 位置即契约:必须早于 `createGovernanceSeams`(件D 校验的对象之一)与 `createRunnerDeps`。
308
159
  const checkpointStore = backend?.checkpoint && config.durableApproval ? backend.checkpoint(logger) : undefined;
309
- // design/170 件B/C/D(#252 件1):三个部署治理座席 + 两条拒启判据(锁的装配相容性、留存能力校验)。
310
- // ⚠️ 位置即契约:必须在 store 三件都构造完之后(件D 校验读它们的 `retention` 声明)、`new Runner` 之前
311
- // (座席进 RunnerDeps);拒启在这里发生 = 还没开始服务。
312
- // #270 —— 托管留存的**执行面装配**(设计稿 §7)。⚠️ 位置即契约:必须在 `createGovernanceSeams` 之前
313
- // (件① 的 `retentionExecutorWired` 是它的入参),也必须在 store 三件构造完之后(自持门要读它们的
314
- // 声明位)。两只店都是 **SQL 后端专有**,`local`/无 backend 上恒 undefined —— 那正是 lane 自持门
315
- // 在 `RETENTION_SWEEP_INTERVAL_SEC>0` 时拒启的那一支。
316
- //
317
- // `taskAttachmentTable` 如实告知(车1 交接件③):它是「boot 真的 ensure 过那张表吗」的事实,
318
- // 不是一个默认值 —— 无对象存储的部署上附件面整段不接线、表不建,一条盲发的 DELETE 会以
319
- // unknown-table 打红整轮 sweep(理由逐字见 retention-store-sql.ts 的 `SqlRetentionStoreOptions`)。
320
- // `taskListTables`(#318)同姿势但事实不同:`boot/task-list-lane.ts` 在**任何** SQL 后端上都无条件
321
- // ensure 那两张表(不像附件那样挂在对象存储配置上),所以这里的如实答案就是「车道落在 SQL 臂上」。
322
160
  const retentionExecutor = backend?.retention?.({ taskAttachmentTable: taskAttachmentStore !== undefined, taskListTables: taskListLane.backend !== "memory", logger });
323
161
  const retentionLaneStore = backend?.retentionLane?.();
324
- // lane **自持**的 boot 不变式(件④;codex F3)——与下面 core 的 `assertRetentionCapability` 是两道门,
325
- // 各答各的问题,本道**不看 locked**。判据与三条拒启臂逐字见 `boot/retention-lane.ts`。
326
162
  assertRetentionLaneWirable({
327
163
  intervalSec: config.retentionSweep.intervalSec,
328
164
  policy: config.retentionPolicy,
329
165
  executor: retentionExecutor,
330
- // 与件D 校验**同一份**店清单(下面 `retentionStores` 那三行的同源读点):两道门看的是同一批真身,
331
- // 一门放行另一门拒的分歧才有意义,而"看的不是同一批店"造成的分歧只是 bug。
332
166
  stores: [
333
167
  { name: "sessionStore", store: sessionStore },
334
168
  { name: "checkpointStore", store: checkpointStore },
@@ -339,25 +173,13 @@ async function main() {
339
173
  const governanceSeams = createGovernanceSeams({
340
174
  config,
341
175
  logger,
342
- // 件D 的首批治理对象(design/170 §5.1 点名的三族)。**喂真身**:这道校验的全部价值就是读真店上的
343
- // 声明位,喂一个"我以为装了什么"的替身等于把校验变成自证。`store: undefined` 的行由 core 跳过。
344
176
  retentionStores: [
345
177
  { name: "sessionStore", store: sessionStore },
346
178
  { name: "checkpointStore", store: checkpointStore },
347
- // 🔴 R2-F3(codex 对抗复审,验真后修):喂 **Runner 真用的那一只**(`runnerOffloadStore`),不是
348
- // `toolResultStore` —— 后者缺席时 Runner 拿的是本文件铸的 InMemory 兜底,而那只店同样受留存策略管辖。
349
- // 喂错实例 = 校验漏掉了真正在跑的那一个,正是「校验读的不是真身」这类假绿。
350
179
  { name: "toolResultStore", store: runnerOffloadStore },
351
180
  ],
352
- // 🔴 件①(车1 交接件,codex R2-[critical] 的修复面):**本 build 真的接了执行器吗**。
353
- // core 的 `assertRetentionCapability` 只读店的声明位,而三只 SQL 店从车1 起就诚实地声明了
354
- // `"managed"` —— 于是那道门会为一台**没有任何东西在删**的机器放行 locked policy。这一位问的是
355
- // 另一个问题:有没有东西真会删。合取式逐字 = 上面那道自持门放行之后 lane 真的会起的条件。
356
181
  retentionExecutorWired: config.retentionSweep.intervalSec > 0 && retentionExecutor !== undefined && retentionLaneStore !== undefined,
357
182
  });
358
- // design/158 A10:RunnerDeps 装配段搬到 src/boot/runner-deps.ts(逐字;runStore 晚绑改取值,见该文件头注)。
359
- // #324:撤销台账席取一次、两处(主 runnerDeps + subRunner 共享基座)同一引用。takeMcpRevocations 的
360
- // 取走动作同时打开 restart-signal 的删除向豁免(承接与豁免绑定,见 config-center 的方法注)。
361
183
  const mcpRevocations = configCenter.takeMcpRevocations();
362
184
  const runnerDeps = createRunnerDeps({
363
185
  hands: commitHands,
@@ -373,77 +195,9 @@ async function main() {
373
195
  getRunStore: () => runStore,
374
196
  });
375
197
  const runner = new Runner(runnerDeps);
376
- // #196:`runner` 的无手孪生 —— 同 deps 同实例(sessionStore/toolResultStore/checkpointStore… 全共享,
377
- // `Runner.sessions` 就是传进去的那只 store,两只 Runner 不会各持一份会话缓存),唯独不挂
378
- // `executionEnvFactory`。core 的 `handsEnabled = ownedEnv || deps.executionEnv` 是 **per-Runner** 合同,
379
- // 单 runner 服务全场景时结构上无法逐场景兑现 —— 这只孪生就是兑现它的那一半(设计小票 §2a)。
380
- // 声明 hands:"none" 的场景(scan / code-review 两腿 / discuss / center overlay)在 HTTP 执行点被路由到这里。
381
- //
382
- // 🔴 两条**已知边界**(codex 复审 2026-08-09 两轮提出,逐条亲验 core dist 后如实记账;两条的部署条件
383
- // 互不相同 —— 不要把它们并成一句)。
384
- //
385
- // ① 会话锁是 **per-Runner** 的(core runtask.js 的 `sessionLocks` Map,每只 Runner 一份)。
386
- // **条件:无 store backend 的部署形**。有 runStore 时同会话并发提交被服务端的 run 认领挡成 409
387
- // (routes/tasks.ts:317 / routes/runs.ts:335 的 `createRun` 认领),两车道并发结构上不可达;无
388
- // backend 的部署没有那道认领,同一 sessionId 上「一条 full + 一条 none」并发就失去了修前由单
389
- // Runner 提供的串行化。同类缺口本就存在(subRunner 经 ForkRoutingSessionStore 也会碰宿主会话),
390
- // 本件让它多一条可达路径。补偿:配任一 backend(含 `DB_BACKEND=local`)即恢复认领。
391
- //
392
- // ② 升级窗的旧 checkpoint。**条件恰恰相反:必须有 checkpointStore**(= 有 backend + DURABLE_APPROVAL),
393
- // 且部署在 REMOTE_EXEC 车道上 —— 修前建立的 hands=none 场景 durable checkpoint 会带 workspaceHandle
394
- // (可挂起 env 的快照形,以及**非**可挂起远端 env 的 `restoreMode:"park_only"` 形,两者都算;审批 park
395
- // 与 resource suspend 都会写),续跑时被路由到这只无手 Runner,core 在 **CAS 之前**抛
396
- // `CheckpointError("checkpoint.unsupported_version", "checkpoint has a remote workspaceHandle but no
397
- // RunnerDeps.executionEnvFactory is wired to rebuild the env")`(runtask.js:3704)。
398
- // 🟢 **裁定已翻新(#209 件2,core 5.25.0 提货批 [3443]):本形现在归 RETRIABLE。**
399
- // 沿革留档(免下次重开同一个问题):该码在 core 上共三个铸点 —— 本形 / 版本超上限 / org 治理行
400
- // 落到无 `permissionRuleOrg` 接线的 worker —— 三形都是 PRE-CAS、卡一律留 pending,但**可恢复性
401
- // 不同**;#205 当时 core **不给判别式**(同码、`detail` 同样缺席),两版下游修法各被一轮 codex
402
- // 对抗复审验伪(①三形一律 retriable ②按 `gate.realApproval` 分流),按「源头修复禁下游旁路」全
403
- // 回退、维持 TERMINAL,并把判别式列成上游请托([3434] 请托 1)。
404
- // 请托已兑现:core 5.25.0 给三个铸点各带 `CheckpointError.detail.reason`,本形 = `env_factory_missing`,
405
- // d.ts 逐字「retryable on a factory-wired worker」。⇒ `http/server.ts` 的分类表现在**读词表**判
406
- // (`WORKER_SWAP_REDEEMABLE`,闭集、core 加词 tsc 红),不做形状推断;本形的行**重新 park
407
- // suspended**、那条仍 pending 的卡不再被孤儿化(#205 登记的孤儿代价至此销账)。
408
- // ⚠️ 与旧裁定的差别在**代价的方向**,如实说清:修前拿「释放 task_active」换「卡变死件(下一次
409
- // /decide → 404,只能等 `terminal_at_ms`)」;修后行停在 suspended 攥着会话锁,与它 park 时逐字
410
- // 相同,直到运维给这条车道接上 `executionEnvFactory`(或把该场景路由回有手 Runner)。#205 当年
411
- // 驳掉 blanket-retriable 的那条理由(`handsLanes.laneOf(spec)` 按场景判 ⇒ 换副本重投必同样失败)
412
- // 仍然成立,但它说的是「换个副本再点一次没用」,而 core 的词说的是「接上 factory 的 worker 兑得掉」
413
- // —— 那是一次**运维动作**,与 `governed_unwired` 要求接 `permissionRuleOrg` 完全同族。
414
- // 升级窗仍是**响亮的**(409 + 该 errorCode + `retriable: true`),不是静默损坏;发车说明照写。
415
- // 论证与钉:`http/server.ts` 分类表下方那段 + `test/durable-resume-http.test.ts` 的「#209 件2」六格。
416
- // ⚠️ 未覆盖登记(原样保留):本形需要「真 durable 店 + 带 workspaceHandle 的旧 checkpoint」才能
417
- // 驱动,真店端到端仍属真双库/迁移测试面,未建。
418
198
  const handslessRunner = new Runner(withoutExecutionEnv(runnerDeps));
419
- // codex R10: TRUE ⇒ the Runner just froze a PRIVATE tier-expanded catalog copy (core runtask.js constructor,
420
- // dist-read) — in-place model-plane mutation no longer reaches it, so refresh-time plane changes must be
421
- // DEFERRED to restart (see appliedPlaneEff / applyEffective deferModelPlane). Captured HERE, at the same
422
- // config generation the constructor read.
423
199
  const runnerTierFrozen = Object.keys(config.tiers).length > 0;
424
- // hooks 阶段三b(design/HOOKS-PHASE3B-PROMPT-AGENT.md):prompt/agent 条目的模型调用载体。
425
- // - hookLlm(prompt 条目)= 直连网关一次非流式 completion——一次判定调用不起 core task(重炮打蚊子
426
- // 且引入可重入面);系统注入=事件分档(A-065 P3-10 订正,旧句「无系统注入」已过期):Stop/SubagentStop
427
- // 注入 CC 逐字系统提示(cc-stop-prompt.ts,hook-runner.ts 的 extra.system),其余事件无注入。
428
- // agent 条目现状零系统注入=A-065 P2-5 立案中(UNANCHORED,CC 该臂有自己的两档提示)。默认模型=
429
- // summarize 角色(cheap tier,
430
- // council/压缩同款;缺角色→主模型);entry.model 覆盖须命中部署 catalog(单用户=自家目录全开,与
431
- // workflow 模型 allowlist 同纪律),未命中=该条目 fail(非阻断记账)。费用不折 task budget(hook=
432
- // 部署策略面开销;hook_llm_calls_total/hook_llm_cost 由 metrics 记)。
433
- // - hookAgent(agent 条目)= runner.runTask 读-only 子代理:handsReadOnly + enableFork:false +
434
- // 不装 hooks(🔴 可重入禁——结构性:这个 spec 永不携带 TaskSpec.hooks)+ maxTurns 10 + maxCostUsd
435
- // 0.05 独立小预算,影响范围受限(CC 该臂 M=50 步,收窄=已声明理由的正当偏离)。系统提示=CC
436
- // hook_agent 两档逐字(cc-agent-hook-prompt.ts,A-065 P2-5 修;hook-runner 按事件铸、此处消费进
437
- // spec.systemPrompt 整替形)。final result 文本即输出(与 prompt 同一 SyncHookOutput 决策面)。
438
- // hookModelFor/hookLlm 抽到 src/hooks/hook-llm.ts(可注入 fetch/keyResolver ⇒ 可单测):api 形分派
439
- // (openai /chat/completions 与 anthropic /v1/messages 双腿)、缺省 baseUrl 按 api 分家、per-model key
440
- // 恒胜 + foreign-no-key 连 prompt 都不发的 fail-closed 纪律,全在该模块内(注释含 audit 原文)。
441
- // getKeyResolver 取函数:keyResolver 住在 config-center 运行时的闭包里,registry 热应用会整个换引用,
442
- // 这里必须每次现取。
443
200
  const { hookModelFor, hookLlm } = createHookLlm({ config, getKeyResolver: () => configCenter.getKeyResolver(), metrics });
444
- // hook agent 的 session 绝不进 durable store——每次 runTask 会写 session_meta 且无人
445
- // 清理(reaper 只扫 staging orphan),cap=4/事件 × 长会话=无主行堆积。专用 Runner 覆盖 sessionStore 为
446
- // 进程内 TTL 店(1h 短 TTL,hook 子代理无 resume 语义,行随进程/TTL 消失);其余 deps 原样共享。
447
201
  const hookAgentRunner = new Runner({ ...runnerDeps, sessionStore: new TtlSessionStore({ defaultTtlDays: 1 / 24 }) });
448
202
  const hookAgent = async ({ prompt, model, timeoutMs, system }) => {
449
203
  const pick = hookModelFor(model);
@@ -454,19 +208,11 @@ async function main() {
454
208
  objective: prompt,
455
209
  sessionId: uuidv7(),
456
210
  model: pick.catalogRef,
457
- // A-065 P2-5:CC hook_agent 评估者系统提示(cc-agent-hook-prompt.ts 两档,hook-runner 按事件铸)。
458
- // 整替形与 CC 对齐(CC 该臂 systemPrompt=O 整段)。maxOutputTokens 座刻意不消费:agent 载体
459
- // 的输出预算=limits(maxTurns/maxCostUsd),非单次 completion 概念。
460
211
  ...(system !== undefined ? { systemPrompt: system } : {}),
461
212
  handsReadOnly: true,
462
213
  enableFork: false,
463
- // core 5.8.0:预算四键全入 limits(顶层 maxCostUsd 删);timeoutSec(秒)→ maxWalltimeMs(毫秒,原生同单位)
464
214
  limits: { maxTurns: 10, maxCostUsd: 0.05, maxWalltimeMs: Math.max(30_000, timeoutMs) },
465
- // spec 无 toolPolicy → core `hasEffectAwareGate` 为假 → 每次 agent hook 触发一条
466
- // error 级 UNGATED 警告(非致命但纯噪声)。挂已裁决的 auto-accept 基线(与 singleUserAutoAcceptBaseline
467
- // 同构;hook 子代理本就 handsReadOnly + 单用户信任面,auto-accept 是既定姿态,只是让闸机制在场)。
468
215
  toolPolicy: createAllowDenyPolicy({}),
469
- // 🔴 可重入禁:此 spec 不带 hooks 字段(deps.hooks 是部署观测 tracer,void 观察者,无决策面)。
470
216
  });
471
217
  metrics.inc("hook_llm_calls_total", { type: "agent" });
472
218
  if (result.status !== "completed")
@@ -478,15 +224,12 @@ async function main() {
478
224
  }
479
225
  };
480
226
  const clipHookErr = (s) => (s ?? "").slice(0, 300);
481
- // Auto session titles — fire-and-forget at first
482
- // submit, one cheap hookLlm call (summarize role → MODEL_CHEAP_ID when configured), write-once via the session
483
- // store's setTitleIfNull (SQL twins + local sidecar carry it; the memory dev store doesn't → titler dark, honest).
484
227
  const titleCapable = sessionStore;
485
228
  const sessionTitler = config.sessionAutoTitle && titleCapable.setTitleIfNull && titleCapable.probeTitle
486
229
  ? createSessionTitler({
487
230
  setTitleIfNull: titleCapable.setTitleIfNull.bind(sessionStore),
488
231
  probeTitle: titleCapable.probeTitle.bind(sessionStore),
489
- redact: redactSecrets, // audit 2026-07-13: scrub a model-echoed secret before persist/list/log
232
+ redact: redactSecrets,
490
233
  llm: (o) => hookLlm({ prompt: o.prompt, timeoutMs: o.timeoutMs, model: o.model }),
491
234
  logger,
492
235
  metrics,
@@ -494,60 +237,14 @@ async function main() {
494
237
  : undefined;
495
238
  if (sessionTitler)
496
239
  logger.info("session_auto_title_enabled", {});
497
- // ① workflowsCapability SINGLE-SOURCE (core 1.155 `workflowsCapability(deps)`; ruling = SPLIT two caps):
498
- // `capabilities.workflows` = core's own truth (`workflowScriptRunner.safeForUntrustedScripts` ∧ governance), boot-
499
- // computed from the SAME RunnerDeps the engine uses = the ENGINE-CAN axis ("can the engine self-orchestrate /
500
- // ultracode"), ORTHOGONAL to the durable-LIST axis (`Boolean(workflowRunStore)`, surfaced as `workflowsList`).
501
- // ⚠️ Today these COINCIDE — `workflowRunStore` is built iff self-orch (InMemory/File, ~L527), the same gate that
502
- // wires the safe runner — so this is core's FORWARD-LOOKING contract, not a live-divergence fix (the earlier
503
- // "memory backend diverges" premise was wrong; double-review caught it — see the cap site in http/server.ts).
504
- // Static per deployment (deps are boot-fixed).
505
240
  const workflowsCapable = workflowsCapability(runnerDeps);
506
- // Sub-task runner (code-review council lenses/arbiter, subagent/discussion children). [1364]①/[1367]①(core
507
- // 1.350 fork 终修的部署要求):fork 子任务带 `requireExistingSession:true` 在这里执行而源会话在 HOST 店
508
- // ——拆店=响亮 `resume.session_not_found`(cli fork 案的 server 半场)。但整店共享被 codex 判两 high
509
- // (普通子任务铸 committed durable 会话:无 reaper 覆盖=无界增长+污染 /v1/sessions 枚举),故走
510
- // ForkRoutingSessionStore:fork/resume 形(requireExisting)→ host 店优先,普通子任务 → 私有 TTL 店
511
- // (throwaway 姿势保留)。hookAgentRunner 仍保独立 TtlSessionStore(1h)——纯瞬态 hook 车道,与 fork 无关。
512
- // CC-parity (CRITICAL — 子代理 hands, root cause is the service spawn path): the subagent delegation
513
- // tool runs child tasks on THIS subRunner. Without executionEnvFactory the child's `handsEnabled` (= ownedEnv ||
514
- // deps.executionEnv, prepare-task) is FALSE → the child gets NO hands (Bash/Read/Edit) and can't run `pwd` — the
515
- // "subagent couldn't run pwd" defect hit in real TUI use. core's createSubagentTool gives hands when the env is
516
- // wired (gate3: opts.tools:[] still yields Bash), so the fix is to give the subRunner the SAME executionEnvFactory +
517
- // lspManager as the main runner. Deliberately NOT inherited: selfOrchestration (no recursive workflow spawn),
518
- // scheduler, memory, fileSnapshot, runtimeCaps — a delegated child is a short focused task, not a full session.
519
- // codex-2 F1:promptSource 必须随子代——core 1.341 把父的 centerArtifactDigest 传给委派子任务并在子
520
- // runner 上按 digest 解析;缺席=每个 catalog 任务的 Agent 子代 fail-loud prompt_snapshot_unavailable。
521
- // backgroundAgentStore(1.248 复审 F2):core prepare-task 给每个 task 的 TaskOutput/TaskStop/
522
- // AgentTranscript 装 durable 臂用的是所在 runner 的 deps——子代任务跑在 subRunner 上,缺席=子代读
523
- // 工具无 durable 回落(第三消费点,与 RunnerDeps/scenarioDeps 同一实例)。
524
- // [1543]§三族A(core 穿透审查,历史欠账键批——主 runner 挂了、subRunner 漏挂 = 子代 lane 静默降级;
525
- // 全部直引 runnerDeps 同一实例,双点永不漂移):
526
- // - tiers(H):模型目录双 runner 分叉——tier 词 AgentDefinition 在子代 lane 抛 unknown_model、Agent
527
- // model 参数("opus")静默 modelNote 降级、S3c revive 臂 row.model 记 tier 词时复活换模型。
528
- // - rosterStore(M):具名 spawn 不落 durable 名册 → tier-3 按名复活从子代不可达。
529
- // - onBackgroundChildEvent(M):子代触发的 revive/孙代对 fleet 流全盲(同一 publisher 实例=单管道)。
530
- // - loadProjectMemory/probeInstructionSources(M):委派子代 project-blind(core types 已为子代 ctx
531
- // 设计 isSubagent/agentName;门禁条件随 runnerDeps 条件键自动一致)。
532
- // - hooks/onError/toolResultStore/sessionPolicyStore(L):子代进 tracer/计量;best-effort 故障不静默;
533
- // fork 子代跨 wake 保 offload 引用;fork 形(有 sessionId)会话规则可达。
534
- // 1877 注释的「Deliberately NOT inherited」清单(selfOrchestration/scheduler/memory/fileSnapshot/
535
- // runtimeCaps)不变——本批只补漏配,不动裁定。
536
- // (checkpointStore 的构造已再上移到 RunnerDeps 装配之前 —— design/170 件D 的留存能力校验要把它与
537
- // session/tool-result 一起交给 core 的 `assertRetentionCapability`,而那道校验必须在拒启窗内跑完。)
538
- // [1593] 提名成变量:parked 子代的 ensureChildSessionDurable 晋升半场要拿它做 promoteToHost
539
- // (子会话住私有 TTL 店,park 前整树拷进 host durable 店——只查不迁的 1.263 形在拆店生产形下恒
540
- // 否决 park,cli 真机 14ms~2s expired 即此)。
541
241
  const subRunnerSessions = new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 }));
542
242
  const subRunnerDeps = {
543
- // [1543]§三族A 结构性根治(design/158 冲刺尾件):与主 runnerDeps 重复的 ~15 键统一走
544
- // createSharedRunnerDeps 展开——「双点挂载」从人工纪律变结构性保证(新共享键漏配其一=不可能,
545
- // 基座只有一份)。差异键在展开后显式列出,每个都有为何不同的理由(见 boot/runner-deps.ts 头注)。
546
243
  ...createSharedRunnerDeps({
547
244
  config,
548
- logger, // #240②:onNotice 席落共享基座 ⇒ 两 Runner 同源(EngineNotice→logger.warn)
549
- hands: commitHands, // 交接件⑤:与主 runner 同一个值(基座保证同源)
550
- mcpRevocations, // #324:与主 runner 同一台账实例(委托子代腿的 MCP 撤销同源生效)
245
+ logger,
246
+ hands: commitHands,
247
+ mcpRevocations,
551
248
  brain,
552
249
  pricing,
553
250
  tracer,
@@ -562,61 +259,24 @@ async function main() {
562
259
  sessionPolicyStore,
563
260
  usageWindowStore,
564
261
  orgMemoryAdmission,
565
- governanceSeams, // design/170 件B/C/D:与主 runner 同一份座席(子代平面同受档位/锁约束)
566
- sharedMemoryStores: sharedMemoryStore ? sharedMemoryStore : undefined, // design/177:与主 runner 同实例
262
+ governanceSeams,
263
+ sharedMemoryStores: sharedMemoryStore ? sharedMemoryStore : undefined,
567
264
  }),
568
- // ── 以下为 subRunner 差异键(不在共享基座;逐个有因)──────────────────────────────────────
569
- sessionStore: subRunnerSessions, // 子代转录=私有短 TTL fork 路由店,生命周期异于宿主 durable 店
265
+ sessionStore: subRunnerSessions,
570
266
  onBackgroundChildEvent: runnerDeps.onBackgroundChildEvent ? runnerDeps.onBackgroundChildEvent : undefined,
571
267
  loadProjectMemory: runnerDeps.loadProjectMemory ? runnerDeps.loadProjectMemory : undefined,
572
268
  probeInstructionSources: runnerDeps.probeInstructionSources ? runnerDeps.probeInstructionSources : undefined,
573
269
  onError: runnerDeps.onError ? runnerDeps.onError : undefined,
574
- // checkpointStore = 子代**执行面**的 park 设施([1582] cli TiDB 真验撞获缺口,[1584] core 定谳
575
- // 接线步):core prepare-task `spec.checkpointStore ?? deps.checkpointStore` 里 spec 侧永不被
576
- // subagent 设置,子代 suspendAsk 全靠这里——缺席 = 具名 bg 子代的 gated ask 只能 deny+continue,
577
- // checkpoint 表零行(cli T2 复现原话「denied by an approval constraint」)。必须与 scenarioDeps
578
- // (spawn 面 forwardDurableApproval 判定)**同一实例**:claim/expire/consumeParkedFlip 都作用于
579
- // 同一行。旧 [1546] HIGH-2 撤载(「core 不转发 durableApproval + SAFETY ask 会 park 出搁浅
580
- // checkpoint」)的前提被 core 1.388 件4(转发臂)消除,搁浅顾虑由件1-4+1.389 逐项闭合:watcher
581
- // park 翻行 + reap 豁免 + reconciliation 兜孤儿;unattended SAFETY park 恒带缺省 deadline;
582
- // TaskStop on parked 走 expire CAS 仲裁(1.389 F-1);/decide 可赎回。行为钉 =
583
- // test/subagent-park-wiring.test.ts(SOURCE PIN + park 全链 + 旧形反向控制)。
584
270
  checkpointStore: checkpointStore ? checkpointStore : undefined,
585
271
  };
586
272
  const subRunner = new Runner(subRunnerDeps);
587
- // #196:`subRunner` 的无手孪生(同一份 deps 字面量摘掉 executionEnvFactory —— 差异面只有这一处,
588
- // 结构上不可能漂移)。council 的 lens/arbiter、discuss 的成员/synthesizer 走它:那些子任务的人格声明
589
- // 里一件工具都没有,却在修前照拿全量可写手(设计小票 §1 里最意外的一支)。
590
- // ⚠️ 与主 subRunner 共享 `subRunnerSessions` 与 `checkpointStore` 同实例 —— park/fork 的行是同一批。
591
273
  const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
592
- // S6: resolve identity + owned session from the authenticated channel (never the body).
593
274
  const authorize = createAuthorizer(config, sessionStore);
594
- // S1/S2: durable async run registry + replayable event log (TiDB-backed).
595
275
  const runStore = backend ? backend.run() : undefined;
596
- // E18 resume-at: the eventId→entryId anchor map. REQUIRED on every backend (works local — needs only the session
597
- // tree + this map, no cloud-only checkpoint), so the only undefined case is the env-only/no-backend deploy (which
598
- // also has no async runs — consistent). The shell sends body.resumeAt=eventId; resolveSpec resolves it here.
599
276
  const resumeAnchorStore = backend ? backend.resumeAnchor() : undefined;
600
- // Per-session approval exemption ("本会话不再询问") — REQUIRED on every backend like the anchor
601
- // map; undefined only on the env-only/no-backend deploy (which has no durable approvals either — consistent).
602
277
  const approvalExemptionStore = backend ? backend.approvalExemption() : undefined;
603
278
  const instanceId = uuidv7();
604
- // Per-task wall-clock override (seconds). 0/absent = keep the tenancy default (see taskWallClockSec:
605
- // 2400s, 3600s for the big lane = council/debate/discuss) —
606
- // the override only ever RAISES (Math.max at the use site), so a misconfigured low value cannot shrink
607
- // the council budget.
608
- // 🔴 2026-08-01 env fail-loud 族:原为 `Math.max(0, Math.floor(Number(env ?? "0")) || 0)` ——
609
- // `TASK_TIMEOUT_SEC="3600s"`(带单位后缀,运维最常见的写法)得 NaN,再被 `|| 0` 折成 0,
610
- // 而 0 在这里的语义是**无超时**。运维以为设了一小时上限,实际把超时关掉了,方向恰好相反,
611
- // 且启动期零提示。改走 numEnv:非法值当场 fail-loud 并指名键与实际值。
612
- // (numEnv 的注释早就预言了这个失败模式,只是它没 export、跨文件够不到,于是这两处手写了。)
613
279
  const taskTimeoutSec = Math.max(0, Math.floor(numEnv("TASK_TIMEOUT_SEC", "0")));
614
- // [854]④ per-request 配速的运营方上限旋钮(可选;缺省不设=不封顶):body.limits 每键各自被对应旋钮
615
- // Math.min 封顶(normalizeLimits)。多租部署想约束 caller 自报配速时才设;单用户 turnkey 通常留空。
616
- // ⚠️ 语义=只钳「显式请求值」,不是全队默认限额:body 缺席(或重放体里的非法值被 defensive DROP)⇒ 该键
617
- // 不设限,与升级前行为同形——要给所有任务强加限额是另一个旋钮(未建),别指望这三个(交叉评审 F1 判读)。
618
- // 缝合审 M4:非法值仍 fail-open(不设限),但**必须出声** —— 这三个旋钮的全部用途就是封顶,
619
- // 打错一个字符让封顶静默消失是它最坏的失败形。判据单一真源在 config.ts 的 parseCapEnv。
620
280
  const capEnv = (name) => {
621
281
  const r = parseCapEnv(name, process.env[name]);
622
282
  if (r.invalid !== undefined) {
@@ -633,41 +293,11 @@ async function main() {
633
293
  maxOutputTokens: capEnv("TASK_MAX_OUTPUT_TOKENS_MAX"),
634
294
  maxTurns: capEnv("TASK_MAX_TURNS_MAX"),
635
295
  };
636
- // [2354]/[2356] A-5(5.0.0):F4 轮询门(approvalStore pending 行)整体退役——durable checkpoint 是唯一门形。
637
- // Finding #2 (edge case): the single-user auto-accept tool-policy BASELINE applies ONLY when the operator
638
- // expressed NO gate intent. If they SET approval flags (APPROVAL_REQUIRE/APPROVAL_DENY/DURABLE_APPROVAL) but no
639
- // backing store is wired (e.g. DB_BACKEND=local → no checkpointStore ⇒ durableEnabled
640
- // false), that is a MISCONFIG — do NOT mask it with allow-all; leave toolPolicy undefined. So the baseline needs
641
- // single-user turnkey ∧ zero expressed gate intent.
642
- // 🔴 #177 后本段的**信号面**已换人(原文「so core's UNGATED warning still surfaces it」已过期):守卫集
643
- // (SENSITIVE_WRITE_PATTERNS,缺省非空)现在从 governance 拍无条件铸一条 DENY 基线 ⇒ 缺省部署的
644
- // `spec.toolPolicy` 恒在场 ⇒ core 的 `hasEffectAwareGate`(prepare-task,判据是 policyLayers.length>0)
645
- // 恒为真,那条 UNGATED onError 不再触发。补偿件在 boot/resolve-spec.ts:同一形(durable 门关 ∧ 单用户
646
- // 基线不适用 ∧ 守卫集开)在 boot 期发一次 `tool_policy_only_sensitive_baseline` warn,把「守卫段之外
647
- // 无裁决」这件事按我们自己的话说出来。本行留 `undefined` 仍是对的(不拿 allow-all 掩盖 misconfig),
648
- // 只是它不再是那条信号的产地。
649
- // 单源在 approval.ts(与 inv#2 同文件)—— 这里原是内联的三格枚举,漏了 approvalNeverAuto,详见该函数顶注。
650
296
  const operatorGateIntent = hasOperatorGateIntent(config);
651
- // D3-F1(全窗复审,HIGH):门意图已表达但唯一门形(durable)不可服务 ⇒ boot 拒启带双出路指引。
652
- // 5.0.0 前这形走轮询门真拦;退役后若只留 core 每任务 warn=原先拦、现在放,升级存量静默失守。
653
297
  assertGateIntentServiceable(config, checkpointStore !== undefined);
654
298
  const singleUserAutoAcceptBaseline = config.requirePrincipal !== true && !operatorGateIntent;
655
- // design/45 durable-checkpoint (core 1.67): opt-in (DURABLE_APPROVAL + a pool). When on, F4 uses durable
656
- // suspend/resume instead of the poll gate — a gated `ask` suspends the task (status:"suspended" + checkpoint)
657
- // and an operator resumes it on any replica. Default off → unchanged poll-policy.
658
- // checkpointStore 构造已上移到 subRunner 之前([1535] server 半场:子代 park 设施);本行原位注释保留。
659
- // TOC integration ask③ (INTEGRATION-DESIGN §11): reclaim runs orphaned by a previous host-lane engine
660
- // process (orphaned `running` → failed + release its EEXIST-blocking claim; exclude resume-in-progress rows). The
661
- // logic + its null-safety (runStore undefined on the env-only/no-backend lane) live in the unit-tested
662
- // `reclaimOrphansAtBoot` helper — extracted after a workflow re-review caught a boot crash there (HIGH, 1.49.0).
663
299
  await reclaimOrphansAtBoot(runStore, checkpointStore, logger);
664
- // Sandbox-image-pool index (IMAGE-API-DESIGN.md §2): enables /v1/images* query + select. TiDB-backed projection
665
- // of the bake manifests + publish lifecycle. Present whenever a pool is — read-only catalog until P2/P3 bakes.
666
- const imageIndex = backend?.imageIndex ? backend.imageIndex() : undefined; // local omits imageIndex() → sandbox-image-pool is cloud/fleet-only
667
- // RFC A2 (A 切片先行): the model-facing SelectEnvironment tool — mounted via spec.tools
668
- // (RFC 判定②(a)) whenever the per-task image chain is actually usable (k8s backend + image catalog), opt-out
669
- // via SELECT_ENVIRONMENT_TOOL=false. Viewer scope is rebuilt per-execute from the Runner-threaded read-only
670
- // ctx.principal (auth-channel-derived — a tool argument can never widen visibility).
300
+ const imageIndex = backend?.imageIndex ? backend.imageIndex() : undefined;
671
301
  const selectEnvTool = config.selectEnvironmentTool && config.remoteExec?.provider === "k8s" && imageIndex
672
302
  ? selectEnvironmentTool({
673
303
  catalog: imageIndex,
@@ -675,28 +305,11 @@ async function main() {
675
305
  viewerFor: (principal) => ({ operator: explicitOperatorOk(principal, config.operatorPrincipals), tenantId: principal ?? null }),
676
306
  })
677
307
  : undefined;
678
- // Sandbox-image-pool BAKE control plane (IMAGE-API-DESIGN.md §P2): enables /v1/images/bakes* when a pool exists
679
- // AND IMAGE_BAKES_ENABLED. The config boot-invariant already fail-fasts an empty OPERATOR_PRINCIPALS (a
680
- // world-writable build-host-RCE door). image-api stays the UNPRIVILEGED controller; the privileged build runs
681
- // on the sema-registry-managed bake-runner worker on build-host (it claims + ingests over the internal routes).
682
- const imageBakes = backend?.imageBake && config.imageBakes.enabled ? backend.imageBake() : undefined; // local omits imageBake() → cloud/fleet-only
308
+ const imageBakes = backend?.imageBake && config.imageBakes.enabled ? backend.imageBake() : undefined;
683
309
  if (imageBakes)
684
310
  logger.info("image_bakes_enabled", { operators: config.operatorPrincipals.length, runner: config.imageBakes.runnerPrincipal });
685
- // Durable HITL surface = checkpointStore ALONE (DURABLE_APPROVAL=true). It mounts AskUserQuestion-suspend (the
686
- // agent asks the human + the portal approval center) AND the resumable checkpoint. The F4 TOOL-APPROVAL
687
- // gate is an ADDITIVE layer driven by APPROVAL_REQUIRE — empty = no tool is gated, but AskUserQuestion HITL
688
- // still works. (Decoupled 2026-06-13: previously these were coupled, forcing a placeholder APPROVAL_REQUIRE
689
- // to get HITL — clay: OA writes don't gate (OA-backend RBAC is the authority), but portal HITL must work.)
690
- // durableEnabled 已在 runnerDeps 前按同一条件求值([1.294 G1] 前置声明处)——checkpointStore 构造条件与其
691
- // 同源,恒有 durableEnabled === !!checkpointStore。
692
311
  if (durableEnabled)
693
312
  logger.info("durable_approval_enabled", { toolApprovalGate: config.approvalRequire.length });
694
- // DESIGN-269 车2 件3 —— server-as-peer 上场的**感知链路**(#266 memory_persistence_capable_auto_declared
695
- // 同族纪律:一条对外暴露面上了线,必须在 boot 日志里一眼可核)。这行是 `A2A_SERVE_ENABLED=true` **唯一**
696
- // 的启动期回声:运维据此确认「这台机现在对外是一个 A2A agent、卡在这个路径上、公告的是这个 URL、
697
- // 声明了几条 skill」。OFF(默认)⇒ 一个字都不发,零噪声。
698
- // ⚠️ 半配置的诚实:开了旋钮却没有 durable run store 时,两个 RPC 方法会回具名 -32004 —— 那不是静默
699
- // 降级,但它发生在**调用时**,而运维想在启动时就知道。所以这一行连带把 runStore 在不在场说出来。
700
313
  if (config.a2aServe) {
701
314
  logger.info("a2a_serve_enabled", {
702
315
  card: "/.well-known/agent-card.json",
@@ -704,89 +317,41 @@ async function main() {
704
317
  advertisedUrl: config.a2aServe.url,
705
318
  skills: config.a2aServe.skills.length,
706
319
  blockingWaitMs: config.a2aServe.blockingWaitMs,
707
- // false = 卡照发(发现面不依赖 store),但 message/send 与 tasks/get 会回 -32004 UnsupportedOperation。
708
320
  tasksUsable: runStore !== undefined,
709
321
  });
710
322
  }
711
- // 修4(三路复审 W3):the fs-write ask gate (resolveSpec, applyTaskSettings wiring below) mounts purely off the
712
- // host-semantics lane — DELIBERATELY decoupled from any approval face, so no assembly path is ever gateless:
713
- // with no live coordinator and no durable park, a permissionMode default/auto Write/Edit ask fail-closed
714
- // auto-denies via core resolveAsk (onAsk absent ⇒ headless auto-deny, loud in-transcript). What that posture
715
- // must NOT be is silent at boot: an operator who explicitly set TOOL_APPROVAL_ENABLED=false (or a multi-tenant
716
- // default) would otherwise read the resulting hand-tool denials as an outage. Warn once, at boot.
717
323
  if ((config.remoteExec === undefined || config.remoteExec.provider === "host") && !toolApproval && !durableEnabled) {
718
324
  logger.warn("fs_write_gate_no_responder", {
719
325
  hint: "host-semantics lane with no approval face: permissionMode default/auto Write/Edit asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
720
326
  });
721
327
  }
722
- // [1245]/[1248]② the workflow ask leg attaches on EVERY lane (not fs-dependent — see createWorkflowAskPolicy),
723
- // so a sandbox-lane deployment running self-orchestration with no approval face hits the same fail-closed
724
- // auto-deny class. Same loud-at-boot posture; host lanes are already covered by the broader warn above.
725
328
  if (config.selfOrchestrationEnabled && config.remoteExec !== undefined && config.remoteExec.provider !== "host" && !toolApproval && !durableEnabled) {
726
329
  logger.warn("workflow_gate_no_responder", {
727
330
  hint: "self-orchestration with no approval face: permissionMode default/auto/acceptEdits run_workflow asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
728
331
  });
729
332
  }
730
- // Finding #2: a single-user turnkey worker with no explicit approval/durable/commandPolicy gets an
731
- // ADJUDICATED auto-accept tool-policy BASELINE (createAllowDenyPolicy — a PRESENT effect-aware policy, so core's
732
- // write-capable hand tools are never UNGATED / unadjudicated). Log the posture so the auto-accept is OBSERVABLE,
733
- // not silent; tighten irreversible ops via APPROVAL_REQUIRE / AUTONOMY / commandPolicy (layered tighten-only).
734
333
  if (singleUserAutoAcceptBaseline) {
735
- // Blanket auto-accept over ALL write/hand/MCP tool surfaces (core's per-surface UNGATED enumeration — hand band /
736
- // destructive-MCP / first-party-write — is suppressed for single-user turnkey by design). Log it so the posture is
737
- // observable; tighten (which also re-surfaces per-surface gating) via APPROVAL_REQUIRE / AUTONOMY / commandPolicy.
738
334
  logger.info("tool_policy_single_user_default", {
739
335
  posture: "adjudicated auto-accept — ALL write/hand/MCP tool surfaces auto-allowed (core per-surface UNGATED enumeration suppressed for single-user turnkey)",
740
336
  tighten: "APPROVAL_REQUIRE / AUTONOMY / commandPolicy (re-surfaces + gates specific surfaces)",
741
337
  });
742
338
  }
743
- // D-G: a boot-time signal that this worker is in crypto-bound direct-door mode (trusted-header path disabled).
744
- // Pairs with the /health directDoorActive field for the DG-M2 go-live runbook (a positive go signal, not just
745
- // a behavioral 401). The boot invariant in config.ts already fail-fasts a half-configured direct door.
746
339
  if (config.directDoorActive)
747
340
  logger.info("direct_door_active", { iss: config.principalJwtIss, aud: config.principalJwtAud, operators: config.operatorPrincipals.length });
748
- // CC-parity (adversarial-review finding): a MULTI-TENANT deployment running self-orchestration WITHOUT worker
749
- // isolation is an availability risk — the in-process hardened VM is ESCAPE-isolated but NOT RESOURCE-isolated, so an
750
- // entitled tenant's workflow script can starve the host event loop (microtask loop / OOM), affecting every tenant
751
- // (src/orchestration/hardened-vm-runner.ts documents this). Loud boot warning so the operator sets
752
- // SELF_ORCHESTRATION_WORKER_ISOLATION=true. (Single-user is unaffected — the one principal owns its own worker.)
753
341
  if (config.requirePrincipal && config.selfOrchestrationEnabled && !config.selfOrchestrationWorkerIsolation)
754
342
  logger.warn("self_orchestration_no_worker_isolation", { detail: "multi-tenant self-orchestration without SELF_ORCHESTRATION_WORKER_ISOLATION=true — a tenant workflow can starve the host event loop (availability risk). Set it to enable worker_thread resource isolation." });
755
- // 🔒 (systematic-audit HIGH fix, defense-in-depth): a multi-tenant deployment (requirePrincipal) with NO service/trace
756
- // token now correctly fails CLOSED — fleet/trace/session-list are owner-gated, never fleet-wide (isFleetWide, server.ts).
757
- // Warn the operator: without SERVICE_AUTH_TOKEN/authTokens/TRACE_TOKEN those endpoints only ever return the caller's own
758
- // rows (safe, but may surprise an operator expecting cross-tenant ops visibility — set a token for ops-relay).
759
343
  if (config.requirePrincipal && !config.authToken && Object.keys(config.authTokens ?? {}).length === 0 && !config.traceToken)
760
344
  logger.warn("multi_tenant_no_service_token", { detail: "REQUIRE_PRINCIPAL is on but no SERVICE_AUTH_TOKEN/authTokens/TRACE_TOKEN — fleet/trace/session-list are owner-gated only (never fleet-wide, tenant-isolation safe). Set a token for cross-tenant ops visibility." });
761
- // Rate limiter: TiDB-backed (cross-replica SOFT limit, same write-behind counter as the cost quota)
762
- // when a pool exists, else in-memory per-replica. Both satisfy RateGate. (design/30 P0-2)
763
- //
764
- // 🔴 #322 批1:与 costQuota 同一条改造(恒构造 + 0=关断哨兵)——修前 `rateLimitPerMin > 0` 才构造,
765
- // 默认部署零实例,于是热改 0→N 无座可 set。恒构造之后 0↔N 是纯值变(boot/limit-sync.ts 一次 setLimits),
766
- // 关断态由座位自己表达:恒放行、不记窗、SQL 腿不起刷新环(零 SQL)。
767
- const rateLimiter = backend?.rateLimiter // local omits rateLimiter() → in-memory per-process RateLimiter (single-replica)
345
+ const rateLimiter = backend?.rateLimiter
768
346
  ? backend.rateLimiter(config.rateLimitPerMin, counterDegradeHook).startRefresh()
769
347
  : new RateLimiter(config.rateLimitPerMin);
770
- // Capability layer: scenario → {tools (+ subagent roster), prompt, skills}. Heavy bits (skills,
771
- // git client) are built ONCE here; per request we only select a scenario and bind coordinates.
772
348
  let skills = loadSkills(config.skillsDir);
773
- // design/158 A10 尾刀:center skills overlay + plugins 消费 + boot LKG 落盘/skill-stale 判定 + mcp
774
- // 解析搬到 boot/config-center.ts(逐字)。⚠️ 位置即契约:loadSkills 之后、buildScenarios 之前 ——
775
- // LKG 落盘点必须晚于 skill 正文装载(F7/codex R26),plugins 让位判据要求 plugins 晚于 center 直发 skills。
776
349
  skills = await configCenter.applyCenterCapabilities(skills);
777
350
  const repoClient = config.gitApiBaseUrl ? createRepoClient(config.gitApiKind, config.gitApiBaseUrl, config.gitApiToken) : undefined;
778
- // CC-parity: deployment-injected WebSearch backend (the leg core leaves open). Absent WEB_SEARCH_PROVIDER →
779
- // undefined → the default scenario doesn't assemble the WebSearch tool. The API key stays in the backend closure.
780
351
  const webSearchCfg = webSearchConfigFromEnv();
781
352
  const webSearch = webSearchCfg ? createWebSearchBackend(webSearchCfg) : undefined;
782
353
  if (webSearch)
783
354
  logger.info(`WebSearch enabled (provider=${webSearchCfg.provider}, maxResults=${webSearch.maxResults})`);
784
- // #81④ 探活(boot 期一次性,**默认 OFF**):部署方此前只能靠「跑一个真任务」验证搜索配没配对 ——
785
- // cli 的一键装 SearXNG 向导([2226] 裁 1)装完那一步正需要这个。消费 core 的 `probeSearchBackend`
786
- // (不自铸第二真源,与 searxng adapter 同一条纪律)。
787
- // 🔴 **失败只 warn,不拒启**:搜索是可选工具,配错不该挡住整个服务(功能型能力缺席 ⇒ 降级;
788
- // 与本仓「保护型 ⇒ fail-closed」的方向判据是同一条判据的两面)。
789
- // 🔴 **不 await**:探活是一次真出网,让它拖住 boot 就是把一个可选工具变成启动路径上的依赖。
790
355
  if (webSearch && shouldProbeWebSearchOnBoot()) {
791
356
  void probeSearchBackend(webSearch.search, { timeoutMs: 10_000 })
792
357
  .then((r) => {
@@ -797,56 +362,16 @@ async function main() {
797
362
  })
798
363
  .catch((e) => logger.warn("web_search_probe_failed", { provider: webSearchCfg.provider, error: e instanceof Error ? e.message : String(e) }));
799
364
  }
800
- // core 1.382([1561] 提货单①②,design/153 parked 状态机)「同车必接」的 ensureChildSessionDurable 挂点:
801
- // 只在 checkpointStore 真在场时构造(与 core parkEligible 门「两者全在场才成立」的判据同源——checkpoint
802
- // 不在场时构造这个闭包毫无意义,连带把「local/无 durable backend 部署不该跑这条 acquire/release」的
803
- // 边界直接编码进构造条件,不用额外判断)。实现体拆到 `ensureSessionDurable`(session-store.ts,可独立
804
- // 测试;双复审②抓的 F-7 同族空壳缺口——host 店可能存在 `register()` 留下的零内容 session_meta 行——
805
- // 的校验也在那边)。
806
- // 🔴 [1575] F3 修(cli 复查,红先行确认真红):`checkpointStore` 在场 ⇏ `sessionStore` 就是 TiDB 真店
807
- // ——这是两个独立 config 轴(DB_BACKEND 供 checkpoint/runs 等用途 vs SESSION_BACKEND 单独控 session
808
- // 落哪),`SESSION_BACKEND` 显式留在默认值时 `sessionStore` 是纯内存 `TtlSessionStore`(无 repo 构造,
809
- // core `release()` 语义在该形态下是**整删会话**,不是"归还租约")——探针的 finally release 会销毁它
810
- // 刚证明存在的会话。追加门 `config.sessionBackend === "tidb"`(此刻已被前面装配区从 "auto" 归一,
811
- // 恒是 "tidb"|"memory" 二选一,时序安全):唯一保留可达的店形正是 release() 真安全的那种
812
- // (TiDBSessionStore.release 逐字注释「Durable history is retained; just drop any in-flight cache
813
- // entry」)。非 tidb 场景下 undefined = 子代按 pre-153 settle failed,零回归(与 checkpointStore 缺席
814
- // 同一处置)。
815
- // [1593] 只查不迁 → 主动晋升(cli TiDB 真机撞获:bg 子会话住 subRunner 私有 TTL 店、从不在 host 店,
816
- // 旧形恒 veto → core 当场 expire checkpoint(14ms~2s)+ settle failed,赎回窗口不存在)。晋升链=
817
- // 快查 host → promoteToHost(整树拷贝+私店 forget+路由记 host)→ 同一严检复验(leaf 非空);任何
818
- // 一步失败仍如实 veto(pre-153 行为,不放大)。tidb 门与 F3 裁定不变。
819
365
  const ensureChildSessionDurable = checkpointStore && config.sessionBackend === "tidb" ? (sessionId) => ensureChildSessionDurableWithPromotion(sessionStore, subRunnerSessions, sessionId) : undefined;
820
- // [1900]/[1904] WebFetch 摘要器:core 工厂 + summarize 角色模型(cheap tier;resolveTaskModel 自带
821
- // 默认回落——与压缩线同族档位,不新造)。构造一次全场景共享;多租户形 WebFetch 整体被 filter,
822
- // 本注入无效化由 scenarios 现行为保证。惰性解析 / fail-soft(E-MED-2/E-MED-3)与本席的 key plane
823
- // (A-057.5:#303 的第四条直发面)都在 `boot/webfetch-summarize-lane.ts`,顶注写着全部判据与红先读数。
824
366
  const webFetchSummarize = createWebFetchSummarizeLane({ brain, config, getKeyResolver: () => configCenter.getKeyResolver(), logger });
825
- // design/158 S4:标注 `: ScenarioDeps` —— 无标注的字面量连自己的键名都不校验(打错/多写一个键
826
- // 只是多一个没人读的属性,编译期无声),这是 1.304 落错家那一族的另一半土壤。
827
367
  const scenarioDeps = {
828
368
  runner, subRunner, handslessSubRunner, model: "default", skills, repoClient, requirePrincipal: config.requirePrincipal,
829
369
  webSearch: webSearch ? webSearch : undefined, metrics, logger, webFetchSummarize,
830
- // core 1.364 写半场:与 RunnerDeps.backgroundAgentStore 同实例(组装区注释;半接=静默死特性)。
831
370
  backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
832
- // core 1.382 parked 状态机「同车必接」一对——checkpointStore 与 main runner 的 checkpointStore(subRunner
833
- // 构造区上方 const)同实例;ensureChildSessionDurable 见上方构造闭包注释。
834
371
  checkpointStore: checkpointStore ? checkpointStore : undefined,
835
372
  ensureChildSessionDurable: ensureChildSessionDurable ? ensureChildSessionDurable : undefined,
836
- // #318([4659] F2 → [4692] 定谳):会话级任务清单店解析器(boot/task-list-lane.ts 的产物,
837
- // openStores 里按后端三态构造一次)。修前不接这一键 ⇒ core 每 turn 铸一只私有内存店。
838
373
  taskListStoreFor: taskListLane.storeFor,
839
374
  brandIdentity: config.configProvider === "local",
840
- // core 1.287 extraTools seam ([802]③ server half): SendUserFile reaches the delegated-child roster. SAME
841
- // existence gate as the top-level mount (the one sendUserFileToolSpec built above — lane/tenancy/config
842
- // posture already encoded in its presence). The spec is execute-time late-bound (ctx.taskId →
843
- // TaskEnvRegistry lookup; emit via ALS to the top-level frame tail), so returning the SAME instance per
844
- // spawn is correct — per-spawn state lives in the registry, not the spec ([797] confirmed with core).
845
- // codex F1/F2 gates (subagentSendUserFileExtraTools): (F1) the subRunner carries no toolPolicy and core
846
- // does not propagate the parent's policy into child specs, so an APPROVAL_DENY/ask-gated SendUserFile was
847
- // reachable from delegated children — the factory now pre-checks the LIVE deployment approval face
848
- // (deny/require/neverAuto, canonical-space) and injects nothing when gated (conservative stopgap until a
849
- // core child-policy seam lands); (F2) read-only builtin agents (Explore/Plan) never receive the tool.
850
375
  subagentExtraTools: sendUserFileToolSpec
851
376
  ? subagentSendUserFileExtraTools({
852
377
  spec: sendUserFileToolSpec,
@@ -855,34 +380,7 @@ async function main() {
855
380
  : undefined,
856
381
  };
857
382
  const scenarios = buildScenarios(scenarioDeps);
858
- // design/153 件3d:/decide parked 赎回腿的裸 Agent 工具——boot 期从**内建 default** 场景工厂取一份
859
- // (必须在 center overlay 盖名之前取:赎回腿要的是与 spawn 同源的生产装配,subRunner/bg opts/
860
- // checkpointStore 同实例天然成立;per-request 项如 webSearch 对 revive 无关——objective 在 core
861
- // driveParkedResume 里被丢弃)。checkpointStore/agentStore 任一缺席 = park 本就不可达,分支不接线。
862
- // #318:这条取用**刻意不带** ScenarioContext —— boot 期没有任何一条真会话,给它编一个假 sessionId
863
- // 会在清单表里铸出一份无人认领的分区。它只取裸 Agent 工具,task-list 家族在这份 bundle 里根本不被
864
- // 消费([4692]③ 已核),缺席 = 修前字节形。
865
- // A-067 C3-3(F1b):裸 "Agent" 字面量换 core 单源常量——runtime-governance.ts 同概念已用
866
- // DEFAULT_SUBAGENT_TOOL_NAME(5.0.0 RB-476 旧名 "Task" 改名史=这个字面量真漂过一次)。
867
383
  const parkedReviveTool = checkpointStore && backgroundAgentStore ? scenarios.default?.({})?.tools.find((t) => t.name === DEFAULT_SUBAGENT_TOOL_NAME) : undefined;
868
- // [1596]/[1597] 跨副本父约束重供席工厂:host 任务的 toolPolicy 解析槽按**当前**部署配置重建
869
- // 「部署 ⊇ 操作员」两层完整链(design/181 件二收编;实现与全部理由在 boot/parked-revive-gate.ts,
870
- // 提出去的唯一理由是 main.ts 顶层 `void main()` 让那条腿的运行期语义在原地一格都钉不住)。
871
- // 构造条件逐字保持:裸 Agent 工具在场 ∧ 部署真开了 durable 审批。
872
- // `approverSeat`:与 `RunnerDeps.onAsk` **同一个具名工厂**(单一属主,禁在此处手搓等价闭包)。core
873
- // 5.22.0 起链条目的 `durableMandate` 位进了摘要,而该位的判据正是这只席位在不在场——两处若不同源,
874
- // 赎回腿算出的摘要与 park 记的对不上,跨副本赎回整条腿被 pre-CAS 拒(见 parked-revive-gate.ts
875
- // `mandatePostureOf`)。此处新铸的转发闭包与 runnerDeps 那只行为逐字相同(都只转 `toolApproval.ask`),
876
- // 摘要只看在场性;仅 core 的 hook 席位去重按函数身份判,那一侧的身份未命中是**多筛一次**(core 自述
877
- // 的保守方向),不是漏筛。
878
- // A-010.1 同族的另外两位(`resolveRuntimeCaps` / `autoModeSeatMounted`):5.22.0 的摘要卷进的是**三**位
879
- // 决议链元数据,其中 `autoModeArmed` 与 `durableMandate` 的 `forceDurableGate` 项都由 per-principal 的
880
- // `RuntimeCaps` 决定。两处必须与 core 看到的**同一只**事实源接线,否则同样是「park 记 true 而赎回腿供
881
- // false ⇒ 摘要恒不匹配 ⇒ 这批 principal 的卡永不可赎」:
882
- // · `resolveRuntimeCaps` = 递给 `RunnerDeps.runtimeCapsResolver` 的同一只(单一属主 boot/runtime-caps.ts);
883
- // · `autoModeSeatMounted` = **读** runnerDeps 上那一位,不写字面 `true` —— core 的武装式是
884
- // `runtimeCaps.autoMode === true ∧ deps.autoMode !== undefined`,第二个半场的真值只有 runnerDeps 知道,
885
- // 在此处手抄 `true` 会在 runner-deps 那边改成有条件挂载的那一天静默错供一位。
886
384
  const parkedReviveInheritedGate = parkedReviveTool && config.durableApproval
887
385
  ? createParkedReviveInheritedGate({
888
386
  config, question, approvalExemptionStore, logger, localRoot,
@@ -891,20 +389,11 @@ async function main() {
891
389
  autoModeSeatMounted: runnerDeps.autoMode !== undefined,
892
390
  })
893
391
  : undefined;
894
- // 场景详情:内建 details 必须在 overlay 合并【前】构建(探针要打纯内建工厂,不是被 center 顶掉的);
895
- // center details 随 overlay 同判定源盖同名——source 语义与 selectScenario 实际取用永一致(约定①)。
896
392
  const scenarioDetails = builtinScenarioDetails(scenarios, scenarioDeps);
897
- const builtinScenarioNames = Object.keys(scenarioDetails); // overlay 前捕获(六内建名)
898
- // design/158 A10 尾刀:center 声明场景 overlay 搬到 boot/config-center.ts(逐字)。⚠️ 位置即契约:
899
- // 必须在内建 details + 六内建名捕获之后 —— 探针要打纯内建工厂,不是被 center 顶掉的。
393
+ const builtinScenarioNames = Object.keys(scenarioDetails);
900
394
  configCenter.applyCenterScenarios({ scenarioDeps, scenarios, scenarioDetails, builtinScenarioNames });
901
395
  logger.info("capabilities", { skills: skills.length, scenarios: Object.keys(scenarios), repoTools: Boolean(repoClient) });
902
- // Background reapers: recover runs whose instance died, expire never-answered approvals, sweep
903
- // rate windows. Unref'd so they never hold the process open.
904
- // design/80 D-D (SLA-timer): the deny-sweep reuses the server's resumeCheckpoint, so it is wired in AFTER
905
- // createHttpServer returns it (below) via this holder — the reaper is defined before the server.
906
396
  let runDenySweep;
907
- // design/158 A10:reaper 段搬到 src/boot/reapers.ts(逐字;deny-sweep 晚绑改取值,见该文件头注)。
908
397
  const reaper = startReapers({
909
398
  config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore,
910
399
  rateLimiter, costQuota, toolResultStore, fileSnapshotStore, taskAttachmentStore, imageBakes, worktreeReap,
@@ -912,11 +401,6 @@ async function main() {
912
401
  backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores,
913
402
  getRunDenySweep: () => runDenySweep,
914
403
  });
915
- // #270 车2 —— 托管留存 sweep lane(**独立**定时器,不折进上面那条维护 tick;理由逐字见
916
- // `boot/retention-lane.ts` 的文件头:那二十条腿全都容忍多副本,而本 lane 必须单执行者)。
917
- // 两只店在场性已由上面的自持门保证(interval>0 而店缺席 ⇒ 那道门已经拒启了);这里的
918
- // `&&` 只是把类型收窄出来,不是第二道门。
919
- /** 租约 TTL = 2× sweep 间隔(§3)。抢租与每 domain 的续租共用同一个值 —— 两处手抄迟早只改一处。 */
920
404
  const ttlMs = config.retentionSweep.intervalSec * 1000 * RETENTION_LEASE_TTL_FACTOR;
921
405
  const retentionLaneStarted = retentionExecutor !== undefined && retentionLaneStore !== undefined && config.retentionPolicy !== undefined
922
406
  ? startRetentionLane({
@@ -924,20 +408,15 @@ async function main() {
924
408
  policy: config.retentionPolicy,
925
409
  mode: config.retentionSweep.mode,
926
410
  executor: retentionExecutor,
927
- // 租约 holder = 本副本的 `instanceId`(与 run 行的 claim 同一个身份):审计行里的 holder/token
928
- // 于是能直接对上"哪台副本跑的那一轮"。
929
411
  lease: {
930
412
  acquire: async () => {
931
413
  const claim = await retentionLaneStore.acquire(instanceId, ttlMs, Date.now());
932
414
  return claim.held ? { held: true, fencingToken: claim.fencingToken } : { held: false };
933
415
  },
934
- // 复核**并续租**(一条 CAS)——续租的机会只有这一处:重入守卫会跳过下一 tick(理由逐字见
935
- // `boot/retention-lane.ts` 的 `startRetentionLane` 头注与店侧 `renew` 的顶注)。
936
416
  renew: (fencingToken) => retentionLaneStore.renew(instanceId, fencingToken, ttlMs, Date.now()),
937
417
  release: () => retentionLaneStore.release(instanceId),
938
418
  },
939
419
  audit: { append: (row) => retentionLaneStore.appendAudit(row, Date.now()) },
940
- // 省调预检(§5 v1.3:**不承重** —— 承重判在店事务内的哨兵行锁上)。
941
420
  holdInForce: (domain) => retentionLaneStore.holdInForce(domain),
942
421
  logger,
943
422
  metrics,
@@ -952,25 +431,14 @@ async function main() {
952
431
  holder: instanceId,
953
432
  });
954
433
  }
955
- // Optional OTLP/HTTP metrics export (1.37). Periodically pushes the registry to an OTel collector;
956
- // best-effort (a collector outage is logged, never affects serving). /metrics stays available too.
957
434
  const otelExporter = config.otel
958
435
  ? startOtlpExporter(metrics, {
959
436
  ...config.otel,
960
437
  onError: (err) => logger.warn("otel_export_failed", { err: String(err) }),
961
438
  })
962
439
  : undefined;
963
- // design/158 A10 尾刀:60s refresh 循环 + boot-deferred 到货续接搬到 boot/config-center.ts(逐字)。
964
- // ⚠️ 位置即契约:必须在 new Runner 之后 —— runnerTierFrozen 与构造函数读的是同一 config 代际。
965
- // #322 批1:限额座的换代口(恒构造的两只座 —— rateLimiter 上面建、costQuota 在 budget-tracing 段建)。
966
- // boot 期先 sync 一次:让「上一代读数」落位,于是第一次真变更才会留 `limit_gates_reloaded` 那一行。
967
440
  const limitSync = createLimitSync({ config, rateGate: rateLimiter, quota: costQuota, logger });
968
441
  limitSync.sync();
969
- // core 5.49 `Runner.swapModels` 接缝([4775]①,models-tiers defer 臂退役):plane 候选在 commit 前
970
- // 喂给**全部** boot 单例 Runner 原子换代。五实例必须同代——swap 替代 restart 后,漏换的长命 Runner
971
- // 会永久停在旧代(比 defer+restart 更糟);leader lane 的 Runner 是逐调用现构(读 config.models 活
972
- // 引用),不在此列。校验对同一候选输入是确定性的:首个 swap 抛 ⇒ 零实例换装、候选整拒;首个成功后
973
- // 后继实例抛=结构性不可达,若真发生则 error 留痕后照抛(下一拍幂等重 swap 收敛)。
974
442
  const bootRunners = [
975
443
  ["runner", runner],
976
444
  ["handsless", handslessRunner],
@@ -992,23 +460,14 @@ async function main() {
992
460
  }
993
461
  };
994
462
  configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerModels });
995
- // design/158 A10 尾刀:per-model key resolver 首建(+ Bearer 网关组合告警)搬到 boot/config-center.ts。
996
- // ⚠️ 位置即契约:首建读的必须是 boot apply **之后**的 config.modelApiKeyEnv/modelApiKeys。
997
463
  configCenter.initKeyResolver();
998
- // design/158 A10:session 只读/删除面搬到 src/boot/session-faces.ts(逐字;watch→purge 次序即契约,见该文件头注)。
999
464
  const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
1000
465
  config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore,
1001
466
  resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileSnapshotStore,
1002
467
  workflowCompletionInbox, taskListLane,
1003
468
  });
1004
- // design/158 A10:leader 段搬到 src/boot/leader.ts(逐字)。
1005
469
  const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend });
1006
- // Graceful drain: shared mutable state — SIGTERM flips `draining` (shutdown below), createHttpServer
1007
- // assigns `inflight` (live leg count on this instance), /health + the submit 503 gate read it.
1008
470
  const drainState = { draining: false };
1009
- // 鉴权桥(registry-core api/auth-bridge):SEMA_REGISTRY_URL 在位即点亮(零新配置——worker 已信
1010
- // registry 的 effective config,同一信任根延伸到其 JWKS);split 部署用 AUTH_BRIDGE_ISSUER/JWKS_URL/AUDIENCE
1011
- // 显式覆盖。缺 registry(纯 env 部署)=分支不存在,bearer 行为与今天字节一致。
1012
471
  const authBridgeIssuer = process.env.AUTH_BRIDGE_ISSUER || config.configCenter?.baseUrl;
1013
472
  const registryJwtVerifier = authBridgeIssuer
1014
473
  ? createRegistryJwtVerifier({
@@ -1021,238 +480,129 @@ async function main() {
1021
480
  : undefined;
1022
481
  if (registryJwtVerifier)
1023
482
  logger.info("auth_bridge_enabled", { issuer: authBridgeIssuer });
1024
- // asyncRewake wake 管道 holder:server 启动时装 deliver(session→本副本活流 steer);createTaskHooks 的
1025
- // ctx.wake 闭包经它投递(main 建壳/server 填实,双向不循环依赖)。
1026
483
  const hookWakeBus = {};
1027
- // ═══════════ #154 件二:启动期静态装配自检(coordinators 构造后、listen 之前)═══════════
1028
- // core 的 `describeStaticWiring` 与本仓的装配谓词是**两个独立实现**在回答同一组问题。答案矛盾 =
1029
- // 装配谎言(不是「配置不理想」:是服务端对自己接了什么线有两套互斥说法),fail-loud 拒启 ——
1030
- // 与 memory+durable 两条既有拒启同族。判据本体在 http/routes/diagnostics.ts(诊断读面与拒启判据同源)。
1031
- //
1032
- // specTemplate = resolve-spec 对**每一条**任务腿都会盖的那两个 durable 键(它的 durableEnabled 分支)。
1033
- // 静态半场只读它们的**在场性**(`parkDurableApprovalOptIn = spec.durableApproval !== undefined`),
1034
- // 所以这里给的是那个形,而不是某条腿的 principal 真值。
1035
484
  const wiringSpecTemplate = durableEnabled
1036
485
  ? { checkpointStore, durableApproval: { scope: encodeCheckpointScope(undefined) } }
1037
486
  : {};
1038
487
  const staticWiring = describeStaticWiring(runnerDeps, wiringSpecTemplate);
1039
- // `parkFacility` 传的必须是**协调器真喂给 `resolveStreamApprovalGate` 的那个实参**(durableEnabled),
1040
- // 不是在这里重算一遍 —— 重算会把「两个站点算出了不同答案」这件正要被查的事抹平。
1041
488
  assertStaticWiringConsistent({ manifest: staticWiring, parkFacility: durableEnabled, backendKind: backend?.kind }, logger);
1042
- // operator 日志面(**非 wire**)。`configFingerprint` 只有 effective 半场才铸(core 按腿算),静态半场
1043
- // 没有这一位 ⇒ 诚实报 null,不自造一个服务端哈希(那会是一个要跨仓对表的新语义)。
1044
489
  logger.info("wiring_static", {
1045
490
  fingerprint: staticWiring.configFingerprint ?? null,
1046
491
  parkEffective: staticWiring.parkLane.effective,
1047
492
  sessionDurability: staticWiring.session.store,
1048
493
  posture: staticWiring.interaction.posture,
1049
494
  streamApproval: streamApprovalGate.active ? "active" : streamApprovalGate.reason,
1050
- // debt #292-manifest:C18 部署级读数上 operator 日志——`full` = agent 行店在线 ∧ session 店
1051
- // 声明 subagent placement 分区 durable(ForkRoutingSessionStore 从 host 透传声明)。诊断端点
1052
- // GET /v1/diagnostics/wiring 携带全量 manifest,这里只投这枚部署级结论。
1053
495
  subagentTranscripts: staticWiring.fleet.subagentTranscripts,
1054
496
  });
1055
- // ═══════════ design/158 A8:ServiceDeps 分组装配(七组窄接口)═══════════
1056
- // 每组一个**带类型标注**的中间 const —— 标注位触发 TS 多余属性检查,键放错组当场编译红。
1057
- // (平铺形做不到:69 键的全可选面里任何键名都「属于」它,于是键落错家/从未接线只能靠源码钉
1058
- // 逐个盯 —— 1.300/1.301/1.304/1.307/1.310 五起真缺陷都是这个病族。)组形状门见
1059
- // test/deps-literal-shape-gate.test.ts;消费点仍读平铺 `deps.x`(createHttpServer 入口摊平)。
1060
- /** durable 持久面(键 present ⇔ 路由/能力位在场;absent ⇒ 501 诚实缺席) */
1061
497
  const stores = {
1062
498
  runStore,
1063
- resumeAnchorStore: resumeAnchorStore ? resumeAnchorStore : undefined, // E18 resume-at eventId→entryId map (tidb/pg/local)
1064
- approvalExemptionStore: approvalExemptionStore ? approvalExemptionStore : undefined, // decide remember="session" + list/revoke
499
+ resumeAnchorStore: resumeAnchorStore ? resumeAnchorStore : undefined,
500
+ approvalExemptionStore: approvalExemptionStore ? approvalExemptionStore : undefined,
1065
501
  checkpointStore,
1066
- sessionPolicyStore: sessionPolicyStore ? sessionPolicyStore : undefined, // E6 operator session-rule store (PUT/GET /v1/sessions/:id/policy)
1067
- ruleConsent, // #154 车二:CC settings 导入两口的属主(缺席 ⇒ 两口 501)
1068
- // E19 cap: core's gate-split (1.134.0) REMOVED the isRemoteExecutionEnv skip — core now snapshots each
1069
- // completed turn + restores on resumeAt for ANY ExecutionEnv when fileSnapshotStore is wired (captureManifest/
1070
- // applyManifest run over any env's FileSystem ops; a 30s timeout bounds a slow remote walk). So rewind works for
1071
- // host/e2b/k8s/ssh/adb/local-docker AND the in-process worker → advertise `rewindFiles` whenever the store is wired.
502
+ sessionPolicyStore: sessionPolicyStore ? sessionPolicyStore : undefined,
503
+ ruleConsent,
1072
504
  fileSnapshotStore: fileSnapshotStore ? fileSnapshotStore : undefined,
1073
- // [3321] tool-results 读面(GET /v1/tasks/:id/tool-results/:ref)的数据源。传的是 **durable** 的那一只
1074
- // (`toolResultStore`,present ⇔ 有 store backend),**不是** Runner 侧的内存兜底 `runnerOffloadStore`:
1075
- // 无 backend 部署里那只是进程内的、跨副本读不到的,把它接上读面只会让调用方读到「有时有有时无」。
1076
- // undefined ⇒ 路由 404 同形(理由见 ServiceStoreDeps.toolResultStore 的头注)。
1077
505
  toolResultStore,
1078
- taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined, // D-1 上传/取回/删除三动词面
1079
- // design/153 件3d(/decide parked 赎回腿):durable bg 行店 + boot 裸 Agent 工具,与 RunnerDeps/
1080
- // scenarioDeps 同实例(claim/expire/consumeParkedFlip 作用于同一行)。任一缺席=分支不存在。
506
+ taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
1081
507
  backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
1082
- // §0.5 session-ownership: the durable session abstraction (SessionRepo-style list/fork/delete) + the delete
1083
- // coordinator. `ownerAware` is the (possibly cache-wrapped) session store; both delegate to the DB-backed
1084
- // TiDB/PG store. Undefined-safe: on an in-memory dev store these carry no SessionRepo ops → the routes 501.
1085
508
  sessionStorage: ownerAware,
1086
509
  workflowRunStore: workflowRunStore ? workflowRunStore : undefined,
1087
- workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined, // [1402] journal 读面(GET /v1/workflows/:id/journal 数据源,与 runner 同实例)
510
+ workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined,
1088
511
  imageIndex,
1089
512
  imageBakes,
1090
- outcomeSink: outcomeSink ? outcomeSink : undefined, // P2: GET /v1/outcomes 读面(design/73 §7.2 仪表盘底料)
1091
- sendFileLedger: sendFileLedger ? sendFileLedger : undefined, // SendUserFile 账本读面:GET /v1/sendfile-links(owner-scope 镜像 sessions list)
1092
- // 2c session-sync (P1d): the WHOLE backend, because the /v1/sessions/:id/sync/* routes fan out across session
1093
- // entries + fileSnapshot + sessionPolicy + resumeAnchor, which must come from ONE backend in the §8 atomic
1094
- // order. (2026-07-29 纠正:原写「so the routes can call exportSession/importSession」——路由早已是流式实现,
1095
- // 不调那两个整包编排器;它们现为 test-only,判定见 src/session-sync.ts 顶注。理由不变,指路改对。)
1096
- // Undefined-safe: the env-only/no-backend worker 501s the routes.
513
+ outcomeSink: outcomeSink ? outcomeSink : undefined,
514
+ sendFileLedger: sendFileLedger ? sendFileLedger : undefined,
1097
515
  backend: backend ? backend : undefined,
1098
516
  };
1099
- // [3193] 病理1:fleet 快照的 recovery 屏障(deferred 形,声明先于 deps 装配、落定在下方 recover 块——
1100
- // 对装配顺序鲁棒;无 notify gate 的部署在 recover 块 else 臂立即落)。
1101
517
  let resolveFleetRecovery;
1102
518
  const fleetRecoveryDone = new Promise((r) => { resolveFleetRecovery = r; });
1103
- /** 进程内活体协调面(本副本 only,重启即空:HITL 协调器/可 steer 句柄注册表/总线/收件箱) */
1104
519
  const coordinators = {
1105
- elicitation: elicitation ? elicitation : undefined, // E23: live-only inbound-elicitation coordinator (respond route + ALS wraps)
1106
- question: question ? question : undefined, // §4④: AskUserQuestion live coordinator (respond route + ALS wraps on streaming legs)
1107
- toolApproval: toolApproval ? toolApproval : undefined, // [816]/[820]②: live tool-approval coordinator (respond route + ALS wrap on the sync streaming leg)
1108
- workflowAgentRegistry: workflowAgentRegistry ? workflowAgentRegistry : undefined, // SVC-5: steerable workflow-agent handle registry
1109
- subagentSteerRegistry, // C2 (core 1.219): steerable Task-subagent handle registry (POST /v1/runs/:id/subagents/:target/steer)
1110
- workflowCompletionInbox: workflowCompletionInbox ? workflowCompletionInbox : undefined, // P1 ①②: drain → workflow_complete frame on stream-open
520
+ elicitation: elicitation ? elicitation : undefined,
521
+ question: question ? question : undefined,
522
+ toolApproval: toolApproval ? toolApproval : undefined,
523
+ workflowAgentRegistry: workflowAgentRegistry ? workflowAgentRegistry : undefined,
524
+ subagentSteerRegistry,
525
+ workflowCompletionInbox: workflowCompletionInbox ? workflowCompletionInbox : undefined,
1111
526
  fleetBus,
1112
- fleetRecoveryDone, // [3193] 病理1:快照组装的有界等待信号(recover 块落定)
1113
- // [1196] session SSE 订阅:watch registry(N 订阅共享一条探针,自适应拍距,无订阅即停)。谓词与
1114
- // /head 探针同门(getLeafId+ownerOf);缺 store 面不建=路由 501+capabilities.sessionEvents=false。
1115
- sessionWatch: sessionWatchRegistry ? sessionWatchRegistry : undefined, // [1196] 建于 purgeSession 之前(delete 即 dropSession 的同副本围栏)
527
+ fleetRecoveryDone,
528
+ sessionWatch: sessionWatchRegistry ? sessionWatchRegistry : undefined,
1116
529
  hookWakeBus,
1117
- sendUserFile: sendUserFileEmitter ? sendUserFileEmitter : undefined, // SendUserFile 切片2:file_link 帧 ALS(bg/sync 腿 wrap)
530
+ sendUserFile: sendUserFileEmitter ? sendUserFileEmitter : undefined,
1118
531
  leaderEndpoint,
1119
- sessionTitler: sessionTitler ? sessionTitler : undefined, // fire-and-forget auto-title at first submit
532
+ sessionTitler: sessionTitler ? sessionTitler : undefined,
1120
533
  };
1121
- /** 函数/构件缝(main 把跨子系统的动作编译成一个可调用面交给路由) */
1122
534
  const seams = {
1123
535
  sessionAudit,
1124
536
  purgeSession: purgeSession ? purgeSession : undefined,
1125
- // [1488]③(b): bg agent-handle read face — GET /v1/runs/:id/subagents/:handle/output → the same registry
1126
- // face the TaskOutput tool reads (defaultTaskRegistry is the process singleton every run leg registers
1127
- // its background children into). Access tuple is derived from the run row in server.ts, never the caller.
1128
- // codex R2: kind-gated BEFORE the poll (backgroundAgentOutput) — generic pollTask has side effects on
1129
- // other kinds (workflow onServedTerminal ack, bash output-cursor advance).
1130
537
  subagentTaskOutput: (handle, access) => backgroundAgentOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
1131
- // [1499] CC TaskOutput/TaskStop 人侧对位 — the GENERIC task-handle verbs over the same registry singleton
1132
- // (kind gate at the seam: bash/monitor/agent pass, workflow refused — journal face owns it).
1133
538
  taskHandleOutput: (handle, access) => taskHandleOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
1134
539
  taskHandleStop: (handle, access) => taskHandleStop(defaultTaskRegistry, handle, access, backgroundAgentStore),
1135
- // 142-S5 §1.4: GET /v1/memory/export 数据源(DB memory plane only;file 形态 route 501)。
1136
540
  memoryExport: memoryExportBackend ? (scope) => exportMemoryScope(memoryExportBackend, scope) : undefined,
1137
- // 142-S2.5: POST /v1/memory/sync/:scope 执行位(同一 DB entry plane + sync_cursors 游标面;
1138
- // plan 随数据走——core 纯函数在 performMemorySync 内直调,file 形态 route 501)。
1139
541
  memorySync: memoryExportBackend && memorySyncCursors
1140
542
  ? (scope, syncReq) => performMemorySync(memoryExportBackend, memorySyncCursors, scope, syncReq)
1141
543
  : undefined,
1142
- // #264 v2-c(core 5.40.0):治理携出 bundle 的两口 —— POST /v1/memory/export · POST /v1/memory/import。
1143
- // 数据源是**装配好的记忆引擎本体**(不是 memoryExportBackend 那条 DB entry plane):bundle 是引擎
1144
- // 的单锁复合面(条目 + 托管链 + 挑战 + lineage + 污染标记同一个 epoch),不是条目读的超集。
1145
- // 🔴 两口共用一个在场判据,因为它们是同一个引擎面的两半(半挂 = 导得出导不进,而这条产品面的全部
1146
- // 意义就是那趟往返)。判据是**两项**:引擎在场 ∧ 其后端实现了 bundle 复合面 —— 后者由
1147
- // createMemoryBundleFaces 在**挂载期**判(返回 undefined ⇒ 两口不挂、能力位翻假、打上去诚实 501),
1148
- // 而不是照常挂着让每次调用确定性 409(codex 对抗复审 [medium] 驳回的那一版:一个恒假的 yes)。
1149
- // 绝不在本层为缺面的后端现造替代实现——理由逐字见 routes/memory-bundle.ts 的「能力面的诚实形」。
1150
544
  memoryBundleExport: memoryBundleFaces ? memoryBundleFaces.exportScopes : undefined,
1151
545
  memoryBundleImport: memoryBundleFaces ? memoryBundleFaces.importBundle : undefined,
1152
- // design/170 件A §7 收编:memory-policy 面的 `org:` 属主门与 core 准入 seam 读**同一个**目录实例
1153
- // (`createOrgMemoryAdmissionWiring` 的产物),两面因此共享 TTL 缓存/退避窗/gen 高水位 —— 一个进程
1154
- // 对「谁属于 org:acme」只有一个答案。缺席(无 center 且无 env 表)⇒ 策略面 `org:` 键仍 operator-only。
1155
546
  orgMemoryDirectory: orgMemoryAdmission.directory,
1156
- // design/177:HTTP 只读面与模型面共用**同一个** provider 实例 —— 两面看到的库集合按定义一致,
1157
- // 不可能出现「壳能读到模型读不到的库」。缺席 ⇒ `/v1/shared-memory/*` 整域不挂载(见 server.ts 域头)。
1158
547
  sharedMemoryStore: sharedMemoryStore ? sharedMemoryStore : undefined,
1159
- // sessionMirror 观测面(server 非执法端,论证在 ServiceDeps.sessionMirrorRuling):
1160
- // 与 executionRuling 同车同缓存(零额外 center RTT);无 center/dry-run ⇒ 不接线,观测面暗、零行为差。
1161
548
  sessionMirrorRuling: principalCaps
1162
549
  ? async (p) => (await principalCaps.executionRuling(p))?.sessionMirror
1163
550
  : undefined,
1164
551
  instrumentDegenerate,
1165
- sideQueryAccounting, // [1469] side-query 四路 sink 记账(与 tracer 同源装配)
552
+ sideQueryAccounting,
1166
553
  parkedReviveTool: parkedReviveTool ? parkedReviveTool : undefined,
1167
- // design/158 S2(F-1)修:工厂在 :2308 造好、server.ts 消费分支在等,但从未进本实参段——HIGH-1
1168
- // 同族第二例(条件 spread 的键从不接线,编译期无声)。缺席后果=带 requiresParentConstraint 的
1169
- // parked checkpoint 重启后恒被 core pre-CAS 门拒,**不可赎回**。接线钉:test/parked-revive-gate-wiring.test.ts。
1170
554
  parkedReviveInheritedGate: parkedReviveInheritedGate ? parkedReviveInheritedGate : undefined,
1171
555
  };
1172
- /** 可观测面(全体 fail-open,观测失败绝不改业务面行为) */
1173
556
  const observability = {
1174
557
  logger,
1175
558
  metrics,
1176
- modelUsage: modelUsageTracker, // E8: shared with createTracer above → runs drain it into the modelUsage echo
1177
- promptManifests: promptManifestTracker, // [998]②: shared with createTracer → the durable legs drain prompt_assembled events
559
+ modelUsage: modelUsageTracker,
560
+ promptManifests: promptManifestTracker,
1178
561
  planCacheProbe,
1179
562
  };
1180
- /** 准入/治理面(每件都是拒绝的权力;缺席 = 该道门不存在,不是「默认放行的门」) */
1181
563
  const governance = {
1182
564
  authorize,
1183
565
  registryJwtVerifier: registryJwtVerifier ? registryJwtVerifier : undefined,
1184
566
  rateLimiter,
1185
567
  costQuota,
1186
- fleetLease: fleetLease ? fleetLease : undefined, // lease admission 门(提交面,镜像 quotaExceeded)
1187
- usageWindowStore: usageWindowStore ? usageWindowStore : undefined, // design/166-T1:治理窗 pre-admission(与 RunnerDeps 同店同键)
568
+ fleetLease: fleetLease ? fleetLease : undefined,
569
+ usageWindowStore: usageWindowStore ? usageWindowStore : undefined,
1188
570
  };
1189
- // B1 活体 store 探测环(门与判据见 store-live-probe.ts 头注;探针原语=既有 S10 dbNowMs 真 DB 往返)
1190
571
  const storeLiveProbe = backend && backend.kind !== "local" && backend.dbNowMs && config.storeProbeIntervalMs > 0
1191
572
  ? createStoreLiveProbe({ probe: () => backend.dbNowMs(), intervalMs: config.storeProbeIntervalMs, logger })
1192
573
  : undefined;
1193
- /** 部署自述面(/health + /v1/capabilities + 提交前置门读的部署事实;多为 live getter) */
1194
574
  const deployment = {
1195
- // 海外 pilot 实机发现:/health 曾 verbatim 回显 sessionBackend 枚举——auto/DB_BACKEND 收编把它
1196
- // coerce 成 LABEL "tidb"(泛指 durable),pg 部署上误读成 SQL-TiDB 带偏排障。回显有效 store(与 listening
1197
- // 日志 session 标签同款诚实修)。
1198
575
  sessionStoreLabel: config.sessionBackend === "tidb" && backend ? `durable(${backend.kind})` : config.sessionBackend,
1199
- modelReady: () => configCenter.modelReady(), // boot ready 门:roster 未落地=计费提交 503+health ready:false
1200
- scenarioDetails, // 场景详情只读面(内建+center 同判定源)
1201
- workflowsCapable, // ① core split: capabilities.workflows = engine-can (workflowsCapability); workflowsList = store (below)
1202
- staticWiring, // #154 件三:GET /v1/diagnostics/wiring 的 static 半场(与上面的拒启自检同一份产物)
1203
- memoryPosture, // #252 件2/件3:记忆面姿态(boot/stores.ts 的单一推导点;诊断面全量 + 能力位窄投影)
576
+ modelReady: () => configCenter.modelReady(),
577
+ scenarioDetails,
578
+ workflowsCapable,
579
+ staticWiring,
580
+ memoryPosture,
1204
581
  instanceId,
1205
- // Capability discovery: the deployment facts only main knows. Store-derived booleans
1206
- // (asyncRuns/artifacts/approvals/leader) are computed inside the server from the deps themselves.
1207
- // pricingConfigured disambiguates costMicroUsd=0 ("free run" vs "MODEL_COST_* not configured") —
1208
- // it is worker-level config, so one flag here beats decorating every result's stats.
1209
582
  capabilities: {
1210
583
  version: serviceVersion(),
1211
584
  scenarios: Object.keys(scenarios),
1212
- // 缝合审 M3:**getter,不是 boot 快照** —— `pricing` 是被 config-center 就地热改的同一个引用
1213
- // (`boot/config-center.ts` 的 `mutateInPlace(pricing, buildPricing(config.models))`)。算成快照的话,
1214
- // 运维加上/撤掉价目表之后这个键还报旧值,而它的全部用途就是给消费端消歧「0 = 免费跑 vs 没配价目表」——
1215
- // 报错了就把消歧信号变成误导信号。判据单一真源在 `budget.ts` 的 `isPricingConfigured`。
1216
- // (同一字面量里紧邻的 `restartState` 已经是 live getter 并写着为什么;这个兄弟键当时漏了。)
1217
585
  get pricingConfigured() {
1218
586
  return isPricingConfigured(pricing);
1219
587
  },
1220
588
  },
1221
- // center: orchestrator polls GET /health for this — set by the sema-registry refresh loop when a
1222
- // pull's restart-to-apply slices differ from boot. A live getter (not a snapshot) so /health always reads current.
1223
589
  restartState: () => configCenter.restartState(),
1224
- planeDeferredState: () => configCenter.planeDeferredState(), // codex R18: /health.modelPlaneDeferred (stuck-deferred plane, no handoff)
1225
- configApplyState: () => configCenter.configApplyState(), // #322 世代账:/health 三键 + 诊断 configApply 段(同一份读数)
1226
- mcpRevocations, // #324:GET /v1/sessions/:id/mcp 展示面与引擎执行面同一台账
1227
- mcpRevocationState: () => configCenter.mcpRevocationState(), // #324:诊断端点撤销名单投影
1228
- configRefreshNow: () => configCenter.refreshNow(), // #322 追加件:POST /v1/admin/config/refresh(触发既有拍,在飞则 join)
1229
- // shared drain state — SIGTERM flips `draining`, createServer assigns `inflight`, /health mirrors it.
590
+ planeDeferredState: () => configCenter.planeDeferredState(),
591
+ configApplyState: () => configCenter.configApplyState(),
592
+ mcpRevocations,
593
+ mcpRevocationState: () => configCenter.mcpRevocationState(),
594
+ configRefreshNow: () => configCenter.refreshNow(),
1230
595
  drainState,
1231
- storeDegraded: storeBackendDegraded, // S5: /health twin of the store_backend_degraded gauge
1232
- // B1 活体 store 探测:后台缓存环(座注见 http/server.ts storeLiveState;环判据见 store-live-probe.ts)。
1233
- // 门 = SQL 后端 ∧ dbNowMs 在场 ∧ 旋钮>0;local/memory 恒不挂(/health 形状不变)。timer 全 unref,
1234
- // 进程退出零阻塞;pool 生命周期归 backend.close()。直键非条件展开(deps-literal-shape 门:条件
1235
- // 展开旁路多余属性检查——E-HIGH-1 死键病族)。
596
+ storeDegraded: storeBackendDegraded,
1236
597
  storeLiveState: storeLiveProbe ? () => storeLiveProbe.state() : undefined,
1237
598
  };
1238
- /** 数值旋钮(缺省写在消费点,此处只承载覆写) */
1239
599
  const knobs = {
1240
- // (a) SQL 默认店的 per-blob 帽 → PUT /sync/blobs 前置 413(与 SqlBlobBackend 方言默认同源:
1241
- // 显式 env > tidb 的 6MiB entry-限默认;pg 默认无帽=不前置)。
1242
- // 🔴 复审 2026-07-28(E-HIGH-1)修:此键是 **ServiceDeps** 字段,原先被 spread 进 `runnerDeps`
1243
- // (core 的 RunnerDeps 无此字段,条件 spread 绕过 TS 多余属性检查 ⇒ 编译期无声)⇒ HTTP 面恒
1244
- // undefined、413 前置是死枝、超帽仍走 502。落错家的死键病族(verify-cost 先例)——接线钉见
1245
- // test/deps-literal-shape-gate.test.ts(2026-07-28 那批复审文件已按仓规折进主题文件,钉本体在 E-HIGH-1 那个 describe)。
1246
600
  snapshotBlobSqlCapBytes: backend && backend.kind !== "local" && !config.snapshotBlobStore
1247
601
  ? (config.snapshotBlobSqlMaxBytes ?? (backend.kind === "mysql" ? SQL_BLOB_DEFAULT_MAX_BYTES : undefined))
1248
602
  : undefined,
1249
- // 帽只在 watch 面在场时才有意义(缺席时路由 501,读不到它)——保持与原条件段同门,不单独接线。
1250
603
  sessionEventsMaxConnections: sessionWatchRegistry ? posIntEnv(process.env.SESSION_EVENTS_MAX_CONNS, 256, 100_000) : undefined,
1251
604
  };
1252
- // #196:hands lane 登记簿 —— resolveSpec 登记本请求场景的表态,HTTP 执行点凭 spec 取回选 Runner。
1253
- // 两处**同一实例**(交给 createResolveSpec 与 runnerFor 闭包),配对由这一行结构性保证。
1254
605
  const handsLanes = createHandsLaneRegistry();
1255
- // design/158 A10:resolveSpec 段搬到 src/boot/resolve-spec.ts(逐字;两处活引用改取值,见该文件头注)。
1256
606
  const resolveSpec = createResolveSpec({
1257
607
  handsLanes,
1258
608
  config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver,
@@ -1263,35 +613,17 @@ async function main() {
1263
613
  selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore,
1264
614
  singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage,
1265
615
  sessionEnvSelection,
1266
- lockedKeys: governanceSeams.lockedKeys, // design/170 件C:同步拒面判据(与 RunnerDeps.lockedConfig 同源)
1267
- liveQuestionFace: question, // #152:活体问答面在场 ⇒ durable question 门按活流分腿(见 ResolveSpecCtx 注)
616
+ lockedKeys: governanceSeams.lockedKeys,
617
+ liveQuestionFace: question,
1268
618
  });
1269
- // #196:执行点选路。resolveSpec 已按场景判别位登记 lane;`full` 恒回主 runner(修前行为逐字不变)。
1270
619
  const runnerFor = (spec) => pickHandsRunner(handsLanes.laneOf(spec), { full: runner, handsless: handslessRunner });
1271
- // #303:side-query 执行席 —— **不是** runner.sideQuery(core 的 SideQuerySpec 无 key 座位,那条路恒把
1272
- // 网关 key 发到模型自己的 baseUrl)。key plane 第三面在此接线,取的是与另两面(任务面
1273
- // boot/resolve-spec.ts、hook 面 hooks/hook-llm.ts)**同一只**活引用 getKeyResolver;brain 与
1274
- // RunnerDeps.brain 同实例(wrapper 在外层包,主推理链零扰动)。
1275
620
  const sideQuery = createSideQueryLane({ brain, config, getKeyResolver: () => configCenter.getKeyResolver() });
1276
621
  const server = createHttpServer({ runner, runnerFor, sideQuery, config, resolveSpec, stores, coordinators, seams, observability, governance, deployment, knobs });
1277
- // D-D SLA-timer: wire the server's deny-sweep into the reaper holder declared above (the reaper is defined
1278
- // before the server, so it calls through this late-bound reference).
1279
622
  runDenySweep = server.denyExpiredApprovals;
1280
- // #329:PARKED 迟到决议的赎回席接线(晚绑 holder,见其声明处)。
1281
- // 🔴 **直连门部署上刻意不接**(fail-closed,#157 无静默 fail-open):`directDoorActive` 的形下
1282
- // `/v1/approvals/:sessionId/decide` 要求每条决议携密码学信封(签名 principal JWT + cnf.bnd + HMAC over
1283
- // 决议内容),而 live 回决腿 `POST /v1/tool-approvals/:id/respond` 的门只有 principal —— 把赎回席接上去
1284
- // 等于给这类部署开一条**免信封**的 gate 决议口(信封正是为「BFF 被攻陷也不能替人批」而立的)。缺席 ⇒
1285
- // 那条臂逐字回落修前 404,壳按既有 durable gate 流程走(行的 410 体已带 sessionId 指路)。
1286
- // 收紧方向、不改任何已有腿:非直连门部署(绝大多数)照常受益。
1287
623
  if (!config.directDoorActive)
1288
624
  parkedAskRedeem = server.redeemParkedAsk;
1289
625
  else
1290
626
  logger.info("parked_late_decision_disabled", { reason: "direct_door_requires_signed_decision_envelope" });
1291
- // #261:fleet 总线存活对账腿。⚠️ 位置即契约 —— 必须在 `createHttpServer` **之后**:活体谓词
1292
- // (`inflightRuns` ∪ `steerableRuns` 的探针)是 server 造出来的,而 reaper 段按其自身的位置契约必须在
1293
- // server 之前起,所以本腿独立起表(60s 档,unref;判据/五条界全在 fleet-reconciler.ts)。
1294
- // 零写 durable:窄口 `getRun`/`get` 两个读方法,判死属主仍是 reaper。
1295
627
  const fleetReconcile = startFleetReconciler({
1296
628
  bus: fleetBus,
1297
629
  isLiveOnThisReplica: server.isRunLiveOnThisReplica,
@@ -1301,15 +633,7 @@ async function main() {
1301
633
  logger,
1302
634
  metrics,
1303
635
  });
1304
- // [1934]:绑址旋钮 + 无鉴权自收窄(理由见 resolveBindHost 顶注)。undefined = Node 默认全接口。
1305
636
  const bindHost = resolveBindHost(config);
1306
- // [2062]③ HOST 继承暗通道告警:zsh 常把 HOST 设成机器名,`{...process.env}` 起底的壳会静默继承给
1307
- // 引擎——bind 面被 shell 环境暗配而部署者不知情(HOST=机器名解析到 LAN 口时=静默扩暴露面)。
1308
- // 🔴 [2067]③ 次序承重:必须在 listen **之前**打——HOST=不可解析名时 listen 直接 ENOTFOUND 崩溃,
1309
- // 恰是「继承 HOST 把 boot 弄崩」这个最需要解释的场景;warn 在 listen 后=诊断在崩溃里缺席,用户只见
1310
- // 裸 getaddrinfo 栈(cli 真机取证)。
1311
- // 缝合审 M5:收窄现在会**压过**继承的 HOST(见 resolveBindHost)。分两臂 —— 否则这条 warn 打的
1312
- // `bindHost` 已是收窄后的 127.0.0.1,而 note 还说「came from the HOST env var」,自己变成一句谎。
1313
637
  if (config.bindHostSource === "HOST" && bindHost !== config.bindHost) {
1314
638
  logger.warn("bind_host_from_HOST_env_overridden", {
1315
639
  bindHost,
@@ -1323,20 +647,15 @@ async function main() {
1323
647
  note: "the bind address came from the HOST env var (not BIND_HOST). Shells like zsh commonly set HOST to the machine name — if you did not set it yourself, this may be an inherited shell variable silently configuring the bind face. Set BIND_HOST explicitly to silence this warning.",
1324
648
  });
1325
649
  }
1326
- // 缝合审 M8:listen 期的 rejecter 必须**具名**,因为它必须在 listen 结束后被摘掉。留着不摘的话,
1327
- // 运行期第一个 server-level error 会喂给一个已 settle 的 Promise(reject = no-op)⇒ 零日志零抛;
1328
- // 而 `once` 自摘之后,第二个 error 才 uncaught 崩进程 —— 这条缺口吃掉的恰好是**第一个**证据,
1329
- // 而第一个证据往往是唯一能解释后面那次崩溃的东西。
1330
650
  let onListenError;
1331
651
  try {
1332
652
  await new Promise((resolve, reject) => {
1333
- onListenError = reject; // listen 失败(ENOTFOUND/EADDRINUSE…)不吊死这个 Promise
653
+ onListenError = reject;
1334
654
  server.once("error", onListenError);
1335
655
  bindHost ? server.listen(config.port, bindHost, resolve) : server.listen(config.port, resolve);
1336
656
  });
1337
657
  }
1338
658
  catch (e) {
1339
- // [2067]③:崩溃路径自带来源诊断——不可解析的 HOST 继承名在这里最常见。
1340
659
  logger.error("listen_failed", {
1341
660
  port: config.port,
1342
661
  bindHost: bindHost ?? "0.0.0.0/::(all interfaces)",
@@ -1347,8 +666,6 @@ async function main() {
1347
666
  throw e;
1348
667
  }
1349
668
  finally {
1350
- // listen 结束(成功或失败)即摘。换上持久 handler:server-level error 之后进程通常仍能服务,
1351
- // 崩掉整个副本太重 —— 但绝不能无声。
1352
669
  if (onListenError)
1353
670
  server.removeListener("error", onListenError);
1354
671
  }
@@ -1358,9 +675,6 @@ async function main() {
1358
675
  ...("code" in e ? { code: String(e.code) } : {}),
1359
676
  });
1360
677
  });
1361
- // fleet worker 接线(announce/heartbeat + usage 批报;lease 消费后一拍):boot 完成后
1362
- // 注册(=首次心跳)。门 = center lane 配置 ∧ FLEET_ADVERTISE_ADDRESS 显式声明(不自猜可达地址);任一缺 → undefined
1363
- // = 非 fleet 部署零行为。draining 翻转的即刻 announce 在 drainThenShutdown;退出 DELETE 在 hardShutdown。
1364
678
  const fleetClient = startFleetClientFromEnv(config, {
1365
679
  instanceId,
1366
680
  version: serviceVersion(),
@@ -1370,9 +684,6 @@ async function main() {
1370
684
  });
1371
685
  if (fleetClient)
1372
686
  logger.info("fleet_client_started", { worker: config.configCenter?.worker, address: process.env.FLEET_ADVERTISE_ADDRESS });
1373
- // 缝合审 M7:这是**唯一**一处 `listening`。此前 listen 成功处还有第二条同名事件(只带
1374
- // port/bindHost/note),日志消费方 grep "listening" 会撞见两条键集不同的同名事件,按前一条解析
1375
- // 部署快照的人拿到残缺形。bindHost + loopback 收窄 note 并进这里,别再另起一行。
1376
687
  logger.info("listening", {
1377
688
  port: config.port,
1378
689
  bindHost: bindHost ?? "0.0.0.0/::(all interfaces)",
@@ -1381,44 +692,28 @@ async function main() {
1381
692
  : {}),
1382
693
  model: config.model.id,
1383
694
  brain: brainSummary(config),
1384
- // shared = cross-replica TiDB breaker state; in-process = core's per-replica Map; off = breaker disabled
1385
- breakerState: config.resilience.circuitBreaker ? (breakerState ? `shared(${backend.kind})` : "in-process") : "off", // 1.108: was hard-coded "shared(tidb)" — lied under DB_BACKEND=pg
1386
- // On DB_BACKEND=local the wiring coerces sessionBackend to the LABEL "tidb" (meaning "the
1387
- // backend's durable store, generically") — printing it verbatim read as a SQL deployment on a file-everything
1388
- // box. Print the effective store instead (same honesty fix as breakerState above).
695
+ breakerState: config.resilience.circuitBreaker ? (breakerState ? `shared(${backend.kind})` : "in-process") : "off",
1389
696
  session: config.sessionBackend === "tidb" && backend ? `durable(${backend.kind})` : config.sessionBackend,
1390
- // design/138 S1: the file-based memory engine (single-user only); "off" = MEMORY_ENGINE=off or multi-tenant.
1391
697
  memory: memoryEngine ? `engine(${memoryEngine.root})` : "off",
1392
698
  instanceId,
1393
699
  rateLimitPerMin: config.rateLimitPerMin,
1394
700
  rateLimit: config.rateLimitPerMin > 0 ? counterStoreLabel(backend?.rateLimiter, backend?.kind) : "off",
1395
701
  maxPrincipalCostUsd: config.maxPrincipalCostUsd || null,
1396
702
  costQuota: config.maxPrincipalCostUsd > 0 ? counterStoreLabel(backend?.costQuota, backend?.kind) : "off",
1397
- // durable offload store for large tool results (cross-replica wake); else core's in-process default
1398
703
  toolResultStore: toolResultStore ? (backend?.kind === "local" ? "file(local)" : "shared(sql)") : "in-process",
1399
704
  otel: config.otel ? config.otel.endpoint : null,
1400
- perModelKeys: Object.keys(config.modelApiKeyEnv).length, // models with their own upstream key (else gateway)
705
+ perModelKeys: Object.keys(config.modelApiKeyEnv).length,
1401
706
  sealedModelKeys: Object.values(config.modelApiKeys).filter((v) => typeof v === "string").length,
1402
- sealedPoisonedModels: Object.values(config.modelApiKeys).filter((v) => typeof v !== "string").length, // sealed-box custody keys unsealed (count only — values never log)
1403
- // SVC-1: durable workflow record + at-least-once notify backend (file = crash-safe ledger; memory = ephemeral)
707
+ sealedPoisonedModels: Object.values(config.modelApiKeys).filter((v) => typeof v !== "string").length,
1404
708
  workflowRunStore: config.selfOrchestrationEnabled
1405
709
  ? config.workflowRunStoreBackend === "memory"
1406
710
  ? "in-process"
1407
711
  : sqlWorkflowRunStore
1408
- ? `db(${backend.kind})` // P1: cross-replica twin active
712
+ ? `db(${backend.kind})`
1409
713
  : "file"
1410
714
  : "off",
1411
715
  });
1412
- // SVC-1 at-least-once notify — CRASH-RECOVERY sweep: once at boot, re-derive every un-acked (started-but-not-
1413
- // delivered) workflow's terminal state from the durable run store and re-deliver the completion notify that a
1414
- // prior replica crash dropped (idempotent on the receiver). Fire-and-forget AFTER listen so startup isn't
1415
- // blocked on it (recovery only re-fires completion notifications — independent of serving new requests). A
1416
- // sweep throw is logged, never fatal.
1417
716
  if (workflowNotifyGate) {
1418
- // [2995]/[2999] opts 单点合成在 workflow-orchestration(可见性回灌 + replica-local 形 boot 孤儿判死)。
1419
- // [3193] 病理1:recover 收官(成败皆算)即落 fleet 快照屏障——「serving 不等 recovery」对**提交请求**
1420
- // 仍成立,但 fleet 快照组装现在对它做有界等待(routes/fleet.ts,3s cap):SIGKILL 后壳重连不再
1421
- // 与 boot 判死赛跑(cli [3193] 插桩实证的竞态)。
1422
717
  void workflowNotifyGate
1423
718
  .recover(workflowRecoverOpts)
1424
719
  .then((tally) => logger.info("workflow_notify_recovered", tally))
@@ -1428,14 +723,11 @@ async function main() {
1428
723
  else {
1429
724
  resolveFleetRecovery();
1430
725
  }
1431
- // design/158 A10:收尾段搬到 src/boot/shutdown.ts(逐字)。⚠️ 调用点必须留在 listen 之后 —— 位置即契约,
1432
- // 理由(信号注册时点/三信号相对次序/clearInterval 先于 server.close)见该文件头注。
1433
726
  installShutdownHandlers({
1434
727
  config, logger, server, reaper, fleetReconcile, otelExporter, breakerState, costQuota, rateLimiter,
1435
- version: serviceVersion(), // #320①:崩溃遗言的 version 键(与 capabilities.version 同源)
728
+ version: serviceVersion(),
1436
729
  runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState,
1437
- storeLiveProbe, configCenter, // #131-2:两个漏网的进程级后台环进收尾链
1438
- // #270 车2:留存 lane 的定时器 + 主动让租(收尾契约 3 同族,理由见 shutdown.ts 的两条注)。
730
+ storeLiveProbe, configCenter,
1439
731
  retentionLane: retentionLaneStarted,
1440
732
  releaseRetentionLease: retentionLaneStore !== undefined ? () => retentionLaneStore.release(instanceId) : undefined,
1441
733
  });