@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,3 @@
1
- /**
2
- * Durable-store BACKEND seam (clay 2026-06-21 PG migration). One place that, given `config.dbBackend`, builds the
3
- * SQL pool (TiDB/MySQL via mysql2 OR PostgreSQL via pg) and constructs the right store TWIN for every durable
4
- * subsystem. main.ts wires through this instead of `new TiDB*(pool)` so `DB_BACKEND=pg` boots the whole service on
5
- * PG with zero call-site churn. The twins are interchangeable behaviourally (one shared real-DB integration suite
6
- * keeps them in lock-step); the unions below exist only because TS private fields make the concrete classes
7
- * nominally distinct (run/imageIndex/imageBake have no core interface — the rest implement one).
8
- */
9
1
  import { homedir } from "node:os";
10
2
  import { join } from "node:path";
11
3
  import { AdoptionError, FileStorageBackend, FileSessionRepo, DEFAULT_SNAPSHOT_BOUNDS } from "@sema-agent/core";
@@ -43,23 +35,13 @@ import { TiDBSessionStore } from "./tidb-session-store.js";
43
35
  import { PgSessionStore } from "./pg-session-storage.js";
44
36
  import { TiDBSharedMemoryStore, PgSharedMemoryStore } from "./shared-memory-store-sql.js";
45
37
  import { TiDBAdoptionLogStore, PgAdoptionLogStore } from "./adoption-log-sql.js";
46
- // #270:托管留存的两半场(破坏性三方法 = 车1;租约/hold/审计 = 车2)。SQL 后端专有,local 诚实缺席。
47
38
  import { TiDBRetentionStore, PgRetentionStore } from "./retention-store-sql.js";
48
39
  import { TiDBRetentionLaneStore, PgRetentionLaneStore } from "./retention-lane-store-sql.js";
49
- // #154 车二:持久化权限规则店(三面一束)。`mysqlDriver`/`pgDriver` 是既有的方言中立 seam。
50
- // #203 §1:local 车道的 File 三面束(core 现成 provider + 本仓 File 形审批记录/导入票)。
51
40
  import { createSqlPermissionRuleStores } from "./permission-rule-store-sql.js";
52
41
  import { createFilePermissionRuleStores } from "./permission-rule-store-file.js";
