@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,204 +1,32 @@
1
- /**
2
- * #307 件6(core 5.46.0 提货批,design/336 §2.3-2)—— **external-origin 标记不可变律**的 SQL 孪生实现。
3
- *
4
- * ## 这条法是什么、为什么落在本仓
5
- *
6
- * design/336 slice 1 给「已暴露会话」的普通记忆写打上引擎铸的 `frontmatter.origin` 标记(taint +
7
- * 成因闭集 + 铸造时刻),取代 design/178 那种「整条隔离」的全禁。标记的全部价值来自**不可洗白**:
8
- * 一旦某个 id 的**已提交**状态带了标记,之后**任何**写法(update / 裸 add / `guard:"absent"` add /
9
- * 同批 delete+re-add)都必须把它 DEEP-EQUAL 地带下去,否则拒绝。合法出口只有一个 —— **已提交的**
10
- * 墓碑(上一批真的 delete 掉了),那之后这个 id 开始一段未标记的新生命。
11
- *
12
- * core 5.46.0 把这条法写进了**记忆后端契约试剂盒**
13
- * (`dist/core/memory-engine/memory-backend-contract.js`,新增一条必跑条目),而该契约**压在本仓身上**:
14
- * core 只养 File backend,Pg / TiDB 两方言归 server。提货当天真双库实跑的红读数(A-002.21 教训的
15
- * 直接兑现,不是自我声明「本批无 SQL 面」):
16
- * `[engine=tidb] contract case "design/336: origin marker round-trips; …":
17
- * an origin-stripping update must not apply — 1 !== 0`
18
- * ⇒ 两只 SQL 孪生此前**没有**这条法:一条被标记为外部来源的记忆条目,可以被后续一次普通 update
19
- * 悄悄抹掉标记(洗白),而 File backend 会拒。这是安全轴上的真缺口,不是「新契约还没接」。
20
- *
21
- * ## 为什么是一个共享模块而不是两处各写一遍
22
- *
23
- * 本仓的成文教训(`key-resolver.ts` 顶注「链只有一条,重复实现就会长出半条链的偏差」;
24
- * `memory-key-guards.ts` 同族先例):两支方言各写一份判据,迟早在某一支上漏掉一个 op 拼写 ——
25
- * 而漏掉的那一支**没有任何报错面**,表现只是「这台机器上的标记可以被洗掉」。判据函数与批次基线
26
- * 记录器都在这里,两支后端只负责在自己的 I/O 点上调用。
27
- *
28
- * ## 判据的两半(缺一不可)
29
- *
30
- * ① **判据本身**({@link originWhitewashRefusal}):`committedOriginOf` / `originEquals` 都用 core 的
31
- * 导出实现,**不重写** —— 它们要处理 `frontmatter.origin` 与「保留在 `extra` 里的 origin 形字节块」
32
- * 两种承载形(legacy / 外来),自己写一份近似的等于给洗白留一条后门。
33
- * ② **批次基线**({@link rememberBatchOrigin}):两支 SQL 后端逐条 patch 直打真库(没有 File backend
34
- * 那种「先规划整批再落盘」的阶段),所以同批 `delete` 之后再 `add` 时,库里已经没有那一行了 ——
35
- * 只看当下的库会把它误判成合法新生命。基线 map 按**本批第一次触碰**记下该 id 的已提交 origin,
36
- * 之后同批的任何 op 都按那一份判。⇒「同批 delete+re-add 拒 / 已提交墓碑后 add 放」这条分界
37
- * (core 契约逐字要求的那条)才成立。
38
- */
39
1
  import { ambiguousOriginRepresentation, committedDistilledOf, committedOriginOf, distilledEquals, originEquals } from "@sema-agent/core";
40
- /**
41
- * #319 增量(sema-comms [4768] 勘误 + core 仓 `1a5ebaa1`,conformance 30→31)—— **歧义 origin 表示**
42
- * 的拒绝门,与不可变律同批、同一个写路径上。
43
- *
44
- * ## 病
45
- *
46
- * 同一条目**同时**携带 typed `frontmatter.origin` 与一个**冲突的** origin 形 `extra` 块(或多个彼此
47
- * 冲突的 extra 块)时:不可变律的比较器走 `committedOriginOf`,而它在 typed 在场时优先读 typed ——
48
- * 于是那枚 typed 满足「标记原样带下去」,冲突的块却照样进了存储。任何一次**重解析**(File 的投影
49
- * 往返 / sync 对拍 / bundle 校验)都可能答出**另一枚**标记 ⇒ 成员改写的载具。不可变律本身没漏,
50
- * 漏的是「根本不该收下这种表示」这一步。
51
- *
52
- * ## 钥匙在 DISAGREEMENT,不在载体数
53
- *
54
- * 载体彼此**一致**的多载形(typed 旁边一个逐成员相等的 extra 形)是合法的 legacy 承载,必须照收 ——
55
- * 那正是序列化闭合(同一条记录在 File 投影里往返一圈就长这样)。「见到 extra 就拒」会把正常的
56
- * legacy 条目全部打死。
57
- *
58
- * ## 判据禁手抄
59
- *
60
- * 谓词一律用 core 导出的 {@link ambiguousOriginRepresentation}:它要解 `extra` 里的 origin 形块
61
- * (跨块字段组合、解析期块重排都算歧义),自己写一份近似的等于给洗白开后门 —— 这条在 #307 就写死过。
62
- * core 5.47.0 起该谓词进公开导出面([4698] 请托兑现),本仓的「撤除条件钉」也因此在 5.47 提货当天
63
- * 准时翻红,本批就是那条钉指定的兑现动作。
64
- *
65
- * ## 文案与 File 逐字同
66
- *
67
- * 拒绝文案照抄 core `file-backend.ts` 的 `planOne`(亲读 `1a5ebaa1` 前后两版确认未变),因为
68
- * 契约按 `/representation conflict|malformed patch refused/` 匹配,而**跨后端同一输入必须同一答复**
69
- * —— 消费方按 reason 分类处置,两支方言各写各的文案就等于让同一件事在不同部署上分叉。
70
- *
71
- * ## 判位
72
- *
73
- * 与 File 同precedence:**表示合法性先于状态比较** —— 排在 id/键形守卫之后、add/update 分支
74
- * (guard 算术、跨 scope 拒、白洗判据、rev CAS)之前,且在**任何 I/O 之前**(零写拒)。
75
- */
76
2
  export function ambiguousOriginRefusal(fm) {
77
3
  if (!ambiguousOriginRepresentation(fm))
78
4
  return undefined;
79
5
  return "external-origin marker representation conflict: the entry carries conflicting or duplicated origin representations (malformed patch refused)";
80
6
  }
