@sema-agent/server 7.43.0 → 7.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,43 +1,11 @@
1
1
  import { parseModelMention } from "@sema-agent/core";
2
- /**
3
- * Resolve the model ref for a task spec from the request (CLI `/model` picker + `run --model`).
4
- *
5
- * Priority (a deliberate selection beats incidental free text):
6
- * 1) an explicit, allow-listed `body.model` (the picker / `--model <ref>` flag),
7
- * 2) an inline `@name` mention in the objective (core's `parseModelMention`),
8
- * 3) the `default` role.
9
- *
10
- * Security: BOTH the explicit field and the @mention are gated by the SAME catalog (`config.models` keys), so
11
- * untrusted body/text can pick WHICH configured model but can never inject a baseUrl/apiKey (parseModelMention
12
- * only ever returns an allow-listed catalog name).
13
- *
14
- * [865]②(生产实锤:面板上选了 A 模型,实际跑的是目录里的另一个):
15
- * - explicit ref 走 name+id 双键索引({@link matchCatalogModel})——壳 boot 回读的 mainLoopModel 是 **id 形**,
16
- * 目录键是 **name 形**,单键索引把 id 形请求当「未知」静默落 default = session 中途静默换模型(最恶性的
17
- * 上下文污染路径)。
18
- * - 真未知不再无声:FRESH submit 在 HTTP 门 400 fail-loud(带 available 列表,http/server.ts);本函数保持
19
- * 不 throw(RESUME 重放持久化 body 不过 400 门——模型可能事后被移出目录,砖死 resume 比降级更糟),但把
20
- * 未知 ref 透出给调用方(`unknownExplicit`),resolveSpec 记 warn——降级永远可见,绝不静默。
21
- *
22
- * Returns the chosen model ref AND the objective with any `@mention` stripped (parseModelMention's cleanedText).
23
- *
24
- * 🔴 Was a real defect: resolveSpec previously read ONLY the inline @mention, so `body.model` (the picker + the
25
- * `--model` flag) was SILENTLY DROPPED — the picker UX was a no-op server-side. This restores it.
26
- */
27
2
  export function resolveTaskModel(bodyModel, objective, catalog, allowlist) {
28
3
  const mention = parseModelMention(objective, Object.keys(catalog));
29
4
  const explicit = typeof bodyModel === "string" ? matchCatalogModel(bodyModel, catalog) : undefined;
30
- // 未匹配的「在场」ref 一律透出(codex L1:历史 checkpoint 里的空串/非字符串 model 在 RESUME 重放也不许
31
- // 静默降级——非字符串给类型表示,不 stringify 值本体防日志泄内容)。
32
5
  const present = bodyModel !== undefined && bodyModel !== null;
33
6
  const unknown = present && explicit === undefined;
34
7
  const repr = typeof bodyModel !== "string" ? `<non-string:${typeof bodyModel}>` : bodyModel === "" ? "<empty-string>" : bodyModel;
35
8
  const model = explicit ?? mention.model ?? "default";
36
- // #233 / A-002.8:名单只裁**用户逐 turn 选择通道**——显式 ref(body.model / settings.model)与 objective
37
- // 内联 `@name`。两者都缺席时落 `default`(operator 配置通道),本门**不碰**:名单管"用户可点什么",
38
- // 不管"operator 配什么"(registry-core 也只校验名单 ⊆ catalog,从不要求 default ∈ 名单)。
39
- // 不 throw:resume 重放持久化 body 不过 HTTP 门,砖死 resume 比降级糟(本文件 [865]② 同判据)——
40
- // 判别式拒因交给调用点按 leg 分流(fresh 400 / resume warn 续跑)。
41
9
  const picked = explicit !== undefined ? { ref: explicit, source: "explicit", requested: repr } : mention.model !== undefined ? { ref: mention.model, source: "mention", requested: mention.model } : undefined;
42
10
  const notAllowed = picked !== undefined && !isModelAllowlisted(picked.ref, catalog, allowlist)
43
11
  ? { requested: picked.requested, resolved: terminalCatalogName(picked.ref, catalog) ?? picked.ref, source: picked.source }
@@ -49,28 +17,6 @@ export function resolveTaskModel(bodyModel, objective, catalog, allowlist) {
49
17
  ...(notAllowed ? { notAllowed } : {}),
50
18
  };
51
19
  }
