@sema-agent/server 7.141.0 → 7.143.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 (341) hide show
  1. package/MIGRATION.md +657 -0
  2. package/USAGE.md +102 -30
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +61 -43
  5. package/dist/approval-ask-audit-store.js +69 -44
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +126 -27
  9. package/dist/approval-card.js +103 -23
  10. package/dist/approval-crash-convergence.d.ts +17 -7
  11. package/dist/approval-crash-convergence.js +94 -22
  12. package/dist/approval-deny-reasons.d.ts +34 -30
  13. package/dist/approval-deny-reasons.js +21 -6
  14. package/dist/approval-policy-settlement.d.ts +1 -1
  15. package/dist/approval-queue-kind.d.ts +27 -0
  16. package/dist/approval-queue-kind.js +6 -0
  17. package/dist/approval-reconciler.d.ts +41 -9
  18. package/dist/approval-reconciler.js +45 -42
  19. package/dist/audit.js +4 -2
  20. package/dist/auth-bridge.d.ts +9 -6
  21. package/dist/auth-bridge.js +9 -3
  22. package/dist/boot/adoption.js +2 -1
  23. package/dist/boot/budget-tracing.d.ts +17 -1
  24. package/dist/boot/budget-tracing.js +10 -6
  25. package/dist/boot/device-lane.d.ts +1 -1
  26. package/dist/boot/execution-env.d.ts +24 -4
  27. package/dist/boot/execution-env.js +26 -23
  28. package/dist/boot/execution-lane.js +2 -1
  29. package/dist/boot/exit-boundary.js +5 -4
  30. package/dist/boot/governance-seams.d.ts +26 -41
  31. package/dist/boot/governance-seams.js +61 -28
  32. package/dist/boot/late-binds.d.ts +7 -4
  33. package/dist/boot/late-binds.js +3 -1
  34. package/dist/boot/leader.js +2 -4
  35. package/dist/boot/memory-consolidation.d.ts +0 -8
  36. package/dist/boot/memory-consolidation.js +5 -4
  37. package/dist/boot/parked-revive-gate.js +2 -1
  38. package/dist/boot/permission-rules-audit.d.ts +0 -28
  39. package/dist/boot/permission-rules-audit.js +2 -1
  40. package/dist/boot/read-deny-anchor-audit.js +2 -1
  41. package/dist/boot/reapers.js +9 -7
  42. package/dist/boot/resolve-spec.d.ts +1 -1
  43. package/dist/boot/resolve-spec.js +19 -10
  44. package/dist/boot/retention-lane.js +5 -3
  45. package/dist/boot/runner-deps.d.ts +10 -7
  46. package/dist/boot/runner-deps.js +20 -29
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.js +4 -3
  49. package/dist/boot/shutdown.d.ts +9 -0
  50. package/dist/boot/shutdown.js +19 -4
  51. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  52. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  53. package/dist/boot/stage-03-store-backend.js +1 -2
  54. package/dist/boot/stage-04-stores.d.ts +5 -2
  55. package/dist/boot/stage-04-stores.js +6 -7
  56. package/dist/boot/stage-05-execution-env.d.ts +6 -3
  57. package/dist/boot/stage-05-execution-env.js +8 -6
  58. package/dist/boot/stage-06-runners.d.ts +6 -3
  59. package/dist/boot/stage-06-runners.js +3 -4
  60. package/dist/boot/stage-07-capability-layer.d.ts +6 -3
  61. package/dist/boot/stage-07-capability-layer.js +7 -2
  62. package/dist/boot/stage-08-reapers.d.ts +6 -3
  63. package/dist/boot/stage-08-reapers.js +3 -2
  64. package/dist/boot/stage-09-leader.d.ts +6 -3
  65. package/dist/boot/stage-10-http-server.d.ts +6 -3
  66. package/dist/boot/stage-10-http-server.js +14 -5
  67. package/dist/boot/stores.d.ts +5 -2
  68. package/dist/boot/stores.js +10 -9
  69. package/dist/boot/web-admin.d.ts +5 -1
  70. package/dist/boot/web-admin.js +48 -36
  71. package/dist/boot/webfetch-summarize-lane.js +2 -1
  72. package/dist/boot/workflow-orchestration.js +3 -2
  73. package/dist/capabilities/builtin-tools.d.ts +2 -1
  74. package/dist/capabilities/builtin-tools.js +2 -4
  75. package/dist/capabilities/composite-members.d.ts +2 -1
  76. package/dist/capabilities/composite-members.js +2 -1
  77. package/dist/capabilities/hands-lane.d.ts +9 -39
  78. package/dist/capabilities/hands-lane.js +8 -13
  79. package/dist/capabilities/mcp-probe.d.ts +3 -2
  80. package/dist/capabilities/scenarios.d.ts +11 -1
  81. package/dist/capabilities/scenarios.js +1 -0
  82. package/dist/capabilities/skills.d.ts +1 -17
  83. package/dist/capabilities/skills.js +0 -8
  84. package/dist/capability-facts.d.ts +31 -7
  85. package/dist/capability-facts.js +27 -4
  86. package/dist/card-void.d.ts +6 -9
  87. package/dist/config-catalog.js +2 -1
  88. package/dist/config-dir-trust.js +58 -29
  89. package/dist/config-invariants.d.ts +7 -1
  90. package/dist/config-invariants.js +7 -0
  91. package/dist/config-provider.d.ts +10 -0
  92. package/dist/config-provider.js +15 -4
  93. package/dist/config-root-migration.d.ts +13 -0
  94. package/dist/config-root-migration.js +99 -0
  95. package/dist/config-types.d.ts +19 -8
  96. package/dist/config.d.ts +10 -3
  97. package/dist/config.js +18 -4
  98. package/dist/delegated-session-owner.d.ts +5 -3
  99. package/dist/delegated-session-owner.js +1 -1
  100. package/dist/device-ws-protocol.d.ts +1 -0
  101. package/dist/device-ws-protocol.js +2 -1
  102. package/dist/env-model-table-gate.d.ts +53 -0
  103. package/dist/env-model-table-gate.js +53 -0
  104. package/dist/execution-lane-caps.d.ts +2 -1
  105. package/dist/fleet/fleet-bus.d.ts +34 -55
  106. package/dist/fleet/fleet-bus.js +35 -100
  107. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  108. package/dist/fleet/fleet-reconciler.js +1 -1
  109. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  110. package/dist/fleet/subagent-tail-bus.js +15 -28
  111. package/dist/hooks/hook-runner.d.ts +6 -0
  112. package/dist/hooks/hook-runner.js +58 -8
  113. package/dist/host-decision.d.ts +54 -20
  114. package/dist/host-decision.js +42 -14
  115. package/dist/http/admission.d.ts +1 -1
  116. package/dist/http/admission.js +7 -3
  117. package/dist/http/approval-sweeps.js +11 -8
  118. package/dist/http/caller-trust.d.ts +1 -1
  119. package/dist/http/dispatch.js +4 -2
  120. package/dist/http/durable-run-leg.js +2 -2
  121. package/dist/http/engine-refusal.d.ts +16 -0
  122. package/dist/http/engine-refusal.js +17 -0
  123. package/dist/http/execution-context.d.ts +2 -2
  124. package/dist/http/idempotency.d.ts +1 -1
  125. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  126. package/dist/http/memory-compliance-failure-reply.js +4 -6
  127. package/dist/http/owner-access.d.ts +11 -0
  128. package/dist/http/owner-access.js +13 -0
  129. package/dist/http/principal-gate.d.ts +3 -1
  130. package/dist/http/principal-gate.js +17 -8
  131. package/dist/http/resume-legs.d.ts +21 -7
  132. package/dist/http/resume-legs.js +76 -58
  133. package/dist/http/resume-restatement.d.ts +39 -0
  134. package/dist/http/resume-restatement.js +25 -0
  135. package/dist/http/route-ctx.d.ts +35 -10
  136. package/dist/http/route-ctx.js +1 -1
  137. package/dist/http/route-table.js +2 -0
  138. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  139. package/dist/http/routes/approvals-assistant.js +142 -31
  140. package/dist/http/routes/attachments.js +18 -0
  141. package/dist/http/routes/capabilities.js +16 -4
  142. package/dist/http/routes/memory-policy.d.ts +2 -2
  143. package/dist/http/routes/memory-policy.js +27 -7
  144. package/dist/http/routes/notify-wake.js +23 -25
  145. package/dist/http/routes/rules.js +48 -13
  146. package/dist/http/routes/runs.js +12 -11
  147. package/dist/http/routes/session-memory-sync.d.ts +91 -0
  148. package/dist/http/routes/session-memory-sync.js +174 -0
  149. package/dist/http/routes/session-sync.js +13 -20
  150. package/dist/http/routes/sessions-list.d.ts +2 -1
  151. package/dist/http/routes/sessions.d.ts +2 -2
  152. package/dist/http/routes/sessions.js +22 -23
  153. package/dist/http/routes/tasks.js +34 -54
  154. package/dist/http/run-read-face.d.ts +7 -0
  155. package/dist/http/run-read-face.js +9 -3
  156. package/dist/http/send.d.ts +5 -0
  157. package/dist/http/send.js +9 -2
  158. package/dist/http/server.d.ts +27 -15
  159. package/dist/http/server.js +4 -6
  160. package/dist/http/stored-body-replay.js +15 -4
  161. package/dist/http/submit-replay.d.ts +4 -3
  162. package/dist/http/submit-replay.js +2 -2
  163. package/dist/http/sync-submit-body.d.ts +21 -4
  164. package/dist/http/sync-submit-body.js +13 -0
  165. package/dist/http/wire-types.d.ts +17 -7
  166. package/dist/identity-source.d.ts +21 -2
  167. package/dist/identity-source.js +6 -0
  168. package/dist/leader/leader.js +5 -2
  169. package/dist/leader/wire.d.ts +7 -1
  170. package/dist/leader/wire.js +6 -5
  171. package/dist/leg-facts.d.ts +37 -0
  172. package/dist/leg-facts.js +15 -0
  173. package/dist/memory-bundle-engine.d.ts +1 -4
  174. package/dist/memory-bundle-engine.js +2 -2
  175. package/dist/memory-incident-sink.d.ts +19 -6
  176. package/dist/memory-incident-sink.js +8 -4
  177. package/dist/memory-operator-faces.d.ts +30 -66
  178. package/dist/memory-operator-faces.js +17 -88
  179. package/dist/memory-scope-stores.d.ts +38 -40
  180. package/dist/memory-scope-stores.js +51 -36
  181. package/dist/memory-scope.d.ts +0 -9
  182. package/dist/memory-scope.js +0 -9
  183. package/dist/memory-sync-client.d.ts +21 -21
  184. package/dist/memory-sync-client.js +62 -43
  185. package/dist/memory-sync-identity.d.ts +24 -0
  186. package/dist/memory-sync-identity.js +57 -0
  187. package/dist/memory-sync.d.ts +27 -5
  188. package/dist/memory-sync.js +29 -7
  189. package/dist/model-plane-face.d.ts +13 -7
  190. package/dist/model-plane-face.js +2 -1
  191. package/dist/observability/fail-open.d.ts +47 -14
  192. package/dist/observability/fail-open.js +37 -11
  193. package/dist/observability/run-terminal.d.ts +92 -34
  194. package/dist/observability/run-terminal.js +171 -162
  195. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  196. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  197. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  198. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  199. package/dist/orchestration/workflow-park-decision.js +5 -3
  200. package/dist/org-delivery-gate.d.ts +11 -0
  201. package/dist/org-delivery-gate.js +19 -1
  202. package/dist/parked-decide.d.ts +13 -9
  203. package/dist/parked-decide.js +7 -7
  204. package/dist/parked-revive-surface.js +1 -1
  205. package/dist/peer-inbound-face.d.ts +127 -0
  206. package/dist/peer-inbound-face.js +158 -0
  207. package/dist/permission-rule-list.d.ts +13 -9
  208. package/dist/permission-rule-list.js +7 -3
  209. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  210. package/dist/plugins/approval-ask-store-file.js +64 -11
  211. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  212. package/dist/plugins/approval-ask-store-memory.js +17 -14
  213. package/dist/plugins/approval-ask-store-sql.d.ts +70 -23
  214. package/dist/plugins/approval-ask-store-sql.js +79 -30
  215. package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
  216. package/dist/plugins/checkpoint-store-sql.js +9 -7
  217. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  218. package/dist/plugins/file-history-store-sql.js +24 -19
  219. package/dist/plugins/file-run-store.d.ts +35 -1
  220. package/dist/plugins/file-run-store.js +108 -9
  221. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  222. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  223. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  224. package/dist/plugins/local-checkpoint-store.js +2 -2
  225. package/dist/plugins/local-session-store.d.ts +42 -9
  226. package/dist/plugins/local-session-store.js +83 -33
  227. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  228. package/dist/plugins/local-task-attachment-store.js +67 -43
  229. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  230. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  231. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  232. package/dist/plugins/mailbox-store-sql.js +76 -7
  233. package/dist/plugins/memory-key-guards.d.ts +3 -3
  234. package/dist/plugins/memory-run-store.d.ts +6 -1
  235. package/dist/plugins/memory-run-store.js +11 -1
  236. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  237. package/dist/plugins/memory-sync-store-pg.js +21 -0
  238. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  239. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  240. package/dist/plugins/one-time-migrations.d.ts +58 -2
  241. package/dist/plugins/one-time-migrations.js +161 -4
  242. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  243. package/dist/plugins/org-delivery-quota-store.js +2 -12
  244. package/dist/plugins/park-commit-watch.d.ts +1 -1
  245. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  246. package/dist/plugins/permission-rule-store-file.js +15 -2
  247. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  248. package/dist/plugins/permission-rule-store-sql.js +35 -2
  249. package/dist/plugins/remote-env-device.d.ts +8 -11
  250. package/dist/plugins/remote-env-device.js +6 -3
  251. package/dist/plugins/remote-env-e2b.d.ts +23 -4
  252. package/dist/plugins/remote-env-e2b.js +111 -28
  253. package/dist/plugins/remote-env-k8s.d.ts +61 -22
  254. package/dist/plugins/remote-env-k8s.js +133 -33
  255. package/dist/plugins/remote-env-local-docker.d.ts +15 -3
  256. package/dist/plugins/remote-env-local-docker.js +40 -12
  257. package/dist/plugins/retention-store-sql.d.ts +2 -2
  258. package/dist/plugins/run-store-sql.d.ts +9 -1
  259. package/dist/plugins/run-store-sql.js +9 -1
  260. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  261. package/dist/plugins/sandbox-read-floor.js +2 -7
  262. package/dist/plugins/sql-errors.d.ts +10 -1
  263. package/dist/plugins/sql-errors.js +13 -0
  264. package/dist/plugins/store-backend.d.ts +33 -35
  265. package/dist/plugins/store-backend.js +10 -1
  266. package/dist/plugins/store-contracts.d.ts +50 -1
  267. package/dist/plugins/store-contracts.js +6 -0
  268. package/dist/plugins/task-attachment-store.d.ts +112 -14
  269. package/dist/plugins/task-attachment-store.js +207 -29
  270. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  271. package/dist/plugins/tool-result-store-sql.js +23 -2
  272. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  273. package/dist/plugins/wiring-section-codec.js +55 -0
  274. package/dist/plugins/workspace-fill.d.ts +60 -0
  275. package/dist/plugins/workspace-fill.js +106 -0
  276. package/dist/project-bundle.d.ts +38 -0
  277. package/dist/project-bundle.js +609 -0
  278. package/dist/rule-entry-list.d.ts +3 -2
  279. package/dist/rule-entry-list.js +5 -1
  280. package/dist/rules-consent.d.ts +28 -5
  281. package/dist/rules-consent.js +40 -28
  282. package/dist/run-local.js +11 -13
  283. package/dist/runs.d.ts +12 -8
  284. package/dist/runs.js +6 -8
  285. package/dist/runtime-governance.d.ts +1 -1
  286. package/dist/sandbox-egress-policy.d.ts +4 -1
  287. package/dist/sandbox-egress-policy.js +3 -1
  288. package/dist/sandbox-git-plane.d.ts +31 -12
  289. package/dist/sandbox-git-plane.js +24 -4
  290. package/dist/security.d.ts +9 -0
  291. package/dist/security.js +8 -0
  292. package/dist/server-secret-env.d.ts +4 -0
  293. package/dist/session-error-wire.d.ts +36 -0
  294. package/dist/session-error-wire.js +13 -0
  295. package/dist/session-incarnation.d.ts +1 -1
  296. package/dist/session-notify-door.d.ts +106 -0
  297. package/dist/session-notify-door.js +93 -0
  298. package/dist/session-sync.d.ts +9 -14
  299. package/dist/session-sync.js +5 -5
  300. package/dist/session-workspace.d.ts +3 -13
  301. package/dist/session-workspace.js +0 -9
  302. package/dist/standing-answer-grant.d.ts +5 -0
  303. package/dist/standing-answer-grant.js +10 -0
  304. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  305. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  306. package/dist/task-mcp.d.ts +1 -1
  307. package/dist/task-settings.d.ts +21 -10
  308. package/dist/task-settings.js +46 -13
  309. package/dist/terminal.d.ts +39 -19
  310. package/dist/terminal.js +65 -22
  311. package/dist/tool-approval.d.ts +56 -37
  312. package/dist/tool-approval.js +78 -71
  313. package/dist/trace/core-keyset-guard.d.ts +134 -14
  314. package/dist/trace/engine-notice-wire.d.ts +9 -3
  315. package/dist/trace/engine-notice-wire.js +7 -3
  316. package/dist/trace/exit-seat.d.ts +9 -2
  317. package/dist/trace/exit-seat.js +1 -0
  318. package/dist/trace/ledger-sink.d.ts +10 -6
  319. package/dist/trace/ledger-sink.js +2 -2
  320. package/dist/trace/model-segment.d.ts +55 -25
  321. package/dist/trace/model-segment.js +101 -33
  322. package/dist/trace/project.d.ts +25 -5
  323. package/dist/trace/project.js +22 -0
  324. package/dist/trace/redact.d.ts +61 -9
  325. package/dist/trace/redact.js +1490 -468
  326. package/dist/trace/self-minted-wire.d.ts +745 -24
  327. package/dist/trace/self-minted-wire.js +239 -9
  328. package/dist/trace/wire-projection-faces.d.ts +2 -2
  329. package/dist/trace/wire-projection-faces.js +2 -2
  330. package/dist/verifier-pause-card.d.ts +33 -5
  331. package/dist/verifier-pause-card.js +15 -10
  332. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  333. package/dist/web-admin-client/apply-effective.js +109 -9
  334. package/dist/web-admin-client/effective-keys.js +2 -0
  335. package/dist/web-admin-client/types.d.ts +4 -0
  336. package/dist/workspace-space.d.ts +19 -3
  337. package/dist/workspace-space.js +8 -4
  338. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  339. package/package.json +3 -2
  340. package/wire-changes/server@7.142.0.json +639 -0
  341. package/wire-changes/server@7.143.0.json +1167 -0
