@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,21 +1,13 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
3
3
  export const ADOPTION_LOG_TABLE = "adoption_log";
4
- /** 弧的阶段(183 §3.2 根级状态机的 form b 投影)。**单调**,只增不减。 */
5
4
  export const ADOPTION_PHASE = {
6
- /** ② 意图已落库(行在,尚未动任何数据行)。 */
7
5
  INTENT: 2,
8
- /** ③ 身份轴重绑腿全部完成(与腿的 UPDATE **同一个事务**)。 */
9
6
  REBOUND: 3,
10
- /** ④ 解析切换 / 配置清单产出。form b 的 server 半场:配置账已物化进 `configs`。 */
11
7
  CONFIGS: 4,
12
- /** ⑤ 承运腿。**form b 恒零承运**(数据本来就在 SQL)—— 阶段位仍显式推进,好让 form a 上车时挂得上。 */
13
8
  CARRIED: 5,
14
- /** ⑥ 永久终态。 */
15
9
  TERMINAL: 6,
16
10
  };
17
- /** MySQL 协议方言的建表语句(展开进 tidb-pool 的中央 `SCHEMA_STATEMENTS`,与 approval-ask 同姿势:
18
- * 跟着中央 ensureSchema 在 named-lock 的那条 conn 上建,不绕开 DDL 串行化)。 */
19
11
  export const TIDB_ADOPTION_LOG_STATEMENTS = [
20
12
  `CREATE TABLE IF NOT EXISTS ${ADOPTION_LOG_TABLE} (
21
13
  adoption_id VARCHAR(64) NOT NULL,
@@ -38,7 +30,6 @@ export const TIDB_ADOPTION_LOG_STATEMENTS = [
38
30
  KEY idx_adoption_log_state (state)
39
31
  ) COLLATE utf8mb4_bin`,
40
32
  ];