81
- /**
82
- * #319(core 5.47/5.48 契约冻结面,design/336 §5.2)—— **头面 exposure 事实**的同一份归一化。
83
- *
84
- * 契约逐字:`listHeaders` 与 `search` 两个头面都要携带 `exposure`,且**同一条归一化**
85
- * (`committedOriginOf`:typed `frontmatter.origin` 与 legacy 的 `extra` origin 形字节块两种承载,
86
- * 一次判、两面同答)。这是**数据事实**不是模式:后端恒报库里存着什么,读侧怎么处置(不透明句柄/
87
- * 横幅/两带序)归引擎层的 provenance 模式。缺席 ⇔ 没标记(不等于「证明干净」)。
88
- *
89
- * 为什么与不可变律同住一个模块:两者读的是**同一个** committed origin。判据(白洗拒)与事实
90
- * (exposure 携带)分家写,迟早在某一支方言上一边认得 extra 形、另一边只认 typed 形 —— 那时
91
- * 「洗不掉的标记」在检索面**看不见**,而两支后端谁都不会报错。
92
- */
93
7
  export function exposureCarriage(fm) {
94
8
  return committedOriginOf(fm) !== undefined ? { exposure: "external" } : {};
95
9
  }
96
- /**
97
- * 两带序的带号:未标记 = 0(**先**),已标记 = 1。
98
- *
99
- * 契约的载荷理由(core r1-9,逐字转述):两带序必须落在**截断之前** —— 一页 limit 全是带标条目时,
100
- * 排在 limit+1 位的干净条目会被饿死,而工具层**无法**从一页已截断的结果里把它捞回来。
101
- * `exposureBands` 缺席/false ⇒ 单带序,与 pre-336 逐字节同(`provenance:"off"` 的读面)。
102
- */
103
10
  export function exposureBandOf(header) {
104
11
  return header.exposure === "external" ? 1 : 0;
105
12
  }
106
- /**
107
- * `committedOriginOf` 的**下推形**:同一条判据的 SQL 写法,给「排序与截断都发生在 SQL 里」的那一档
108
- * (Pg 的原生 pgvector 档)用。
109
- *
110
- * ## 为什么非要下推(不能只在进程里排)
111
- *
112
- * 原生档 `ORDER BY … LIMIT n` 是**库里**排完就截断的:进程拿到的已经是一页 n 行,被截掉的那条干净
113
- * 条目在页里根本不存在,重排救不回来 —— 而「带序必须先于截断」正是这条契约的载荷所在。所以带号
114
- * 必须进 ORDER BY,也就必须有一份 SQL 侧的判据。
115
- *
116
- * ## 为什么写成逐元素的行首正则(codex 对抗复审 R1-[high] 的直接产物)
117
- *
118
- * 初版图省事写的是 `frontmatter->>'extra' LIKE '%origin:%'`,自称「只多认不漏认」。**错了**,两个方向
119
- * 都不成立:`['origin :']`(冒号前有空格)是 core 认的合法承载形,而 LIKE 漏掉它 —— 一条**带标记**的
120
- * 条目被排进干净带,占掉的正是干净条目的位置;反过来 `['prefix origin: suffix']` 被 LIKE 误认,一条
121
- * **干净**条目沉进后带、可能被截断饿死。两个方向的结局是同一个:干净条目被饿死,而这正是本条款要拒的。
122
- *
123
- * 现形逐元素跑 core 那条行首正则(`/^\s*origin\s*:/`)。真库实测 10 种拼写(裸 / 冒号前空格 / 前导
124
- * 空格 / 制表 / 全角空格 U+3000 / 行中出现 / originx: / 非 origin 行 …)与 JS 判据**逐条同答**。
125
- *
126
- * ## 这份「第二实现」的同步义务
127
- *
128
- * 两份实现同表一个语义 = 本仓明令的危险形,所以它带**机器对表**:
129
- * `test/memory-engine-db-integration.test.ts` 的钉6 拿同一组拼写在**真 PG** 上让本谓词与
130
- * `committedOriginOf` 逐条对答,不一致当场红(离线格做不了这件事:被测的正是 PG 自己的正则引擎)。
131
- * core 改那条正则时,红的是对表格 —— 不是某台部署上悄悄跑偏的排序。
132
- *
133
- * ## 字符类为什么是**穷举码点**而不是 `[[:space:]]`(codex 第二轮 R2-[medium],采纳)
134
- *
135
- * PG 的 POSIX 字符类**跟着数据库 LC_CTYPE 走**:同一条 SQL 在 C locale 与某个 UTF-8 locale 下认的
136
- * 空白集合可以不一样(例如 U+0085 NEL —— JS 的 `\s` **不**含它)。而本谓词的正确性判据是「与 JS 的
137
- * `\s` 逐字同」,一条依赖部署 locale 的判据等于把同一条记忆在两台机器上排进不同的带 —— 而带号错了
138
- * 就会在 LIMIT 那一刀上不可恢复地截掉本该入页的干净条目。所以这里**不用**任何 locale 敏感构造,把
139
- * ECMAScript `\s` 的码点逐个写死(PG 的 ARE 支持 `\uXXXX` 转义,已在真库上验过);对表格里同时钉了
140
- * U+0085 这条**反例**(JS 不认 ⇒ SQL 也必须不认),locale 一旦重新混进来它当场红。
141
- */
142
- /** ⚠️ 本常量**只准写 ASCII**:那些 unicode 空白必须以 `\\uXXXX` 的**字面反斜杠形**交给 PG 的 ARE
143
- * 自己解析(已在真库上验过)。把真字符敲进源码会同时踩两条线 —— 源码卫生门(不可见字节)与
144
- * U+2028/U+2029(它们在 JS 源码里就是换行,会把这一行劈开)。 */
145
13
  const SQL_JS_WHITESPACE_CLASS = "[\\u0009\\u000a\\u000b\\u000c\\u000d\\u0020\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]";