@@ -1,6 +1,7 @@
1
1
  import type { ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
2
2
  import { FileApprovalAskAuditStore } from "./approval-ask-audit-store.js";
3
3
  import { type HolderGoneCause } from "./approval-deny-reasons.js";
4
+ import { type ReconcileCheckpointPort } from "./approval-reconciler.js";
4
5
  import { type RunStoreReader } from "./user-input-ledger.js";
5
6
  import type { Logger } from "./observability/logger.js";
6
7
  /** 启动收敛的读数(进启动日志)。 */
@@ -10,17 +11,19 @@ export interface CrashConvergenceReport {
10
11
  readonly decided: number;
11
12
  readonly inconsistent: number;
12
13
  readonly corruptLines: number;
13
- /** ask 账半场:按成因词计的、本次收成 VOID 的前世 `STREAM_PENDING` 行数。 */
14
+ /** ask 账半场:按成因词计的、本次收成 VOID 的前世 `STREAM_PENDING` 行与审计判孤儿的 `PARKING` 行数。 */
14
15
  readonly asksVoided: Readonly<Record<HolderGoneCause, number>>;
16
+ /** 审计判孤儿的 `PARKING` 行里判据 ① 命中、收成 `PARKED` 的行数(停车行其实已提交;每行另有一行 warn 点名)。 */
17
+ readonly parkingBound: number;
15
18
  /** 其中卡信封读不出、成因词是回落(`crashed_before_park`)的行数(同时记 fail-open tag)。 */
16
19
  readonly causeUnreadable: number;
17
20
  }
18
21
  /** 本次收成 VOID 的行数合计 —— 由成因闭集派生(对 `asksVoided` 的每个词求和;闭集加词不需要改这里)。 */
19
22
  export declare function totalAsksVoided(report: Pick<CrashConvergenceReport, "asksVoided">): number;
20
- /** 本函数读写的那几口(窄形:测试可以直给一只内存店)。 */
21
- export type CrashConvergenceAskPort = Pick<ApprovalAskStore, "listByState" | "transitionAsk">;
22
- /** 审计店的两口(收敛半场 + 成因判点)。 */
23
- export type CrashConvergenceAuditPort = Pick<FileApprovalAskAuditStore, "convergeCrashedAsks" | "causeFor">;
23
+ /** 本函数读写的那几口(窄形:测试可以直给一只内存店)。`getBatch` / `bindBatch` 是判据 ① 执行体的两口。 */
24
+ export type CrashConvergenceAskPort = Pick<ApprovalAskStore, "listByState" | "transitionAsk" | "getBatch" | "bindBatch">;
25
+ /** 审计店的两口(收敛半场 + 持有者没了的判点)。 */
26
+ export type CrashConvergenceAuditPort = Pick<FileApprovalAskAuditStore, "convergeCrashedAsks" | "holderGoneVerdict">;
24
27
  /**
25
28
  * 启动收敛读哪一份**审计证据**:活的审计店在场就用它;不在场(这一世 `TOOL_APPROVAL_ENABLED` 关着 ⇒ 装配三合取不成立)而盘上有前世的审计账本
26
29
  * ⇒ 开一只读它 —— 成因是盘上证据的函数,不随这一世的开关变(否则前世有序关停截断的问在这一世被写成 `crashed_before_park`,审批重新打开那天审计读面
@@ -32,12 +35,16 @@ export declare function openAuditEvidenceForConvergence(live: CrashConvergenceAu
32
35
  release: () => void;
33
36
  };
34
37
  /**
35
- * 启动收敛(见文件头)。只在数据根独占的车道上调,且必须在本进程铸出第一问之前。幂等跨启动:收过的行已不是 `STREAM_PENDING`,
36
- * 审计半场有自己的收敛锚;成因读冻结的前世审计账 ⇒ 半途崩溃再启,剩下的行得到与第一次相同的词。
38
+ * 启动收敛(见文件头)。只在数据根独占的车道上调,且必须在本进程铸出第一问之前。幂等跨启动:收过的行已不在 `STREAM_PENDING` / `PARKING`,
39
+ * 审计半场有自己的收敛锚;判定与成因读冻结的前世审计账 ⇒ 半途崩溃再启,剩下的行得到与第一次相同的判定与词。
40
+ * `checkpoints` = 判据 ① 的候选读口,必填、不可缺席(装配点交后端的只读口 `checkpointReadView()`;没有 checkpoint 店的后端交恒空读口):缺席会让判据 ① 恒不命中、
41
+ * 把已提交的停车行收成 VOID,所以既不给缺省,也不收 `undefined`。
37
42
  */
38
43
  export declare function convergeCrashedAsksAtBoot(input: {
39
44
  askStore: CrashConvergenceAskPort | undefined;
40
45
  audit: CrashConvergenceAuditPort | undefined;
46
+ checkpoints: ReconcileCheckpointPort;
47
+ logger: Pick<Logger, "warn">;
41
48
  now?: () => number;
42
49
  }): Promise<CrashConvergenceReport>;
43
50
  /**
@@ -45,12 +52,15 @@ export declare function convergeCrashedAsksAtBoot(input: {
45
52
  * · 只在数据根独占的车道上跑 —— 车道事实与用户输入受理账本同一只 `writerGoneEvidenceFor`(local ⇒ `exclusive_data_root`);SQL 车道整步不做。
46
53
  * · ask 店取 `backend.approvalAsk()`,**不看协议开关**:前世开着协议留下的行,今世关着也照收(否则重新打开协议那天它们又变得可决)。
47
54
  * · 审计证据经 {@link openAuditEvidenceForConvergence}(这一世的审批开关不改变成因)。
55
+ * · 判据 ① 的候选读口 = 后端的只读口 `backend.checkpointReadView()`,**不看 `DURABLE_APPROVAL`**(前世已提交的停驻不因这一世的开关消失;local 车道它与 durable 装配的那只是同一只店,
56
+ * 只是不接 run 店的收割探针)。
48
57
  * · 有任何收敛 / 坏行 ⇒ 一行 warn `approval_ask_audit_crash_converged`(零动作不打)。收敛写入抛错 ⇒ 抛出 ⇒ 拒启。
49
58
  */
50
59
  export declare function runBootCrashConvergence(ctx: {
51
60
  backend: {
52
61
  readonly kind: "mysql" | "pg" | "local";
53
62
  approvalAsk(): CrashConvergenceAskPort;
63
+ checkpointReadView(): ReconcileCheckpointPort;
54
64
  } | undefined;
55
65
  runStore: RunStoreReader | undefined;
56
66
  liveAudit: CrashConvergenceAuditPort | undefined;
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { APPROVAL_ASK_AUDIT_DIR, APPROVAL_ASK_AUDIT_LEDGER, FileApprovalAskAuditStore } from "./approval-ask-audit-store.js";
4
4
  import { readStoredCardEnvelope } from "./approval-card.js";
5
5
  import { APPROVAL_ENDING_CAUSES } from "./approval-deny-reasons.js";
6
+ import { bindParkingAskAtGate } from "./approval-reconciler.js";
6
7
  import { writerGoneEvidenceFor } from "./user-input-ledger.js";
7
8
  import { recordFailOpen } from "./observability/fail-open.js";
8
9
  const CONVERGE_PAGE = 500;
@@ -17,57 +18,128 @@ export function openAuditEvidenceForConvergence(live, localDataRoot) {
17
18
  const opened = new FileApprovalAskAuditStore(localDataRoot);
18
19
  return { audit: opened, release: () => opened.close() };
19
20
  }
21
+ async function forEachRowInState(store, state, visit) {
22
+ const seen = new Set();
23
+ let stayed = 0;
24
+ for (;;) {
25
+ const page = (await store.listByState(state, stayed + CONVERGE_PAGE)).filter((r) => !seen.has(r.askId));
26
+ if (page.length === 0)
27
+ return;
28
+ for (const row of page) {
29
+ seen.add(row.askId);
30
+ if (!(await visit(row)))
31
+ stayed += 1;
32
+ }
33
+ }
34
+ }
35
+ function refusingToStart(state, row, err) {
36
+ return new Error(`approval crash convergence: could not close the previous process's ${state === "STREAM_PENDING" ? "pending" : "parking"} approval ask ${row.askId} (task ${row.taskId}): ` +
37
+ `${err instanceof Error ? err.message : String(err)} — refusing to start, because serving now would leave a card that nothing waits for ` +
38
+ "decidable with a 200. Fix the data root (disk full / read-only?) and start again.");
39
+ }
20
40
  export async function convergeCrashedAsksAtBoot(input) {
21
41
  const now = input.now ?? Date.now;
22
- const audited = input.audit?.convergeCrashedAsks() ?? { pending: 0, decided: 0, inconsistent: 0, corruptLines: 0 };
42
+ const audit = input.audit;
43
+ const audited = audit?.convergeCrashedAsks() ?? { pending: 0, decided: 0, inconsistent: 0, corruptLines: 0 };
23
44
  const asksVoided = { [APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK]: 0, [APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK]: 0 };
24
45
  let causeUnreadable = 0;
46
+ let parkingBound = 0;
25
47
  const store = input.askStore;
26
48
  if (store !== undefined) {
27
- const seen = new Set();
28
- for (;;) {
29
- const page = (await store.listByState("STREAM_PENDING", CONVERGE_PAGE)).filter((r) => !seen.has(r.askId));
30
- if (page.length === 0)
31
- break;
32
- for (const row of page) {
33
- seen.add(row.askId);
49
+ await forEachRowInState(store, "STREAM_PENDING", async (row) => {
50
+ const envelope = readStoredCardEnvelope(row.cardJson);
51
+ if (envelope === undefined) {
52
+ causeUnreadable += 1;
53
+ recordFailOpen("server.approval-crash-convergence.cause-unreadable", `ask ${row.askId} (task ${row.taskId}): card envelope unreadable — cause defaulted to ${APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK}`);
54
+ }
55
+ const reason = envelope !== undefined && audit !== undefined ? audit.holderGoneVerdict(envelope.approvalId).cause : APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK;
56
+ let won;
57
+ try {
58
+ won = await store.transitionAsk(row.askId, "STREAM_PENDING", "VOID", { endingCause: reason, updatedAtMs: now() });
59
+ }
60
+ catch (err) {
61
+ throw refusingToStart("STREAM_PENDING", row, err);
62
+ }
63
+ if (won)
64
+ asksVoided[reason] += 1;
65
+ return true;
66
+ });
67
+ if (audit !== undefined) {
68
+ const orphanOf = (row) => {
34
69
  const envelope = readStoredCardEnvelope(row.cardJson);
35
- if (envelope === undefined) {
36
- causeUnreadable += 1;
37
- recordFailOpen("server.approval-crash-convergence.cause-unreadable", `ask ${row.askId} (task ${row.taskId}): card envelope unreadable — cause defaulted to ${APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK}`);
70
+ if (envelope === undefined)
71
+ return undefined;
72
+ const verdict = audit.holderGoneVerdict(envelope.approvalId);
73
+ return verdict.orphan ? { approvalId: envelope.approvalId, cause: verdict.cause } : undefined;
74
+ };
75
+ let voidedSiblings = [];
76
+ const gate = {
77
+ askStore: store,
78
+ checkpoints: input.checkpoints,
79
+ warn: (event, fields) => input.logger.warn(event, fields),
80
+ revoke: (_row, askIds) => {
81
+ voidedSiblings = askIds;
82
+ },
83
+ };
84
+ await forEachRowInState(store, "PARKING", async (row) => {
85
+ const orphan = orphanOf(row);
86
+ if (orphan === undefined)
87
+ return false;
88
+ voidedSiblings = [];
89
+ let bound;
90
+ try {
91
+ bound = (await bindParkingAskAtGate(gate, row, now())).bound;
38
92
  }
39
- const reason = envelope !== undefined && input.audit !== undefined ? input.audit.causeFor(envelope.approvalId) : APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK;
93
+ catch (err) {
94
+ throw refusingToStart("PARKING", row, err);
95
+ }
96
+ if (!bound)
97
+ return false;
98
+ parkingBound += 1;
99
+ input.logger.warn("approval_crash_convergence_park_committed", {
100
+ askId: row.askId,
101
+ taskId: row.taskId,
102
+ approvalId: orphan.approvalId,
103
+ voidedSiblings,
104
+ note: "the audit ledger saw no settlement for this approval ask (an orphan of the previous process), but its park row had committed — bound PARKED, decidable through the durable approval endpoint; the commit landed in the window between the park commit and its observation. Same-batch siblings still parking were voided as superseded by this park (batch_bound_elsewhere)",
105
+ });
106
+ return true;
107
+ });
108
+ await forEachRowInState(store, "PARKING", async (row) => {
109
+ const orphan = orphanOf(row);
110
+ if (orphan === undefined)
111
+ return false;
40
112
  let won;
41
113
  try {
42
- won = await store.transitionAsk(row.askId, "STREAM_PENDING", "VOID", { deniedReason: reason, updatedAtMs: now() });
114
+ won = await store.transitionAsk(row.askId, "PARKING", "VOID", { endingCause: orphan.cause, updatedAtMs: now() });
43
115
  }
44
116
  catch (err) {
45
- throw new Error(`approval crash convergence: could not close the previous process's pending approval ask ${row.askId} (task ${row.taskId}): ` +
46
- `${err instanceof Error ? err.message : String(err)} — refusing to start, because serving now would leave a card that nothing waits for ` +
47
- "decidable with a 200. Fix the data root (disk full / read-only?) and start again.");
117
+ throw refusingToStart("PARKING", row, err);
48
118
  }
49
119
  if (won)
50
- asksVoided[reason] += 1;
51
- }
120
+ asksVoided[orphan.cause] += 1;
121
+ return true;
122
+ });
52
123
  }
53
124
  }
54
- return { ...audited, asksVoided, causeUnreadable };
125
+ return { ...audited, asksVoided, causeUnreadable, parkingBound };
55
126
  }
56
127
  export async function runBootCrashConvergence(ctx) {
57
128
  const { backend, runStore, logger } = ctx;
58
129
  if (backend === undefined || runStore === undefined || writerGoneEvidenceFor(backend.kind, runStore).kind !== "exclusive_data_root")
59
130
  return undefined;
60
131
  const evidence = openAuditEvidenceForConvergence(ctx.liveAudit, ctx.localDataRoot);
61
- const converged = await convergeCrashedAsksAtBoot({ askStore: backend.approvalAsk(), audit: evidence.audit }).finally(evidence.release);
62
- if (converged.pending > 0 || converged.decided > 0 || converged.inconsistent > 0 || converged.corruptLines > 0 || totalAsksVoided(converged) > 0) {
132
+ const converged = await convergeCrashedAsksAtBoot({ askStore: backend.approvalAsk(), audit: evidence.audit, checkpoints: backend.checkpointReadView(), logger }).finally(evidence.release);
133
+ if (converged.pending > 0 || converged.decided > 0 || converged.inconsistent > 0 || converged.corruptLines > 0 || totalAsksVoided(converged) > 0 || converged.parkingBound > 0) {
63
134
  logger.warn("approval_ask_audit_crash_converged", {
64
135
  pending: converged.pending,
65
136
  decided: converged.decided,
66
137
  inconsistent: converged.inconsistent,
67
138
  corruptLines: converged.corruptLines,
68
139
  asksVoided: converged.asksVoided,
140
+ parkingBound: converged.parkingBound,
69
141
  causeUnreadable: converged.causeUnreadable,
70
- note: "approval asks left pending by the previous process were converged: each such ask is now VOID in the ask ledger (the decision endpoint answers 410 gone.ask with this cause) and is listed under GET /v1/approvals crashConverged when the audit ledger saw it (cause crashed_before_park = the process crashed; cause shutdown_before_park = an orderly shutdown cut the leg off before it closed or before its park committed)",
142
+ note: "approval asks left by the previous process were converged: each pending ask, and each parking ask the audit ledger judged an orphan, is now VOID in the ask ledger (the decision endpoint answers 410 gone.ask with this cause) and is listed under GET /v1/approvals crashConverged when the audit ledger saw it (cause crashed_before_park = the process crashed; cause shutdown_before_park = the previous process completed its orderly shutdown wind-down while the ask was still unsettled); a parking ask whose park row had committed is bound PARKED instead",
71
143
  });
72
144
  }
73
145
  return converged;
@@ -3,10 +3,10 @@
3
3
  *
4
4
  * ## 它治的病(同一件事、四张词表、一词多义)
5
5
  * 一张卡没被人答就结束了,今天有四个读面各说各话:审计 settle 行(`expired` 一词盖 TTL 到期 / run 取消 / run 退出清理 / 广播全灭,批内兄弟被撤卡也是同一个词)、
6
- * 问账 VOID/DENIED 行(`denied_reason` 列)、晚答 404 的判别位、决断口 409 的码(多种成因借 `approval_binding_mismatch` 一个名)。壳读账写不出「已取消」,
6
+ * 问账 VOID/DENIED 行(成因列)、晚答 404 的判别位、决断口 409 的码(多种成因借 `approval_binding_mismatch` 一个名)。壳读账写不出「已取消」,
7
7
  * 也分不出「重取坐标再决」与「改答案再决」。收成**一张表、一个词、各面同读**:
8
8
  * · 审计 settle 行的 `cause`(非人结局必填,人答缺席;`settle(verdict)` 判别联合逼每个非人调用点点名一个)与活卡结局帧 `tool_approval_complete` 的 additive `cause`(同一个词,同一条律);
9
- * · 问账 VOID/DENIED 行的 `denied_reason` 列 → 决断口 410 `gone.ask` 体的 `cause` 与晚答 404 体的 `cause`(读同一列,经唯一读口 {@link causeOfAskRow});
9
+ * · 问账 VOID/DENIED 行的 `ending_cause` 列 → 决断口 410 `gone.ask` 体的 `cause` 与晚答 404 体的 `cause`(读同一列,经唯一读口 {@link askRowEndingCause});
10
10
  * · `GET /v1/approvals` `crashConverged[].cause`(本表的 {@link HolderGoneCause} 两词子集);
11
11
  * · 决断口 409 的码:`binding_changed` / `answer_invalid` / `outcome_cell_invalid` 三词各归一个码。`retriable` 不由成因说 —— 三词都出自 core 的
12
12
  * `checkpoint.invalid_outcome`,由 `http/resume-legs.ts` 的 core 码 → 处置一张表派生(一个意思:卡仍 pending、行重新 park)。
@@ -16,8 +16,9 @@
16
16
  * · 成因**只从事实读**:run 行 `errorCode:"cancelled"`、钟到、emit 零接收、停车行 / 问账行的列、`detail.field`;不读拒句散文。
17
17
  * · 「停驻行的格式比本引擎新」**不是**本表的词:它只有 core 一个来源(`checkpoint.unsupported_version` + `detail.reason:"version_newer"`,原码镜像);服务层不从续跑上下文的写者版本推断成因。
18
18
  * · **人答的结局(`allowed` / `denied`)没有 cause**:`cause` 在场 ⇔ 非人结局。
19
- * · 落列面:问账行的成因写进 `approval_ask.denied_reason`(VARCHAR(64)),一列按 `state` 承载两种意思 —— VOID / DENIED 行上是**终态成因**,PARKING / PARKED 行上是
20
- * **转 park 的触发成因**;读它只经 {@link causeOfAskRow}(按 state 穷举分派,两张读词表只被它读)。列名将在下一个 DDL 窗按意义改名。
19
+ * · 落列面:问账行的成因落**两列,一列一义**(各 VARCHAR(64)):`approval_ask.ending_cause` = **终态成因**(这一行为什么没被人答就结束了;到 `VOID` / `DENIED` 的转移带着它写),
20
+ * `approval_ask.park_trigger` = **转 park 的触发成因**(这一行为什么被转进投递面;只由那一次 `claimTerminal(expire, { cause })` 写,此后不再被写 —— 终态不覆盖它)。
21
+ * 两只读口各读一列:{@link askRowEndingCause} / {@link askRowParkTrigger}(两张读词表各只被一只读)。
21
22
  *
22
23
  * ## 为什么 `routing_failure_fail_closed` 也在表里(归因诚实性)
23
24
  * 审计面唯一能证明「超时永不产生 denial」的东西是**终态 + 成因**的组合:人拒恒为 `DECIDED(decision=deny)`,路由失败恒为 `DENIED(routing_failure_fail_closed)`,
@@ -71,11 +72,15 @@ export declare const APPROVAL_ENDING_CAUSES: {
71
72
  */
72
73
  readonly ROUTING_FAILURE: "routing_failure_fail_closed";
73
74
  /**
74
- * 启动收敛(`approval-crash-convergence.ts`,local 车道):**承载这一问的进程没了** —— 前世进程崩溃 / 被杀,留下的 `STREAM_PENDING` 行在下一次启动、任何卡能开始之前收成 VOID。
75
- * 证据是数据根 `LOCK`(前世写者全死),不是「等够久了」。值与 `GET /v1/approvals` `crashConverged[].cause` 是**同一个词**。
75
+ * 启动收敛(`approval-crash-convergence.ts`,local 车道):**承载这一问的进程没了** —— 前世进程崩溃 / 被杀,留下的 `STREAM_PENDING` 行与审计判为孤儿的 `PARKING` 行
76
+ * (停车行没提交)在下一次启动、任何卡能开始之前收成 VOID。证据是数据根 `LOCK`(前世写者全死)+ 审计账的孤儿判定,不是「等够久了」。
77
+ * 值与 `GET /v1/approvals` `crashConverged[].cause` 是**同一个词**(一处判:审计店的 `holderGoneVerdict`)。
76
78
  */
77
79
  readonly CRASHED_BEFORE_PARK: "crashed_before_park";
78
- /** 同上,但承载它的腿是被**有序关停**截断的(审计账本上有本腿的关停记号;关停收尾没能在宽限窗内把这张卡结清),或有序关停期间这张卡被改路停驻(settle 行的成因)。 */
80
+ /**
81
+ * 同上,但前世走完了**有序关停**的审批收尾(审计账本上那一世的进程级关停记录落在这张卡的末事件之后 —— 收尾没能在宽限窗内把它结清;是进程的事实,
82
+ * 不看承载它的腿在不在追踪视野里),或有序关停期间这张卡被改路停驻(settle 行 / 问账行 PARKING 的触发成因)。
83
+ */
79
84
  readonly SHUTDOWN_BEFORE_PARK: "shutdown_before_park";
80
85
  /** 判据 5:任何在 `STREAM_APPROVAL_ORPHAN_TTL_MS` 内仍未收敛的 `PARKING` 行(量 immutable 的 `createdAtMs`,§9 C5)—— 遗孤最终可判的最后兜底,带一条 warn。 */
81
86
  readonly ORPHAN_TTL_EXCEEDED: "orphan_ttl_exceeded";
@@ -117,7 +122,7 @@ export declare const SETTLE_CAUSE_WORDS: {
117
122
  };
118
123
  /** 一个值是不是非人结局的成因词(审计账读盘用这一只判词)。 */
119
124
  export declare function isSettleCause(v: unknown): v is SettleCause;
120
- /** 「承载这一问的进程没了」的两个成因词(启动收敛写进问账行 `denied_reason`,审计收敛行 `cause` 读同一型)。 */
125
+ /** 「承载这一问的进程没了」的两个成因词(启动收敛写进问账行 `ending_cause`(前世的 `STREAM_PENDING` 与审计判孤儿的 `PARKING`),审计收敛行 `cause` 读同一型)。 */
121
126
  export type HolderGoneCause = typeof APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK | typeof APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK;
122
127
  /** 对账器 `DENIED` 终态的成因(单员:全仓 DENIED 只有一个写点)。 */
123
128
  export type ReconcileDenyCause = typeof APPROVAL_ENDING_CAUSES.ROUTING_FAILURE;
@@ -128,7 +133,7 @@ export type ReconcileVoidCause = typeof APPROVAL_ENDING_CAUSES.RUN_CANCELLED | t
128
133
  * (取消赢下 VOID ⇒ `run_cancelled` / `run_exited`,兄弟被撤卡 ⇒ `batch_sibling_withdrawn`)。窗到期 / 广播全灭 / 决断类的词只落审计 settle 行或 409 码,不落这一列。
129
134
  */
130
135
  export type AskRowEndingCause = ReconcileDenyCause | ReconcileVoidCause | HolderGoneCause | typeof APPROVAL_ENDING_CAUSES.RUN_EXITED | typeof APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN;
131
- /** 问账行 `denied_reason` 列在 VOID / DENIED 行上的读词表({@link AskRowEndingCause} 的穷举,`satisfies` ⇒ 子集加词而本表没跟 = 编译红)。唯一读者 = {@link causeOfAskRow}。 */
136
+ /** 问账行 `ending_cause` 列的读词表({@link AskRowEndingCause} 的穷举,`satisfies` ⇒ 子集加词而本表没跟 = 编译红)。唯一读者 = {@link askRowEndingCause}。 */
132
137
  export declare const ASK_ROW_ENDING_CAUSE_WORDS: {
133
138
  readonly routing_failure_fail_closed: true;
134
139
  readonly run_cancelled: true;
@@ -142,12 +147,12 @@ export declare const ASK_ROW_ENDING_CAUSE_WORDS: {
142
147
  readonly shutdown_before_park: true;
143
148
  };
144
149
  /**
145
- * `expire` 终局 claim(窗到期 / 广播全灭 / 有序关停把一张卡转投递面 = `PARKING`)赢下时写进这一行 `denied_reason` 的**触发成因**:这一行此后若走到 `DENIED` / `VOID`,终态成因会覆盖它
146
- * ({@link AskRowEndingCause});`PARKED` 行的这一格就是「它为什么被转 park」。跨副本的读者(另一进程的悬挂腿轮询、CAS 败方读数)读到 `PARKING` / `PARKED` 时靠它归因,
147
- * 不替没写的行猜一个词。
150
+ * `expire` 终局 claim(窗到期 / 广播全灭 / 断连 / 呈不出 / 有序关停把一张卡转投递面 = `PARKING`)赢下时写进这一行 `park_trigger` 的**触发成因**:此后不再被写 ——
151
+ * 这一行走到 `PARKED` / `DENIED` / `VOID` 之后它仍是「这一行为什么被转 park」(终态成因在另一列,{@link AskRowEndingCause})。跨副本的读者(另一进程的悬挂腿轮询、
152
+ * CAS 败方读数)读到 `PARKING` / `PARKED` 时靠它归因,不替没写的行猜一个词。
148
153
  */
149
154
  export type ParkTriggerCause = typeof APPROVAL_ENDING_CAUSES.TTL_EXPIRED | typeof APPROVAL_ENDING_CAUSES.BROADCAST_LOST | typeof APPROVAL_ENDING_CAUSES.STREAM_DISCONNECTED | typeof APPROVAL_ENDING_CAUSES.UNDELIVERABLE | typeof APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK;
150
- /** 问账行 `denied_reason` 在 `PARKING` / `PARKED` 行上的读词表({@link ParkTriggerCause} 的穷举)。唯一读者 = {@link causeOfAskRow}。 */
155
+ /** 问账行 `park_trigger` 列的读词表({@link ParkTriggerCause} 的穷举)。唯一读者 = {@link askRowParkTrigger}。 */
151
156
  export declare const PARK_TRIGGER_CAUSE_WORDS: {
152
157
  readonly ttl_expired: true;
153
158
  readonly broadcast_lost: true;
@@ -155,25 +160,24 @@ export declare const PARK_TRIGGER_CAUSE_WORDS: {
155
160
  readonly undeliverable: true;
156
161
  readonly shutdown_before_park: true;
157
162
  };
158
- /** 问账行成因的读数:`kind` 说这一个词在这一行上是哪种意思(终态成因 / 转 park 的触发成因)。 */
159
- export type AskRowCause = {
160
- readonly kind: "ending";
161
- readonly cause: AskRowEndingCause;
162
- } | {
163
- readonly kind: "park_trigger";
164
- readonly cause: ParkTriggerCause;
165
- };
166
163
  /**
167
- * 🔴 **问账行成因的唯一读口**:`denied_reason` 一列按 `state` 承载两种意思,本函数按 `state` 穷举分派(加状态 ⇒ 这里编译红),读者不自己先判 state 再查表:
168
- * · `VOID` / `DENIED` ⇒ 终态成因({@link AskRowEndingCause};决断口 410、晚答 404、CAS 败方读数、悬挂腿的轮询读这一种);
169
- * · `PARKING` / `PARKED` ⇒ 转 park 的触发成因({@link ParkTriggerCause};跨副本的悬挂腿与 CAS 败方读这一种);
170
- * · `DECIDED` / `STREAM_PENDING` / `absent`(败方读数的「行不在」)⇒ 无成因。
171
- * 列值 `null` / 键缺席(没写归因的存量行)或与 state 不配的词(手改 / 将来版本写的词)⇒ `undefined`(读面缺席即缺席,不替没写的行编一个成因)。返回纯数据。
164
+ * 🔴 **终态成因的唯一读口**(决断口 410、晚答 404、`GET` 读面、CAS 败方读数、悬挂腿的轮询读这一只):只读 `ending_cause` 一列,不看别的列。
165
+ * 行不变式由本口守:终态成因只在 `VOID` / `DENIED` 上作数(写者只在到这两态的转移上写它;别的态上读到 ⇒ 不是这一行的事实)—— 按 `state` 穷举(加状态 ⇒ 这里编译红)。
166
+ * 列值 `null` / 键缺席(没写归因的行)、词表外的词(手改 / 将来版本写的词)或与 state 不配 ⇒ `undefined`(读面缺席即缺席,不替没写的行编一个成因)。返回纯数据。
167
+ */
168
+ export declare function askRowEndingCause(row: {
169
+ readonly state: AskState;
170
+ readonly endingCause?: string | null;
171
+ }): AskRowEndingCause | undefined;
172
+ /**
173
+ * 🔴 **转 park 触发成因的唯一读口**(跨副本的悬挂腿、CAS 败方读数读这一只):只读 `park_trigger` 一列,不看别的列。
174
+ * 行不变式由本口守:触发成因只在**走过 `PARKING`** 的行上作数 —— `PARKING` / `PARKED`,以及从 `PARKING` 收成的 `DENIED` / `VOID`(终态不覆盖它);
175
+ * `STREAM_PENDING` / `DECIDED`(状态机上没有经 `PARKING` 到它们的边)上读到 ⇒ `undefined`。其余与 {@link askRowEndingCause} 同律。
172
176
  */
173
- export declare function causeOfAskRow(row: {
174
- readonly state: AskState | "absent";
175
- readonly deniedReason?: string | null;
176
- }): AskRowCause | undefined;
177
+ export declare function askRowParkTrigger(row: {
178
+ readonly state: AskState;
179
+ readonly parkTrigger?: string | null;
180
+ }): ParkTriggerCause | undefined;
177
181
  /** 非人**结局**的成因(审计 settle 行 `cause`、活卡结局帧 `tool_approval_complete.cause`):闭集去掉决断口 409 的三个词({@link DecisionRefusalCause})—— 那三个词不结束一张卡,只拒一次决议。 */
178
182
  export type SettleCause = Exclude<ApprovalEndingCause, DecisionRefusalCause>;
179
183
  /** 决断口 409 的成因子集(core 的 `detail.field` → 码;每词一个码,见 `http/resume-legs.ts`)。 */
@@ -63,22 +63,37 @@ export const PARK_TRIGGER_CAUSE_WORDS = {
63
63
  function isParkTriggerCause(v) {
64
64
  return Object.hasOwn(PARK_TRIGGER_CAUSE_WORDS, v);
65
65
  }
66
- export function causeOfAskRow(row) {
67
- const stored = row.deniedReason;
66
+ export function askRowEndingCause(row) {
67
+ const stored = row.endingCause;
68
68
  switch (row.state) {
69
69
  case "VOID":
70
70
  case "DENIED":
71
- return stored !== null && stored !== undefined && isAskRowEndingCause(stored) ? { kind: "ending", cause: stored } : undefined;
71
+ return stored !== null && stored !== undefined && isAskRowEndingCause(stored) ? stored : undefined;
72
72
  case "PARKING":
73
73
  case "PARKED":
74
- return stored !== null && stored !== undefined && isParkTriggerCause(stored) ? { kind: "park_trigger", cause: stored } : undefined;
75
74
  case "DECIDED":
76
75
  case "STREAM_PENDING":
77
- case "absent":
78
76
  return undefined;
79
77
  default: {
80
78
  const unhandled = row.state;
81
- throw new Error(`causeOfAskRow: unhandled ask row state ${String(unhandled)}`);
79
+ throw new Error(`askRowEndingCause: unhandled ask row state ${String(unhandled)}`);
80
+ }
81
+ }
82
+ }
83
+ export function askRowParkTrigger(row) {
84
+ const stored = row.parkTrigger;
85
+ switch (row.state) {
86
+ case "PARKING":
87
+ case "PARKED":
88
+ case "DENIED":
89
+ case "VOID":
90
+ return stored !== null && stored !== undefined && isParkTriggerCause(stored) ? stored : undefined;
91
+ case "DECIDED":
92
+ case "STREAM_PENDING":
93
+ return undefined;
94
+ default: {
95
+ const unhandled = row.state;
96
+ throw new Error(`askRowParkTrigger: unhandled ask row state ${String(unhandled)}`);
82
97
  }
83
98
  }
84
99
  }
@@ -29,6 +29,6 @@ import type { ParkTriggerCause } from "./approval-deny-reasons.js";
29
29
  * `batchId` 必须是**行上**的批(幂等命中的存量行可能是别的政策 / 旧版本铸的整腿批,错 scope 的 claim 如实 `absent`)。
30
30
  */
31
31
  export declare function settleByPolicy(store: Pick<ApprovalAskStore, "claimTerminal">, row: Pick<AskRow, "askId" | "batchId">, policy: UnattendedApprovalPolicy,
32
- /** 转 park 的触发成因(窗到期 / 广播全灭 / 有序关停):`park` 政策的 `expire` 赢下 PARKING 时随同一步写进行(`denied_reason`),跨副本的读者据它归因;`deny` 政策落的是一条决议,不带。 */
32
+ /** 转 park 的触发成因(窗到期 / 广播全灭 / 有序关停):`park` 政策的 `expire` 赢下 PARKING 时随同一步写进行(`park_trigger`),跨副本的读者据它归因;`deny` 政策落的是一条决议,不带。 */
33
33
  cause: ParkTriggerCause): Promise<AskTerminalClaimOutcome>;
34
34
  //# sourceMappingURL=approval-policy-settlement.d.ts.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * **运维审批队列 `pending[]` 的行族闭集** —— 全仓唯一的定义处([ref])。
3
+ *
4
+ * ## 规则(一句)
5
+ * `GET /v1/approvals` 的 `pending[]`(与 `/v1/approvals/stream` 的 `pending` / `resolved` 帧)= **决断口是 `POST /v1/approvals/:id/decide` 的全部行**;
6
+ * 每一行自报它属于哪一族(`kind`,本闭集一词),消费方**先按 `kind` 分支**,再读那一族的键。数组名仍是决断口的判据(`livePending[]` 的口是
7
+ * `POST /v1/tool-approvals/:approvalId/respond`,不在本闭集里);本闭集回答的是「同一扇门后面是哪一种卡」。
8
+ *
9
+ * · `checkpoint` —— 停驻门(durable park):行 = 店的 `listPending` 那一行(键集照旧,另带 `kind`),门的 `:id` = 行的 `sessionId`,
10
+ * 再按 `gateKind`(core 的门型闭集)分路;
11
+ * · `peer_inbound` —— 被 `crossSessionInbound: hold` 扣住、等收信方决断的跨会话来信:行 = core 进程级 held 队列那一张卡的投影
12
+ * (`peer-inbound-face.ts`),门的 `:id` = 行的 `id`(`peer_inbound:` 前缀的坐标串),决断 = core `PeerHeldQueue.settle`。
13
+ *
14
+ * 加一族 = 在 {@link APPROVAL_QUEUE_KINDS} 加一个词:决断口分派、流的键与离场帧({@link approvalQueueKindOfDoorId} 的调用点)
15
+ * 都是对 {@link ApprovalQueueKind} 的穷举 `switch`,漏一臂 = **编译红**(`test/peer-inbound-approvals.test.ts` 的 P6 钉)。
16
+ */
17
+ /** 行族闭集(序无义;加词 = 全部穷举点编译红)。 */
18
+ export declare const APPROVAL_QUEUE_KINDS: readonly ["checkpoint", "peer_inbound"];
19
+ export type ApprovalQueueKind = (typeof APPROVAL_QUEUE_KINDS)[number];
20
+ /**
21
+ * `peer_inbound` 族决断口 id 的前缀 —— 一个 id 是不是这一族,**只**看它(`checkpoint` 族的 id 是会话 id:规范 uuid 或服务现铸的
22
+ * 内部 id,都不含 `:`,所以两族的 id 空间不交)。
23
+ */
24
+ export declare const PEER_INBOUND_ID_PREFIX = "peer_inbound:";
25
+ /** 决断口 `:id` 段 → 它属于哪一族(唯一判据:前缀)。前缀对而其余不成形的 id 仍归 `peer_inbound`(那一族自己答 404,不落回停驻门)。 */
26
+ export declare function approvalQueueKindOfDoorId(id: string): ApprovalQueueKind;
27
+ //# sourceMappingURL=approval-queue-kind.d.ts.map
@@ -0,0 +1,6 @@
1
+ export const APPROVAL_QUEUE_KINDS = ["checkpoint", "peer_inbound"];
2
+ export const PEER_INBOUND_ID_PREFIX = "peer_inbound:";
3
+ export function approvalQueueKindOfDoorId(id) {
4
+ return id.startsWith(PEER_INBOUND_ID_PREFIX) ? "peer_inbound" : "checkpoint";
5
+ }
6
+ //# sourceMappingURL=approval-queue-kind.js.map
@@ -79,7 +79,7 @@ export type GateMatchOutcome =
79
79
  kind: "hash_mismatch";
80
80
  candidates: number;
81
81
  }
82
- /** 三元组身份就对不上(不是摘要的事):别的 sourceTaskId / 别的 callId / 早于本 ask 的 park。 */
82
+ /** 三元组身份就对不上(不是摘要的事):别的 sourceSessionId / 别的 callId / 早于本 ask 的 park。 */
83
83
  | {
84
84
  kind: "identity_miss";
85
85
  };
@@ -91,8 +91,8 @@ export type GateMatchOutcome =
91
91
  * `delegation.parentToolCallId` 是必填字段,铸行时逐字透传进 `parent_tool_call_id` 列 ⇒ **列非 NULL
92
92
  * ⇔ delegation 在场**(不需要新列,也不需要回 `req` 里再读一次)。
93
93
  *
94
- * 第二维 `sourceTaskId !== sessionId` 把「根腿」摘出去:根腿的 `sourceTaskId` 恒等于会话锚(core 给
95
- * `AskRequest.sourceTaskId` 填的就是该腿 sessionId),它根本不在任何 fold 里,`parent_tool_call_id`
94
+ * 第二维 `sourceSessionId !== sessionId` 把「根腿」摘出去:根腿的 `sourceSessionId` 恒等于会话锚(core 给
95
+ * `AskRequest.sourceSessionId` 填的就是该腿 sessionId),它根本不在任何 fold 里,`parent_tool_call_id`
96
96
  * 为 NULL 是它的常态而不是信号。
97
97
  *
98
98
  * 命中 ⇒ 退出硬相等(判据 1 结构上不命中,行落 ②③④⑤)。实测(test AI 围栏)checkpoint 存的摘要**逐字
@@ -105,11 +105,11 @@ export declare function isAncestorFoldMint(ask: AskRow): boolean;
105
105
  * 逐条:
106
106
  * - 祖先层 fold 中途铸点 ⇒ 直接退出(见 {@link isAncestorFoldMint});
107
107
  * - `unparseable` 候选直接出局(读不出 ⇒ 不确定 ⇒ 不命中);
108
- * - **身份三元组**:`sourceTaskId` **相等** ∧ `toolCallId` **相等** ∧ 时间维的**因果下界**
108
+ * - **身份三元组**:`sourceSessionId` **相等** ∧ `toolCallId` **相等** ∧ 时间维的**因果下界**
109
109
  * `candidate.createdAtMs >= ask.createdAtMs`(⚠️ 第三维**不是等式** —— 把它读成等式会让合法的 park
110
110
  * 几乎命不中,行随后落 ②③⑤ 被误判)—— 摘要**不是**身份([ref]③①:
111
111
  * 同 args 的两次调用摘要天然相同,只靠摘要硬相等会把第二次投递并进第一次的票);任一维在候选侧
112
- * 缺席(读不出的 blob 没有 `sourceTaskId`)即身份不成立;
112
+ * 缺席(读不出的 blob 没有 `sourceSessionId`)即身份不成立;
113
113
  * - **hash 双等**:两侧都必须在场且相等,任一侧缺席一律不 bind;禁「能取到时才比」的可选谓词。
114
114
  * 归因分两级(顺序即代码顺序):身份这一层先判 —— 同身份候选为空**且**候选集非空 ⇒ `identity_miss`;
115
115
  * 走到 hash 这一层才把缺席记成**单铸** `single_mint`(见 {@link GateMatchOutcome})。
@@ -123,6 +123,17 @@ export declare function classifyGateMatch(ask: AskRow, candidates: readonly Chec
123
123
  export declare function selectGateCandidate(ask: AskRow, candidates: readonly CheckpointAskCandidate[]): CheckpointAskCandidate | undefined;
124
124
  /** 判据表 v2 的判定(纯函数;顺序 = 设计稿 §9 尾的五臂汇总,注见文件头)。 */
125
125
  export declare function decideReconcileAction(input: ReconcileInput): ReconcileAction;
126
+ /** 判据 1 执行体({@link bindParkingAskAtGate})一次尝试的结果。没绑上 ⇒ 交回批的真实态,周期扫据它续判 ②③④⑤。 */
127
+ export type GateBindAttempt = {
128
+ bound: true;
129
+ match: GateMatchOutcome;
130
+ } | {
131
+ bound: false;
132
+ match?: GateMatchOutcome;
133
+ batchState: BatchState | "MISSING" | null;
134
+ batchBoundAskId: string | null;
135
+ irreversibleBatch: boolean;
136
+ };
126
137
  /** 收敛器一次 tick 的产出(可观测 + 测试断言面)。 */
127
138
  export interface ReconcileStats {
128
139
  /** 段一扫到的 `PARKING` 行数。 */
@@ -167,10 +178,10 @@ export interface ReconcileStats {
167
178
  *
168
179
  * 🔴 **今天恒零,而且是结构性的**(扫描P2,2026-08-12 亲读验真;登记而不假称已解):读口
169
180
  * `findCheckpointCandidatesForAsk` 按 `session_id = ask.sessionId` 查,`session_id` 列写的是
170
- * `cp.sessionId`,而 core 每个 checkpoint 铸点都填 `sourceTaskId: sessionId`
181
+ * `cp.sessionId`,而 core 每个 checkpoint 铸点都填 `sourceSessionId: sessionId`
171
182
  * (`runner/prepare-task.js` 四处;`checkpoint-store.d.ts` 逐字「= sessionId at the mint」)⇒ 读口
172
- * 返回的候选恒满足 `c.sourceTaskId === ask.sessionId`;而判据 1 命中要 `c.sourceTaskId ===
173
- * ask.sourceTaskId`,祖先层否决要 `ask.sourceTaskId !== ask.sessionId` —— 两者互斥,这一臂在生产
183
+ * 返回的候选恒满足 `c.sourceSessionId === ask.sessionId`;而判据 1 命中要 `c.sourceSessionId ===
184
+ * ask.sourceSessionId`,祖先层否决要 `ask.sourceSessionId !== ask.sessionId` —— 两者互斥,这一臂在生产
174
185
  * 接线下一次也不会被返回。
175
186
  *
176
187
  * 所以**别把零读数当健康信号**(与 {@link unmatchableNoHash} 的「恒零才是健康态」正相反:那条能非零,
@@ -202,7 +213,7 @@ export type ApprovalRevokeEmitter = (frame: ApprovalRevokeFrame, target: {
202
213
  sessionId: string;
203
214
  taskId: string;
204
215
  }) => void;
205
- /** 判据 1 的读口(窄接口,不绑具体 checkpoint 店实现——local 车道的店没有这个面,装配点传 undefined)。 */
216
+ /** 判据 1 的读口(窄接口,不绑具体 checkpoint 店实现;三个后端的 checkpoint 店都有这一面,后端的只读口 `checkpointReadView()` 也交它 —— 起服收敛读那一口)。 */
206
217
  export interface ReconcileCheckpointPort {
207
218
  findCheckpointCandidatesForAsk(scope: string, sessionId: string, toolCallId: string, sinceMs: number): Promise<CheckpointAskCandidate[]>;
208
219
  }
@@ -245,5 +256,26 @@ export interface ApprovalReconciler {
245
256
  * 段级错误由调用方(reaper 腿的 throttled catch)兜。 */
246
257
  runOnce(nowMs: number): Promise<ReconcileStats>;
247
258
  }
259
+ /**
260
+ * {@link bindParkingAskAtGate} 的依赖(三个调用方交同一形):批 / 绑两口、判据 1 的候选读口、warn 与撤卡两个出口。
261
+ */
262
+ export interface GateBindDeps {
263
+ readonly askStore: Pick<ApprovalAskStore, "getBatch" | "bindBatch">;
264
+ /** 缺席 ⇒ 候选集恒空 ⇒ 判据 1 恒不命中(诚实降级,与 {@link ApprovalReconcilerDeps.checkpoints} 同一句)。 */
265
+ readonly checkpoints: ReconcileCheckpointPort | undefined;
266
+ /** warn 的出口(调用方保证它不抛 —— 可观测面绝不成为故障源)。 */
267
+ readonly warn: (event: string, fields: Record<string, unknown>) => void;
268
+ /** 被 `bindBatch` 连坐的兄弟的撤卡帧(帧是通知,真源是行;失败不回滚 CAS)。 */
269
+ readonly revoke: (row: AskRow, askIds: readonly string[], reason: ApprovalRevokeFrame["reason"], nowMs: number) => void;
270
+ }
271
+ /**
272
+ * 🔴 **判据 1 的唯一执行体**([ref] 起一只判官;[ref] 起三个调用方):⓪ 批态先读 → 候选读口 → {@link classifyGateMatch} → 命中即 `bindBatch`
273
+ * (+ 被连坐兄弟的撤卡帧)。调用方:
274
+ * · 周期扫(`createApprovalReconciler` 的 `reconcileOne`)—— 不命中 / 绑失败时拿回批的真实态,续判 ②③④⑤;
275
+ * · park 提交那一拍(`bindCommittedPark`)—— 只问「这一行此刻能不能收口」,不命中即止(其余臂是周期扫的事);
276
+ * · local 车道起服收敛(`approval-crash-convergence.ts`)—— 审计判为孤儿的前世 `PARKING` 行:命中 ⇒ `PARKED`;不命中 ⇒ 调用方收 `VOID`。
277
+ * 判据、读口、写口一个字都不分叉:事件路径与起服都是本函数的即时调用,不是第二份判据。
278
+ */
279
+ export declare function bindParkingAskAtGate(deps: GateBindDeps, ask: AskRow, nowMs: number, deadlineMs?: number): Promise<GateBindAttempt>;
248
280
  export declare function createApprovalReconciler(deps: ApprovalReconcilerDeps): ApprovalReconciler;
249
281
  //# sourceMappingURL=approval-reconciler.d.ts.map