41
- /** PostgreSQL 孪生(由 `ensurePgSchema` 在 advisory lock 的那条 client 上调用)。 */
42
33
  export async function ensurePgAdoptionLogSchema(q) {
43
34
  await q(`CREATE TABLE IF NOT EXISTS ${ADOPTION_LOG_TABLE} (
44
35
  adoption_id VARCHAR(64) COLLATE "C" NOT NULL,
@@ -76,54 +67,16 @@ function toRow(r) {
76
67
  updatedAtMs: Number(r.updated_at_ms),
77
68
  };
78
69
  }
79
- /** PG advisory lock 的 32 位对象键(类键固定,对象键 = 名字的 sha256 前 4 字节转有符号 int4)。 */
80
70
  const PG_ADOPTION_LOCK_CLASS = 4907;
81
71
  function pgLockObjectKey(name) {
82
72
  return createHash("sha256").update(name).digest().readInt32BE(0);
83
73
  }
84
- /**
85
- * 弧锁的名字。**定长**(前缀 14 + 32 位十六进制 = 46 字符)。
86
- *
87
- * 🔴 为什么必须 hash 而不是把 principal 直接拼进去(codex R3-F3,亲核属实):TiDB/MySQL 的 `GET_LOCK`
88
- * 锁名上限是 **64 字符**,而请求面收的 principal 到 190 —— 超过 50 字符的源身份会让取锁**在弧动起来
89
- * 之前**就失败,而那时意图行已经落库,于是每一次重发、每一次副本 boot 续跑都确定性地再撞一次同一堵墙。
90
- * (PG 那条腿本来就走 hash 出来的 int4 对象键,这里只是把 MySQL 腿补齐到同一姿势。)
91
- */
92
74
  export function adoptionLockName(fromPrincipal) {
93
75
  return `sema_adoption:${createHash("sha256").update(fromPrincipal).digest("hex").slice(0, 32)}`;
94
76
  }
95
- /**
96
- * 把弧锁名限定到**一个库**的键空间(A-010.13,验真后修)。
97
- *
98
- * 🔴 病:两条腿的锁键空间**不等价**。MySQL/TiDB 的 `GET_LOCK` 名字是**服务器实例全局**的
99
- * (`performance_schema.metadata_locks` 里就是一个进程级命名空间,与你连的是哪个 schema 无关);
100
- * PG 的 advisory lock 是**每数据库**的(`pg_locks` 的 database 列参与身份)。而锁名此前只 hash 了
101
- * `fromPrincipal`、前缀写死 `sema_adoption:` —— 于是同一台 MySQL 上并存的两套部署
102
- * (`aiagent_prod` / `aiagent_staging`,同一个源身份)会**互相抢同一把锁**:一边正在跑弧,另一边
103
- * 取不到锁、按「在飞」回一份 `stalled` 回执 —— 一个与它自己的库状态完全对不上的答复。切到 PG 同样
104
- * 两套部署却各跑各的。同一份代码、同一份配置,两个后端上语义不同 = 方言不等价。
105
- *
106
- * 🔴 姿势:限定符 = **当前数据库名**(MySQL `DATABASE()` / PG `current_database()`),与 principal
107
- * 一起进 hash 原像。于是 MySQL 腿被收窄到与 PG 腿同一个键空间语义(每库一把),而不是把 PG 放宽到
108
- * 实例全局(放宽的那个方向会让**同库**的两副本以为各自持锁,那是真丢互斥,方向错得多)。
109
- *
110
- * 🔴 为什么仍然 hash 而不是 `${db}/${name}` 直接拼:`GET_LOCK` 的名字上限是 **64 字符**,而库名没有
111
- * 本仓能保证的上限。拼接形会让一个长库名的部署在**取锁那一步**才失败,而那时意图行已经落库 ——
112
- * 每一次重发、每一次 boot 续跑都确定性地再撞一次同一堵墙(与 `adoptionLockName` 自己头注里那条
113
- * codex R3-F3 是同一个病)。hash 后定长 46 字符,与库名长度无关。
114
- * 分隔符取 **NUL**(源码里写成转义 `\u0000`,不落裸控制字节 —— 那会让 grep 把整个文件当二进制):
115
- * 库名可以含 `/`、空格这类字符(MySQL 反引号标识符 / PG 双引号标识符都允许),拿它们当分隔符时
116
- * `(db="a", name="b/c")` 与 `(db="a/b", name="c")` 会铸出**同一个原像** —— 弱分隔符拼接的经典撞形。
117
- * NUL 在两种引擎的标识符里都不合法,所以它是真的不可能出现在任何一段里。
118
- */
119
77
  export function qualifyAdoptionLockName(database, name) {
120
78
  return `sema_adoption:${createHash("sha256").update(`${database}\u0000${name}`, "utf8").digest("hex").slice(0, 32)}`;
121
79
  }
122
- /**
123
- * 收编日志店(单文件双方言,SqlDriver 形——checkpoint-store / approval-ask-store 同款)。
124
- *
125
- * 每条语句的两方言文本**并排写在调用点**(A12 判据:方言差异必须显式,不许用占位符编号循环拼)。
126
- */
127
80
  export class SqlAdoptionLogStore {
128
81
  db;
129
82
  constructor(db) {
@@ -132,15 +85,9 @@ export class SqlAdoptionLogStore {
132
85
  q(tidb, pg) {
133
86
  return this.db.dialect === "tidb" ? tidb : pg;
134
87
  }
135
- /** 池级只读执行面(phase 0 的 preflight 扫描用——它**零写**,不需要也不该占一条事务连接)。
136
- * 写路径一律走 {@link rebindTransaction};把驱动整个暴露出去会让调用方能绕开 phase CAS。 */
137
88
  get reader() {
138
89
  return this.db;
139
90
  }
140
- /**
141
- * 发起腿的 **insert-or-fetch 原子形**。返回读回的行(可能是别人插的)+ 是否本次插入。
142
- * 调用方据 `row.toPrincipal !== toPrincipal` 判「同 from 异 to」⇒ typed 409。
143
- */
144
91
  async ensureAdoption(row) {
145
92
  const emptyLegs = "[]";
146
93
  const res = await this.db.query(this.q(`INSERT IGNORE INTO ${ADOPTION_LOG_TABLE} (${COLS}) VALUES (?, ?, ?, ?, 'in_flight', ?, ?, NULL, NULL, NULL, ?, ?)`, `INSERT INTO ${ADOPTION_LOG_TABLE} (${COLS}) VALUES ($1, $2, $3, $4, 'in_flight', $5, $6, NULL, NULL, NULL, $7, $8) ON CONFLICT (from_principal) DO NOTHING`), [row.adoptionId, row.fromPrincipal, row.toPrincipal, ADOPTION_PHASE.INTENT, emptyLegs, emptyLegs, row.nowMs, row.nowMs]);
@@ -158,29 +105,15 @@ export class SqlAdoptionLogStore {
158
105
  const res = await exec.query(this.q(`SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE from_principal = ?`, `SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE from_principal = $1`), [fromPrincipal]);
159
106
  return res.rows[0] ? toRow(res.rows[0]) : null;
160
107
  }
161
- /** I6 server 同族:每副本 boot 必查的在飞名单(**响亮**,不静默跳过)。 */
162
108
  async listInFlight(exec = this.db) {
163
109
  const res = await exec.query(this.q(`SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE state = 'in_flight' ORDER BY created_at_ms ASC`, `SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE state = 'in_flight' ORDER BY created_at_ms ASC`), []);
164
110
  return res.rows.map(toRow);
165
111
  }
166
- /**
167
- * 迁移事务:腿的全部 UPDATE **与** phase CAS 落在**同一个事务**里。
168
- *
169
- * 🔴 为什么必须同事务(而不是「先迁完再推 phase」):两句分开就留下一个窗 —— 腿提交了、phase 没推,
170
- * 崩溃后接棒副本重跑腿(幂等,零行变化)却把**首次的行数读数**丢了,回执里的 `legs` 会变成一串 0。
171
- * 同事务之后这个窗根本不存在:要么「行迁了且 phase=3 且 legs 读数在」,要么什么都没发生。
172
- * 任何异常(含唯一键违例 = 跨轴组合冲突)⇒ ROLLBACK ⇒ **源/目的地两侧字节零变更**。
173
- */
174
112
  async rebindOn(conn, adoptionId, expectPhase, nextPhase, nowMs, work, encodeLegs) {
175
113
  try {
176
114
  await conn.begin();
177
115
  const legs = await work(conn);
178
- const cas = await conn.query(
179
- // 🔴 谓词里必须带 `state = 'in_flight'`(codex R2-F1..F3 的 F3):被拒的行**故意**停在 phase INTENT
180
- // (「两侧字节零变更」要求它一步都别走),所以只判 phase 的 CAS 对一条已 rejected 的行是**满足**的
181
- // —— 一个在锁上排队的过期调用方于是能把一条永久拒了的弧推去迁数据。终态永久性是库级不变量,
182
- // 这道守卫就该在库里,不在调用方的 if 里。
183
- this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, legs = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ? AND state = 'in_flight'`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, legs = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5 AND state = 'in_flight'`), [nextPhase, encodeLegs(legs), nowMs, adoptionId, expectPhase]);
116
+ const cas = await conn.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, legs = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ? AND state = 'in_flight'`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, legs = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5 AND state = 'in_flight'`), [nextPhase, encodeLegs(legs), nowMs, adoptionId, expectPhase]);
184
117
  if (cas.affected !== 1) {
185
118
  await conn.rollback();
186
119
  return { ok: false, reason: "phase_moved" };
@@ -193,20 +126,11 @@ export class SqlAdoptionLogStore {
193
126
  await conn.rollback();
194
127
  }
195
128
  catch (rollbackErr) {
196
- // 🔴 不空吞(#191 静默降级门):回滚本身失败通常意味着连接已经废了,原始异常才是要上抛的那个,
197
- // 但「回滚没成功」是运维必须看见的事 —— 它决定了库里可能留着一段未提交/已废弃的事务状态。
198
129
  console.warn("[adoption-log] rollback after a failed rebind transaction also failed:", rollbackErr);
199
130
  }
200
131
  throw err;
201
132
  }
202
133
  }
203
- /**
204
- * 终态**之后**的迟到行清扫事务(无 phase 变更 —— phase 已经是终态,而终态是永久的)。
205
- *
206
- * 🔴 它不是「再收编一次」:腿本身幂等,清扫只是把收编**结束后**又落到旧身份下的行(配置未随迁的
207
- * 部署会持续制造它们)一并搬过去,并让 `current.residualSourceRows` 有个能归零的动作。
208
- * `immutableReport` 一个字节都不动(史实与现势分家,183 §6)。
209
- */
210
134
  async sweepOn(conn, work) {
211
135
  try {
212
136
  await conn.begin();
@@ -219,52 +143,29 @@ export class SqlAdoptionLogStore {
219
143
  await conn.rollback();
220
144
  }
221
145
  catch (rollbackErr) {
222
- console.warn("[adoption-log] rollback after a failed sweep transaction also failed:", rollbackErr); // 同上:不空吞
146
+ console.warn("[adoption-log] rollback after a failed sweep transaction also failed:", rollbackErr);
223
147
  }
224
148
  throw err;
225
149
  }
226
150
  }
227
- /** 单调 CAS 推进(无数据写的阶段;`false` = 别人已经推过了/相位不符 ⇒ 调用方重读行)。 */
228
151
  async advancePhase(adoptionId, expectPhase, nextPhase, nowMs, exec = this.db) {
229
152
  const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ?`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, updated_at_ms = $2 WHERE adoption_id = $3 AND phase = $4`), [nextPhase, nowMs, adoptionId, expectPhase]);
230
153
  return res.affected === 1;
231
154
  }
232
- /** phase 4:配置账物化(与 phase 推进同一条语句,免得账落了相位没推)。 */
233
155
  async putConfigs(adoptionId, expectPhase, nextPhase, configsJson, nowMs, exec = this.db) {
234
156
  const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, configs = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ?`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, configs = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5`), [nextPhase, configsJson, nowMs, adoptionId, expectPhase]);
235
157
  return res.affected === 1;
236
158
  }
237
- /**
238
- * ⑥ 终态:`report` 一次写入,`state='adopted'`。
239
- * 🔴 `AND report IS NULL` 是 I4(永久终态 + 不可变史实)的**数据库强形**:即使调用序漂了,快照也
240
- * 不可能被第二次写盖掉 —— 「逐字节恒同」于是不依赖调用方自觉。
241
- */
242
159
  async finalizeAdopted(adoptionId, expectPhase, reportJson, nowMs, exec = this.db) {
243
160
  const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, state = 'adopted', report = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ? AND report IS NULL`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, state = 'adopted', report = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5 AND report IS NULL`), [ADOPTION_PHASE.TERMINAL, reportJson, nowMs, adoptionId, expectPhase]);
244
161
  return res.affected === 1;
245
162
  }
246
- /** 目的地冲突 ⇒ rejected 终态(源/目的地两侧字节零变更;phase 停在 INTENT)。 */
247
163
  async finalizeRejected(adoptionId, code, detail, nowMs, exec = this.db) {
248
164
  const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET state = 'rejected', reject_code = ?, reject_detail = ?, updated_at_ms = ? WHERE adoption_id = ? AND state = 'in_flight'`, `UPDATE ${ADOPTION_LOG_TABLE} SET state = 'rejected', reject_code = $1, reject_detail = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND state = 'in_flight'`), [code, detail, nowMs, adoptionId]);
