@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,11 +1,5 @@
1
- const SEP = "\u0000"; // NUL — can't appear in a principal / scope / IP key
2
- /** S9 deeper fix: default per-query timeout for the counter family's statements. Always ON (unlike the
3
- * opt-in pool-wide DB_QUERY_TIMEOUT_MS): every statement here is a tiny single-row/single-bucket op, so
4
- * 30s is ~1000× headroom — a query that slow IS the S9 pathology (a DB-side hang would otherwise pin the
5
- * flush round forever; the stalled watchdog can then only report, not recover). The rejection lands in
6
- * the existing flush/refresh catch → the S9 streak telemetry counts it; the round ENDS and retries. */
1
+ const SEP = "\u0000";
7
2
  export const DEFAULT_COUNTER_QUERY_TIMEOUT_MS = 30_000;
8
- /** Dual-dialect write-behind aggregating counter. See the file header for the dialect-delta ledger. */
9
3
  export class SqlWriteBehindCounter {
10
4
  dialect;
11
5
  pool;
@@ -14,20 +8,13 @@ export class SqlWriteBehindCounter {
14
8
  now;
15
9
  onDegraded;
16
10
  queryTimeoutMs;
17
- pendingDelta = new Map(); // unflushed local, by rowKey (key + window bucket)
18
- fleetTotal = new Map(); // peers' current-window view, refreshed periodically
11
+ pendingDelta = new Map();
12
+ fleetTotal = new Map();
19
13
  timer;
20
14
  flushing = false;
21
- flushStartedAt = 0; // S9 review MED-1: hang-watchdog anchor // reentrancy guard: a flush slower than the timer interval must not overlap
22
- failStreak = new Map(); // S9: consecutive-failure runs, per kind
23
- constructor(dialect, pool, t,
24
- /** 🔴 不再 `readonly`(#322):`costQuotaWindowSec` 热更新经 {@link setWindowMs} 换代,语义见那里。 */
25
- windowMs, now = () => Date.now(), onDegraded, // S9: observability tap — never affects counting
26
- // S9 deeper fix: per-query inactivity timeout. mysql2's is the client-side `{sql, timeout}` form (the
27
- // connection is held until the hung command really ends, so a poisoned connection is never reused
28
- // mid-query); node-pg's is `query_timeout` on the QueryConfig. Overridable via DB_QUERY_TIMEOUT_MS
29
- // (threaded by store-backend.ts); the default keeps the counter face always bounded.
30
- queryTimeoutMs = DEFAULT_COUNTER_QUERY_TIMEOUT_MS) {
15
+ flushStartedAt = 0;
16
+ failStreak = new Map();
17
+ constructor(dialect, pool, t, windowMs, now = () => Date.now(), onDegraded, queryTimeoutMs = DEFAULT_COUNTER_QUERY_TIMEOUT_MS) {
31
18
  this.dialect = dialect;
32
19
  this.pool = pool;
33
20
  this.t = t;
@@ -36,7 +23,6 @@ export class SqlWriteBehindCounter {
36
23
  this.onDegraded = onDegraded;
37
24
  this.queryTimeoutMs = queryTimeoutMs;
38
25
  }
39
- /** The ONLY place the two drivers' query shapes diverge. Returns {rows, affected}. */
40
26
  async exec(sql, params) {
41
27
  if (this.dialect === "tidb") {
42
28
  const [r] = (await this.pool.query({ sql, timeout: this.queryTimeoutMs }, params));
@@ -48,8 +34,6 @@ export class SqlWriteBehindCounter {
48
34
  const res = await this.pool.query(q);
49
35
  return { rows: (res.rows ?? []), affected: res.rowCount ?? 0 };
50
36
  }
51
- /** S9: track the consecutive-failure streak per kind and notify on every failure + on the recovery edge
52
- * (streak>0 → 0). Steady healthy state stays silent. The hook must never break the counter. */
53
37
  bump(kind, failed, error) {
54
38
  if (!this.onDegraded)
55
39
  return;
@@ -62,7 +46,6 @@ export class SqlWriteBehindCounter {
62
46
  this.onDegraded({ table: this.t.table, kind, streak: next, prevStreak: prev, ...(failed && error !== undefined ? { error: error instanceof Error ? error.message : String(error) } : {}) });
63
47
  }
64
48
  catch {
65
- /* observability must never break the counter */
66
49
  }
67
50
  }
68
51
  bucket() {
@@ -71,7 +54,6 @@ export class SqlWriteBehindCounter {
71
54
  rk(key) {
72
55
  return `${key}${SEP}${this.bucket()}`;
73
56
  }
74
- /** Cached fleet total (peers, from the last refresh) + this replica's unflushed delta, current window. */
75
57
  used(key) {
76
58
  const rk = this.rk(key);
77
59
  return (this.fleetTotal.get(rk) ?? 0) + (this.pendingDelta.get(rk) ?? 0);
@@ -82,20 +64,11 @@ export class SqlWriteBehindCounter {
82
64
  const rk = this.rk(key);
83
65
  this.pendingDelta.set(rk, (this.pendingDelta.get(rk) ?? 0) + n);
84
66
  }
85
- /** Seconds until the current aligned window rolls over. */
86
67
  retryAfterSec() {
87
68
  return Math.ceil(((this.bucket() + 1) * this.windowMs - this.now()) / 1000);
88
69
  }
89
- /** Atomically push pending deltas, then refresh the current window's fleet totals + reap old buckets.
90
- * Reentrancy-guarded: two overlapping flushes would each apply `+delta` from the same snapshot and
91
- * double-count into the shared table (inflating fleet totals → spurious rate-limit/quota rejection). */
92
70
  async flush() {
93
71
  if (this.flushing) {
94
- // S9 review MED-1: a hung query holds this guard: every later tick returns HERE and bump() is never
95
- // reached, so the reject-path telemetry goes silent exactly when the fleet is degraded. Surface the
96
- // stall itself — >60s inside one flush round = a "stalled" failure signal per tick; its streak clears
97
- // when the round finally settles. (The per-query timeout above is the deeper fix — a round now self-
98
- // terminates at queryTimeoutMs; this watchdog stays as the backstop for anything it doesn't cover.)
99
72
  if (this.now() - this.flushStartedAt > 60_000)
100
73
  this.bump("stalled", true, "flush round stalled >60s (hung query?)");
101
74
  return;
@@ -107,18 +80,17 @@ export class SqlWriteBehindCounter {
107
80
  }
108
81
  finally {
109
82
  this.flushing = false;
110
- this.bump("stalled", false); // watchdog recovery edge (no-op if it never fired)
83
+ this.bump("stalled", false);
111
84
  }
112
85
  }
113
86
  async flushOnce() {
114
87
  const { table, keyCol, valCol } = this.t;
115
- // The atomic-increment upsert — the ONE statement whose dialect delta actually matters here.
116
88
  const sql = this.dialect === "tidb"
117
89
  ? `INSERT INTO ${table} (${keyCol}, window_bucket, ${valCol}, updated_at) VALUES (?,?,?,?) ` +
118
90
  `ON DUPLICATE KEY UPDATE ${valCol} = ${valCol} + VALUES(${valCol}), updated_at = VALUES(updated_at)`
119
91
  : `INSERT INTO ${table} (${keyCol}, window_bucket, ${valCol}, updated_at) VALUES ($1,$2,$3,$4) ` +
120
92
  `ON CONFLICT (${keyCol}, window_bucket) DO UPDATE SET ${valCol} = ${table}.${valCol} + EXCLUDED.${valCol}, updated_at = EXCLUDED.updated_at`;
121
- let attempted = false; // S9: only score a flush round that actually tried a write
93
+ let attempted = false;
122
94
  let failure;
123
95
  let hadFailure = false;
124
96
  for (const [rk, delta] of [...this.pendingDelta]) {
@@ -129,8 +101,7 @@ export class SqlWriteBehindCounter {
129
101
  const windowBucket = Number(rk.slice(sep + 1));
130
102
  attempted = true;
131
103
  try {
132
- await this.exec(sql, [key, windowBucket, delta, new Date()]); // S9 deeper fix: a hung INSERT rejects into the catch below (streak counts it)
133
- // subtract what we flushed (not delete — a concurrent addLocal may have grown it meanwhile)
104
+ await this.exec(sql, [key, windowBucket, delta, new Date()]);
134
105
  const remaining = (this.pendingDelta.get(rk) ?? 0) - delta;
135
106
  if (remaining > 0)
136
107
  this.pendingDelta.set(rk, remaining);
@@ -138,22 +109,15 @@ export class SqlWriteBehindCounter {
138
109
  this.pendingDelta.delete(rk);
139
110
  }
140
111
  catch (err) {
141
- hadFailure = true; // S9: surfaced via bump() below — the retry/soft-limit semantics are unchanged
112
+ hadFailure = true;
142
113
  failure = err;
143
- // keep the delta; retry next flush (advisory — never throw into the caller).
144
- // KNOWN/ACCEPTED (audit B): an ambiguous failure (INSERT applied but the ack was lost) re-adds
145
- // the delta next flush = double-count; conversely a reader between our INSERT and a peer's sees
146
- // a momentary under-count. Both are bounded by one flush window and this counter backs SOFT
147
- // limits only (quota/rate-limit, design: eventual consistency; hard caps use per-task budgets).
148
- // Exactly-once would need a flush ledger/CAS — not worth it at this tier.
149
114
  }
150
115
  }
151
116
  if (attempted)
152
- this.bump("flush", hadFailure, failure); // S9: empty rounds carry no health signal
117
+ this.bump("flush", hadFailure, failure);
153
118
  await this.refresh();
154
119
  await this.reap();
155
120
  }
156
- /** Load the current window's fleet totals into the cache (pendingDelta is separate → never clobbered). */
157
121
  async refresh() {
158
122
  const { table, keyCol, valCol } = this.t;
159
123
  const b = this.bucket();
@@ -163,36 +127,26 @@ export class SqlWriteBehindCounter {
163
127
  : `SELECT ${keyCol}, ${valCol} FROM ${table} WHERE window_bucket = $1`, [b]);
164
128
  const next = new Map();
165
129
  for (const row of rows) {
166
- next.set(`${String(row[keyCol])}${SEP}${b}`, Number(row[valCol])); // node-pg returns BIGINT/INT as STRING
130
+ next.set(`${String(row[keyCol])}${SEP}${b}`, Number(row[valCol]));
167
131
  }
168
132
  this.fleetTotal = next;
169
- this.bump("refresh", false); // S9: recovery edge (no-op while healthy)
133
+ this.bump("refresh", false);
170
134
  }
171
135
  catch (err) {
172
- // keep last-known cache on a transient read failure
173
- this.bump("refresh", true, err); // S9
136
+ this.bump("refresh", true, err);
174
137
  }
175
138
  }
176
- /**
177
- * Delete expired window buckets to bound the table.
178
- *
179
- * 🔴 失败**必须留痕**(#192 codex 复审 round2 修):这里原先是裸 `catch { return 0 }` —— 一个只影响
180
- * DELETE 的故障(权限被收回、引擎单独拒 DELETE)会被吞成"删了 0 行",而 flush/refresh 两条腿照常成功
181
- * ⇒ 健康遥测全绿,同时 rate_limit / cost_quota 的历史桶无界增长。那正是本仓禁止的静默 fail-open。
182
- * 处置是**留痕不改失败方向**:清理是 best-effort,失败仍返回 0(把一次运维层面的清理失败升级成业务
183
- * 失败是错的方向),但走与 flush/refresh 同一套 `bump()` ⇒ 有连败计数、有降级钩子、有恢复边沿。
184
- */
185
139
  async reap() {
186
140
  try {
187
141
  const { affected } = await this.exec(this.dialect === "tidb"
188
142
  ? `DELETE FROM ${this.t.table} WHERE window_bucket < ?`
189
143
  : `DELETE FROM ${this.t.table} WHERE window_bucket < $1`, [this.bucket()]);
190
- this.bump("reap", false); // 恢复边沿(健康时是 no-op)
144
+ this.bump("reap", false);
191
145
  return affected;
192
146
  }
193
147
  catch (err) {
194
148
  this.bump("reap", true, err);
195
- return 0; // best-effort:留痕之后照旧不抛
149
+ return 0;
196
150
  }
197
151
  }
198
152
  startRefresh(intervalMs = 5_000) {
@@ -205,31 +159,18 @@ export class SqlWriteBehindCounter {
205
159
  clearInterval(this.timer);
206
160
  this.timer = undefined;
207
161
  }
208
- /** #322:刷新环是否在跑(关断哨兵的判据:off 态必须**零 SQL**,含这条后台环)。 */
209
162
  refreshRunning() {
210
163
  return this.timer !== undefined;
211
164
  }
212
- /**
213
- * #322 批1:窗长换代(`costQuotaWindowSec` 热更新)。
214
- *
215
- * 🔴 如实语义:窗长是**桶键的一部分**(`floor(now/windowMs)`),不是行里的一个可改字段。换窗长 ⇒ 当代
216
- * 桶重算 ⇒ 本副本对「当前窗已用多少」的读数从新桶起算(旧桶的行还在库里,按它自己的窗长归属)。
217
- * 未 flush 的本地增量仍带着**它自己的**桶键(rowKey 里已含),所以它们仍落回原来那个桶,不会被搬家。
218
- * 这与限额值的换代不同(那个是纯比较参数,窗内计数一个不动),因此单独成注:运营改窗长要知道自己在
219
- * 换的是**记账周期**,不是一个阈值。
220
- */
221
165
  setWindowMs(windowMs) {
222
166
  this.windowMs = windowMs;
223
167
  }
224
168
  }
225
- /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
226
169
  export class WriteBehindCounter extends SqlWriteBehindCounter {
227
170
  constructor(pool, t, windowMs, now, onDegraded, queryTimeoutMs) {
228
171
  super("tidb", pool, t, windowMs, now, onDegraded, queryTimeoutMs);
229
172
  }
230
173
  }
231
- /** PostgreSQL binding — the class pg-rate-limiter.ts used to define inline (and pg-cost-quota.ts duplicated
232
- * privately, hardwired to the cost_quota columns). Same ctor shape as the mysql2 twin. */
233
174
  export class PgWriteBehindCounter extends SqlWriteBehindCounter {
234
175
  constructor(pool, t, windowMs, now, onDegraded, queryTimeoutMs) {
235
176
  super("pg", pool, t, windowMs, now, onDegraded, queryTimeoutMs);
@@ -1,29 +1,8 @@
1
- /**
2
- * design/80 D-G: principal crypto-binding (the direct-door IDENTITY gate) + the combined direct-door proof
3
- * verifier. Split out of `security.ts` (design/158 A13; consumers import from HERE — [2354] cleared the security.ts compat re-exports;
4
- * `verifyDirectDoorProof` imports {@link verifyApprovalHmac} from `./approval-hmac.js` for the
5
- * integrity half of the combined proof; `security.ts`'s own `verifiedPrincipal` imports {@link verifyPrincipalJwt}
6
- * from here for the direct-door identity branch).
7
- *
8
- * ✅ WIRED (was "🔴 SCAFFOLD — NOT YET WIRED" — STALE, corrected by the 2026-07-25 doc-rot sweep). Two live call
9
- * sites, both traced: `verifiedPrincipal` (security.ts) takes the direct-door branch when `directDoorActive` and
10
- * derives the principal from {@link verifyPrincipalJwt} instead of the header, and {@link verifyDirectDoorProof}
11
- * verifies the same JWT for the decide routes. Still true, and the part worth keeping: `directDoorActive` is
12
- * computed from the anchors (config.ts) and is OFF by default, so the direct branch never runs in a stock
13
- * deployment — activation = provisioning the issuer JWKS + iss/aud, a deployment task, not a coding one.
14
- * The direct-connect /decide door must NEVER trust a client-supplied principal header
15
- * (invariant #1). Instead the client presents a SIGNED JWT (sub = principal), and the worker verifies the SIGNATURE
16
- * against the issuer's PUBLIC key — so identity is crypto-DERIVED, not header-asserted. PUBLIC keys ⇒ non-secret
17
- * (ride the config projection like OPERATOR_PRINCIPALS, not the HMAC secret path). The issuer's PRIVATE key lives
18
- * ONLY with the trusted minter (the BFF/identity service), never on a worker. HMAC stays integrity; this is identity.
19
- */
20
1
  import { createHash, createPublicKey, verify as cryptoVerify } from "node:crypto";
21
2
  import { verifyApprovalHmac } from "./approval-hmac.js";
22
3
  function b64urlToBuf(s) {
23
4
  return Buffer.from(s, "base64url");
24
5
  }
25
- /** Map a PINNED JWS alg → the node:crypto verify call. EdDSA(Ed25519): algorithm=null. RS256: RSA-PKCS1+sha256.
26
- * ES256: ECDSA P-256 with the JWS raw r||s encoding (dsaEncoding ieee-p1363, NOT DER). Returns false on mismatch. */
27
6
  function verifyJwsSignature(alg, signingInput, sig, pub) {
28
7
  try {
29
8
  if (alg === "EdDSA")
@@ -38,12 +17,6 @@ function verifyJwsSignature(alg, signingInput, sig, pub) {
38
17
  return false;
39
18
  }
40
19
  }
41
- /**
42
- * design/80 D-G: verify a compact JWS principal token. Returns the crypto-verified principal (`sub`) + the
43
- * channel-binding (`bnd`) on success, or `null` on ANY failure (caller fail-closes — NEVER falls back to a header).
44
- * 🔴 Hardening: the JWS header `alg` MUST equal the kid's PINNED alg (rejects `alg:none`, symmetric, downgrade);
45
- * the kid MUST resolve to a configured key; iss/aud/exp/nbf (with a small skew) are enforced; `sub` MUST be present.
46
- */
47
20
  export function verifyPrincipalJwt(token, keys, expect) {
48
21
  if (keys.length === 0)
49
22
  return null;
@@ -62,15 +35,13 @@ export function verifyPrincipalJwt(token, keys, expect) {
62
35
  catch {
63
36
  return null;
64
37
  }
65
- // A payload of the JSON literal `null` (or any non-object) parses WITHOUT throwing — guard before any deref so a
66
- // crafted `base64url("null").<payload>.<sig>` token fail-closes to a typed reject, not an uncaught TypeError → 500.
67
38
  if (typeof header !== "object" || header === null || typeof payload !== "object" || payload === null)
68
39
  return null;
69
40
  if (typeof header.alg !== "string" || typeof header.kid !== "string" || sig.length === 0)
70
41
  return null;
71
42
  const key = keys.find((k) => k.kid === header.kid);
72
43
  if (!key || key.alg !== header.alg)
73
- return null; // 🔴 alg PINNED to the kid's key — no confusion/downgrade
44
+ return null;
74
45
  let pub;
75
46
  try {
76
47
  pub = createPublicKey(key.key);
@@ -81,7 +52,6 @@ export function verifyPrincipalJwt(token, keys, expect) {
81
52
  const signingInput = Buffer.from(`${h}.${p}`, "utf8");
82
53
  if (!verifyJwsSignature(key.alg, signingInput, sig, pub))
83
54
  return null;
84
- // claims — signature verified, now enforce the envelope
85
55
  if (payload.iss !== expect.iss)
86
56
  return null;
87
57
  const aud = payload.aud;
@@ -92,57 +62,32 @@ export function verifyPrincipalJwt(token, keys, expect) {
92
62
  return null;
93
63
  const now = expect.nowSec ?? Math.floor(Date.now() / 1000);
94
64
  const skew = expect.skewSec ?? 30;
95
- // 🔴 exp is MANDATORY (the design mandates a short-lived token, ≤120s) — a missing/non-numeric exp must FAIL, else a
96
- // token minted without exp would never expire (anti-replay = single-use checkpointToken ONLY, with no time bound).
97
65
  if (typeof payload.exp !== "number" || payload.exp + skew < now)
98
- return null; // missing / non-numeric / expired
66
+ return null;
99
67
  if (payload.nbf !== undefined && (typeof payload.nbf !== "number" || payload.nbf - skew > now))
100
- return null; // nbf, if present, must be numeric + reached
101
- // Cap the TTL so a sloppy/over-permissive minter can't widen the replay window past the short-lived mandate.
68
+ return null;
102
69
  if (expect.maxTtlSec && payload.exp - now > expect.maxTtlSec + skew)
103
70
  return null;
104
71
  const bnd = typeof payload.cnf?.bnd === "string" ? payload.cnf.bnd : undefined;
105
72
  return { sub: payload.sub, bnd };
106
73
  }
107
- /** The channel-binding value a direct client's principal JWT must carry in `cnf.bnd`: the hex SHA-256 of the
108
- * CLIENT-VISIBLE action identity (sessionId + boundCallId + boundInputHash — NOT the unexposed checkpointToken).
109
- * Binds the identity assertion to THIS specific suspended action so a captured JWT cannot be lifted onto another. */
110
74
  export function approvalBnd(env) {
111
75
  return createHash("sha256").update(JSON.stringify([env.sessionId, env.boundCallId ?? null, env.boundInputHash ?? null])).digest("hex");
112
76
  }
113
- /**
114
- * design/80 D-G: verify the WHOLE direct-door proof for one /decide — the signed principal JWT (identity) + its
115
- * cnf.bnd channel-binding + the HMAC envelope (integrity) — and return the CRYPTO-VERIFIED principal, or a typed
116
- * reject. 🔴 Fail-closed: every failure rejects (the caller must NOT fall back to the trusted header). Order:
117
- * verify principal → bind → mac-present → mac-valid (so a non-operator never gets a MAC oracle, an unverified
118
- * caller never reaches the operator list).
119
- *
120
- * 🔴 `opts.actionBinding`: the boundCallId/boundInputHash action-binding is `policy_ask`-
121
- * SPECIFIC (a pending TOOL call). A NON-ACTION gate — `plan_review` (and any review-pause) — `binds NO action`
122
- * (core checkpoint-store.ts: pendingAction `{kind:"plan_review"}`, editedPlan is a typed sibling that binds no
123
- * action), so the proof correctly DEGRADES to "verify WHO": JWT identity + cnf.bnd channel-binding + the HMAC over
124
- * {sessionId, decision} (bound fields null) — WITHOUT requiring a boundInputHash. Pass `actionBinding:false` for a
125
- * non-action gate; the default (true) keeps the /decide tool-approval contract byte-identical. Binding the decision
126
- * to a plan HASH was REJECTED (the plan artifact lives in a profile ref store; the worker has no TOCTOU original).
127
- */
128
77
  export function verifyDirectDoorProof(proof, envelope, config, opts) {
129
- const actionBinding = opts?.actionBinding ?? true; // default = the policy_ask tool-approval action-binding (unchanged)
78
+ const actionBinding = opts?.actionBinding ?? true;
130
79
  if (!proof.jwt)
131
80
  return { ok: false, status: 401, errorCode: "principal_unverified", error: "direct door requires a signed principal token (X-Approval-Principal-Token)" };
132
81
  const verified = verifyPrincipalJwt(proof.jwt, config.principalJwtPubkeys, { iss: config.principalJwtIss ?? "\0", aud: config.principalJwtAud ?? "\0", maxTtlSec: config.principalJwtMaxTtlSec });
133
82
  if (!verified)
134
83
  return { ok: false, status: 401, errorCode: "principal_unverified", error: "principal token failed verification" };
135
- // action-binding gates (policy_ask) MUST carry the bound action; a non-action gate (plan_review) skips this — the
136
- // cnf.bnd + HMAC below still bind {sessionId, decision} (with null bound fields), so WHO + decision-integrity hold.
137
84
  if (actionBinding && !envelope.boundInputHash)
138
85
  return { ok: false, status: 400, errorCode: "approval_mac_required", error: "direct door requires boundInputHash (the bound action) in the body" };
139
86
  if (verified.bnd !== approvalBnd(envelope))
140
87
  return { ok: false, status: 401, errorCode: "principal_unbound", error: "principal token is not bound to this decision (cnf.bnd mismatch)" };
141
88
  if (!proof.mac)
142
89
  return { ok: false, status: 401, errorCode: "approval_mac_required", error: "direct door requires an approval MAC (X-Approval-Mac)" };
143
- const ok = verifyApprovalHmac(
144
- // reason 与两个绑定位同样归一到 null(缺席=null),使"服务端算的"与"签名器签的"在缺席形上逐字节一致。
145
- { sessionId: envelope.sessionId, boundCallId: envelope.boundCallId ?? null, boundInputHash: envelope.boundInputHash ?? null, decision: envelope.decision, reason: envelope.reason ?? null }, proof.mac, proof.kid, config.approvalHmacKeys);
90
+ const ok = verifyApprovalHmac({ sessionId: envelope.sessionId, boundCallId: envelope.boundCallId ?? null, boundInputHash: envelope.boundInputHash ?? null, decision: envelope.decision, reason: envelope.reason ?? null }, proof.mac, proof.kid, config.approvalHmacKeys);
146
91
  if (!ok)
147
92
  return { ok: false, status: 401, errorCode: "approval_mac_invalid", error: "approval envelope MAC invalid" };
148
93
  return { ok: true, principal: verified.sub };