146
- /** core `/^\s*origin\s*:/`(`extraOriginCarriers` 的开块判据)的 SQL 孪生。 */
147
14
  export const ORIGIN_CARRIER_LINE_SQL_REGEX = `^${SQL_JS_WHITESPACE_CLASS}*origin${SQL_JS_WHITESPACE_CLASS}*:`;
148
- /**
149
- * 「这一行带 committed origin 标记」的 SQL 布尔式(两种承载形同判,与 {@link exposureCarriage} 同源)。
150
- * `column` = 该表 frontmatter 列的表达式(调用方自己保证它是列名/限定名,**不接受外来串**)。
151
- */
152
15
  export function originMarkedSqlPredicate(column) {
153
16
  return (`(${column}->'origin' IS NOT NULL OR (jsonb_typeof(${column}->'extra') = 'array' AND EXISTS (` +
154
17
  `SELECT 1 FROM jsonb_array_elements_text(${column}->'extra') AS origin_line(line) ` +
155
18
  `WHERE origin_line.line ~ '${ORIGIN_CARRIER_LINE_SQL_REGEX}')))`);
156
19
  }
157
- /** 新建一只批次基线(每次 `applyPatches` 一只;跨批次**不得**复用 —— 已提交的墓碑正是靠「换批清零」放行)。 */
158
20
  export function createBatchCommittedBaseline() {
159
21
  return new Map();
160
22
  }
161
- /**
162
- * 记下(或取回)某 id 在**本批开始时**的已提交 frontmatter。
163
- *
164
- * `committedFm` = 这一次 I/O 真的从库里读到的已提交 frontmatter(没有行就传 `undefined`)。
165
- * **首次触碰写入,之后只读** —— 同批里后面的 op 看到的必须还是批开始那一份,否则本批自己的写会把
166
- * 基线推走(delete 之后 re-add 就又变成「库里没有 ⇒ 未标记」)。
167
- */
168
23
  export function rememberBatchCommitted(baseline, id, committedFm) {
169
24
  if (baseline.has(id))
170
25
  return baseline.get(id);
171
26
  baseline.set(id, committedFm);
172
27
  return committedFm;
173
28
  }
174
- /**
175
- * 不可变律的判据。返回 `undefined` = 放行;返回字符串 = 该字符串就是 `PatchReport.conflicts[].reason`。
176
- *
177
- * 文案与 core File backend 的两处**逐字同形**(`external-origin marker whitewash refused: the
178
- * {add,update} strips or rewrites the origin marker of a marked entry (malformed patch refused)`):
179
- * 契约试剂盒按 `/malformed patch refused/` 匹配,而跨后端同一输入必须给同一个答复 —— 消费方(引擎的
180
- * harvest 腿、sync 对拍)按 reason 分类处置,两支方言各写各的文案就等于让同一件事在不同部署上分叉。
181
- *
182
- * 未标记的已提交条目(`committed === undefined`)一律放行:本法只约束**已经**带标记的那些 id,
183
- * 「给未标记条目加标记」是引擎写路径自己的事(server 不铸 origin,见台账的 skip 行)。
184
- */
185
- /** `NotePatch.op` 的运行期闭集(与 core 的联合逐字同)。 */
186
29
  const PATCH_OPS = new Set(["add", "update", "delete"]);