249
165
  return res.affected === 1;
250
166
  }
251
- /**
252
- * 本连接所在**数据库**的名字 = 弧锁的键空间限定符(A-010.13,理由见 {@link qualifyAdoptionLockName})。
253
- *
254
- * 在**交出来的那条连接**上问(不向池要第二条 —— 弧全程单连接,见 `AdoptionLogStore.getById` 的注),
255
- * 结果缓存在店上:库名在一条驱动的生命周期里不会变,而每次取锁多打一个往返是白付的。
256
- *
257
- * 读不出来 ⇒ **响亮抛**,不回落到未限定的旧名字:那正是本条要消灭的形,静默回落等于「修了个寂寞」
258
- * 且只在多部署共库的那台机器上才发作(安全轴禁静默 fail-open 的同族判据)。
259
- */
260
167
  lockKeyspace;
261
168
  resolveLockKeyspace(conn) {
262
- // 🔴 缓存的是**这一次尝试**,而失败的那一次必须从缓存里消失(codex 对抗复审 R1 [medium],验真后修)。
263
- // 病:`??=` 把 promise 本身钉住,于是**一次瞬时**的 `SELECT DATABASE()` 失败(连接抖动、库短暂不可
264
- // 达)会被永久缓存 —— 此后每一条弧都复用那个已拒的 promise、连库都不再问一次。而意图行是在取锁
265
- // **之前**就落库的,所以首条弧永远停在 in_flight、每次 boot 续跑再撞同一堵墙,整台副本的收编面
266
- // 直到重启为止都是死的。一次抖动升级成一次停机,方向完全反了。
267
- // 修:只有**解析成功**的读数留在缓存里;拒绝时清掉,下一次真的重问。
268
169
  const attempt = (this.lockKeyspace ??= (async () => {
269
170
  const res = await conn.query(this.q("SELECT DATABASE() AS db", "SELECT current_database() AS db"));
270
171
  const db = res.rows[0]?.db;
@@ -273,29 +174,15 @@ export class SqlAdoptionLogStore {
273
174
  }
274
175
  return db;
275
176
  })());
276
- // 清缓存要**认准这一次**(`=== attempt`):否则一次迟到的失败会把别人刚缓存好的成功读数抹掉。
277
177
  return attempt.catch((err) => {
278
178
  if (this.lockKeyspace === attempt)
279
179
  this.lockKeyspace = undefined;
280
180
  throw err;
281
181
  });
282
182
  }