52
- /**
53
- * #233 / A-002.8 —— **归一条款**:membership 判定前把 ref 归一到**终端 catalog 条目**再比。
54
- *
55
- * 为什么不能拿字符串直接跟名单比:`expandTiers` 的增广目录里,档位词(`pro`)与 CC 别名(`sonnet`)
56
- * 的值与 `config.models[<name>]` 是**同一个对象引用**——裸串比较会让 `@pro` 绕过一张按 name 写的名单
57
- * (同一只模型,两个键)。所以两边(ref 与每个名单条目)都先经 {@link matchCatalogModel} 解到目录条目,
58
- * 再按 **引用相等** 判同。id 形的 ref 不需要特别处理:`matchCatalogModel` 本身就是 name+id 双键索引,
59
- * 它会把 id 解成目录键,引用相等接着就对上了。
60
- *
61
- * 🔴 **刻意没有「id 相等」退路**(codex 复审 R1 [high],已采纳):目录**允许两条不同 name 的条目共享
62
- * 同一个上游 `Model.id`,却各自带不同的 `baseUrl` / `apiKeyEnv` / `cost` / provider** —— 那正是多网关、
63
- * 多账号部署形。按 id 判同会把 `safe` 与 `premium` 合并,于是一张只含 `safe` 的名单会放行 `premium`,
64
- * 而真正执行的是 `premium` 那份坐标与计费。判据只认**目录条目本身**:两条条目就是两只模型,哪怕上游
65
- * id 撞车。(引用相等因此不是"优化",是唯一正确的判据;它天然覆盖 tier 词/别名/id 三形。)
66
- *
67
- * 缺席/空名单 ⇒ 恒 `true`(ship-dark:缺省 = 旧行为,全放行)。
68
- * 目录里根本没有的 ref ⇒ 恒 `true`:那是「未知模型」那道门的辖区(fresh 400 / resume 降级留声),
69
- * 本门只答「在目录里,但准不准点」——两道门同码同拒会让消费端分不清"没这模型"和"不让你点"。
70
- *
71
- * ⚠️ 射程:本门是**提交时**门。core 运行时路由(degrade 降级链 / cascade 梯 / roles 解析)按 operator
72
- * 配置自己走,**不经本门**——那不是一次用户选择,如实声明,不假装是运行时执法。
73
- */
74
20
  export function isModelAllowlisted(ref, catalog, allowlist) {
75
21
  if (allowlist === undefined || allowlist.length === 0)
76
22
  return true;
@@ -80,25 +26,16 @@ export function isModelAllowlisted(ref, catalog, allowlist) {
80
26
  for (const name of allowlist) {
81
27
  const allowed = catalogEntryOf(name, catalog);
82
28
  if (allowed === undefined)
83
- continue; // 悬空/不可解名单条目(apply 期已 warn):它准不了任何东西
29
+ continue;
84
30
  if (allowed === entry)
85
- return true; // 引用相等 —— 增广目录里 tier 词/别名/id 最终都落到这同一只条目
31
+ return true;
86
32
  }
87
33
  return false;
88
34
  }
89
- /** ref(name / 档位词 / 别名 / id)→ 它在目录里的**条目对象**;未知 ⇒ undefined。 */
90
35
  function catalogEntryOf(ref, catalog) {
91
36
  const name = matchCatalogModel(ref, catalog);
92
37
  return name === undefined ? undefined : catalog[name];
93
38
  }
94
- /**
95
- * #233 —— ref → **终端 catalog name**(拒因/日志的可读形)。
96
- *
97
- * 为什么不是 `matchCatalogModel` 的返回值:增广目录里 `pro`/`opus`/`default` 都是**真实存在的键**,
98
- * 于是 `matchCatalogModel("pro")` 回的就是 `"pro"` —— 那是用户点的那个别名,不是他真会跑的模型。
99
- * 取条目自己的 `Model.name`(applyEffective 建目录时 `models[m.name].name === m.name`,env lane 同)
100
- * 才是终端名;拿不到时如实退回匹配键(诚实缺席,不猜)。
101
- */
102
39
  export function terminalCatalogName(ref, catalog) {
103
40
  const key = matchCatalogModel(ref, catalog);
104
41
  if (key === undefined)
@@ -106,13 +43,6 @@ export function terminalCatalogName(ref, catalog) {
106
43
  const name = catalog[key]?.name;
107
44
  return typeof name === "string" && name.length > 0 ? name : key;
108
45
  }
109
- /**
110
- * [865]② name+id 双键目录索引:返回 ref 命中的目录 **name 键**(spec.model 的规范形),未知 → undefined。
111
- * - name 命中用 Object.hasOwn(🔴 不用真值索引:Object.prototype 继承键 "constructor"/"__proto__" 等在裸
112
- * 索引下是真值,会绕过 allow-list——沿袭原实现的纪律)。
113
- * - id 命中扫目录值(`Model.id`),跳过 "default" 别名键(它与其指向的真身共享 id,解析到别名会让 /model
114
- * 面板与实际路由再度撕裂);同 id 多 name 时取目录序第一个(与 /v1/models 列表序一致,确定性)。
115
- */
116
46
  export function matchCatalogModel(ref, catalog) {
117
47
  if (Object.hasOwn(catalog, ref))
118
48
  return ref;
@@ -124,16 +54,9 @@ export function matchCatalogModel(ref, catalog) {
124
54
  }
125
55
  return undefined;
126
56
  }
127
- /**
128
- * vision precheck: a catalog model can read images iff its `input` modality list
129
- * includes `"image"` (the SAME test as the vision capability on GET /v1/models — `server.ts`). FALSE-SAFE on
130
- * missing/unknown modality info: returns `true` (do NOT block) — only an EXPLICIT text-only `input` triggers the
131
- * precheck, so a model that never declared its modalities is never wrongly 422'd. Lets a request with images 422
132
- * up-front instead of hitting the downstream gateway as an opaque deserialize 400 on a text-only model.
133
- */
134
57
  export function modelSupportsImages(model) {
135
58
  if (!model || !Array.isArray(model.input))
136
- return true; // no modality info → fail-open (don't block)
59
+ return true;
137
60
  return model.input.includes("image");
138
61
  }
139
62
  //# sourceMappingURL=model-select.js.map
@@ -1,25 +1,13 @@
1
1
  export class CostQuota {
2
2
  now;
3
3
  windows = new Map();
4
- /** 🔴 不再 `readonly`:限额/窗长是比较参数,换代只动它们,窗内已累计的花费一个不动(design/322 §5)。 */
5
4
  limitMicroUsd;
6
5
  windowMs;
7
- /** @param limitMicroUsd ceiling per window (integer micro-USD; `<=0` = OFF sentinel). @param windowMs rolling window. */
8
6
  constructor(limitMicroUsd, windowMs, now = () => Date.now()) {
9
7
  this.now = now;
10
8
  this.limitMicroUsd = limitMicroUsd;
11
9
  this.windowMs = windowMs;
12
10
  }
13
- /**
14
- * 值换代。两件事**语义不同**,别混:
15
- * · **限额**换代 = 纯比较参数 ⇒ 窗内已累计的花费一个不动(design/322 §5 定案:调小当场更严)。
16
- * · **窗长**换代 = 换**记账周期** ⇒ 当代累计重开。
17
- *
18
- * 🔴 后者是 codex R1-F4(真 finding,红先):SQL 腿的窗长是**桶键的一部分**(`floor(now/windowMs)`),
19
- * 换窗长后当代桶天然重算 ⇒ 已累计不跟着搬家;内存腿修前却保留原窗口累计。于是**同一条发布**在
20
- * 内存部署上继续拒绝超额 principal、在 PG/TiDB 部署上重新放行一整个额度 —— 后端相关的成本 fail-open。
21
- * 统一取 SQL 腿那一侧(唯一能在两条腿上都实现的语义:SQL 侧无法把旧桶累计搬进新桶)。
22
- */
23
11
  setLimits(next) {
24
12
  this.limitMicroUsd = next.limitMicroUsd;
25
13
  if (next.windowMs !== this.windowMs) {
@@ -27,9 +15,7 @@ export class CostQuota {
27
15
  this.windows.clear();
28
16
  }
29
17
  }
30
- /** Is this principal under quota right now? (Reads only — does not consume.) */
31
18
  check(key) {
32
- // 关断哨兵:零窗表访问,如实报 `limitMicroUsd: 0`(消费方据此知道「本部署此刻没有 ceiling」)。
33
19
  if (this.limitMicroUsd <= 0)
34
20
  return { allowed: true, usedMicroUsd: 0, limitMicroUsd: 0, retryAfterSec: 0 };
35
21
  const t = this.now();
@@ -40,10 +26,9 @@ export class CostQuota {
40
26
  const retryAfterSec = !allowed && live ? Math.ceil((w.start + this.windowMs - t) / 1000) : 0;
41
27
  return { allowed, usedMicroUsd: used, limitMicroUsd: this.limitMicroUsd, retryAfterSec };
42
28
  }
43
- /** Add spend for a principal, starting a fresh window if the prior one expired. */
44
29
  add(key, micro) {
45
30
  if (this.limitMicroUsd <= 0)
46
- return; // 关断哨兵:不为一个不存在的门攒内存(0→N 打开时是全新的窗口)
31
+ return;
47
32
  if (micro <= 0)
48
33
  return;
49
34
  const t = this.now();
@@ -53,7 +38,6 @@ export class CostQuota {
53
38
  else
54
39
  w.micro += micro;
55
40
  }
56
- /** Drop expired windows (called from the background reaper) to bound memory. */
57
41
  reap() {
58
42
  const t = this.now();
59
43
  let dropped = 0;
@@ -1,25 +1,4 @@
1
- /**
2
- * design/80 b (item 3) — the INFRA cost axes the SERVICE owns, composed with core's LLM-family `costBreakdown`
3
- * for the supervisor cost surface.
4
- *
5
- * (design/158 N24: moved here from the single-file `src/finance/` directory — lens4 §4.4 flagged the "cost"
6
- * concept as split across `finance/cost-taxonomy.ts` + `observability/cost-quota.ts` + `budget.ts`. Chose
7
- * "dissolve finance/, fold into observability/" over "collect all three into finance/": this module + the
8
- * quota tracker are both cost REPORT/GUARD layers that feed metrics/decisions (observability's existing
9
- * job), and budget.ts stays put — it has ~19 import sites and was already the N18 move target this same
10
- * batch; stacking a second wide-fanout move on it in one session was the higher-risk option for no added
11
- * clarity.)
12
- *
13
- * The engine prices ONLY LLM tokens (core's `Stats.costBreakdown` = llmRoot / nestedSubagent / memoryConsolidation
14
- * / compaction). It has NO $ for tool calls, sandbox wall-clock, or network egress — those are INFRA/service costs
15
- * the service owns the data + rates for. This module is the thin report layer: it turns a
16
- * per-run infra USAGE + the deployment's RATES into an infra breakdown, then composes it with the core LLM
17
- * breakdown into one supervisor-facing total. Pure + rate-driven (rates default 0 ⇒ infra reads $0, exactly like
18
- * MODEL_COST_* — opt-in pricing). The per-run usage extraction (sandbox wall-clock from the run's remote-env
19
- * spans, tool-call count from the trace) is the data-source wiring fed in by the caller; this layer is the math.
20
- */
21
1
  const nonNeg = (n) => (Number.isFinite(n) && n > 0 ? n : 0);
22
- /** Price a run's infra usage. Every term clamps negatives to 0 (a bad usage/rate never produces a negative cost). */
23
2
  export function infraCost(usage, rates) {
24
3
  const toolCallMicroUsd = nonNeg(usage.toolCalls) * nonNeg(rates.toolCallMicroUsd);
25
4
  const sandboxWalltimeMicroUsd = (nonNeg(usage.sandboxWalltimeMs) / 1000) * nonNeg(rates.sandboxSecMicroUsd);
@@ -31,25 +10,12 @@ export function infraCost(usage, rates) {
31
10
  totalMicroUsd: toolCallMicroUsd + sandboxWalltimeMicroUsd + egressMicroUsd,
32
11
  };
33
12
  }
34
- /**
35
- * Compose the supervisor cost surface from core's LLM breakdown + total and the service's infra breakdown.
36
- * `llmTotalMicroUsd` is core's authoritative priced-LLM total (`stats.costMicroUsd`) — the grand total is that +
37
- * infra, NOT a re-sum of the LLM sub-categories (so a breakdown category bug can't move the grand total).
38
- */
39
13
  export function composeSupervisorCost(llm, llmTotalMicroUsd, infra) {
40
14
  return { llm, infra, totalMicroUsd: nonNeg(llmTotalMicroUsd) + infra.totalMicroUsd };
41
15
  }
42
- /** True iff ANY infra axis is priced — the gate for surfacing the composed supervisor cost (when all rates are 0
43
- * the infra contribution is always $0, so the client just reads core's `stats.costBreakdown` directly). */
44
16
  export function hasInfraPricing(r) {
45
17
  return r.toolCallMicroUsd > 0 || r.sandboxSecMicroUsd > 0 || r.egressGbMicroUsd > 0;
46
18
  }
47
- /**
48
- * Extract a run's infra USAGE from its event log + wall-clock span. `toolCalls` = the `tool_start` events (one per
49
- * tool invocation). `sandboxWalltimeMs` ≈ the run's wall-clock duration (the remote-env is connected for ~the
50
- * run's active life; the precise per-span sum lives in logs, not the event trace — the run duration is the
51
- * faithful per-run proxy). `egressBytes` = 0 (not yet metered per run).
52
- */
53
19
  export function infraUsageFromEvents(events, runDurationMs) {
54
20
  return {
55
21
  toolCalls: events.reduce((n, e) => (e.type === "tool_start" ? n + 1 : n), 0),
@@ -1,36 +1,4 @@
1
- /**
2
- * #157 fail-open 治理专项 —— 兜底路径的**响亮化**工具(server 半场;跨仓方案见 sema-comms
3
- * `audits/failopen-governance-176.md` §2,三仓语义对齐、实现各自)。
4
- *
5
- * ## 它解决的问题
6
- * 「走了兜底」与「一切正常」在外部**同形**——成功回执一样、日志一样、指标一样。于是一个静默降级
7
- * 能带病运行数周,只能靠"该发生的事没发生"这种间接症状发现。本模块给每条合法兜底臂一个**登记过的
8
- * tag**,走到即留痕三件套:
9
- *
10
- * 1. **计数(逐次)** —— `fail_open_total{tag,cls}`;
11
- * 2. **探针文件(逐次)** —— `SEMA_FAILOPEN_PROBE=<path>` 在场时逐次追加 `fail-open <tag> <detail>`。
12
- * 逐次是承重的:同进程多腿时"第二腿起无行"会让「无行=没走兜底」的黑盒断言变成假绿;
13
- * 3. **一次性 warn(每进程每 tag 一次)** —— 防刷屏。**只有这一件是一次性的**,计数与探针都逐次。
14
- *
15
- * ## 它**不**解决的问题(封装不是合法化通道)
16
- * 词表的 `cls` 只有 `F` 与 `P-DEBT` 两种,**结构性**地把 P/Z 类挡在外面:
17
- * - **P 保护型**(权限门/判定词表/凭证/车道选择):正解是 fail-closed,不是"留痕后继续放行"。
18
- * 确实一时改不动的,才允许以 `P-DEBT` 登记——让债在遥测里显形,而不是让它消失。
19
- * - **Z 计量型**(重试/预算/超时/清理范围):正解是归零 + 自己的节流告警
20
- * (先例:`boot/reapers.ts` 的 `createThrottledReaperCatch`),不走本封装。
21
- *
22
- * ## 词表是闭集
23
- * {@link FailOpenTag} 由 {@link FAIL_OPEN_TAGS} 的键推导,未登记的 tag 是**编译期错误**——
24
- * "新 tag 须登记"因此是机器执法的,不靠自觉。新增 tag 同时要在 `docs/FAIL-OPEN-CENSUS.md` 补行。
25
- *
26
- * ## 进程级而非注入级
27
- * 三件套里有两件(一次性 warn、进程计数)天然是**进程**范围的,且真正的兜底臂往往在没有 logger/
28
- * metrics 句柄的深处(`question.ts` / `elicitation.ts` 全文零观测通道)。所以对外主口是自由函数
29
- * {@link recordFailOpen},装配层用 {@link installFailOpenRecorder} 把 logger/metrics 接上即可;
30
- * 装配之前的调用不会丢——探针与 stderr 契约行不依赖注入,计数也照常累加并在装配时继承过去。
31
- */
32
1
  import { appendFileSync } from "node:fs";
33
- /** 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。 */
34
2
  export const FAIL_OPEN_TAGS = {
35
3
  "server.run-store.file-hydrate-unreadable": {
36
4
  cls: "P-DEBT",
@@ -149,26 +117,12 @@ export const FAIL_OPEN_TAGS = {
149
117
  note: "A-057.59:一只 **baseUrl 指向别家主机**(非本部署 `gatewayBaseUrl`)的目录模型**没有** per-model key ⇒ `createPerModelAuthSeat` 回 `undefined`(side-query 面 = core 5.46.0 的 `SideQuerySpec.getApiKeyAndHeaders` 座位;WebFetch 摘要面 = 同一只座位包在 `createPerModelKeyBrain` 里,那面 core 至今无座位)⇒ core 的 `options?.apiKey ?? config.apiKey` 回落**主网关 key**,而同一行下面的 `model.baseUrl || config.baseUrl` 仍把请求路由到那台外部主机 —— 这一次调用真把共享网关凭据发给了运维在 config-center 里指定的另一家。方向明知不对(hook 面对逐字同形的场景是 TRUE fail-closed:「refusing to send the prompt off-gateway」),之所以记债而不是当场收严:该缺席语义与**主推理链**(`dist/engine/harness/agent-harness.js` 的 `auth?.apiKey !== undefined` 臂)逐字同语义,只收严 wrapper 这两面会造成「同一只模型跑任务能用、问一句 401」,且「同账号多网关主机/多区域」是既有且正当的部署形、无旋钮可退。计数覆盖两面(side-query / WebFetch 摘要),`detail` = 模型名。⚠️ 判据需要参照系:调用方没给 `gatewayBaseUrl` ⇒ **不计**(宁可漏计不可错计),故计数是**下限**。终局收口 = 跨面收严件 #309(三面同批 + 旋钮 + 表态制),届时本 tag 同批销。",
150
118
  },
151
119
  };
152
- /**
153
- * 断流丢帧该记哪个 tag —— **按帧型分类**,纯函数(与写流的那条闭包解耦,才单测得动)。
154
- *
155
- * 一刀切记同一个 tag 是错的:承载 HITL 帧的那条闭包同时驮着完成面包屑与其它运行流帧,于是**一次**
156
- * 断连会让保护型计数涨两次(open 一次、complete 又一次),真正的 open 失败率被面包屑的假阳性盖住。
157
- * 只有 open 帧是"人在环这道门被跳过"的证据,其余都是体验损失。
158
- *
159
- * ⚠️ **两条 question 臂在生产上已不可达**(#173 之后,2026-08-07):本函数唯一的生产调用点是
160
- * `routes/tasks.ts` 的 `emitAsk` 断流臂,而 question 与 question_complete 两型帧都先过同文件
161
- * `emitQuestion` 的断流守卫(两道守卫同步相邻、其间无 await)并在断流时 throw,永不抵达这里;另一条
162
- * question 路(`runs.ts` 的 durable append)根本不经 emitAsk。在产的只剩 elicitation 族与 default。
163
- * 两臂**保留不删**:它们是本映射的语义定义(单测直打的纯函数),elicit 侧若哪天照 question 收紧、或
164
- * 新增第四条 emit 腿忘了守卫,留着的臂是正确落点而不是死码。读遥测时按上面这段判在产分布。
165
- */
166
120
  export function failOpenTagForDroppedFrame(frameType) {
167
121
  switch (frameType) {
168
- case "question": // 见头注:#173 后生产不可达(emitQuestion 先 throw),保留为映射定义
122
+ case "question":
169
123
  case "elicitation":
170
124
  return "server.hitl.frame-undelivered-stream-closed";
171
- case "question_complete": // 同上,生产不可达
125
+ case "question_complete":
172
126
  return "server.question.complete-breadcrumb-dropped";
173
127
  case "elicitation_complete":
174
128
  return "server.elicitation.complete-breadcrumb-dropped";
@@ -176,9 +130,7 @@ export function failOpenTagForDroppedFrame(frameType) {
176
130
  return "server.stream.frame-dropped-stream-closed";
177
131
  }
178
132
  }
179
- /** 探针文件的 env 名(跨仓同名同形:`audits/failopen-governance-176.md` §2-r2,壳/引擎同认)。 */
180
133
  export const FAIL_OPEN_PROBE_ENV = "SEMA_FAILOPEN_PROBE";
181
- /** 计数器名。`cls` 进标签:一眼分得出"合法兜底"与"保护型欠账"两种曲线,后者应当只降不升。 */
182
134
  const FAIL_OPEN_COUNTER = "fail_open_total";
183
135
  export function createFailOpenRecorder(deps = {}) {
184
136
  const counts = new Map(deps.seedCounts ?? []);
@@ -189,52 +141,40 @@ export function createFailOpenRecorder(deps = {}) {
189
141
  const writeStderr = deps.writeStderr ?? ((line) => void process.stderr.write(line));
190
142
  const appendProbe = deps.appendProbe ?? ((path, line) => appendFileSync(path, line));
191
143
  deps.metrics?.counter(FAIL_OPEN_COUNTER, "fallback (fail-open) arms traversed, by registered tag — see docs/FAIL-OPEN-CENSUS.md");
192
- // 补账:装配前(无 metrics/无 logger)命中的那几次不能凭空消失——否则 `fail_open_total` 永久
193
- // 少算,少的还正是 boot 期兜底。metrics 到场即把欠账一次性灌进去并清零(所以再 install 一次不会重放),
194
- // 结构化 warn 同理按 tag 补一条。stderr 契约行**不补**:它在装配前就已经喊过了,一 tag 一次是它的全部价值。
195
- // 🔴 记账**后于**投递(codex R2 复审 MEDIUM):先清 backlog / 先 `logged.add` 再投递,等于把一次
196
- // 投递失败记成"已送达"——欠账从此无法重放,而它保的正是最需要补的那批 boot 期兜底。
197
144
  if (deps.metrics !== undefined && backlog.size > 0) {
198
145
  for (const [tag, n] of [...backlog]) {
199
146
  try {
200
147
  deps.metrics.inc(FAIL_OPEN_COUNTER, { tag, cls: FAIL_OPEN_TAGS[tag].cls }, n);
201
- backlog.delete(tag); // 只有真进了汇才销账
148
+ backlog.delete(tag);
202
149
  }
203
150
  catch {
204
- /* 留在 backlog 里,下一次装配再补 */
205
151
  }
206
152
  }
207
153
  }
208
- // 补痕的起算集=**观测到的 tag 全集**(`counts`),不是"stderr 喊成功过的那批"(`warned`)——
209
- // 后者在"装配前命中 + stderr 正断裂"这条组合路上恒空,那次兜底的结构化痕迹就永久没了(codex R3)。
210
154
  if (deps.logger !== undefined) {
211
155
  for (const tag of counts.keys()) {
212
156
  if (logged.has(tag))
213
157
  continue;
214
158
  try {
215
159
  deps.logger.warn("fail_open", { tag, cls: FAIL_OPEN_TAGS[tag].cls, note: FAIL_OPEN_TAGS[tag].note, backfilled: true });
216
- logged.add(tag); // 同上:warn 真发出去才算已报
160
+ logged.add(tag);
217
161
  }
218
162
  catch {
219
- /* 不记 logged,下一次装配/下一次命中再补 */
220
163
  }
221
164
  }
222
165
  }
223
166
  return {
224
167
  record: (tag, detail) => {
225
- // 三件套逐件独立 try:一件的失败(坏路径的探针文件、抛错的 logger 传输)绝不能让**另外两件**
226
- // 也丢,更不能让调用方的兜底臂变成抛错——那等于把"降级"换成"故障",正是本模块要消灭的方向。
227
168
  const entry = FAIL_OPEN_TAGS[tag];
228
169
  counts.set(tag, (counts.get(tag) ?? 0) + 1);
229
170
  if (deps.metrics === undefined) {
230
- backlog.set(tag, (backlog.get(tag) ?? 0) + 1); // 记欠账,等 install 补
171
+ backlog.set(tag, (backlog.get(tag) ?? 0) + 1);
231
172
  }
232
173
  else {
233
174
  try {
234
175
  deps.metrics.inc(FAIL_OPEN_COUNTER, { tag, cls: entry.cls });
235
176
  }
236
177
  catch {
237
- // 汇在场但这一次没进去 ⇒ 同样记欠账(而不是蒸发),等下一次装配补。
238
178
  backlog.set(tag, (backlog.get(tag) ?? 0) + 1);
239
179
  }
240
180
  }
@@ -243,30 +183,23 @@ export function createFailOpenRecorder(deps = {}) {
243
183
  appendProbe(probePath, `fail-open ${tag}${detail !== undefined && detail !== "" ? ` ${detail}` : ""}\n`);
244
184
  }
245
185
  catch {
246
- /* 探针路径不可写(只读盘/权限)⇒ 静默放弃,不牵连本次兜底 */
247
186
  }
248
187
  }
249
- // 只有**真发出去**才记进 logged —— 没有 logger 时记了,会让日后 install 的补账把这条当成
250
- // "已经报过"而跳过,那正是本轮复审要修的漏账形。
251
188
  if (deps.logger !== undefined && !logged.has(tag)) {
252
189
  try {
253
190
  deps.logger.warn("fail_open", { tag, cls: entry.cls, note: entry.note, ...(detail !== undefined ? { detail } : {}) });
254
- logged.add(tag); // 记账后于投递:没发出去就不算已报,下一次命中重试
191
+ logged.add(tag);
255
192
  }
256
193
  catch {
257
- /* 观测不得成为故障 */
258
194
  }
259
195
  }
260
196
  if (warned.has(tag))
261
197
  return;
262
198
  try {
263
- // 跨仓约定的黑盒锚,逐字 `[sema] fail-open: <tag>`。**不依赖** logger 是否装配上——
264
- // 评测线的断言打的就是这一行,它在 boot 之前的兜底上也必须成立。
265
199
  writeStderr(`[sema] fail-open: ${tag}\n`);
266
- warned.add(tag); // 同上。stderr 永久断裂时代价=每次命中试写一次(有界),不是丢掉这条契约行
200
+ warned.add(tag);
267
201
  }
268
202
  catch {
269
- /* 断裂的 stderr(EPIPE 族)⇒ 丢这行,不丢进程 */
270
203
  }
271
204
  },
272
205
  counts: () => counts,
@@ -275,16 +208,7 @@ export function createFailOpenRecorder(deps = {}) {
275
208
  loggedTags: () => logged,
276
209
  };
277
210
  }
278
- /** 进程单例。装配前也可用(探针+stderr 契约行照常,计数照常;只是没有结构化 warn/metric)。 */
279
211
  let current = createFailOpenRecorder();
280
- /**
281
- * 装配层接线:把 logger/metrics 接到进程单例上。搬四份状态过去,各有各的理由:
282
- * - `seedCounts` —— 逻辑总数续算,不从 0 重来;
283
- * - `seedWarned` —— boot 期已喊过的 stderr 契约行不再喊(「每进程每 tag 一次」是它的全部价值);
284
- * - `seedBacklog` —— 装配**前**命中的次数还没进任何 metrics 汇,交给新实例补账(补完清零,
285
- * 所以第二次 install 不会重放);
286
- * - `seedLogged` —— 已真发出去的结构化 warn 不重发;没发过的由新 logger 补一条。
287
- */
288
212
  export function installFailOpenRecorder(deps) {
289
213
  current = createFailOpenRecorder({
290
214
  ...deps,
@@ -295,15 +219,12 @@ export function installFailOpenRecorder(deps) {
295
219
  });
296
220
  return current;
297
221
  }
298
- /** 兜底臂的调用口。`tag` 必须已登记(闭集类型),`detail` 是可选的一行现场(路径/原因,不放密钥)。 */
299
222
  export function recordFailOpen(tag, detail) {
300
223
  current.record(tag, detail);
301
224
  }
302
- /** 本进程各 tag 累计次数——`/metrics` 之外的进程内读数(测试与自检面用)。 */
303
225
  export function failOpenCounts() {
304
226
  return current.counts();
305
227
  }
306
- /** test-only:换一个全新单例(计数与 warn 集合清零),让每个用例都从"第一次"看起。 */
307
228
  export function resetFailOpenRecorderForTest(deps = {}) {
308
229
  current = createFailOpenRecorder(deps);
309
230
  return current;
@@ -1,9 +1,4 @@
1
1
  const ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
2
- // EPIPE hardening (live catch): a host that feeds this engine's stdout through a
3
- // PIPE and exits first leaves the fd broken — the NEXT log write raises EPIPE. Without a listener
4
- // that async 'error' event on process.stdout is fatal, which killed drainThenShutdown ON ITS FIRST
5
- // LOG LINE (the SIGHUP reap never ran). Logs are droppable; the process is not — swallow both the
6
- // sync throw (try/catch below) and the async 'error' event (no-op listener, attached once).
7
2
  let stdioErrorsSwallowed = false;
8
3
  function swallowStdioErrors() {
9
4
  if (stdioErrorsSwallowed)
@@ -28,7 +23,6 @@ export function createLogger(minLevel = "info") {
28
23
  (level === "error" || level === "warn" ? process.stderr : process.stdout).write(line + "\n");
29
24
  }
30
25
  catch {
31
- // Broken/destroyed stream (EPIPE family): drop the line, never the process.
32
26
  }
33
27
  };
34
28
  return {