53
42
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
54
- /** Build the snapshot-BLOB object-store backend from config, or undefined to fall back to the SqlBlobBackend default.
55
- * When `config.snapshotBlobStore` (MINIO_* env) is set, the E19/2c file-snapshot stores route ONLY the blob BYTES to
56
- * MinIO; the MANIFEST stays in SQL (the transcript hot read never touches MinIO). The MinioBlobBackend is given the
57
- * SAME pool+dialect so it keeps the bytes-NULL `snapshot_blob` INDEX in SQL (the orphan/grace reference-tracking +
58
- * presence checks read it — see blob-backend.ts). Shared by the tidb + pg backends. */
59
43
  function snapshotBlobBackend(config, dialect, pool) {
60
44
  const s = config.snapshotBlobStore;
61
- // (a): no MinIO ⇒ construct the SQL default EXPLICITLY so the per-blob cap (env override or the tidb
62
- // packet-wall default) rides along — the stores' internal `?? new SqlBlobBackend(...)` fallback would lose it.
63
45
  if (!s)
64
46
  return new SqlBlobBackend(dialect, pool, config.snapshotBlobSqlMaxBytes);
65
47
  return new MinioBlobBackend({
@@ -72,11 +54,6 @@ function snapshotBlobBackend(config, dialect, pool) {
72
54
  ...(s.presignTtlSec ? { presignTtlSec: s.presignTtlSec } : {}),
73
55
  }, { dialect, pool });
74
56
  }
75
- /** (c)(clay 拍 a+c,2026-07-27)云形快照 blob 姿势门——boot 时调用(main.ts),纯函数可单测。
76
- * SQL 后端(mysql|pg)把 blob 字节落 `snapshot_blob` 单行的形已被两役实证撞 mysql-协议包墙
77
- * (D-1 附件 → 对象存储裁定;snapshot_blob >31MiB 同墙):大字节归对象存储。云形缺 MinIO ⇒
78
- * fail-loud(启动了才是假安全);`SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true` = 显式接受 SQL 店 +
79
- * per-blob 帽(单机/测试台形)。local 后端不经此门(file/in-memory 快照店,无 SQL 包墙)。 */
80
57
  export function assertCloudSnapshotBlobPosture(kind, config) {
81
58
  if (kind === "local" || config.snapshotBlobStore || config.snapshotBlobAllowSql)
82
59
  return;
@@ -86,10 +63,6 @@ export function assertCloudSnapshotBlobPosture(kind, config) {
86
63
  "same ruling as the D-1 attachment store). Single-box/test rigs may explicitly opt out with " +
87
64
  "SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true (a per-blob cap then applies; oversized blobs are rejected 413).");
88
65
  }
89
- /** REWIND_SNAPSHOT_MAX_MB overrides ONLY the byte cap of core's DEFAULT_SNAPSHOT_BOUNDS (raise for
90
- * big monorepos, lower for tight DBs). File-count / ignoreDirs stay core defaults — the knob answers the one
91
- * field the 20G-workspace case needed; more knobs when a case shows up. Exported for tests
92
- * (fractional-MB rounding + partial-merge semantics need a regression lock). */
93
66
  export function snapshotBoundsFromConfig(config) {
94
67
  const mb = config.rewindSnapshotMaxMb;
95
68
  return mb !== undefined ? { ...DEFAULT_SNAPSHOT_BOUNDS, maxBytes: Math.round(mb * 1024 * 1024) } : DEFAULT_SNAPSHOT_BOUNDS;
@@ -107,32 +80,32 @@ class TiDBBackend {
107
80
  run() { return new TiDBRunStore(this.pool); }
108
81
  resumeAnchor() { return new TiDBResumeAnchorStore(this.pool); }
109
82
  approvalExemption() { return new TiDBApprovalExemptionStore(this.pool); }
110
- approvalAsk() { return new TiDBApprovalAskStore(this.pool); } // #151 流内审批协议持久层(无状态 store,每次新建同兄弟)
111
- permissionRule() { return createSqlPermissionRuleStores(mysqlDriver(this.pool)); } // #154 车二 持久化权限规则店
83
+ approvalAsk() { return new TiDBApprovalAskStore(this.pool); }
84
+ permissionRule() { return createSqlPermissionRuleStores(mysqlDriver(this.pool)); }
112
85
  sendFileLedger() { return new TiDBSendFileLedger(this.pool); }
113
86
  sessionPolicy() { return new TiDBSessionPolicyStore(this.pool); }
114
87
  fileSnapshot() { return new TiDBFileSnapshotStore(this.pool, snapshotBlobBackend(this.config, "tidb", this.pool), snapshotBoundsFromConfig(this.config)); }
115
88
  workflowJournal() { return new TiDBWorkflowJournalStore(this.pool); }
116
- outcomeSink() { return new TiDBOutcomeLedger(this.pool); } // design/73 §1→§7 bridge (recordCore)
117
- workflowRun(onWarn) { return new TiDBWorkflowRunStore(this.pool, onWarn); } // P1: cross-replica workflow record(onWarn = C5 oversize-slim 留痕,completionInbox 同款先例)
118
- completionInbox(onWarn) { return new TiDBWorkflowCompletionInbox(this.pool, onWarn); } // P1: cross-replica push half
119
- notifyJournal() { return new TiDBWorkflowNotifyJournalStore(this.pool); } // 1.108: cross-replica at-least-once notify
120
- sharedMemoryStore(authorizer) { return new TiDBSharedMemoryStore(this.pool, authorizer); } // design/177 org 共享记忆库
89
+ outcomeSink() { return new TiDBOutcomeLedger(this.pool); }
90
+ workflowRun(onWarn) { return new TiDBWorkflowRunStore(this.pool, onWarn); }
91
+ completionInbox(onWarn) { return new TiDBWorkflowCompletionInbox(this.pool, onWarn); }
92
+ notifyJournal() { return new TiDBWorkflowNotifyJournalStore(this.pool); }
93
+ sharedMemoryStore(authorizer) { return new TiDBSharedMemoryStore(this.pool, authorizer); }
121
94
  checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger); }
122
95
  toolResult() { return new TiDBToolResultStore(this.pool); }
123
96
  imageIndex() { return new TiDBImageIndex(this.pool); }
124
97
  imageBake() { return new TiDBImageBake(this.pool); }
125
- leaderRun() { return new TiDBLeaderRunStore(this.pool); } // #193 车4 件1(无状态 store,每次新建同兄弟)
126
- retention(opts) { return new TiDBRetentionStore(this.pool, opts); } // #270 车1(破坏性三方法)
127
- retentionLane() { return new TiDBRetentionLaneStore(this.pool); } // #270 车2(租约/hold/审计,删不了业务数据)
98
+ leaderRun() { return new TiDBLeaderRunStore(this.pool); }
99
+ retention(opts) { return new TiDBRetentionStore(this.pool, opts); }
100
+ retentionLane() { return new TiDBRetentionLaneStore(this.pool); }
128
101
  breaker(onWriteFail) { return new TiDBBreakerState(this.pool, onWriteFail); }
129
102
  costQuota(limitMicroUsd, windowMs, onDegraded) { return new TiDBCostQuota(this.pool, limitMicroUsd, windowMs, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
130
103
  rateLimiter(limit, onDegraded) { return new TiDBRateLimiter(this.pool, limit, undefined, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
131
104
  session() { return new TiDBSessionStore(this.pool); }
132
- adoptionLog() { return new TiDBAdoptionLogStore(this.pool); } // design/183 form b 收编日志(无状态 store,每次新建同兄弟)
105
+ adoptionLog() { return new TiDBAdoptionLogStore(this.pool); }
133
106
  mysqlPool() { return this.pool; }
134
107
  pgPool() { return undefined; }
135
- async dbNowMs() { const [rows] = await this.pool.query("SELECT ROUND(UNIX_TIMESTAMP(NOW(3))*1000) AS ms"); return Number(rows[0]?.ms); } // S10
108
+ async dbNowMs() { const [rows] = await this.pool.query("SELECT ROUND(UNIX_TIMESTAMP(NOW(3))*1000) AS ms"); return Number(rows[0]?.ms); }
136
109
  }
137
110
  class PgBackend {
138
111
  pool;
@@ -147,57 +120,33 @@ class PgBackend {
147
120
  run() { return new PgRunStore(this.pool); }
148
121
  resumeAnchor() { return new PgResumeAnchorStore(this.pool); }
149
122
  approvalExemption() { return new PgApprovalExemptionStore(this.pool); }
150
- approvalAsk() { return new PgApprovalAskStore(this.pool); } // #151 流内审批协议持久层(SQL twin)
151
- permissionRule() { return createSqlPermissionRuleStores(pgDriver(this.pool)); } // #154 车二 持久化权限规则店(SQL twin)
123
+ approvalAsk() { return new PgApprovalAskStore(this.pool); }
124
+ permissionRule() { return createSqlPermissionRuleStores(pgDriver(this.pool)); }
152
125
  sendFileLedger() { return new PgSendFileLedger(this.pool); }
153
126
  sessionPolicy() { return new PgSessionPolicyStore(this.pool); }
154
127
  fileSnapshot() { return new PgFileSnapshotStore(this.pool, snapshotBlobBackend(this.config, "pg", this.pool), snapshotBoundsFromConfig(this.config)); }
155
128
  workflowJournal() { return new PgWorkflowJournalStore(this.pool); }
156
- outcomeSink() { return new PgOutcomeLedger(this.pool); } // design/73 §1→§7 bridge (recordCore)
157
- workflowRun(onWarn) { return new PgWorkflowRunStore(this.pool, onWarn); } // P1: cross-replica workflow record(onWarn = C5 oversize-slim 留痕,completionInbox 同款先例)
158
- completionInbox(onWarn) { return new PgWorkflowCompletionInbox(this.pool, onWarn); } // P1: cross-replica push half
159
- notifyJournal() { return new PgWorkflowNotifyJournalStore(this.pool); } // 1.108: cross-replica at-least-once notify
160
- sharedMemoryStore(authorizer) { return new PgSharedMemoryStore(this.pool, authorizer); } // design/177 org 共享记忆库
129
+ outcomeSink() { return new PgOutcomeLedger(this.pool); }
130
+ workflowRun(onWarn) { return new PgWorkflowRunStore(this.pool, onWarn); }
131
+ completionInbox(onWarn) { return new PgWorkflowCompletionInbox(this.pool, onWarn); }
132
+ notifyJournal() { return new PgWorkflowNotifyJournalStore(this.pool); }
133
+ sharedMemoryStore(authorizer) { return new PgSharedMemoryStore(this.pool, authorizer); }
161
134
  checkpoint(logger) { return new PgCheckpointStore(this.pool, logger); }
162
135
  toolResult() { return new PgToolResultStore(this.pool); }
163
136
  imageIndex() { return new PgImageIndex(this.pool); }
164
137
  imageBake() { return new PgImageBake(this.pool); }
165
- leaderRun() { return new PgLeaderRunStore(this.pool); } // #193 车4 件1(SQL twin)
166
- retention(opts) { return new PgRetentionStore(this.pool, opts); } // #270 车1(SQL twin)
167
- retentionLane() { return new PgRetentionLaneStore(this.pool); } // #270 车2(SQL twin)
138
+ leaderRun() { return new PgLeaderRunStore(this.pool); }
139
+ retention(opts) { return new PgRetentionStore(this.pool, opts); }
140
+ retentionLane() { return new PgRetentionLaneStore(this.pool); }
168
141
  breaker(onWriteFail) { return new PgBreakerState(this.pool, onWriteFail); }
169
142
  costQuota(limitMicroUsd, windowMs, onDegraded) { return new PgCostQuota(this.pool, limitMicroUsd, windowMs, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
170
143
  rateLimiter(limit, onDegraded) { return new PgRateLimiter(this.pool, limit, undefined, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
171
144
  session() { return new PgSessionStore(this.pool); }
172
- adoptionLog() { return new PgAdoptionLogStore(this.pool); } // design/183 form b 收编日志(SQL twin)
145
+ adoptionLog() { return new PgAdoptionLogStore(this.pool); }
173
146
  mysqlPool() { return undefined; }
174
147
  pgPool() { return this.pool; }
175
- async dbNowMs() { const res = await this.pool.query("SELECT ROUND(EXTRACT(EPOCH FROM now())*1000) AS ms"); return Number(res.rows[0]?.ms); } // S10
148
+ async dbNowMs() { const res = await this.pool.query("SELECT ROUND(EXTRACT(EPOCH FROM now())*1000) AS ms"); return Number(res.rows[0]?.ms); }
176
149
  }
177
- /**
178
- * LOCAL backend (clay 2026-06-25 seamless local↔cloud; P0.5 variant-2 + variant-1 file-backed 2026-06-26): a DB-less StoreBackend
179
- * so a locally-run HTTP service serves the SAME contract a cloud worker does (runs + trace + sessions + resume;
180
- * long-term memory rides the design/138 memory ENGINE, not this backend). Sessions / per-session policy / file snapshots are
181
- * FILE-BACKED via core's `FileStorageBackend` over `root`, so a session SURVIVES a process restart and the resume
182
- * picker (GET /v1/sessions → LocalSessionStore.listSessions) is restart-visible — the 2c "first local, then sync"
183
- * hard prerequisite.
184
- *
185
- * The session store stays the §0.5 owner-aware {@link LocalSessionStore} wrapper, but over a fresh `FileSessionRepo`
186
- * (NOT `fileBackend.sessionStore`, which is core's bare `TtlSessionStore` lacking the §0.5 owner/list/fork/delete
187
- * ops); policy/snapshot delegate to the fileBackend's own file stores. The fileBackend takes a coarse boot
188
- * single-instance lock on `root/LOCK` (an `O_EXCL`+`linkSync` PIDFILE, not flock — [2557] F4; same-host only:
189
- * liveness = `process.kill(pid,0)`, so a foreign PID namespace or NFS defeats it, see DEPLOY-PREREQS) —
190
- * `close()` MUST `dispose()` it so a graceful restart can
191
- * re-acquire (a stale lock from a dead PID is pruned by core, but graceful restart relies on the release).
192
- *
193
- * P0.5 variant-1 (clay 2026-06-26): `run()`/`resumeAnchor()` are now FILE-backed too (FileRunStore /
194
- * FileResumeAnchorStore over core 1.138.0's crash-safe fs primitives — atomicWriteFile/writeThenLink/AppendLog), so the
195
- * runs ledger + trace event log + E18 anchors SURVIVE a process restart, completing variant-2's promised follow-on. The
196
- * picker still lists from the session abstraction (variant-2), but a restart now also re-reads run history + resume
197
- * anchors. Owner identity is still process-local (single-user acceptable; durable owners = P1). The cloud-only stores
198
- * (checkpoint/approval/toolResult/imageIndex/imageBake/breaker/costQuota/rateLimiter) are OMITTED → the consumers'
199
- * `backend?.X?.()` gates degrade them exactly like a no-DB env.
200
- */
201
150
  class LocalBackend {
202
151
  kind = "local";
203
152
  fileBackend;
@@ -205,51 +154,19 @@ class LocalBackend {
205
154
  sessionStore;
206
155
  resumeAnchorStore;
207
156
  approvalExemptionStore;
208
- /** #151 —— 进程内易失(见 approvalAsk() 的注);File 形落地前它就是 local 车道的占位。 */
209
157
  approvalAskStore = new InMemoryApprovalAskStore();
210
158
  sendFileLedgerStore;
211
- // SVC-2: FILE-backed durable journal (was core's InMemory — a TOC restart silently resolved
212
- // resumeFromRunId to an empty journal → full live re-run). Now restart-durable on one box; crash-safe JSONL,
213
- // scope-guarded, oversize skip-journal — byte-matched degrade with the SQL twins. Singleton: fds live here.
214
159
  workflowJournalStore;
215
- /** design/73 §1 — owner-only JSONL outcome-fact sink (stateless append; no fd held). */
216
160
  outcomeSinkInst;
217
- /** boot 期的告警口(`openStoreBackendWithFallback` 把它接到 logger.warn 上)。可选:直接 `new` 一只
218
- * LocalBackend 的测试/工具不必装配日志面 —— 缺席时 core 的披露仍留在 core 那侧,只是本仓不复述。 */
219
161
  onWarn;
220
- /** boot 期的 **error 级**口(F-D):`FileRunStore.hydrate` 的 readdir 失败按三处成文(本文件下方接线处 /
221
- * `fail-open.ts` 的 tag note / 普查表第 29 行)承诺的是 **error 级**披露,而 warn 座满足不了那句话 ——
222
- * 按 `level >= error` 配告警的运维会一条都收不到。可选:缺席时退回 {@link onWarn}(降一级好过无声)。 */
223
162
  onError;
224
163
  constructor(root, config, onWarn, onError) {
225
164
  this.onWarn = onWarn;
226
165
  this.onError = onError;
227
- // The fileBackend ctor mkdirs `root` + takes the boot pidfile lock — a same-host SECOND instance FAILS FAST.
228
- // Re-throw with a clear operator message (the raw core error is "another instance owns this data dir").
229
- //
230
- // 🔴 core 5.23.0([3372] 提货批件④):这个构造器现在有**第二个**拒绝面 —— design/183 的 I6 adoption
231
- // boot 门(`dist/stores/file/index.js:45` 的 `assertAdoptionBootGate`):数据根下 `adoption.json` 是
232
- // 在飞标记 ⇒ `AdoptionError("adoption_in_flight")`,标记读不出 ⇒ `"adoption_marker_corrupt"`。
233
- // 原来的 catch 是 **catch-all**,会把它一并改写成下面那句 boot-lock 文案 —— 那是**误诊**:它给的两条
234
- // 出路对 adoption 在飞都不对,而「把 LOCAL_DATA_ROOT 指到一个空目录」更是直接劝运维**丢下一个迁移
235
- // 到一半的数据根**;`AdoptionError` 的类型身份与 typed `code` 也在改写中被抹掉。
236
- // ⇒ `AdoptionError` **原样穿过**:core 的拒绝句本就是写给人读的(点名在飞的那条弧,并给出「resume
237
- // adoptLocalDataRoot 到完成,或修 adoption.json」的出路),我们没有更好的话可说,包一层只会更差。
238
- // 包装只留给真正的 boot-lock 类失败(那句文案的适用条件)。
239
166
  try {
240
167
  this.fileBackend = new FileStorageBackend({
241
168
  root,
242
- ...(config.rewindSnapshotMaxMb !== undefined ? { snapshotBounds: { maxBytes: Math.round(config.rewindSnapshotMaxMb * 1024 * 1024) } } : {}), // REWIND_SNAPSHOT_MAX_MB
243
- // 🔴 腐读披露座(交接件④,与 `run-local.ts` 的同座同形、同 warn 名 `file_store_corrupt_read`)。
244
- // core 对**读不出来的**持久文件是 documented fail-open —— 当作「缺席」继续跑(抛会让整条任务死,
245
- // 也会自锁修复写)。这条 fail-open 是 core 的裁定,本仓不改它;但它**不许无声**(#157 安全轴纪律)。
246
- // 此前 HTTP 服务的 local 车道**根本没接这个座**:同一份坏字节,run-local 上打一行、服务上一个字
247
- // 都没有 —— 而服务形恰恰是没人盯着终端的那一个。
248
- //
249
- // ⚠️ 文案按脸分列后果、**不下统一结论**(run-local 那次 codex R2-medium 的教训一并吸收):一个座
250
- // 覆盖 core 转发的三张脸,后果各不相同 —— 把「少了会话规则这一层」写成「整个任务无约束」会误导
251
- // 事故定级,而且对另外两张脸根本不成立。共同事实只有一句:一次持久读被当成了缺席。
252
- // ENOENT 不触发(那是真缺席,不是腐读)。
169
+ ...(config.rewindSnapshotMaxMb !== undefined ? { snapshotBounds: { maxBytes: Math.round(config.rewindSnapshotMaxMb * 1024 * 1024) } } : {}),
253
170
  onCorruptRead: (info) => this.onWarn?.("file_store_corrupt_read", {
254
171
  path: info.path,
255
172
  reason: info.reason,
@@ -268,21 +185,7 @@ class LocalBackend {
268
185
  throw new Error(`DB_BACKEND=local cannot open data dir ${root}: ${e instanceof Error ? e.message : String(e)} ` +
269
186
  "(another HTTP service / run-local instance may own it; stop it first or set LOCAL_DATA_ROOT to a free dir)");
270
187
  }
271
- // §0.5 owner-aware wrapper over a fresh FileSessionRepo on the SAME root (NOT fileBackend.sessionStore — that's
272
- // the bare TtlSessionStore without the §0.5 owner/list/fork/delete ops). Both touch root/sessions/*.jsonl.
273
- // Pass `root` as the lastActivity sidecar dir so the /resume picker's newest-first order survives a restart (TOC #10):
274
- // without it the in-memory recency map starts empty on boot and every session degrades to createdAt ordering.
275
188
  this.sessionStore = new LocalSessionStore(new FileSessionRepo(this.fileBackend.root), this.fileBackend.root);
276
- // P0.5 variant-1: file-backed runs ledger + E18 anchors over the SAME canonical root (under root/runs + root/
277
- // resume-anchors). Hydrate the in-memory index from disk on construction → restart-visible.
278
- // 件6(#43 案 [4281]):hydrate 的 readdir 失败要**响亮**——把 boot 告警口接过去(缺席则只走 fail-open
279
- // 遥测那一半)。包一层箭头而不是直接传方法引用:后者会把方法从接收者上摘下来,带 `this` 的实现会在
280
- // 第一次真披露时炸,而那一刻正是最不该炸的时候(本文件 :658 同族先例)。
281
- // 🔴 F-D(2026-08-17 合并码扫描):这一座必须接 **error** 口。件6 三处成文(FileRunStore 那条 catch 注 /
282
- // `fail-open.ts` 的 tag note / 普查表第 29 行)承诺的都是「一行 **error 级**启动日志」,而修前它被绑到
283
- // `onWarn` 上、且 `openStoreBackendWithFallback` 的 logger 参数类型里压根没有 error 通道 ⇒ 生产恒 warn,
284
- // 按 `level >= error` 配告警的运维一条都收不到,而这条债的全部价值就是「恒 miss 不再被当成本来就没有」。
285
- // `onError` 缺席(只装配了 warn 座的调用方)⇒ 退回 warn 座:降一级好过无声。
286
189
  this.runStore = new FileRunStore(this.fileBackend.root, {
287
190
  error: (msg, fields) => void (this.onError ?? this.onWarn)?.(msg, fields),
288
191
  });
@@ -292,47 +195,32 @@ class LocalBackend {
292
195
  this.workflowJournalStore = new FileWorkflowJournalStore(this.fileBackend.root);
293
196
  this.outcomeSinkInst = new FileOutcomeSink(this.fileBackend.root);
294
197
  }
295
- async ensureSchema() { } // FileStorageBackend ctor already created root + the boot lock → always "reachable"
198
+ async ensureSchema() { }
296
199
  async close() {
297
- await this.sessionStore.dispose(); // AWAIT the final lastActivity flush (bug C) BEFORE releasing the dir lock below
298
- this.runStore.dispose(); // release the per-run event-log fds so a graceful restart can re-open the data dir
299
- this.resumeAnchorStore.dispose(); // release the per-session anchor-log fds
300
- this.approvalExemptionStore.dispose(); // release the per-session exemption-log fds
301
- this.sendFileLedgerStore.dispose(); // release the sendfile-ledger append fd (lazy — may never have opened)
302
- this.workflowJournalStore.dispose(); // release the per-run journal-ledger fds
303
- this.checkpointStoreInst?.close(); // release the checkpoint ledger fd (lazy, may never have been built)
304
- // #203 §1: release core's permission-rule DIRECTORY write lock (lazy — may never have been built).
305
- // Same discipline as root/LOCK below: an un-released writer lock tells the next boot that a live process
306
- // owns the rule directory, and core refuses the second holder LOUDLY rather than queueing.
200
+ await this.sessionStore.dispose();
201
+ this.runStore.dispose();
202
+ this.resumeAnchorStore.dispose();
203
+ this.approvalExemptionStore.dispose();
204
+ this.sendFileLedgerStore.dispose();
205
+ this.workflowJournalStore.dispose();
206
+ this.checkpointStoreInst?.close();
307
207
  this.permissionRuleStoresInst?.dispose();
308
- await this.fileBackend.dispose(); // MUST release root/LOCK so a graceful restart can re-acquire the data dir
208
+ await this.fileBackend.dispose();
309
209
  }
310
210
  run() { return this.runStore; }
311
- resumeAnchor() { return this.resumeAnchorStore; } // singleton — state lives in the instance (mirrors run()/session())
312
- approvalExemption() { return this.approvalExemptionStore; } // singleton — same posture
313
- /** #151 流内审批协议持久层的 local 形。**必须**是 singleton:状态全在实例的两个 Map 里,每次
314
- * new 一个就等于每个消费者各拿一份互不可见的账(而 SQL twin 天然共享一张表)。易失代价 +
315
- * 「local 车道默认不上协议」的裁定见 StoreBackend.approvalAsk() 的接口注。 */
211
+ resumeAnchor() { return this.resumeAnchorStore; }
212
+ approvalExemption() { return this.approvalExemptionStore; }
316
213
  approvalAsk() { return this.approvalAskStore; }
317
- /** #203 §1:local 车道的 File 三面束。**必须**是 singleton —— core 的 File provider 在第一次取写面时
318
- * 对规则目录取一把进程级写锁,第二个持有者被**响亮拒绝**(不是排队),所以每次 `new` 一个就等于给
319
- * 自己造一次「另一个进程占着这个目录」的假象。`close()` 里 `dispose()` 释放它(与数据根 `root/LOCK`
320
- * 同一条纪律:不释放 ⇒ 优雅重启被自己上一条命留下的锁挡在门外)。惰性建:没开旋钮的部署不该因为
321
- * 一个从不使用的面而在数据根里长出目录、更不该白占一把写锁。 */
322
214
  permissionRule() {
323
215
  if (!this.permissionRuleStoresInst) {
324
216
  this.permissionRuleStoresInst = createFilePermissionRuleStores(this.fileBackend.root, {
325
- // core 规则文件的披露面(读不出 / 校验和不符 / 撞上符号链接 ⇒ 答零规则**并说出来**)。
326
- // 不接住它就是一次静默的 fail-closed:用户突然被反复询问,而日志里一个字都没有。
327
217
  onError: (message) => this.onWarn?.("permission_rule_store_disclosure", { message }),
328
218
  });
329
219
  }
330
220
  return this.permissionRuleStoresInst;
331
221
  }
332
222
  permissionRuleStoresInst;
333
- sendFileLedger() { return this.sendFileLedgerStore; } // singleton — append fd lives in the instance
334
- /** Durable HITL / plan-review parking on the LOCAL lane. Lazy singleton (the inner core store holds
335
- * an append fd). taskId join ← FileRunStore; E21 owner guard ← LocalSessionStore.ownerOf (same root). */
223
+ sendFileLedger() { return this.sendFileLedgerStore; }
336
224
  checkpoint() {
337
225
  if (!this.checkpointStoreInst) {
338
226
  const cs = new LocalCheckpointStore(this.fileBackend.root, {
@@ -340,14 +228,6 @@ class LocalBackend {
340
228
  ownerOf: (sessionId) => this.sessionStore.ownerOf(sessionId),
341
229
  });
342
230
  this.checkpointStoreInst = cs;
343
- // [868] the reverse join: FileRunStore's suspended-run reapers cannot JOIN the checkpoint plane (separate
344
- // store), so they take the two EXISTS predicates as an injected probe — wired HERE because this is the one
345
- // place both stores exist over the same root. Without it the reapers stay honest NO-OPs, and an abandoned
346
- // durable HITL park (approval never answered) held its task_active claim forever (session locked; the
347
- // [868] incident). NB the probe only tells the reaper what the SQL twins read via JOIN — the reaper's
348
- // D-D NOT-pending guard still decides; and the APPROVAL_TIMEOUT_SEC=0 default still keeps the time-based
349
- // leg OFF (durable HITL waits for a human indefinitely BY DESIGN; the checkpoint-expiry leg + the cancel
350
- // verb are the recovery handles).
351
231
  this.runStore.setCheckpointProbe({
352
232
  hasPending: async (sessionId) => (await cs.findPendingTokenBySession(sessionId)) !== null,
353
233
  hasExpired: (sessionId) => cs.hasExpiredBySession(sessionId),
@@ -356,53 +236,24 @@ class LocalBackend {
356
236
  return this.checkpointStoreInst;
357
237
  }
358
238
  checkpointStoreInst;
359
- sessionPolicy() { return this.fileBackend.sessionPolicyStore; } // E6 — file-backed (survives restart)
360
- fileSnapshot() { return this.fileBackend.fileSnapshotStore; } // E19 — file-backed content-addressed blobs (survive restart)
361
- workflowJournal() { return this.workflowJournalStore; } // SVC-2 — FILE-backed (restart-durable on one box, see field doc)
362
- outcomeSink() { return this.outcomeSinkInst; } // design/73 §1 — owner-only JSONL fact ledger
239
+ sessionPolicy() { return this.fileBackend.sessionPolicyStore; }
240
+ fileSnapshot() { return this.fileBackend.fileSnapshotStore; }
241
+ workflowJournal() { return this.workflowJournalStore; }
242
+ outcomeSink() { return this.outcomeSinkInst; }
363
243
  session() { return this.sessionStore; }
364
- // core 1.219 (dogfood finding "ReadToolResult(ref) 恒空"): the TOC lane now has a DURABLE tool-result
365
- // store — core's FileToolResultStore under the same data root (one file per ref, write-once, restart-durable).
366
- // In-process cross-task deref was already fixed core-side (1.219 Runner-shared fallback); this adds the
367
- // cross-RESTART half. deleteBySession:core 5.28 起 FileToolResultStore **有**(R3 复扫更正过一次、
368
- // R4 复扫再纠:上一版注写成「两者都有」过头了——`reapOlderThan` 在 core 5.28.0 dist 全文零命中,
369
- // TTL 腿仍是 SQL twins only,reapers.ts 的 `?.` 可选链在 local 上永久 no-op,别拆)。deleteBySession
370
- // 按 .owner.json 边车判属主,无边车的存量计 unattributable 不删,与 SQL 孪生同口径。
371
244
  toolResult() { return this.fileBackend.toolResultStore; }
372
245
  mysqlPool() { return undefined; }
373
246
  pgPool() { return undefined; }
374
247
  }
375
- /**
376
- * 启动日志里「跨副本 or 进程内」那一格的标签。
377
- *
378
- * 🔴 判据必须是**那个 store 本身在不在**,不是「有没有 backend」(2026-07-31 缝合审)。
379
- * `LocalBackend` 明确省略 `costQuota`/`rateLimiter`(见它类尾那行 "intentionally absent"),
380
- * 而 `DB_BACKEND=local` 是**裸 boot 的默认值** —— 原来 main.ts 那两行用 `backend ? "shared()" : …` 判,
381
- * 于是单机默认形配了 `RATE_LIMIT_PER_MIN` 会打出 `shared()`,实际跑的是每副本一份的进程内限流器。
382
- * 运维扩到 N 副本时读这行判定"限流是跨副本的",真实额度是 N 倍。
383
- * (同一个说谎病族在紧邻的 `breakerState` 上修过一次——"was hard-coded 'shared(tidb)' — lied under
384
- * DB_BACKEND=pg"——`session:` 也跟着修了,这两行没跟上。顺带:原来的 `shared()` 括号是空的,
385
- * 而兄弟行都写 `shared(${kind})`。)
386
- */
387
248
  export function counterStoreLabel(store, kind) {
388
249
  return store ? `shared(${kind ?? "unknown"})` : "in-process";
389
250
  }
390
- /** Build the durable-store backend for `config.dbBackend`. Does NOT connect (the pool connects lazily; the
391
- * reachability probe is the caller's first `ensureSchema`). Returns undefined when no DB is configured.
392
- *
393
- * `onWarn` (additive, optional) is the boot-time warning sink the LOCAL backend needs for core's
394
- * permission-rule DISCLOSURE face (#203 §1): an unreadable / checksum-mismatched / symlinked rule file
395
- * answers ZERO rules and says so — swallowing that turns a fail-closed degrade into a silent one.
396
- * `onError` (additive, optional; F-D) is the **error-level** twin of that seat — 件6 的
397
- * `file_run_store_hydrate_unreadable` 按成文承诺是 error 级,warn 座满足不了那句话。两座都是**加项**:
398
- * 既有一/二参调用方逐字不变(缺席 ⇒ 该披露退回 warn 座 / 只剩 fail-open 遥测那一半)。 */
399
251
  export function createStoreBackend(config, onWarn, onError) {
400
252
  if (config.dbBackend === "local")
401
253
  return new LocalBackend(config.localDataRoot ?? join(homedir(), ".ai-agent"), config, onWarn, onError);
402
254
  if (config.dbBackend === "pg") {
403
255
  if (!config.pg)
404
256
  return undefined;
405
- // pgPoolOptions also arms the DB_QUERY_TIMEOUT_MS pool-wide per-query ceiling (S9 deeper fix) when set.
406
257
  const pool = createPgPool(pgPoolOptions(config.pg, config.dbQueryTimeoutMs));
407
258
  return new PgBackend(pool, config);
408
259
  }
@@ -410,50 +261,19 @@ export function createStoreBackend(config, onWarn, onError) {
410
261
  return undefined;
411
262
  return new TiDBBackend(createTidbPool(config), config);
412
263
  }
413
- /** boot 期打开 store 后端的**统一入口**:构造 + ensureSchema 同罩一层降级臂。
414
- *
415
- * 为什么必须把 `createStoreBackend()` 也罩进来(2026-07-28 修):clay 1.292 拍的口径是「裸 boot
416
- * **默认推导**的 local 在只读文件系统/mkdir 失败时不得拒启」,但原先 try 只罩 `ensureSchema()`——
417
- * 而 LocalBackend 的 `ensureSchema` 是**空实现**,真正会抛的 mkdir + 数据根 BootLock 在构造函数里、
418
- * 落在 try 之外。于是那条降级臂对 local 形从来没生效过,注释宣称的行为与代码真做的事撕裂。
419
- *
420
- * 降级条件不变(两形同一姿势):`sessionBackend=auto`(配置的 DB 不可达,S5 原形)或**默认推导**的
421
- * local(`dbBackend=local && !dbBackendExplicit`)。**显式** DB_BACKEND=local / tidb 仍 fail-loud——
422
- * operator 要了 durable,静默丢=丢数据。 */
423
- export async function openStoreBackendWithFallback(config,
424
- // 🔴 `error` 是**可选加项**(F-D):件6 的 hydrate 披露按成文承诺是 error 级,而本参数此前只有 warn 通道
425
- // ⇒ 那句承诺在生产链上结构性地兑不了(注释说 error、代码只出 warn,正是本文件下方 :659 自陈的病族)。
426
- // 仍可选:只装配 warn 座的调用方(测试/工具)逐字不变,它们的 hydrate 披露退回 warn 座。
427
- logger) {
428
- // 判据按后端分岔(2026-07-28 同批修):原先写的是 `sessionBackend === "auto" || (local && !explicit)`,
429
- // 而 `auto` 是 SESSION_BACKEND 的**默认值** ⇒ 显式 `DB_BACKEND=local` 的部署照样落进第一条臂被静默降级,
430
- // 「显式仍 fail-loud」这句话同样从未生效。local 形只看 DB_BACKEND 是不是显式;SQL 形维持 S5 原义
431
- // (auto=尽力而为可降级,显式 tidb/pg=fail-loud,静默丢持久化会丢 run)。
264
+ export async function openStoreBackendWithFallback(config, logger) {
432
265
  const mayFallback = config.dbBackend === "local" ? !config.dbBackendExplicit : config.sessionBackend === "auto";
433
266
  let backend;
434
267
  try {
435
- // 包一层箭头而不是直接传 `logger.warn`:后者会把方法从它的接收者上摘下来,一个带 `this` 的
436
- // logger 实现会在第一次真披露时炸——而那一刻正是最不该炸的时候(本仓 fail-loud 病族先例)。
437
- // error 座同款包法;调用方没有 error 通道时**不传**(LocalBackend 那侧据此退回 warn 座)。
438
268
  backend = createStoreBackend(config, (msg, meta) => logger.warn(msg, meta), logger.error === undefined ? undefined : (msg, meta) => logger.error(msg, meta));
439
- // connects + creates schema (also the reachability probe).
440
269
  if (backend)
441
270
  await backend.ensureSchema();
442
271
  }
443
272
  catch (err) {
444
- // 🔴 core 5.23.0([3372] 提货批,codex 复审 high 抓获、验真后修):**I6 adoption 门不许被降级臂吃掉**。
445
- // 1.292 拍的降级裁定说的是「只读 FS / mkdir 失败」那一类——它们的意思是「这台机器存不了盘」,降级到
446
- // 内存是诚实的等价物。`AdoptionError` 说的是完全不同的一句话:「这份数据**有主、正在搬**」(design/183
447
- // I6:数据根的 `adoption.json` 在飞,或标记读不出)。对后者降级 = **在搬家途中另开一个写者**,而那正是
448
- // I6 门存在的全部理由;更坏的是它还会留下一行「DB 不可达」的 warn,把运维支去查连通性。
449
- // ⇒ 与显式 DB_BACKEND 同待遇:无条件 fail-loud,穿过 `mayFallback`。
450
273
  if (err instanceof AdoptionError)
451
274
  throw err;
452
275
  if (!mayFallback)
453
276
  throw err;
454
- // S5 (SILENT-FALLBACK P0-b): one boot warn was the ONLY trace of "this replica is not persisting".
455
- // The caller renders the gauge + /health twin flag so a degraded replica is distinguishable from
456
- // an in-memory-by-choice one.
457
277
  logger.warn("store_db_unreachable_fallback_memory", {
458
278
  backend: backend?.kind ?? config.dbBackend,
459
279
  error: err instanceof Error ? err.message : String(err),
@@ -1,22 +1,4 @@
1
1
  import { parseJsonOr, toIso as iso, normalizeIsoOrNull as isoOrNull } from "./sql-row-helpers.js";
2
- /**
3
- * 「这一行已经走到**终局**了吗」——`RunRecord["status"]`(= `"running" | TaskStatus`)的**穷举**判据,
4
- * 与类型定义同住一个文件(词表的属主 = 判据的属主)。
5
- *
6
- * 🔴 为什么是 `switch` 而不是一张 `ReadonlySet<string>`(2026-08-08,#176 二轮扫描 finding):终局词表
7
- * **真变过**(core 5.8.0 把 `"timeout"` 从 `TaskStatus` 里删了)。而集合形的门对象是 `string`,对词表
8
- * 两个方向都无钉:
9
- * · **增词**(core 新加一个终局态)⇒ 集合不含它 ⇒ 该行被静默判成「非终局」,收敛器会一直等一个
10
- * 永远不会再动的行;
11
- * · **删词** ⇒ 集合里留一个死字面量,没有任何东西会说话。
12
- * 穷举 `switch` 把两个方向都变成**编译期**问题:core 加成员 ⇒ `default` 臂收不敛到 `never` ⇒ tsc 红,
13
- * 加成员的人被迫当场回答「这个新态算不算终局」;删成员 ⇒ 对应 `case` 变成不可达字面量、同样红。
14
- * 这条判据不许再被手抄成第二份 —— 两个消费点(`approval-reconciler.ts` 判据 2、`http/routes/runs.ts`
15
- * 的取消后复读)都从这里取。
16
- *
17
- * 词表现状:终局 = completed / failed / blocked;非终局 = running / suspended / needs_review
18
- * (suspended 与 needs_review 都还能再动,`running` 更不必说)。
19
- */
20
2
  export function isTerminalRunStatus(status) {
21
3
  switch (status) {
22
4
  case "completed":
@@ -28,35 +10,12 @@ export function isTerminalRunStatus(status) {
28
10
  case "needs_review":
29
11
  return false;
30
12
  default: {
31
- // core 的 `TaskStatus` 出了新成员 ⇒ 这里收不敛到 never ⇒ tsc 红(整条判据的**编译期**执行点)。
32
13
  const unhandled = status;
33
- // 🔴 运行时**必须**照旧返回 false(#176 codex R1 真缺陷,红先复现):`never` 只活在编译期,
34
- // `return unhandled` 在运行时返回的是那个字符串本身 = **truthy**。而 `plugins/run-store-sql.ts` 的
35
- // rowToRun 把无约束的 SQL 文本列**裸 cast** 成本联合 ⇒ 滚动升级里一个更新的副本写入的新状态词
36
- // 会被这里读到。判成「终局」的代价不可逆:approval-reconciler 判据 2 会把一张还活着的 ask 永久
37
- // 打成 DENIED/routing_failure,取消路由也会把这一行当终局上报。未知 ⇒ 保守判**非终局**(落 hold,
38
- // 下一轮重试),与本函数取代的那份 `ReadonlySet<string>.has()` 逐字节同行为。
39
14
  void unhandled;
40
15
  return false;
41
16
  }
42
17
  }
43
18
  }
44
- /**
45
- * 「这一行**停在门上**(park)了吗」——`RunRecord["status"]` 的第二张穷举判据,与 {@link isTerminalRunStatus}
46
- * 同住词表属主处(扫描P2,2026-08-12)。
47
- *
48
- * 🔴 为什么必须有名字:park 是**两个词**,不是一个 —— `suspended`(tool_approval / policy_ask / human /
49
- * resource_limit 门)与 `needs_review`(`plan_review` / dry-run 拦截门,写点 = 各 store 的
50
- * `setNeedsReview`)。两者的行为面**完全一致**:行还活着、`task_active` claim 还占着、checkpoint 还能被
51
- * 决议。凡是「行是不是还停着」的判断只手抄 `=== "suspended"` 的地方,对 plan_review 腿一律给出**相反**
52
- * 结论 —— 它会被当成终局:取消谎报 "already terminal — no-op"(claim 从此无人释放,正是 [868] 那个把会话
53
- * 锁死的指纹)、trace 面无帧、leader 面不触发 park 反应。这条判据的存在就是为了不再有第三次手抄。
54
- *
55
- * 未知词的方向与 {@link isTerminalRunStatus} 同向保守:判**不是** park。理由是消费点会据此做**不可逆**
56
- * 动作(cancel 的恢复把手会 CAS-expire 一张 checkpoint);对一个本进程读不懂的状态,少动一次远好过错杀。
57
- * 真正的执法点是下面那条 `never`:core 的 `TaskStatus` 一加成员就 tsc 红,加成员的人必须当场回答
58
- * 「这个新态算不算 park」。
59
- */
60
19
  export function isParkedRunStatus(status) {
61
20
  switch (status) {
62
21
  case "suspended":
@@ -68,13 +27,12 @@ export function isParkedRunStatus(status) {
68
27
  case "blocked":
69
28
  return false;
70
29
  default: {
71
- const unhandled = status; // 词表增删两向的**编译期**执行点(同 isTerminalRunStatus 顶注)
30
+ const unhandled = status;
72
31
  void unhandled;
73
- return false; // 运行时保守:未知 ⇒ 不当 park(不可逆动作宁可不做)
32
+ return false;
74
33
  }
75
34
  }
76
35
  }
77
- /** SELECT row → {@link BakeRecord} — shared by BOTH SQL twins (design/158 S8 归位, A3 沉底). */
78
36
  export function mapBakeRow(r) {
79
37
  return {
80
38
  bakeId: r.bake_id,
@@ -106,11 +64,9 @@ export function mapBakeRow(r) {
106
64
  updatedAt: iso(r.updated_at),
107
65
  };
108
66
  }
109
- /** Column list mapBakeRow expects — shared by BOTH SQL twins (design/158 S8 归位, A3 沉底). */
110
67
  export const BAKE_SELECT_COLS = "bake_id, profile, bands, base_ref, push, dry_run, logs, argv, status, state, digest, repo, ref, index_id, " +
111
68
  "exit_code, error, error_code, manifest_sha, tag, idem_key, ingest_secret, runner_id, lease_until, " +
112
69
  "cancel_requested, requested_by, created_at, updated_at";
113
- /** SELECT row → {@link ImageIndexEntry} — shared by BOTH SQL twins (design/158 S8 归位, A3 沉底). */
114
70
  export function mapImageRow(r) {
115
71
  return {
116
72
  id: r.id,