283
- /**
284
- * 收编弧的 advisory 锁(183 I1 的 form b 形:「与任何活写互斥」在 SQL 侧 = 一次只有一个副本在推这条弧)。
285
- *
286
- * **非阻塞取 + 有界轮询**(tidb-pool 的 `acquireEnsureSchemaLock` 同款判据:阻塞式 GET_LOCK 会把并发
287
- * 调用方全部park 在 TiDB 的悲观锁行上,既耗它的重试预算又饿死应用自己的 FOR UPDATE 路)。取不到 ⇒
288
- * 返回 `undefined`,调用方按「在飞」应答 —— **不假装成功,也不无限等**。
289
- *
290
- * 🔴 入参 `name` 是**逻辑**锁名(`adoptionLockName(fromPrincipal)`);真正发给引擎的是它被
291
- * {@link qualifyAdoptionLockName} 限定到本库之后的形(A-010.13:两方言键空间不等价)。限定发生在
292
- * **这里**而不是调用方,因为「键空间是每库还是每实例」是**存储层**的知识,协议层(runner)不该知道。
293
- */
294
183
  async withLock(name, fn, attempts = 20, sleepMs = 100) {
295
184
  const conn = await this.db.connect();
296
185
  let held = false;
297
- // 🔴 在 try **之外**声明:释放腿(finally)必须用**取锁时用过的那一个**名字。第一版把它 const 在
298
- // try 里,于是 finally 只能回头用未限定的 `name` —— 取的是 A、放的是 B,锁会一直持到连接回收。
299
186
  let lockName = "";
300
187
  try {
301
188
  lockName = qualifyAdoptionLockName(await this.resolveLockKeyspace(conn), name);
@@ -308,8 +195,6 @@ export class SqlAdoptionLogStore {
308
195
  }
309
196
  if (!held)
310
197
  return undefined;
311
- // 🔴 把**这一条**连接交出去:整条弧(读 / 事务 / CAS)都跑在它上面,弧内绝不再向池要第二条
312
- // ——`connectionLimit=1` 的部署上那第二条永远等不到(codex R3-F2)。
313
198
  return await fn(conn);
314
199
  }
315
200
  finally {
@@ -318,8 +203,6 @@ export class SqlAdoptionLogStore {
318
203
  await conn.query(this.q("SELECT RELEASE_LOCK(?) AS released", "SELECT pg_advisory_unlock($1, $2) AS released"), this.db.dialect === "tidb" ? [lockName] : [PG_ADOPTION_LOCK_CLASS, pgLockObjectKey(lockName)]);
319
204
  }
320
205
  catch (releaseErr) {
321
- // 锁在连接关闭时会自动释放,所以释放失败不该盖掉真正的结果 —— 但同样不空吞:
322
- // 释放失败意味着这把弧锁会一直持到连接回收,下一个副本要多等一会儿,那是要能看见的。
323
206
  console.warn("[adoption-log] releasing the adoption arc lock failed (it auto-frees when the connection closes):", releaseErr);
324
207
  }
325
208
  }
@@ -1,19 +1,5 @@
1
- /**
2
- * `InMemoryApprovalAskStore` —— #151 车1,`ApprovalAskStore` 的语义真源(twin 对拆基准)+ LOCAL
3
- * 车道占位(File 形不在本车,见设计定稿 §7)。单进程内两个 `Map`,方法体全同步(不含 `await` 中断点)——
4
- * Node 单线程下这就是天然原子:`Promise.all` 发起的并发调用不会在方法体内部交错执行,与
5
- * `approval-ask-store-sql.ts` 的 CAS UPDATE 语义逐字段一致(SQL twin 对拆套件的基准就是这份实现)。
6
- *
7
- * 行为口径与 `SqlApprovalAskStore` 逐条对齐:
8
- * - ensureAsk 幂等 upsert(ask_id 已在 ⇒ 返回既有行,不改写;batch 行若不存在则以 OPEN 补齐)。
9
- * - transitionAsk 先过 machine 层 `canAskTransition`(非法转移 throw,不返回 false)。
10
- * - decideAsk/expireAsk/bindBatch/abortBatch 的双/三步判定顺序与 SQL twin 的事务步骤一一对应
11
- * (哪一步先判、判什么、失败时是否触碰另一半状态——照抄,不是「反正内存里原子就随便」)。
12
- * - resolveProvisional 故意不经 canAskTransition(同 SQL twin 头注:这是版本化补偿的例外通道)。
13
- */
14
1
  import { canAskTransition } from "../approval-ask-machine.js";
15
2
  import { assertIdempotencyKeyShape, ApprovalAskReadAbortedError } from "./approval-ask-store-sql.js";
16
- /** patch 里真出现的字段搬到行上(未出现的字段维持原值)——`transitionAsk`/`resolveProvisional` 共用。 */
17
3
  function applyPatch(row, patch) {
18
4
  if (patch.decision !== undefined)
19
5
  row.decision = patch.decision;
@@ -50,12 +36,8 @@ export class InMemoryApprovalAskStore {
50
36
  });
51
37
  }