187
- /**
188
- * 未知 op 拼写的**前置**拒绝(codex R1-[high],验真后采纳;**既有**缺口,不是本批引入)。
189
- *
190
- * 病:两支 SQL 后端的 `applyOne` 形状是「`if (op === "add") {…}` → 落到 update/delete 段」,而段内
191
- * 的两道白洗守卫(repo_file provenance / 本模块的 origin 不可变律)都写成 `if (patch.op === "update")`。
192
- * ⇒ 一个**词表外**的 op(`"upsert"` / 打错的字 / 将来 core 加的新词)会:跳过两道守卫 → 走到 update
193
- * 的 SQL → 把行改掉 → 在 `report.applied` 里报成一次 update。TypeScript 的联合型是**编译期**的,
194
- * 拦不住任何运行期调用方(sync 腿、嵌入方自建的调用点、future core)。
195
- *
196
- * 今日可达性(如实):本仓唯一的调用点 `src/memory-sync.ts` 自己铸 op(基线有 ⇒ update、无 ⇒ add),
197
- * 词表外的值到不了。所以这条是**加固**而不是在修一次已发生的事故 —— 但它落在安全轴(白洗守卫的
198
- * 有效性完全依赖「op 只有三种」这个未被检查的前提),按 CLAUDE.md #157「闭集 + 不可避免的运行期
199
- * miss 臂必须说出来」补一道显式拒绝:未知 op **零写**,报 `malformed patch refused`(与本仓既有的
200
- * id-mismatch 拒绝同族文案,消费方按同一条正则分类)。
201
- */
202
30
  export function unknownPatchOpRefusal(op) {
203
31
  if (PATCH_OPS.has(op))
204
32
  return undefined;
@@ -212,15 +40,6 @@ export function originWhitewashRefusal(op, committedFm, next) {
212
40
  return undefined;
213
41
  return `external-origin marker whitewash refused: the ${op} strips or rewrites the origin marker of a marked entry (malformed patch refused)`;
214
42
  }
215
- /**
216
- * #337(core 5.55.0 conformance c36/c37,design/339)—— **distilled 血统不可变律**的判据,与 origin
217
- * 律同族同基线同判位:一旦某 id 的已提交状态带 `frontmatter.distilled` 块(consolidation 蒸馏产物的
218
- * 血统:planId/at/carrierRev/inputs),任何 update / 裸 add / guard add / 同批 delete+re-add 都必须把
219
- * 它 DEEP-EQUAL 地带下去,否则拒 —— 合法出口只有已提交的墓碑(c36 逐字)。判序:**先于 rev CAS**
220
- * (c37:骑着 stale baseRev 的 strip 也必须答 malformed refusal,不是 rev mismatch)。
221
- * 判据用 core 导出的 {@link committedDistilledOf} / {@link distilledEquals},不手抄(F1b);文案与
222
- * core File backend 两处逐字同形,契约按 `/malformed patch refused/` 匹配。
223
- */
224
43
  export function distilledWhitewashRefusal(op, committedFm, next) {
225
44
  const committed = committedFm !== undefined ? committedDistilledOf(committedFm) : undefined;
226
45
  if (committed === undefined)
@@ -229,12 +48,6 @@ export function distilledWhitewashRefusal(op, committedFm, next) {
229
48
  return undefined;
230
49
  return `distilled lineage whitewash refused: the ${op} strips or rewrites the distilled block of a consolidation product (malformed patch refused)`;
231
50
  }
232
- /**
233
- * #337(conformance c38)—— **distilled 事实的头面携带**,与 {@link exposureCarriage} 同住同因:
234
- * `listHeaders` 与 `search` 两个头面都携带 `{ carrierRev, supersedes }` 投影(supersedes = inputs 中
235
- * `superseded === true` 的行,形状与 core File backend 的 header 投影逐字同);无块条目两面都不携带。
236
- * 蒸馏产物的「取代了哪些输入行」必须在检索面可见,否则被取代的旧行与蒸馏产物在读侧无从判序。
237
- */
238
51
  export function distilledCarriage(fm) {
239
52
  const d = fm.distilled;
240
53
  if (d === undefined)
@@ -1,16 +1,6 @@
1
- /**
2
- * In-memory resume-at anchor map — the LOCAL (DB-less) twin of {@link TiDBResumeAnchorStore}/{@link PgResumeAnchorStore}
3
- * (E18 resume-at; clay seamless local↔cloud). The shell holds the live E2 `eventId` (core never persists it);
4
- * core's `TaskSpec.resumeAt` takes a persisted `SessionTreeEntry.id`. This store is the service-owned translation
5
- * ("a deployment that holds eventIds owns the eventId→entryId map", core types). resume-at works LOCALLY (it needs
6
- * only the session tree + this map, no cloud-only checkpoint), so this is PRESENT on the local backend (a singleton,
7
- * like MemoryRunStore) — not omitted like the cloud-only stores. Process-local + ephemeral (lost on restart, same
8
- * blast radius as MemoryRunStore; the file-backed follow-on persists it alongside run history).
9
- */
10
1
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
11
2
  export class MemoryResumeAnchorStore {
12
3
  bySession = new Map();
13
- /** Record/overwrite the anchor for one settled message boundary (idempotent upsert; parity with the SQL ON DUPLICATE/CONFLICT). */
14
4
  async put(sessionId, eventId, entryId, owner) {
15
5
  let m = this.bySession.get(sessionId);
16
6
  if (!m) {
@@ -19,15 +9,10 @@ export class MemoryResumeAnchorStore {
19
9
  }
20
10
  m.set(eventId, { entryId, owner });
21
11
  }
22
- /** Resolve a shell-supplied eventId → the persisted SessionTreeEntry.id for TaskSpec.resumeAt; undefined = unknown
23
- * anchor (the route 4xx's before handing core a bad id). owner null-safe guard = the SQL `owner <=> ?` defense-in-depth. */
24
12
  async resolve(sessionId, eventId, owner) {
25
13
  const e = this.bySession.get(sessionId)?.get(eventId);
26
14
  return e && ownerEq(e.owner, owner) ? e.entryId : undefined;
27
15
  }
28
- /** 2c session-sync: ALL anchors for a session (every eventId→entryId + its owner), UNFILTERED — a cross-backend
29
- * session EXPORT bundles the whole anchor map so the importer can re-key owner to the target principal (§9). Twin of
30
- * the SQL/file stores; kept on all three backends so the ResumeAnchorStore union shares a single contract. */
31
16
  async listBySession(sessionId) {
32
17
  const m = this.bySession.get(sessionId);
33
18
  if (!m)
@@ -37,8 +22,6 @@ export class MemoryResumeAnchorStore {
37
22
  out.push({ eventId, entryId: e.entryId, owner: e.owner });
38
23
  return out;
39
24
  }
40
- /** E21 (§0.5 delete) — purge ALL anchors for a session, scoped by session_id ALONE (the route already proved session
41
- * ownership; a per-row owner guard would orphan rows stored under a divergent per-run principal). Idempotent (count). */
42
25
  async deleteBySession(sessionId) {
43
26
  const removed = this.bySession.get(sessionId)?.size ?? 0;
44
27
  this.bySession.delete(sessionId);
@@ -1,27 +1,10 @@
1
- /**
2
- * In-memory async run registry + replayable event log — the LOCAL (DB-less) twin of {@link TiDBRunStore}/
3
- * {@link PgRunStore} (clay 2026-06-25: the seamless local↔cloud switch — a locally-run HTTP service must serve the
4
- * SAME contract a cloud worker does, incl. `/v1/runs` async runs + the trace event log). Behaviourally identical to
5
- * the SQL twins (the shared `storeBehaviorSuite` keeps them in lock-step), but process-local + ephemeral: a restart
6
- * loses run history (file-backed durability is the immediate follow-on; the conversation HISTORY + checkpoints DO
7
- * persist via core's FileStorageBackend). Single-process, so the cross-replica/durable reapers degrade — see below.
8
- *
9
- * Mirrors the SQL semantics exactly: the `task_active` single-active-run claim (a Map keyed by sessionId), the
10
- * NON-terminal suspend/needs_review parks that KEEP the claim, the markResuming CAS that resets cancel/preempt, the
11
- * owner null-safe guard on the flag/heartbeat paths (`<=>` → JS `(a??null)===(b??null)`), and the keyset list order.
12
- */
13
1
  import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
14
- /** Null-safe owner equality — the JS twin of the SQL `owner <=> ?` guard (a legacy null-owner run matches owner=null). */
15
2
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
16
3
  export class MemoryRunStore {
17
4
  runs = new Map();
18
- /** sessionId → taskId — the single-active-run claim (the `task_active` table). */
19
5
  active = new Map();
20
- /** taskId → events (kept seq-sorted on read). */
21
6
  events = new Map();
22
- /** [868] the checkpoint-table stand-in (see {@link RunStoreCheckpointProbe}); absent ⇒ reapers NO-OP. */
23
7
  checkpointProbe;
24
- /** [868] wire the checkpoint probe (backend assembly, where checkpoint + run store are built together). */
25
8
  setCheckpointProbe(probe) {
26
9
  this.checkpointProbe = probe;
27
10
  }
@@ -42,7 +25,6 @@ export class MemoryRunStore {
42
25
  updatedAt: r.updatedAt.toISOString(),
43
26
  };
44
27
  }
45
- /** Create a run iff the session has no active run (the task_active unique-key claim). */
46
28
  async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
47
29
  const claimed = this.active.get(sessionId);
48
30
  if (claimed !== undefined)
@@ -97,12 +79,8 @@ export class MemoryRunStore {
97
79
  }
98
80
  async appendEvent(taskId, seq, type, data) {
99
81
  const list = this.events.get(taskId) ?? [];
100
- // Mirror the (task_id, seq) PK: a duplicate seq is a caller bug (callers use ++seq); ignore a re-append of the
101
- // same seq rather than corrupt the log (the SQL twin would throw ER_DUP_ENTRY — callers never hit it).
102
82
  if (list.some((e) => e.seq === seq))
103
83
  return;
104
- // Deep-clone `data` on WRITE so the stored event is isolated from later caller mutation — the SQL twin
105
- // JSON.stringify's it into the column, giving the same write-isolation (parity oracle).
106
84
  list.push({ seq, type, data: data == null ? null : structuredClone(data), ts: new Date().toISOString() });
107
85
  this.events.set(taskId, list);
108
86
  }
@@ -116,19 +94,14 @@ export class MemoryRunStore {
116
94
  }
117
95
  async getEvents(taskId, afterSeq) {
118
96
  const list = this.events.get(taskId) ?? [];
119
- // Deep-clone `data` on READ so each call yields a fresh, mutation-isolated object — the SQL twin re-parses the
120
- // JSON column per read (parity oracle); a shallow `{...e}` would alias `data` into the stored Map (review footgun).
121
97
  return list
122
98
  .filter((e) => e.seq > afterSeq)
123
99
  .sort((a, b) => a.seq - b.seq)
124
100
  .map((e) => ({ ...e, data: e.data == null ? null : structuredClone(e.data) }));
125
101
  }
126
- /** [888] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
127
- * [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
128
102
  async setTerminal(taskId, status, result, error) {
129
103
  const r = this.runs.get(taskId);
130
104
  if (!r) {
131
- // [2255]③ file 孪生同判据(parity oracle):行缺席时终局动词仍放锁(反查 active)。
132
105
  for (const [sessionId, tid] of this.active)
133
106
  if (tid === taskId) {
134
107
  this.active.delete(sessionId);
@@ -143,11 +116,9 @@ export class MemoryRunStore {
143
116
  r.errorCode = result?.errorCode ?? null;
144
117
  r.updatedAt = new Date();
145
118
  }
146
- // Release the single-active-run claim (DELETE FROM task_active WHERE task_id = ?).
147
119
  if (this.active.get(r.sessionId) === taskId)
148
120
  this.active.delete(r.sessionId);
149
121
  }
150
- /** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard). */
151
122
  async setSuspended(taskId) {
152
123
  const r = this.runs.get(taskId);
153
124
  if (r && (r.status === "running" || r.status === "suspended")) {
@@ -155,7 +126,6 @@ export class MemoryRunStore {
155
126
  r.updatedAt = new Date();
156
127
  }
157
128
  }
158
- /** design/80 D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review. */
159
129
  async setNeedsReview(taskId) {
160
130
  const r = this.runs.get(taskId);
161
131
  if (r && (r.status === "running" || r.status === "needs_review")) {
@@ -163,7 +133,6 @@ export class MemoryRunStore {
163
133
  r.updatedAt = new Date();
164
134
  }
165
135
  }
166
- /** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags. */
167
136
  async markResuming(taskId) {
168
137
  const r = this.runs.get(taskId);
169
138
  if (!r || (r.status !== "suspended" && r.status !== "needs_review"))
@@ -181,33 +150,27 @@ export class MemoryRunStore {
181
150
  const r = this.runs.get(taskId);
182
151
  return r ? this.toRecord(r) : undefined;
183
152
  }
184
- /** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
185
153
  async listRuns(opts) {
186
- // 夜测 N1(2026-07-09,LOW):a non-date cursor.createdAt made the four backends diverge (PG threw a raw
187
- // DB error; the other three returned an empty page). HTTP's decodeCursor pre-validates, so this is
188
- // defence-in-depth for direct store callers — normalize ALL backends to the empty page.
189
154
  if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
190
155
  return [];
191
156
  const cursorAt = opts.cursor ? Date.parse(opts.cursor.createdAt) : undefined;
192
157
  const rows = [...this.runs.values()]
193
158
  .filter((r) => (opts.status ? r.status === opts.status : true))
194
- .filter((r) => (opts.jobId !== undefined ? r.jobId === opts.jobId : true)) // "" is a valid exact jobId, not "no filter" (B10)
195
- .filter((r) => (opts.source !== undefined ? r.source === opts.source : true)) // "" is a valid exact source, not "no filter" (B10)
196
- .filter((r) => (opts.owner !== undefined ? r.owner === opts.owner : true)) // exact (SQL `owner = ?`): "" is a valid exact owner, not "no filter" (B10)
159
+ .filter((r) => (opts.jobId !== undefined ? r.jobId === opts.jobId : true))
160
+ .filter((r) => (opts.source !== undefined ? r.source === opts.source : true))
161
+ .filter((r) => (opts.owner !== undefined ? r.owner === opts.owner : true))
197
162
  .filter((r) => {
198
163
  if (cursorAt === undefined)
199
164
  return true;
200
165
  const t = r.createdAt.getTime();
201
- return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId); // keyset: strictly older, ties by taskId
166
+ return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId);
202
167
  })
203
168
  .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
204
169
  return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
205
170
  }
206
- /** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
207
171
  async listSessions(opts) {
208
172
  const bySession = new Map();
209
173
  for (const r of this.runs.values()) {
210
- // exact owner filter (SQL `owner = ?`); "" is a valid exact owner (B10). `includeUnowned` = [4198] 窄臂第三条件(SQL `OR owner IS NULL`)。
211
174
  if (opts.owner !== undefined && r.owner !== opts.owner && !(opts.includeUnowned === true && r.owner === null))
212
175
  continue;
213
176
  const arr = bySession.get(r.sessionId) ?? [];
@@ -215,12 +178,11 @@ export class MemoryRunStore {
215
178
  bySession.set(r.sessionId, arr);
216
179
  }
217
180
  const cursorAt = opts.cursor ? Date.parse(opts.cursor.lastActivityAt) : undefined;
218
- const qlc = opts.q?.toLowerCase(); // ?q= search: any-run preview match (the in-memory twin of the SQL EXISTS)
181
+ const qlc = opts.q?.toLowerCase();
219
182
  const summaries = [];
220
183
  for (const [sessionId, arr] of bySession) {
221
184
  if (qlc && !arr.some((r) => r.objectivePreview?.toLowerCase().includes(qlc)))
222
185
  continue;
223
- // rn=1 = latest run (createdAt DESC, taskId DESC) → preview/status/owner.
224
186
  const sorted = [...arr].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
225
187
  const latest = sorted[0];
226
188
  const times = arr.map((r) => r.createdAt.getTime());
@@ -234,8 +196,8 @@ export class MemoryRunStore {
234
196
  runCount: arr.length,
235
197
  objectivePreview: latest.objectivePreview,
236
198
  lastStatus: latest.status,
237
- lastRunId: latest.taskId, // K-5c: latest run id = the live-tail re-attach anchor
238
- title: null, // honest degrade (no session_meta on the memory twin)
199
+ lastRunId: latest.taskId,
200
+ title: null,
239
201
  });
240
202
  }
241
203
  return summaries
@@ -248,19 +210,8 @@ export class MemoryRunStore {
248
210
  .sort((a, b) => Date.parse(b.lastActivityAt) - Date.parse(a.lastActivityAt) || (a.sessionId < b.sessionId ? 1 : a.sessionId > b.sessionId ? -1 : 0))
249
211
  .slice(0, opts.limit);
250
212
  }
251
- /**
252
- * E21 (§0.5 delete) — purge the runs ledger for one session, scoped by `session_id` ALONE (per-row owner is the
253
- * submitting principal and diverges from the session owner — full 判据 see the SQL twin's head-note, F-A);
254
- * aborts (returns {active}) if a run is live.
255
- *
256
- * `expectedSessionOwner` 是 SQL 双生的**化身围栏**入参(事务内复核 `session_meta.owner`,防「A 验权后 A' 删完、
257
- * 别人用同一 uuid 重新登记」那一形)。本孪生**执行不了**它:local 车道的会话表在另一只店里,这只账本没有
258
- * session_meta 可读 —— 参数只作契约对齐(签名不齐会让 RunStore 联合类型的调用点直接不可调)。local 的替代
259
- * 围栏是两条:①协调器在本调用**紧前**复读一次 canonical owner(不齐即整条中止);②数据根的独占 BootLock
260
- * 保证一个数据根恒一个进程,交错只可能发生在同进程的 await 缝里。这条不对称是**明写**的,不是遗漏。
261
- */
262
213
  async deleteBySession(sessionId, expectedSessionOwner) {
263
- void expectedSessionOwner; // 见上:本孪生无 session_meta 可复核,围栏在协调器那一侧
214
+ void expectedSessionOwner;
264
215
  const claimed = this.active.get(sessionId);
265
216
  if (claimed !== undefined)
266
217
  return { active: claimed };
@@ -274,7 +225,6 @@ export class MemoryRunStore {
274
225
  }
275
226
  return { removed };
276
227
  }
277
- /** 用量扫窗——in-memory 腿:遍历窗内行,投影同 usage-analytics 纯函数(SQL 孪生 parity)。 */
278
228
  async usageScan(fromMs, toMs, opts) {
279
229
  const limit = Math.min(opts?.limit ?? USAGE_SCAN_LIMIT, USAGE_SCAN_LIMIT);
280
230
  const all = [];
@@ -295,14 +245,13 @@ export class MemoryRunStore {
295
245
  for (const r of this.runs.values()) {
296
246
  if (r.createdAt.getTime() < sinceMs)
297
247
  continue;
298
- const key = JSON.stringify([r.source, r.status]); // collision-proof + keeps NULL distinct from "" (SQL GROUP BY parity)
248
+ const key = JSON.stringify([r.source, r.status]);
299
249
  const cur = counts.get(key) ?? { source: r.source, status: r.status, count: 0 };
300
250
  cur.count += 1;
301
251
  counts.set(key, cur);
302
252
  }
303
253
  return [...counts.values()];
304
254
  }
305
- /** Fail stale `running` rows (crashed-run backstop) + release task_active for any now-terminal row. */
306
255
  async reapStale(olderThanMs) {
307
256
  const cutoff = Date.now() - olderThanMs;
308
257
  let reaped = 0;
@@ -314,20 +263,15 @@ export class MemoryRunStore {
314
263
  reaped += 1;
315
264
  }
316
265
  }
317
- // Release the claim for TERMINAL rows only (suspended/needs_review intentionally keep it).
318
266
  this.releaseTerminalClaims();
319
267
  return reaped;
320
268
  }
321
- /** [868] shared tail of both suspended-run reapers: flip one parked row → failed('approval.expired'), the
322
- * in-memory twin of the SQL UPDATE (`error`/`error_code` set directly — no result blob to derive from). */
323
269
  failExpiredPark(r) {
324
270
  r.status = "failed";
325
271
  r.error = "approval expired before decision";
326
272
  r.errorCode = "approval.expired";
327
273
  r.updatedAt = new Date();
328
274
  }
329
- /** Release the claim for TERMINAL rows only — suspended/needs_review keep it (the `DELETE ta … WHERE tr.status
330
- * NOT IN ('running','suspended','needs_review')` twin, shared by reapStale + the [868] suspended reapers). */
331
275
  releaseTerminalClaims() {
332
276
  for (const [sessionId, taskId] of [...this.active]) {
333
277
  const r = this.runs.get(taskId);
@@ -335,22 +279,12 @@ export class MemoryRunStore {
335
279
  this.active.delete(sessionId);
336
280
  }
337
281
  }
338
- /**
339
- * [868] Durable F4 expiry — the local twin of TiDBRunStore.reapSuspended, driven by the injected checkpoint
340
- * probe instead of a SQL JOIN (formerly a NO-OP, which was one of the five closed doors in the "suspended run
341
- * locks the session forever" incident). Semantics verbatim from the SQL twin: suspended ∧ updatedAt < cutoff
342
- * ∧ NOT probe.hasPending(session) → failed 'approval expired before decision' / error_code 'approval.expired'
343
- * + release the task_active claim. The NOT-pending clause is the design/80 D-D adversarial-blocker guard
344
- * (SQL 孪生现在的真身:`run-store-sql.ts` 的 `reapSuspended`;`tidb-run-store.ts` 该壳文件已删(5.0.0)): a row whose checkpoint is STILL pending belongs to the checkpoint-state
345
- * sweeps (SLA deny-sweep / failSuspendedWithExpiredCheckpoint) — time-reaping it here would orphan a model
346
- * leg on a now-unlocked session. Probe absent ⇒ honest NO-OP (never kill a park without the guard).
347
- */
348
282
  async reapSuspended(olderThanMs) {
349
283
  const probe = this.checkpointProbe;
350
284
  if (!probe) {
351
285
  this.releaseTerminalClaims();
352
286
  return 0;
353
- } // [2255]③ claim 不变式维护不跟 probe 走
287
+ }
354
288
  const cutoff = Date.now() - olderThanMs;
355
289
  let reaped = 0;
356
290
  for (const r of this.runs.values()) {
@@ -358,10 +292,7 @@ export class MemoryRunStore {
358
292
  continue;
359
293
  const snap = r.updatedAt.getTime();
360
294
  if (await probe.hasPending(r.sessionId))
361
- continue; // D-D guard: a still-pending checkpoint owns this row
362
- // codex H2:probe 的 await 让出事件循环——markResuming(approve 恢复)/cancel/新一轮 park 可能已改行。
363
- // 复核与改写之间无 await(Node 单线程 ⇒ 原子);状态或 updatedAt 变了就放手,绝不覆盖一条已被别腿
364
- // 认领的行(SQL 孪生靠单条条件 UPDATE 天然原子,这里的 snap 复核就是它的本地等价)。
295
+ continue;
365
296
  if (r.status !== "suspended" || r.updatedAt.getTime() !== snap)
366
297
  continue;
367
298
  this.failExpiredPark(r);
@@ -370,19 +301,12 @@ export class MemoryRunStore {
370
301
  this.releaseTerminalClaims();
371
302
  return reaped;
372
303
  }
373
- /**
374
- * [868] design/80 §3 inv#3 crash-safe backstop — the local twin of TiDBRunStore.failSuspendedWithExpiredCheckpoint
375
- * (checkpoint-STATE-driven, not time-driven; runs UNCONDITIONALLY every reaper tick, no APPROVAL_TIMEOUT_SEC gate).
376
- * Semantics verbatim from the SQL twin: (suspended ∨ needs_review) ∧ probe.hasExpired(session) ∧ NOT
377
- * probe.hasPending(session) → same fail + claim release. The NOT-pending clause covers a re-suspended session
378
- * that minted a NEW pending gate (wq64gmm5e: an abandoned needs_review park leaks exactly like a suspended one).
379
- */
380
304
  async failSuspendedWithExpiredCheckpoint() {
381
305
  const probe = this.checkpointProbe;
382
306
  if (!probe) {
383
307
  this.releaseTerminalClaims();
384
308
  return 0;
385
- } // [2255]③ claim 不变式维护不跟 probe 走
309
+ }
386
310
  let reaped = 0;
387
311
  for (const r of this.runs.values()) {
388
312
  if (r.status !== "suspended" && r.status !== "needs_review")
@@ -393,7 +317,6 @@ export class MemoryRunStore {
393
317
  continue;
394
318
  if (await probe.hasPending(r.sessionId))
395
319
  continue;
396
- // codex H2 同款 snap 复核(两个 await 窗都盖住):行被别腿改过就放手。
397
320
  if (r.status !== parkedAs || r.updatedAt.getTime() !== snap)
398
321
  continue;
399
322
  this.failExpiredPark(r);
@@ -1,17 +1,3 @@
1
- /**
2
- * In-memory SessionPolicyStore + the service's `deleteBySession` extension (E21 purge / 2c overwrite-dst wipe).
3
- *
4
- * WHY THIS EXISTS (and not core's `InMemorySessionPolicyStore`): core's in-memory/file policy stores implement only
5
- * the core interface (getRules/putRules/listBySession) — they have NO deletion seam, so neither the E21 session purge
6
- * nor the 2c `overwrite-dst` subsystem wipe can remove policy rows on a backend that uses them (the SQL twins carry
7
- * `deleteBySession` as a service extension; see session-policy-store-sql.ts). This store is the delete-capable
8
- * in-memory twin. The seam itself is also requested upstream (core: optional `deleteBySession?` on the interface +
9
- * the File/InMemory impls) — if core lands it, this class can retire in favor of core's.
10
- *
11
- * BYTE-MATCH DISCIPLINE: getRules/putRules/listBySession replicate core's `InMemorySessionPolicyStore` semantics
12
- * exactly (same key shape, same CAS `conflict` on expectedRev mismatch, same tighten-only `loosen_forbidden` gate via
13
- * core's exported `normalizeRules`/`loosenReasons`/`stripRev`, same rev start/bump). Any divergence is a bug here.
14
- */
15
1
  import { normalizeRules, loosenReasons, stripRev, SessionPolicyError, } from "@sema-agent/core";
16
2
  export class MemorySessionPolicyStore {
17
3
  map = new Map();
@@ -48,9 +34,6 @@ export class MemorySessionPolicyStore {
48
34
  }
49
35
  return out;
50
36
  }
51
- /** Purge ALL policy rows for a session, across every principal. Scoped by session_id alone — the caller
52
- * (E21 route / importSession overwrite-dst) already proved ownership. Shape = core 1.423's optional
53
- * `SessionPolicyStore.deleteBySession?` seam (`Promise<void>` — the count was incidental; nothing consumed it). */
54
37
  async deleteBySession(sessionId) {
55
38
  for (const k of [...this.map.keys()]) {
56
39
  const [sid] = JSON.parse(k);