52
38
  const existing = this.asks.get(row.askId);
53
- // 幂等命中:行是**别人**插的 ⇒ `inserted: false`(收尾权限判别位,语义见 `EnsureAskResult` 顶注)。
54
39
  if (existing)
55
40
  return { row: { ...existing }, inserted: false };
56
- // 🔴 车4 §12-E:`idempotency_key` 已归**回决专用**,`NewAskRow` 的同名字段随之摘除 ⇒ 铸行不再有
57
- // 唯一性可撞(列恒 NULL)。原先在这里镜像 UNIQUE 的那段扫描也随之下车;撞键判定搬到 `decideAsk`
58
- // (SQL twin 那侧是 `(task_id, idempotency_key)` 索引在库层拒 ⇒ typed `idempotency_conflict`)。
59
41
  const created = {
60
42
  askId: row.askId,
61
43
  taskId: row.taskId,
@@ -78,7 +60,7 @@ export class InMemoryApprovalAskStore {
78
60
  gateToken: null,
79
61
  gateBoundCallId: null,
80
62
  gateBoundInputHash: null,
81
- idempotencyKey: null, // 回决专用列(见上注):铸行恒 NULL,decideAsk 才落。
63
+ idempotencyKey: null,
82
64
  cardJson: row.cardJson,
83
65
  schemaVersion: row.schemaVersion,
84
66
  expiresAtMs: row.expiresAtMs,
@@ -103,7 +85,7 @@ export class InMemoryApprovalAskStore {
103
85
  }
104
86
  async decideAsk(askId, batchId, decision) {
105
87
  if (decision.idempotencyKey != null)
106
- assertIdempotencyKeyShape(decision.idempotencyKey); // F2 卫生门(三 twin 同判)
88
+ assertIdempotencyKeyShape(decision.idempotencyKey);
107
89
  const now = Date.now();
108
90
  const batch = this.batches.get(batchId);
109
91
  if (!batch || batch.state !== "OPEN") {
@@ -114,10 +96,6 @@ export class InMemoryApprovalAskStore {
114
96
  if (!ask || ask.state !== "STREAM_PENDING") {
115
97
  return { ok: false, reason: "ask_not_pending", row: ask ? { ...ask } : null };
116
98
  }
117
- // 🔴 UNIQUE `(task_id, idempotency_key)` 的内存镜像(车4 §12-E)——SQL twin 靠索引在库层拒并把方言
118
- // 错误翻成 typed 臂;这里必须同答,否则这个店就不是 SQL twin 的语义真源。判定顺序也照抄 SQL:
119
- // 批 CAS(已过)→ ask CAS(已过)→ 才轮到撞键 ⇒ 一条非 STREAM_PENDING 的 ask 拿到的仍是
120
- // `ask_not_pending` 而不是 `idempotency_conflict`(哪个先判决定了输者看到的 reason)。
121
99
  if (decision.idempotencyKey != null) {
122
100
  for (const [id, r] of this.asks) {
123
101
  if (id !== askId && r.taskId === ask.taskId && r.idempotencyKey === decision.idempotencyKey) {
@@ -139,9 +117,6 @@ export class InMemoryApprovalAskStore {
139
117
  }
140
118
  async expireAsk(askId, batchId) {
141
119
  const now = Date.now();
142
- // 批行存在性先判(镜像 SQL twin 死锁修后的步⓪,§6-4c 钉):批行缺席=ensureAsk 从未为它建批
143
- // (错配 batchId 的编程错误形),如实不赢——不在半空中把 ask 铸成 PARKING(它真正的批还 OPEN,
144
- // 状态会被污染)。批 state 不设谓词,与 SQL twin 一致(ROUTING_UNBOUND 兄弟先到属合法续走)。
145
120
  const batch = this.batches.get(batchId);
146
121
  if (!batch)
147
122
  return { won: false, voidedSiblings: [] };
@@ -170,15 +145,12 @@ export class InMemoryApprovalAskStore {
170
145
  async bindBatch(batchId, askId, gate) {
171
146
  const now = Date.now();
172
147
  const batch = this.batches.get(batchId);
173
- // 判别式失败臂逐条对齐 SQL twin(见 `BindResult` 头注):批行缺席 = "MISSING";批态不对 ⇒ 回真实态
174
- // (ABORTED / ROUTING_BOUND 两条处置完全相反,调用方靠它分臂)。
175
148
  if (!batch)
176
149
  return { ok: false, batchState: "MISSING" };
177
150
  if (batch.state !== "ROUTING_UNBOUND" || batch.boundAskId !== null) {
178
151
  return { ok: false, batchState: batch.state, ...(batch.boundAskId !== null ? { boundAskId: batch.boundAskId } : {}) };
179
152
  }
180
153
  const ask = this.asks.get(askId);
181
- // ask 不在 PARKING ⇒ SQL twin 整事务回滚,批停在 ROUTING_UNBOUND;这里同样一个字节都还没改。
182
154
  if (!ask || ask.state !== "PARKING")
183
155
  return { ok: false, batchState: batch.state };
184
156
  batch.state = "ROUTING_BOUND";
@@ -203,7 +175,6 @@ export class InMemoryApprovalAskStore {
203
175
  return { ok: true, voidedSiblings };
204
176
  }
205
177
  async deferReconcile(askId, expectedState, expectedRev, nowMs) {
206
- // 纯 touch,**不碰 state**,故意不经 canAskTransition(同态转移在转移表上无出边——见接口注)。
207
178
  const row = this.asks.get(askId);
208
179
  if (!row || row.state !== expectedState || row.rev !== expectedRev)
209
180
  return false;
@@ -231,15 +202,12 @@ export class InMemoryApprovalAskStore {
231
202
  return voided;
232
203
  }
233
204
  async listByState(state, limit) {
234
- // 排序键与 SQL twin 逐字同形:`updated_at_ms ASC, created_at_ms ASC`(队列轮转,见 SQL 侧头注)。
235
205
  return [...this.asks.values()]
236
206
  .filter((r) => r.state === state)
237
207
  .sort((a, b) => a.updatedAtMs - b.updatedAtMs || a.createdAtMs - b.createdAtMs)
238
208
  .slice(0, limit)
239
209
  .map((r) => ({ ...r }));
240
210
  }
241
- /** `signal` 的 twin 语义(车3 刀 3b):同步实现里没有「在飞」这回事,所以能做也只需做**入口复核** ——
242
- * 已 abort 就一条都不读、如实抛,与 SQL twin 的第①条语义逐字一致(第②条在这里天然平凡成立)。 */
243
211
  async listPendingByTask(taskId, signal) {
244
212
  if (signal?.aborted === true)
245
213
  throw new ApprovalAskReadAbortedError("listPendingByTask");
@@ -251,7 +219,6 @@ export class InMemoryApprovalAskStore {
251
219
  async listPendingBySession(sessionId, owner, signal) {
252
220
  if (signal?.aborted === true)
253
221
  throw new ApprovalAskReadAbortedError("listPendingBySession");
254
- // 租户门与 SQL twin 逐字同语义:`owner` 必填,`null` 只匹配 owner 为 null 的行(不是「不过滤」)。
255
222
  return [...this.asks.values()]
256
223
  .filter((r) => r.sessionId === sessionId && r.state === "STREAM_PENDING" && r.owner === owner)
257
224
  .sort((a, b) => a.createdAtMs - b.createdAtMs)
@@ -262,8 +229,7 @@ export class InMemoryApprovalAskStore {
262
229
  return r ? { ...r } : null;
263
230
  }
264
231
  async getByIdempotencyKey(taskId, key) {
265
- assertIdempotencyKeyShape(key); // F2 卫生门(三 twin 同判)
266
- // 键的两维都要匹配(UNIQUE 已改 `(task_id, idempotency_key)`)——租户门是读口自己的责任。
232
+ assertIdempotencyKeyShape(key);
267
233
  for (const r of this.asks.values()) {
268
234
  if (r.taskId === taskId && r.idempotencyKey === key)
269
235
  return { ...r };
@@ -271,7 +237,6 @@ export class InMemoryApprovalAskStore {
271
237
  return null;
272
238
  }
273
239
  async resolveProvisional(askId, from, to, patch) {
274
- // 🔴 与 SQL twin 同注:故意不经 canAskTransition——版本化补偿的例外通道。
275
240
  const row = this.asks.get(askId);
276
241
  if (!row || !row.provisional || row.state !== from)
277
242
  return false;