@sema-agent/server 7.122.0 → 7.124.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 (256) hide show
  1. package/MIGRATION.md +209 -0
  2. package/README.md +7 -2
  3. package/README.zh-CN.md +5 -2
  4. package/USAGE.md +76 -56
  5. package/dist/approval-ask-audit-store.d.ts +27 -32
  6. package/dist/approval-ask-audit-store.js +10 -9
  7. package/dist/approval-crash-convergence.d.ts +2 -2
  8. package/dist/approval-crash-convergence.js +5 -5
  9. package/dist/approval-deny-reasons.d.ts +163 -51
  10. package/dist/approval-deny-reasons.js +79 -8
  11. package/dist/approval-policy-settlement.d.ts +4 -1
  12. package/dist/approval-policy-settlement.js +3 -2
  13. package/dist/approval-reconciler.d.ts +3 -3
  14. package/dist/approval-reconciler.js +10 -10
  15. package/dist/approval.d.ts +1 -1
  16. package/dist/approval.js +1 -1
  17. package/dist/audit.d.ts +1 -1
  18. package/dist/audit.js +53 -23
  19. package/dist/auth-bridge.d.ts +8 -8
  20. package/dist/auth-bridge.js +6 -6
  21. package/dist/auth-keys.d.ts +2 -2
  22. package/dist/auto-mode-face.d.ts +13 -9
  23. package/dist/auto-mode-face.js +8 -7
  24. package/dist/boot/budget-tracing.js +1 -1
  25. package/dist/boot/execution-env.js +1 -0
  26. package/dist/boot/leader.d.ts +1 -1
  27. package/dist/boot/memory-consolidation.d.ts +4 -4
  28. package/dist/boot/org-memory.d.ts +3 -3
  29. package/dist/boot/org-memory.js +5 -5
  30. package/dist/boot/resolve-spec.js +7 -7
  31. package/dist/boot/runner-deps.d.ts +1 -1
  32. package/dist/boot/runtime-caps.d.ts +1 -1
  33. package/dist/boot/runtime-caps.js +10 -10
  34. package/dist/boot/shutdown.d.ts +2 -2
  35. package/dist/boot/shutdown.js +2 -2
  36. package/dist/boot/side-query-lane.d.ts +3 -3
  37. package/dist/boot/{stage-02-registry-pull.d.ts → stage-02-web-admin-pull.d.ts} +2 -2
  38. package/dist/boot/{stage-02-registry-pull.js → stage-02-web-admin-pull.js} +4 -4
  39. package/dist/boot/stage-03-store-backend.d.ts +2 -2
  40. package/dist/boot/stage-04-stores.d.ts +1 -1
  41. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  42. package/dist/boot/stage-06-runners.d.ts +2 -2
  43. package/dist/boot/stage-06-runners.js +6 -6
  44. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  45. package/dist/boot/stage-07-capability-layer.js +6 -5
  46. package/dist/boot/stage-08-reapers.d.ts +3 -3
  47. package/dist/boot/stage-08-reapers.js +3 -3
  48. package/dist/boot/stage-09-leader.d.ts +3 -3
  49. package/dist/boot/stage-09-leader.js +7 -7
  50. package/dist/boot/stage-10-http-server.d.ts +3 -3
  51. package/dist/boot/stage-10-http-server.js +19 -19
  52. package/dist/boot/unpriced-ceiling-verdict.d.ts +1 -1
  53. package/dist/boot/{config-center.d.ts → web-admin.d.ts} +11 -11
  54. package/dist/boot/{config-center.js → web-admin.js} +39 -20
  55. package/dist/boot/webfetch-summarize-lane.d.ts +2 -2
  56. package/dist/brain.d.ts +1 -1
  57. package/dist/brain.js +1 -1
  58. package/dist/budget.d.ts +3 -3
  59. package/dist/canonical-json.d.ts +2 -2
  60. package/dist/capabilities/center-plugins.d.ts +1 -1
  61. package/dist/capabilities/center-wiring.d.ts +12 -5
  62. package/dist/capabilities/center-wiring.js +11 -2
  63. package/dist/capabilities/composite-members.d.ts +1 -1
  64. package/dist/capabilities/composite-members.js +1 -1
  65. package/dist/capabilities/scenarios.js +2 -2
  66. package/dist/capabilities/team.d.ts +1 -1
  67. package/dist/capabilities/tool-defer.d.ts +1 -1
  68. package/dist/center-credential-renewer.d.ts +1 -1
  69. package/dist/center-credential-renewer.js +7 -7
  70. package/dist/center-credential.d.ts +9 -9
  71. package/dist/center-credential.js +6 -6
  72. package/dist/config-catalog.d.ts +7 -10
  73. package/dist/config-catalog.js +16 -16
  74. package/dist/config-d-migration.d.ts +32 -0
  75. package/dist/config-d-migration.js +59 -0
  76. package/dist/config-invariants.d.ts +14 -4
  77. package/dist/config-invariants.js +31 -2
  78. package/dist/config-lkg.d.ts +34 -11
  79. package/dist/config-lkg.js +76 -48
  80. package/dist/config-provider.d.ts +26 -21
  81. package/dist/config-provider.js +25 -27
  82. package/dist/config-types.d.ts +59 -50
  83. package/dist/config.d.ts +17 -54
  84. package/dist/config.js +99 -181
  85. package/dist/execution-lane-caps.d.ts +1 -1
  86. package/dist/fleet/drain-entry.d.ts +1 -1
  87. package/dist/fleet/engine-signal.d.ts +61 -30
  88. package/dist/fleet/engine-signal.js +29 -5
  89. package/dist/fleet/fleet-bus.d.ts +12 -3
  90. package/dist/fleet/fleet-bus.js +13 -9
  91. package/dist/fleet/serving-lease.d.ts +3 -3
  92. package/dist/fleet/serving-lease.js +1 -1
  93. package/dist/fleet-client.d.ts +15 -13
  94. package/dist/fleet-client.js +9 -7
  95. package/dist/fleet-lease.d.ts +3 -3
  96. package/dist/fleet-lease.js +2 -2
  97. package/dist/host-lsp-manager.d.ts +1 -1
  98. package/dist/hosted-posture.d.ts +4 -4
  99. package/dist/http/admission.js +4 -4
  100. package/dist/http/checkpoint-ctx.d.ts +20 -3
  101. package/dist/http/checkpoint-ctx.js +13 -5
  102. package/dist/http/dispatch.js +7 -16
  103. package/dist/http/durable-run-leg.js +2 -1
  104. package/dist/http/health-self-report.d.ts +7 -4
  105. package/dist/http/health-self-report.js +3 -3
  106. package/dist/http/idempotency.d.ts +1 -1
  107. package/dist/http/resume-legs.d.ts +4 -3
  108. package/dist/http/resume-legs.js +31 -18
  109. package/dist/http/route-ctx.d.ts +8 -0
  110. package/dist/http/routes/admin-config-refresh.d.ts +1 -1
  111. package/dist/http/routes/admin-config-refresh.js +1 -1
  112. package/dist/http/routes/admin-drain.d.ts +1 -1
  113. package/dist/http/routes/admin-drain.js +5 -4
  114. package/dist/http/routes/approvals-assistant.js +4 -4
  115. package/dist/http/routes/capabilities.js +6 -5
  116. package/dist/http/routes/config-catalog.js +2 -3
  117. package/dist/http/routes/diagnostics.js +1 -1
  118. package/dist/http/routes/runs.js +8 -4
  119. package/dist/http/routes/sessions-list.js +2 -1
  120. package/dist/http/routes/sessions.js +2 -5
  121. package/dist/http/routes/side-query.js +3 -3
  122. package/dist/http/routes/tasks.js +3 -2
  123. package/dist/http/run-meta.d.ts +1 -1
  124. package/dist/http/run-meta.js +2 -2
  125. package/dist/http/server.d.ts +7 -7
  126. package/dist/http/server.js +1 -1
  127. package/dist/http/stored-body-replay.d.ts +4 -3
  128. package/dist/http/stored-body-replay.js +5 -5
  129. package/dist/index.d.ts +2 -1
  130. package/dist/index.js +1 -1
  131. package/dist/key-resolver.d.ts +1 -1
  132. package/dist/leader/leader.d.ts +7 -6
  133. package/dist/leader/leader.js +3 -1
  134. package/dist/leader/run-entry.d.ts +2 -2
  135. package/dist/leader/run-entry.js +3 -3
  136. package/dist/leader/wire.d.ts +4 -4
  137. package/dist/leader/wire.js +2 -1
  138. package/dist/main.js +1 -1
  139. package/dist/mcp-tool-faces.d.ts +2 -2
  140. package/dist/model-entry-refusal.d.ts +20 -11
  141. package/dist/model-entry-refusal.js +22 -17
  142. package/dist/model-provider.d.ts +2 -2
  143. package/dist/model-route-endpoint.d.ts +22 -2
  144. package/dist/model-route-endpoint.js +9 -3
  145. package/dist/model-route-gap.d.ts +103 -0
  146. package/dist/model-route-gap.js +80 -0
  147. package/dist/model-route-url.d.ts +1 -1
  148. package/dist/model-route-url.js +2 -1
  149. package/dist/model-select.d.ts +24 -21
  150. package/dist/model-select.js +22 -17
  151. package/dist/namespaced-rule-name.d.ts +16 -30
  152. package/dist/namespaced-rule-name.js +2 -20
  153. package/dist/observability/fail-open.d.ts +0 -4
  154. package/dist/observability/fail-open.js +0 -4
  155. package/dist/observability/metrics.d.ts +1 -1
  156. package/dist/org-memory-admission.d.ts +1 -1
  157. package/dist/plugins/approval-ask-store-file.d.ts +4 -2
  158. package/dist/plugins/approval-ask-store-file.js +3 -3
  159. package/dist/plugins/approval-ask-store-memory.d.ts +4 -3
  160. package/dist/plugins/approval-ask-store-memory.js +10 -4
  161. package/dist/plugins/approval-ask-store-sql.d.ts +16 -2
  162. package/dist/plugins/approval-ask-store-sql.js +20 -13
  163. package/dist/plugins/caching-session-store.d.ts +0 -10
  164. package/dist/plugins/caching-session-store.js +0 -3
  165. package/dist/plugins/file-run-store.js +9 -3
  166. package/dist/plugins/leader-run-store-sql.d.ts +3 -2
  167. package/dist/plugins/leader-run-store-sql.js +3 -2
  168. package/dist/plugins/local-session-store.d.ts +20 -27
  169. package/dist/plugins/local-session-store.js +19 -17
  170. package/dist/plugins/memory-run-store.js +9 -3
  171. package/dist/plugins/one-time-migrations.js +51 -0
  172. package/dist/plugins/scheduler-support.d.ts +26 -7
  173. package/dist/plugins/scheduler-support.js +96 -53
  174. package/dist/plugins/session-policy-store-sql.js +1 -2
  175. package/dist/plugins/store-backend.js +1 -1
  176. package/dist/plugins/store-contracts.d.ts +20 -4
  177. package/dist/plugins/web-search.d.ts +1 -1
  178. package/dist/plugins/web-search.js +2 -1
  179. package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
  180. package/dist/posture-source.d.ts +1 -1
  181. package/dist/read-face-posture.d.ts +3 -3
  182. package/dist/read-face-posture.js +1 -1
  183. package/dist/rule-entry-list.d.ts +3 -13
  184. package/dist/rule-entry-list.js +1 -12
  185. package/dist/run-local.js +10 -7
  186. package/dist/runtime-caps-resolver.d.ts +4 -4
  187. package/dist/runtime-caps-resolver.js +2 -2
  188. package/dist/runtime-governance.d.ts +36 -13
  189. package/dist/runtime-governance.js +7 -2
  190. package/dist/sealed-key.d.ts +4 -4
  191. package/dist/sealed-key.js +3 -2
  192. package/dist/security.d.ts +3 -3
  193. package/dist/service-version.d.ts +1 -1
  194. package/dist/service-version.js +5 -0
  195. package/dist/session-policy-wire.d.ts +47 -49
  196. package/dist/session-policy-wire.js +41 -36
  197. package/dist/session-sync.d.ts +5 -1
  198. package/dist/session-sync.js +2 -1
  199. package/dist/session-titler.d.ts +0 -11
  200. package/dist/session-titler.js +3 -5
  201. package/dist/shared-memory-scope-authorizer.d.ts +4 -4
  202. package/dist/spec-fields.d.ts +4 -4
  203. package/dist/spec-fields.js +1 -4
  204. package/dist/task-a2a.d.ts +1 -1
  205. package/dist/task-settings.d.ts +4 -5
  206. package/dist/task-settings.js +5 -8
  207. package/dist/tool-approval.d.ts +43 -4
  208. package/dist/tool-approval.js +147 -98
  209. package/dist/trace/core-keyset-guard.d.ts +1 -1
  210. package/dist/trace/project.d.ts +3 -3
  211. package/dist/trace/redact.d.ts +2 -23
  212. package/dist/trace/redact.js +1 -6
  213. package/dist/trace/self-minted-wire.d.ts +147 -3
  214. package/dist/trace/self-minted-wire.js +50 -9
  215. package/dist/unattended-permission-mode.d.ts +3 -3
  216. package/dist/unattended-permission-mode.js +2 -2
  217. package/dist/url-refusal.d.ts +35 -0
  218. package/dist/url-refusal.js +15 -0
  219. package/dist/user-authored-text.d.ts +31 -0
  220. package/dist/user-authored-text.js +18 -0
  221. package/dist/{config-center → web-admin-client}/apply-effective.d.ts +13 -70
  222. package/dist/{config-center → web-admin-client}/apply-effective.js +104 -121
  223. package/dist/{config-center → web-admin-client}/apply-ledger.d.ts +2 -2
  224. package/dist/{config-center → web-admin-client}/center-request.d.ts +7 -7
  225. package/dist/{config-center → web-admin-client}/center-request.js +5 -4
  226. package/dist/{config-center → web-admin-client}/effective-keys.d.ts +1 -3
  227. package/dist/{config-center → web-admin-client}/effective-keys.js +0 -9
  228. package/dist/{config-center → web-admin-client}/facade.d.ts +6 -6
  229. package/dist/{config-center → web-admin-client}/facade.js +2 -2
  230. package/dist/{config-center → web-admin-client}/http-client.d.ts +6 -10
  231. package/dist/{config-center → web-admin-client}/http-client.js +15 -15
  232. package/dist/{config-center → web-admin-client}/pull-state.d.ts +1 -1
  233. package/dist/{config-center → web-admin-client}/read-face.js +4 -4
  234. package/dist/web-admin-client/read-warnings.d.ts +137 -0
  235. package/dist/web-admin-client/read-warnings.js +168 -0
  236. package/dist/{config-center → web-admin-client}/restart-signal.d.ts +5 -3
  237. package/dist/{config-center → web-admin-client}/restart-signal.js +3 -4
  238. package/dist/{config-center → web-admin-client}/skills-mcp.d.ts +1 -1
  239. package/dist/{config-center → web-admin-client}/skills-mcp.js +13 -13
  240. package/dist/{config-center → web-admin-client}/stage-limits.d.ts +1 -7
  241. package/dist/{config-center → web-admin-client}/stage-limits.js +6 -7
  242. package/dist/{config-center → web-admin-client}/types.d.ts +19 -47
  243. package/package.json +3 -3
  244. package/skills/find-skills.md +2 -2
  245. package/dist/config-center/read-warnings.d.ts +0 -109
  246. package/dist/config-center/read-warnings.js +0 -110
  247. package/dist/url-shape.d.ts +0 -31
  248. package/dist/url-shape.js +0 -41
  249. /package/dist/{config-center → web-admin-client}/apply-ledger.js +0 -0
  250. /package/dist/{config-center → web-admin-client}/hot-keys-registry.d.ts +0 -0
  251. /package/dist/{config-center → web-admin-client}/hot-keys-registry.js +0 -0
  252. /package/dist/{config-center → web-admin-client}/mcp-revocation.d.ts +0 -0
  253. /package/dist/{config-center → web-admin-client}/mcp-revocation.js +0 -0
  254. /package/dist/{config-center → web-admin-client}/pull-state.js +0 -0
  255. /package/dist/{config-center → web-admin-client}/read-face.d.ts +0 -0
  256. /package/dist/{config-center → web-admin-client}/types.js +0 -0
@@ -3,20 +3,21 @@ import { join } from "node:path";
3
3
  import { AppendLog, ensureDir, isHostDecider } from "@sema-agent/core";
4
4
  import { recordFailOpen } from "./observability/fail-open.js";
5
5
  import { replayJsonlSkipping } from "./observability/corrupt-read-seat.js";
6
+ import { isSettleCause, SETTLE_OUTCOME } from "./approval-deny-reasons.js";
6
7
  export const APPROVAL_ASK_AUDIT_DIR = "approval-ask-audit";
7
8
  export const APPROVAL_ASK_AUDIT_LEDGER = "ledger.jsonl";
8
9
  export const CRASH_CONVERGED_LIST_MAX = 200;
9
- export const AUDIT_SHUTDOWN_CAUSE_WORDS = { shutdown_before_park: true };
10
+ const SETTLE_OUTCOME_WORDS = new Set(Object.values(SETTLE_OUTCOME));
11
+ function isSettleOutcome(v) {
12
+ return typeof v === "string" && SETTLE_OUTCOME_WORDS.has(v);
13
+ }
10
14
  export function buildCheckpointAnchorHash(token) {
11
15
  return createHash("sha256").update(token, "utf8").digest("hex").slice(0, 16);
12
16
  }
13
- function isAuditShutdownCause(v) {
14
- return typeof v === "string" && Object.hasOwn(AUDIT_SHUTDOWN_CAUSE_WORDS, v);
15
- }
16
- function readShutdownCause(v) {
17
+ function readSettleCause(v) {
17
18
  if (v === undefined)
18
19
  return { cause: undefined, outOfSet: false };
19
- if (isAuditShutdownCause(v))
20
+ if (isSettleCause(v))
20
21
  return { cause: v, outOfSet: false };
21
22
  return { cause: undefined, outOfSet: true };
22
23
  }
@@ -70,11 +71,11 @@ function readLedgerRecord(u, noteOutOfSetSeat) {
70
71
  const approvalId = s("approvalId");
71
72
  const settledAtMs = num("settledAtMs");
72
73
  const outcomeV = o.outcome;
73
- const outcome = outcomeV === "allowed" || outcomeV === "denied" || outcomeV === "expired" ? outcomeV : undefined;
74
+ const outcome = isSettleOutcome(outcomeV) ? outcomeV : undefined;
74
75
  if (approvalId === undefined || settledAtMs === undefined || outcome === undefined)
75
76
  return undefined;
76
77
  const settledBy = s("settledBy");
77
- const cause = readShutdownCause(o.cause);
78
+ const cause = readSettleCause(o.cause);
78
79
  if (cause.outOfSet)
79
80
  noteOutOfSetSeat();
80
81
  return {
@@ -279,7 +280,7 @@ export class FileApprovalAskAuditStore {
279
280
  }
280
281
  }
281
282
  indexSettle(rec) {
282
- if (rec.outcome === "expired" && rec.parkRouted === true)
283
+ if (rec.outcome === SETTLE_OUTCOME.EXPIRED && rec.parkRouted === true)
283
284
  this.parkExpired.add(rec.approvalId);
284
285
  }
285
286
  recordAsk(input) {
@@ -1,6 +1,6 @@
1
1
  import type { ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
2
2
  import { FileApprovalAskAuditStore } from "./approval-ask-audit-store.js";
3
- import { type HolderGoneReason } from "./approval-deny-reasons.js";
3
+ import { type HolderGoneCause } from "./approval-deny-reasons.js";
4
4
  import { type RunStoreReader } from "./user-input-ledger.js";
5
5
  import type { Logger } from "./observability/logger.js";
6
6
  /** 启动收敛的读数(进启动日志)。 */
@@ -11,7 +11,7 @@ export interface CrashConvergenceReport {
11
11
  readonly inconsistent: number;
12
12
  readonly corruptLines: number;
13
13
  /** ask 账半场:按成因词计的、本次收成 VOID 的前世 `STREAM_PENDING` 行数。 */
14
- readonly asksVoided: Readonly<Record<HolderGoneReason, number>>;
14
+ readonly asksVoided: Readonly<Record<HolderGoneCause, number>>;
15
15
  /** 其中卡信封读不出、成因词是回落(`crashed_before_park`)的行数(同时记 fail-open tag)。 */
16
16
  readonly causeUnreadable: number;
17
17
  }
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
2
2
  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 { ApprovalCardEnvelopeSchema } from "./approval-card.js";
5
- import { VOID_REASONS } from "./approval-deny-reasons.js";
5
+ import { APPROVAL_ENDING_CAUSES } from "./approval-deny-reasons.js";
6
6
  import { writerGoneEvidenceFor } from "./user-input-ledger.js";
7
7
  import { recordFailOpen } from "./observability/fail-open.js";
8
8
  const CONVERGE_PAGE = 500;
@@ -20,7 +20,7 @@ export function openAuditEvidenceForConvergence(live, localDataRoot) {
20
20
  export async function convergeCrashedAsksAtBoot(input) {
21
21
  const now = input.now ?? Date.now;
22
22
  const audited = input.audit?.convergeCrashedAsks() ?? { pending: 0, decided: 0, inconsistent: 0, corruptLines: 0 };
23
- const asksVoided = { [VOID_REASONS.HOLDER_CRASHED]: 0, [VOID_REASONS.HOLDER_SHUTDOWN]: 0 };
23
+ const asksVoided = { [APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK]: 0, [APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK]: 0 };
24
24
  let causeUnreadable = 0;
25
25
  const store = input.askStore;
26
26
  if (store !== undefined) {
@@ -34,9 +34,9 @@ export async function convergeCrashedAsksAtBoot(input) {
34
34
  const envelope = ApprovalCardEnvelopeSchema.safeParse(row.cardJson);
35
35
  if (!envelope.success) {
36
36
  causeUnreadable += 1;
37
- recordFailOpen("server.approval-crash-convergence.cause-unreadable", `ask ${row.askId} (task ${row.taskId}): card envelope unreadable — cause defaulted to ${VOID_REASONS.HOLDER_CRASHED}`);
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}`);
38
38
  }
39
- const reason = envelope.success && input.audit !== undefined ? input.audit.causeFor(envelope.data.approvalId) : VOID_REASONS.HOLDER_CRASHED;
39
+ const reason = envelope.success && input.audit !== undefined ? input.audit.causeFor(envelope.data.approvalId) : APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK;
40
40
  let won;
41
41
  try {
42
42
  won = await store.transitionAsk(row.askId, "STREAM_PENDING", "VOID", { deniedReason: reason, updatedAtMs: now() });
@@ -67,7 +67,7 @@ export async function runBootCrashConvergence(ctx) {
67
67
  corruptLines: converged.corruptLines,
68
68
  asksVoided: converged.asksVoided,
69
69
  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 as deniedReason) 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)",
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)",
71
71
  });
72
72
  }
73
73
  return converged;
@@ -1,69 +1,181 @@
1
1
  /**
2
- * 流内审批协议([ref] §3.0 对账三约束)的**终态归因词表** —— [ref] 车5。
2
+ * **审批席「这张卡为什么没被人答」的一只闭集** —— {@link APPROVAL_ENDING_CAUSES}。
3
3
  *
4
- * 两组常量、**两个不相交的值域**(车5 §8 C-1 裁定,与车4 F34 同向):
5
- * - {@link DENY_REASONS} —— `DENIED` 终态的归因。v1 是**单员闭集**:`routing_failure_fail_closed`。
6
- * - {@link VOID_REASONS} —— `VOID` 终态的归因。
4
+ * ## 它治的病(同一件事、四张词表、一词多义)
5
+ * 一张卡没被人答就结束了,今天有四个读面各说各话:审计 settle 行(`expired` 一词盖 TTL 到期 / run 取消 / run 退出清理 / 广播全灭,批内兄弟被撤卡也是同一个词)、
6
+ * 问账 VOID/DENIED 行(`denied_reason` 列)、晚答 404 的判别位、决断口 409 的码(多种成因借 `approval_binding_mismatch` 一个名)。壳读账写不出「已取消」,
7
+ * 也分不出「重取坐标再决」与「改答案再决」。收成**一张表、一个词、各面同读**:
8
+ * · 审计 settle 行的 `cause`(非人结局必填,人答缺席;`settle(verdict)` 判别联合逼每个非人调用点点名一个)与活卡结局帧 `tool_approval_complete` 的 additive `cause`(同一个词,同一条律);
9
+ * · 问账 VOID/DENIED 行的 `denied_reason` 列 → 决断口 410 `gone.ask` 体的 `cause` 与晚答 404 体的 `cause`(读同一列,经唯一读口 {@link causeOfAskRow});
10
+ * · `GET /v1/approvals` `crashConverged[].cause`(本表的 {@link HolderGoneCause} 两词子集);
11
+ * · 决断口 409 的码:`binding_changed` / `answer_invalid` / `outcome_cell_invalid` 三词各归一个码。`retriable` 不由成因说 —— 三词都出自 core 的
12
+ * `checkpoint.invalid_outcome`,由 `http/resume-legs.ts` 的 core 码 → 处置一张表派生(一个意思:卡仍 pending、行重新 park)。
7
13
  *
8
- * 🔴 为什么必须分两组、禁混值域:§3.0 的对账约束②是「**超时永不产生 denial**」,而审计面唯一能证明这条
9
- * 的东西就是终态 + 归因的组合。人拒恒为 `DECIDED(decision=deny)`(车4 §8),路由失败恒为
10
- * `DENIED(routing_failure_fail_closed)`,撤卡/取消/遗孤恒为 `VOID(<VOID_REASONS 之一>)`——三者在
11
- * wire 与审计上全程可分。若两组共用一个值域(或某个值同时可落两种终态),「这条 deny 是人做的、还是
12
- * 路由失败、还是超时兜底」就再也无法从行上读出来,归因诚实性(§6.1)当场失效。
14
+ * ## 规则
15
+ * · 词表属主一处(本文件);加一个成因 = 加一个词:决断类词 ⇒ 409 码表(`http/resume-legs.ts` 的 `WIRE_CODE_OF_DECISION_REFUSAL`)的穷举编译红,结局类词 ⇒ 读盘词表 {@link SETTLE_CAUSE_WORDS} 的穷举编译红。
16
+ * · 成因**只从事实读**:run 行 `errorCode:"cancelled"`、钟到、emit 零接收、停车行 / 问账行的列、`detail.field`;不读拒句散文。
17
+ * · 「停驻行的格式比本引擎新」**不是**本表的词:它只有 core 一个来源(`checkpoint.unsupported_version` + `detail.reason:"version_newer"`,原码镜像);服务层不从续跑上下文的写者版本推断成因。
18
+ * · **人答的结局(`allowed` / `denied`)没有 cause**:`cause` 在场 ⇔ 非人结局。
19
+ * · 落列面:问账行的成因写进 `approval_ask.denied_reason`(VARCHAR(64)),一列按 `state` 承载两种意思 —— VOID / DENIED 行上是**终态成因**,PARKING / PARKED 行上是
20
+ * **转 park 的触发成因**;读它只经 {@link causeOfAskRow}(按 state 穷举分派,两张读词表只被它读)。列名将在下一个 DDL 窗按意义改名。
13
21
  *
14
- * 落列面:两组值都写进 `approval_ask.denied_reason`(VARCHAR(64))——列名沿用车1 的 DDL(本车不改列),
15
- * 但它承载的是**终态归因**而不只是「拒绝理由」。读者靠「值属于哪一组」判别是哪条臂写的,这正是两组
16
- * 值域不相交这条纪律的用处(`ALL_TERMINAL_REASONS` 的机器判据钉住不相交性)。
17
- *
18
- * 写点纪律(grep 钉):全仓 `DENIED` 只有**一个**写点 = 收敛器判据 2 的非取消臂(`approval-reconciler.ts`)。
19
- * 启动收敛(进程没了)落的是 VOID 不是 DENIED:状态机上 `STREAM_PENDING` 没有到 DENIED 的边(DENIED 只在「已路由去 park」之后),
20
- * 而承载它的进程没了 = 这条腿死了 = abort 语义。
22
+ * ## 为什么 `routing_failure_fail_closed` 也在表里(归因诚实性)
23
+ * 审计面唯一能证明「超时永不产生 denial」的东西是**终态 + 成因**的组合:人拒恒为 `DECIDED(decision=deny)`,路由失败恒为 `DENIED(routing_failure_fail_closed)`,
24
+ * 撤卡 / 取消 / 遗孤恒为 `VOID(<成因>)` —— 三者在 wire 与审计上全程可分。终态(问账行的 `state`)是判别位,成因词在同一张表里:一个成因只落一种终态
25
+ * (写点各自的类型固定了这一点:对账器的 `DENIED` 写点只收 {@link ReconcileDenyCause},`VOID` 写点只收 {@link ReconcileVoidCause})。
26
+ * 全仓 `DENIED` 只有**一个**写点 = 对账器判据 2 的非取消臂(`approval-reconciler.ts`);启动收敛(进程没了)落的是 VOID 不是 DENIED:状态机上
27
+ * `STREAM_PENDING` 没有到 DENIED 的边(DENIED 只在「已路由去 park」之后),而承载它的进程没了 = 这条腿死了 = abort 语义。
28
+ */
29
+ import type { AskState } from "./approval-ask-machine.js";
30
+ /**
31
+ * 审计 settle 行 `outcome` 的三词 —— **全仓唯一**的定义处(wire 契约锚:`tool_approval_complete.outcome` 与审计行同三词)。
32
+ * `allowed` / `denied` = 一次**决议**(人答,或行上读回来的决议);`expired` = 这次等待**没有等到决议就结束了**(非人结局,恒带 {@link ApprovalEndingCause})。
21
33
  */
22
- /** `DENIED` 终态的归因闭集(v1 单员)。 */
23
- export declare const DENY_REASONS: {
34
+ export declare const SETTLE_OUTCOME: {
35
+ readonly ALLOWED: "allowed";
36
+ readonly DENIED: "denied";
37
+ readonly EXPIRED: "expired";
38
+ };
39
+ export type SettleOutcome = (typeof SETTLE_OUTCOME)[keyof typeof SETTLE_OUTCOME];
40
+ /** 非人结局的**穷尽**成因(每个非人 `settle` 调用点必须点名一个)。成员的语义随成员。 */
41
+ export declare const APPROVAL_ENDING_CAUSES: {
42
+ /** 窗到期:这张卡的等待窗(行上一次铸定的绝对 deadline)走完,没有人答。 */
43
+ readonly TTL_EXPIRED: "ttl_expired";
44
+ /** emit 广播全灭:两族帧都没送到任何连接(emit 返回零接收)—— 这张卡从未送达任何人。 */
45
+ readonly BROADCAST_LOST: "broadcast_lost";
46
+ /** 承载这张卡的流没了:断连强转 park(`parkOpenAsksForTask`,断连臂的事实 = 这个 wire run 的承载流断了;卡曾送达过,只是不会再有人从那条流上答它)。 */
47
+ readonly STREAM_DISCONNECTED: "stream_disconnected";
24
48
  /**
25
- * 收敛器判据 2:出处 run 已到**非 suspended 的终局**(completed / blocked / 非取消的 failed)而始终
26
- * 没有出现与本 ask 匹配的 checkpoint ⇒ 这只 ask 的投递面永远不会再出现,fail-closed 收 `DENIED`。
27
- * 🔴 证据是**行的终局态**(journal 的肯定证据),不是「等够久了」——约束②因此成立。
49
+ * 这张卡呈不出去:兜底臂(行不可用 / 身份不定 / 悬挂无行,`unattendedOutcome` 的 `persisted_row_unusable` / `ensure_indeterminate` / `unreached_no_row`)—— 持久行证不出属主或读不成,
50
+ * 本次登记根本没把卡呈到任何投递面;同 askId 的其余本地登记按这个词随行收场。与 `broadcast_lost`(emit 零接收)是两个事实。
28
51
  */
29
- readonly ROUTING_FAILURE: "routing_failure_fail_closed";
30
- };
31
- export type DenyReason = (typeof DENY_REASONS)[keyof typeof DENY_REASONS];
32
- /** `VOID` 终态的归因闭集。 */
33
- export declare const VOID_REASONS: {
34
- /** 判据 2 的取消分臂(§9 C3):run `failed ∧ errorCode==="cancelled"` ⇒ 取消不是路由失败,收 VOID。 */
52
+ readonly UNDELIVERABLE: "undeliverable";
53
+ /** run 被取消:出处 run 的中止信号触发(取消动词 / 非 detach 断连),或对账器读到 run 行 `failed ∧ errorCode==="cancelled"`(判据 2 的取消分臂,§9 C3:取消不是路由失败,收 VOID)。 */
35
54
  readonly RUN_CANCELLED: "run_cancelled";
55
+ /** run 结束清理:承载这张卡的那条腿的作用域退出(正常返回或抛出)时卡还挂着。 */
56
+ readonly RUN_EXITED: "run_exited";
36
57
  /** 判据 2 的取消分臂之二:批行已 `ABORTED`(取消腿打过 `abortBatch`)⇒ 同 abort 语义。 */
37
58
  readonly BATCH_ABORTED: "batch_aborted";
59
+ /** 批内兄弟被赢家的原子撤卡连坐(窗到期赢家把同批其余 `STREAM_PENDING` 兄弟一起撤成 VOID)的「没了」。 */
60
+ readonly BATCH_SIBLING_WITHDRAWN: "batch_sibling_withdrawn";
38
61
  /**
39
- * bind-once 的**落选者**(codex 交叉复审 round2 R2-3,2026-08-06 验真):批已 `ROUTING_BOUND` 且
40
- * `bound_ask_id` 是**别人**——本行永远赢不了 `bindBatch`(谓词要求 `state='ROUTING_UNBOUND'`),
41
- * 却又不是路由失败(投递面好好的,只是这个决策点选了兄弟)。172 §3.0 的 bind-once 字面就是「非中选
42
- * 兄弟收敛 VOID」;`bindBatch` 的原子事务只连坐它当时看得见的 PARKING 兄弟,**之后**才进入 PARKING 的
43
- * 迟到行(late `ensureAsk` + `expireAsk` 不查批态,设计如此)由收敛器补判——就是这条臂。
44
- * 不这么判的后果:该行滞留到 run 终局,然后被判据 2 误报成 `routing_failure_fail_closed`(归因失真)。
62
+ * bind-once 的**落选者**(codex 交叉复审 round2 R2-3,2026-08-06 验真):批已 `ROUTING_BOUND` 且 `bound_ask_id` 是**别人**——本行永远赢不了 `bindBatch`
63
+ * (谓词要求 `state='ROUTING_UNBOUND'`),却又不是路由失败(投递面好好的,只是这个决策点选了兄弟)。172 §3.0 的 bind-once 字面就是「非中选兄弟收敛 VOID」;
64
+ * `bindBatch` 的原子事务只连坐它当时看得见的 PARKING 兄弟,**之后**才进入 PARKING 的迟到行由收敛器补判——就是这条臂。不这么判的后果:该行滞留到 run 终局,
65
+ * 然后被判据 2 误报成 `routing_failure_fail_closed`(归因失真)。
45
66
  */
46
67
  readonly BATCH_BOUND_ELSEWHERE: "batch_bound_elsewhere";
47
- /** 判据 4:adhoc 腿(`sessionId === taskId` ∧ `getRun` 无行)窗过 + 宽限 —— 该腿形**结构上**没有
48
- * durable 对账域(一次性 taskId 不进 runStore,park 目的地不可寻址),流死即 abort 语义。
49
- * 🔴 落 VOID 不落 DENIED:VOID 不是 denial,约束②不被触碰。 */
50
- readonly ADHOC_LEG_NO_DURABLE_DOMAIN: "adhoc_leg_no_durable_domain";
51
- /** 判据 5:任何在 `STREAM_APPROVAL_ORPHAN_TTL_MS` 内仍未收敛的 `PARKING` 行(量 immutable 的
52
- * `createdAtMs`,§9 C5)—— 约束①(遗孤最终可判)的最后兜底,带一条 warn。 */
68
+ /**
69
+ * 收敛器判据 2:出处 run 已到**非 suspended 的终局**(completed / blocked / 非取消的 failed)而始终没有出现与本 ask 匹配的 checkpoint ⇒ 这只 ask 的投递面永远不会再出现,
70
+ * fail-closed 收 `DENIED`。🔴 证据是**行的终局态**(journal 的肯定证据),不是「等够久了」——「超时永不产生 denial」因此成立。
71
+ */
72
+ readonly ROUTING_FAILURE: "routing_failure_fail_closed";
73
+ /**
74
+ * 启动收敛(`approval-crash-convergence.ts`,local 车道):**承载这一问的进程没了** —— 前世进程崩溃 / 被杀,留下的 `STREAM_PENDING` 行在下一次启动、任何卡能开始之前收成 VOID。
75
+ * 证据是数据根 `LOCK`(前世写者全死),不是「等够久了」。值与 `GET /v1/approvals` `crashConverged[].cause` 是**同一个词**。
76
+ */
77
+ readonly CRASHED_BEFORE_PARK: "crashed_before_park";
78
+ /** 同上,但承载它的腿是被**有序关停**截断的(审计账本上有本腿的关停记号;关停收尾没能在宽限窗内把这张卡结清),或有序关停期间这张卡被改路停驻(settle 行的成因)。 */
79
+ readonly SHUTDOWN_BEFORE_PARK: "shutdown_before_park";
80
+ /** 判据 5:任何在 `STREAM_APPROVAL_ORPHAN_TTL_MS` 内仍未收敛的 `PARKING` 行(量 immutable 的 `createdAtMs`,§9 C5)—— 遗孤最终可判的最后兜底,带一条 warn。 */
53
81
  readonly ORPHAN_TTL_EXCEEDED: "orphan_ttl_exceeded";
54
82
  /**
55
- * 启动收敛(`approval-crash-convergence.ts`,local 车道):**承载这一问的进程没了** —— 前世进程崩溃 / 被杀,留下的
56
- * `STREAM_PENDING` 行在下一次启动、任何卡能开始之前收成 VOID。证据是数据根 `LOCK`(前世写者全死),不是「等够久了」。
57
- * 🔴 值与 `GET /v1/approvals` `crashConverged[].cause` 是**同一个词**(审计店的 `CrashConvergedCause` 从本表取型):
58
- * 同一问在两个读面上只有一个成因词。
83
+ * 判据 4:adhoc 腿(`sessionId === taskId` ∧ `getRun` 无行)窗过 + 宽限 —— 该腿形**结构上**没有 durable 对账域(一次性 taskId 不进 runStore,park 目的地不可寻址),流死即 abort 语义。
84
+ * 🔴 落 VOID 不落 DENIED:VOID 不是 denial,「超时永不产生 denial」不被触碰。
59
85
  */
60
- readonly HOLDER_CRASHED: "crashed_before_park";
61
- /** 同上,但承载它的腿是被**有序关停**截断的(审计账本上有本腿的关停记号;关停收尾没能在宽限窗内把这张卡结清)。 */
62
- readonly HOLDER_SHUTDOWN: "shutdown_before_park";
86
+ readonly ADHOC_LEG_NO_DURABLE_DOMAIN: "adhoc_leg_no_durable_domain";
87
+ /** 决议绑的动作 / 输入被换了(core `detail.field` ∈ `boundCallId` / `boundInputHash`,不论写者版本 —— 绑定值由写者引擎铸进卡帧、调用方原样回传、core 等值比较):重取 pending 列表换坐标再决。 */
88
+ readonly BINDING_CHANGED: "binding_changed";
89
+ /** 决议体里的 `answer` 不合法(core `detail.field` = `answer`):把答案改对再决。 */
90
+ readonly ANSWER_INVALID: "answer_invalid";
91
+ /** 决议体里某一格不合法(`hostDecision` / `approver` / `boundGrantDigest`,或 plan / dry-run 审阅门上 editedPlan / reason 的内容净化被拒):把那一格改对再决。 */
92
+ readonly OUTCOME_CELL_INVALID: "outcome_cell_invalid";
63
93
  };
64
- export type VoidReason = (typeof VOID_REASONS)[keyof typeof VOID_REASONS];
65
- /** 「承载这一问的进程没了」的两个成因词(启动收敛写进问账行 `deniedReason`,审计收敛行 `cause` 读同一型)。 */
66
- export type HolderGoneReason = typeof VOID_REASONS.HOLDER_CRASHED | typeof VOID_REASONS.HOLDER_SHUTDOWN;
67
- /** 两组的并集(机器判据用:值域不相交 + 列宽 ≤ 64 的钉子在 test/approval-reconciler.test.ts)。 */
68
- export declare const ALL_TERMINAL_REASONS: readonly string[];
94
+ export type ApprovalEndingCause = (typeof APPROVAL_ENDING_CAUSES)[keyof typeof APPROVAL_ENDING_CAUSES];
95
+ /** 全部成因词(机器判据用:值域互不相同 + 列宽 ≤ 64 的钉子在 test/approval-reconciler.test.ts)。 */
96
+ export declare const ALL_APPROVAL_ENDING_CAUSES: readonly ApprovalEndingCause[];
97
+ /**
98
+ * 审计 settle 行 `cause` 的读盘词表(S-929 形):{@link SettleCause} 的穷举,`satisfies Record<SettleCause, true>` ⇒ 闭集加一个非决断类的词而本表没跟 = **编译红**(多一个词同样红);
99
+ * 判定走 `Object.hasOwn`,原型链上的名字不算词。决断类三词({@link DecisionRefusalCause})拒一次决议、不结束一张卡 ⇒ 不是 settle 行的成因,读到它们照词表外处置(计坏行)。
100
+ * 唯一读者 = {@link isSettleCause}。
101
+ */
102
+ export declare const SETTLE_CAUSE_WORDS: {
103
+ readonly ttl_expired: true;
104
+ readonly broadcast_lost: true;
105
+ readonly stream_disconnected: true;
106
+ readonly undeliverable: true;
107
+ readonly run_cancelled: true;
108
+ readonly run_exited: true;
109
+ readonly batch_aborted: true;
110
+ readonly batch_sibling_withdrawn: true;
111
+ readonly batch_bound_elsewhere: true;
112
+ readonly routing_failure_fail_closed: true;
113
+ readonly crashed_before_park: true;
114
+ readonly shutdown_before_park: true;
115
+ readonly orphan_ttl_exceeded: true;
116
+ readonly adhoc_leg_no_durable_domain: true;
117
+ };
118
+ /** 一个值是不是非人结局的成因词(审计账读盘用这一只判词)。 */
119
+ export declare function isSettleCause(v: unknown): v is SettleCause;
120
+ /** 「承载这一问的进程没了」的两个成因词(启动收敛写进问账行 `denied_reason`,审计收敛行 `cause` 读同一型)。 */
121
+ export type HolderGoneCause = typeof APPROVAL_ENDING_CAUSES.CRASHED_BEFORE_PARK | typeof APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK;
122
+ /** 对账器 `DENIED` 终态的成因(单员:全仓 DENIED 只有一个写点)。 */
123
+ export type ReconcileDenyCause = typeof APPROVAL_ENDING_CAUSES.ROUTING_FAILURE;
124
+ /** 对账器 `VOID` 终态的成因(判据 2 取消分臂 / 批已 ABORTED / bind-once 落选者 / 判据 4 adhoc / 判据 5 遗孤 TTL)。 */
125
+ export type ReconcileVoidCause = typeof APPROVAL_ENDING_CAUSES.RUN_CANCELLED | typeof APPROVAL_ENDING_CAUSES.BATCH_ABORTED | typeof APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE | typeof APPROVAL_ENDING_CAUSES.ADHOC_LEG_NO_DURABLE_DOMAIN | typeof APPROVAL_ENDING_CAUSES.ORPHAN_TTL_EXCEEDED;
126
+ /**
127
+ * 问账行(`approval_ask`)上**终态成因**能落的词(决断口 410 `gone.ask` 体与晚答 404 体的 `cause` 的值域):对账器的 DENIED / VOID 写点 + 启动收敛 + 店内的 VOID 写点
128
+ * (取消赢下 VOID ⇒ `run_cancelled` / `run_exited`,兄弟被撤卡 ⇒ `batch_sibling_withdrawn`)。窗到期 / 广播全灭 / 决断类的词只落审计 settle 行或 409 码,不落这一列。
129
+ */
130
+ 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}。 */
132
+ export declare const ASK_ROW_ENDING_CAUSE_WORDS: {
133
+ readonly routing_failure_fail_closed: true;
134
+ readonly run_cancelled: true;
135
+ readonly run_exited: true;
136
+ readonly batch_aborted: true;
137
+ readonly batch_sibling_withdrawn: true;
138
+ readonly batch_bound_elsewhere: true;
139
+ readonly adhoc_leg_no_durable_domain: true;
140
+ readonly orphan_ttl_exceeded: true;
141
+ readonly crashed_before_park: true;
142
+ readonly shutdown_before_park: true;
143
+ };
144
+ /**
145
+ * `expire` 终局 claim(窗到期 / 广播全灭 / 有序关停把一张卡转投递面 = `PARKING`)赢下时写进这一行 `denied_reason` 的**触发成因**:这一行此后若走到 `DENIED` / `VOID`,终态成因会覆盖它
146
+ * ({@link AskRowEndingCause});`PARKED` 行的这一格就是「它为什么被转 park」。跨副本的读者(另一进程的悬挂腿轮询、CAS 败方读数)读到 `PARKING` / `PARKED` 时靠它归因,
147
+ * 不替没写的行猜一个词。
148
+ */
149
+ 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}。 */
151
+ export declare const PARK_TRIGGER_CAUSE_WORDS: {
152
+ readonly ttl_expired: true;
153
+ readonly broadcast_lost: true;
154
+ readonly stream_disconnected: true;
155
+ readonly undeliverable: true;
156
+ readonly shutdown_before_park: true;
157
+ };
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
+ /**
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`(读面缺席即缺席,不替没写的行编一个成因)。返回纯数据。
172
+ */
173
+ export declare function causeOfAskRow(row: {
174
+ readonly state: AskState | "absent";
175
+ readonly deniedReason?: string | null;
176
+ }): AskRowCause | undefined;
177
+ /** 非人**结局**的成因(审计 settle 行 `cause`、活卡结局帧 `tool_approval_complete.cause`):闭集去掉决断口 409 的三个词({@link DecisionRefusalCause})—— 那三个词不结束一张卡,只拒一次决议。 */
178
+ export type SettleCause = Exclude<ApprovalEndingCause, DecisionRefusalCause>;
179
+ /** 决断口 409 的成因子集(core 的 `detail.field` → 码;每词一个码,见 `http/resume-legs.ts`)。 */
180
+ export type DecisionRefusalCause = typeof APPROVAL_ENDING_CAUSES.BINDING_CHANGED | typeof APPROVAL_ENDING_CAUSES.ANSWER_INVALID | typeof APPROVAL_ENDING_CAUSES.OUTCOME_CELL_INVALID;
69
181
  //# sourceMappingURL=approval-deny-reasons.d.ts.map
@@ -1,14 +1,85 @@
1
- export const DENY_REASONS = {
2
- ROUTING_FAILURE: "routing_failure_fail_closed",
3
- };
4
- export const VOID_REASONS = {
1
+ export const SETTLE_OUTCOME = { ALLOWED: "allowed", DENIED: "denied", EXPIRED: "expired" };
2
+ export const APPROVAL_ENDING_CAUSES = {
3
+ TTL_EXPIRED: "ttl_expired",
4
+ BROADCAST_LOST: "broadcast_lost",
5
+ STREAM_DISCONNECTED: "stream_disconnected",
6
+ UNDELIVERABLE: "undeliverable",
5
7
  RUN_CANCELLED: "run_cancelled",
8
+ RUN_EXITED: "run_exited",
6
9
  BATCH_ABORTED: "batch_aborted",
10
+ BATCH_SIBLING_WITHDRAWN: "batch_sibling_withdrawn",
7
11
  BATCH_BOUND_ELSEWHERE: "batch_bound_elsewhere",
8
- ADHOC_LEG_NO_DURABLE_DOMAIN: "adhoc_leg_no_durable_domain",
12
+ ROUTING_FAILURE: "routing_failure_fail_closed",
13
+ CRASHED_BEFORE_PARK: "crashed_before_park",
14
+ SHUTDOWN_BEFORE_PARK: "shutdown_before_park",
9
15
  ORPHAN_TTL_EXCEEDED: "orphan_ttl_exceeded",
10
- HOLDER_CRASHED: "crashed_before_park",
11
- HOLDER_SHUTDOWN: "shutdown_before_park",
16
+ ADHOC_LEG_NO_DURABLE_DOMAIN: "adhoc_leg_no_durable_domain",
17
+ BINDING_CHANGED: "binding_changed",
18
+ ANSWER_INVALID: "answer_invalid",
19
+ OUTCOME_CELL_INVALID: "outcome_cell_invalid",
20
+ };
21
+ export const ALL_APPROVAL_ENDING_CAUSES = Object.values(APPROVAL_ENDING_CAUSES);
22
+ export const SETTLE_CAUSE_WORDS = {
23
+ ttl_expired: true,
24
+ broadcast_lost: true,
25
+ stream_disconnected: true,
26
+ undeliverable: true,
27
+ run_cancelled: true,
28
+ run_exited: true,
29
+ batch_aborted: true,
30
+ batch_sibling_withdrawn: true,
31
+ batch_bound_elsewhere: true,
32
+ routing_failure_fail_closed: true,
33
+ crashed_before_park: true,
34
+ shutdown_before_park: true,
35
+ orphan_ttl_exceeded: true,
36
+ adhoc_leg_no_durable_domain: true,
37
+ };
38
+ export function isSettleCause(v) {
39
+ return typeof v === "string" && Object.hasOwn(SETTLE_CAUSE_WORDS, v);
40
+ }
41
+ export const ASK_ROW_ENDING_CAUSE_WORDS = {
42
+ routing_failure_fail_closed: true,
43
+ run_cancelled: true,
44
+ run_exited: true,
45
+ batch_aborted: true,
46
+ batch_sibling_withdrawn: true,
47
+ batch_bound_elsewhere: true,
48
+ adhoc_leg_no_durable_domain: true,
49
+ orphan_ttl_exceeded: true,
50
+ crashed_before_park: true,
51
+ shutdown_before_park: true,
52
+ };
53
+ function isAskRowEndingCause(v) {
54
+ return Object.hasOwn(ASK_ROW_ENDING_CAUSE_WORDS, v);
55
+ }
56
+ export const PARK_TRIGGER_CAUSE_WORDS = {
57
+ ttl_expired: true,
58
+ broadcast_lost: true,
59
+ stream_disconnected: true,
60
+ undeliverable: true,
61
+ shutdown_before_park: true,
12
62
  };
13
- export const ALL_TERMINAL_REASONS = [...Object.values(DENY_REASONS), ...Object.values(VOID_REASONS)];
63
+ function isParkTriggerCause(v) {
64
+ return Object.hasOwn(PARK_TRIGGER_CAUSE_WORDS, v);
65
+ }
66
+ export function causeOfAskRow(row) {
67
+ const stored = row.deniedReason;
68
+ switch (row.state) {
69
+ case "VOID":
70
+ case "DENIED":
71
+ return stored !== null && stored !== undefined && isAskRowEndingCause(stored) ? { kind: "ending", cause: stored } : undefined;
72
+ case "PARKING":
73
+ case "PARKED":
74
+ return stored !== null && stored !== undefined && isParkTriggerCause(stored) ? { kind: "park_trigger", cause: stored } : undefined;
75
+ case "DECIDED":
76
+ case "STREAM_PENDING":
77
+ case "absent":
78
+ return undefined;
79
+ default: {
80
+ const unhandled = row.state;
81
+ throw new Error(`causeOfAskRow: unhandled ask row state ${String(unhandled)}`);
82
+ }
83
+ }
84
+ }
14
85
  //# sourceMappingURL=approval-deny-reasons.js.map
@@ -23,9 +23,12 @@
23
23
  */
24
24
  import type { ApprovalAskStore, AskRow, AskTerminalClaimOutcome } from "./plugins/approval-ask-store-sql.js";
25
25
  import type { UnattendedApprovalPolicy } from "./tool-approval.js";
26
+ import type { ParkTriggerCause } from "./approval-deny-reasons.js";
26
27
  /**
27
28
  * 按部署政策给这一只 ask 行打原子终局 claim(见文件头:四个调用点、两个意图、答案自足)。`row` 只取 scope 两维 ——
28
29
  * `batchId` 必须是**行上**的批(幂等命中的存量行可能是别的政策 / 旧版本铸的整腿批,错 scope 的 claim 如实 `absent`)。
29
30
  */
30
- export declare function settleByPolicy(store: Pick<ApprovalAskStore, "claimTerminal">, row: Pick<AskRow, "askId" | "batchId">, policy: UnattendedApprovalPolicy): Promise<AskTerminalClaimOutcome>;
31
+ export declare function settleByPolicy(store: Pick<ApprovalAskStore, "claimTerminal">, row: Pick<AskRow, "askId" | "batchId">, policy: UnattendedApprovalPolicy,
32
+ /** 转 park 的触发成因(窗到期 / 广播全灭 / 有序关停):`park` 政策的 `expire` 赢下 PARKING 时随同一步写进行(`denied_reason`),跨副本的读者据它归因;`deny` 政策落的是一条决议,不带。 */
33
+ cause: ParkTriggerCause): Promise<AskTerminalClaimOutcome>;
31
34
  //# sourceMappingURL=approval-policy-settlement.d.ts.map
@@ -10,7 +10,8 @@ function unattendedClaimIntent(policy) {
10
10
  }
11
11
  }
12
12
  }
13
- export function settleByPolicy(store, row, policy) {
14
- return store.claimTerminal(row.askId, row.batchId, unattendedClaimIntent(policy));
13
+ export function settleByPolicy(store, row, policy, cause) {
14
+ const intent = unattendedClaimIntent(policy);
15
+ return store.claimTerminal(row.askId, row.batchId, intent, intent === "expire" ? { cause } : undefined);
15
16
  }
16
17
  //# sourceMappingURL=approval-policy-settlement.js.map
@@ -5,7 +5,7 @@ import type { CheckpointAskCandidate } from "./plugins/checkpoint-store-sql.js";
5
5
  import { type RunRecord } from "./plugins/store-contracts.js";
6
6
  import type { Logger } from "./observability/logger.js";
7
7
  import type { Metrics } from "./observability/metrics.js";
8
- import { type DenyReason, type VoidReason } from "./approval-deny-reasons.js";
8
+ import { type ReconcileDenyCause, type ReconcileVoidCause } from "./approval-deny-reasons.js";
9
9
  import { type ApprovalRevokeFrame } from "./approval-card.js";
10
10
  import type { ParkCommitWatch } from "./plugins/park-commit-watch.js";
11
11
  /** 判据表 v2 的一次判定结果(纯数据;执行器按 kind 分派到店面的一次 CAS)。 */
@@ -18,10 +18,10 @@ export type ReconcileAction = {
18
18
  };
19
19
  } | {
20
20
  kind: "deny";
21
- reason: DenyReason;
21
+ reason: ReconcileDenyCause;
22
22
  } | {
23
23
  kind: "void";
24
- reason: VoidReason;
24
+ reason: ReconcileVoidCause;
25
25
  } | {
26
26
  kind: "hold";
27
27
  };
@@ -1,7 +1,7 @@
1
1
  import { CANCELLED_CODE } from "./run-cancel-context.js";
2
2
  import { settleByPolicy } from "./approval-policy-settlement.js";
3
3
  import { isTerminalRunStatus } from "./plugins/store-contracts.js";
4
- import { DENY_REASONS, VOID_REASONS } from "./approval-deny-reasons.js";
4
+ import { APPROVAL_ENDING_CAUSES } from "./approval-deny-reasons.js";
5
5
  import { buildRevokeFrame } from "./approval-card.js";
6
6
  import { encodeCheckpointScope } from "./security.js";
7
7
  const RECONCILE_STORE_TIMEOUT_MS = 10_000;
@@ -61,21 +61,21 @@ export function decideReconcileAction(input) {
61
61
  }
62
62
  }
63
63
  if (batchState === "ABORTED")
64
- return { kind: "void", reason: VOID_REASONS.BATCH_ABORTED };
64
+ return { kind: "void", reason: APPROVAL_ENDING_CAUSES.BATCH_ABORTED };
65
65
  if (batchState === "ROUTING_BOUND" && input.batchBoundAskId != null && input.batchBoundAskId !== ask.askId) {
66
- return { kind: "void", reason: VOID_REASONS.BATCH_BOUND_ELSEWHERE };
66
+ return { kind: "void", reason: APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE };
67
67
  }
68
68
  if (run !== null && isTerminalRunStatus(run.status)) {
69
69
  if (run.status === "failed" && run.errorCode === CANCELLED_CODE) {
70
- return { kind: "void", reason: VOID_REASONS.RUN_CANCELLED };
70
+ return { kind: "void", reason: APPROVAL_ENDING_CAUSES.RUN_CANCELLED };
71
71
  }
72
- return { kind: "deny", reason: DENY_REASONS.ROUTING_FAILURE };
72
+ return { kind: "deny", reason: APPROVAL_ENDING_CAUSES.ROUTING_FAILURE };
73
73
  }
74
74
  if (run === null && ask.sessionId === ask.taskId && nowMs > ask.expiresAtMs + adhocGraceMs) {
75
- return { kind: "void", reason: VOID_REASONS.ADHOC_LEG_NO_DURABLE_DOMAIN };
75
+ return { kind: "void", reason: APPROVAL_ENDING_CAUSES.ADHOC_LEG_NO_DURABLE_DOMAIN };
76
76
  }
77
77
  if (nowMs - ask.createdAtMs >= orphanTtlMs)
78
- return { kind: "void", reason: VOID_REASONS.ORPHAN_TTL_EXCEEDED };
78
+ return { kind: "void", reason: APPROVAL_ENDING_CAUSES.ORPHAN_TTL_EXCEEDED };
79
79
  void candidates;
80
80
  return { kind: "hold" };
81
81
  }
@@ -157,10 +157,10 @@ export function createApprovalReconciler(deps) {
157
157
  const won = await withDeadline(askStore.transitionAsk(ask.askId, "PARKING", "VOID", { deniedReason: action.reason, updatedAtMs: nowMs }), "transitionAsk(VOID)");
158
158
  if (!won)
159
159
  return { outcome: "held", match: gateMatch };
160
- if (action.reason === VOID_REASONS.ORPHAN_TTL_EXCEEDED) {
160
+ if (action.reason === APPROVAL_ENDING_CAUSES.ORPHAN_TTL_EXCEEDED) {
161
161
  warn("approval_ask_orphan_voided", { askId: ask.askId, taskId: ask.taskId, ageMs: nowMs - ask.createdAtMs, ttlMs: orphanTtlMs });
162
162
  }
163
- revoke(ask, [ask.askId], action.reason === VOID_REASONS.BATCH_BOUND_ELSEWHERE ? "superseded_by_park" : "aborted", nowMs);
163
+ revoke(ask, [ask.askId], action.reason === APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE ? "superseded_by_park" : "aborted", nowMs);
164
164
  return { outcome: "voided", match: gateMatch };
165
165
  }
166
166
  case "hold": {
@@ -274,7 +274,7 @@ export function createApprovalReconciler(deps) {
274
274
  if (row.expiresAtMs + pendingGraceMs >= nowMs)
275
275
  continue;
276
276
  try {
277
- const res = await withDeadline(settleByPolicy(askStore, row, unattendedPolicy), "claimTerminal(orphan)");
277
+ const res = await withDeadline(settleByPolicy(askStore, row, unattendedPolicy, APPROVAL_ENDING_CAUSES.TTL_EXPIRED), "claimTerminal(orphan)");
278
278
  if (!res.claimed)
279
279
  continue;
280
280
  stats.orphansExpired += 1;
@@ -58,7 +58,7 @@ export declare function singleUserAutoAcceptBaselineApplies(config: GateIntentVi
58
58
  * HIGH;与 env 墓碑族同谱系)。v4.3.0 的轮询门形(APPROVAL_REQUIRE + DB backend + DURABLE_APPROVAL 未设)在
59
59
  * 5.0.0 只剩 core 每任务 warn——**原先拦、现在放**(方向反转)。「留 undefined 让 core warn」对新配置是
60
60
  * 曝露误配,对升级存量是门静默消失,后者必须响亮。
61
- * · **热路径**(S-668 波 2 ④,`config-center/apply-effective.ts` 的审批组 stage):对**候选视图**
61
+ * · **热路径**(S-668 波 2 ④,`web-admin-client/apply-effective.ts` 的审批组 stage):对**候选视图**
62
62
  * (活配置 + 候选名单)判,拒因 ⇒ 审批组**整组拒**、活配置零变化、世代账停旧代。进程在跑,拒启不是选项;
63
63
  * 而放行一份「没有 checkpoint 店可停车」的名单 = 单用户 auto-accept 基线仍在、名单静默不生效。
64
64
  * · **每个任务的装配**(S-1144,`boot/resolve-spec.ts` 的 `resolveSpec` 头一道):对**活配置**判,拒因 ⇒ 请求 501
package/dist/approval.js CHANGED
@@ -58,7 +58,7 @@ export function buildForceDurableGateInertNotice(input) {
58
58
  event: "center_force_durable_gate_inert",
59
59
  fields: {
60
60
  durableApproval: input.durableApproval,
61
- note: "this deployment resolves per-principal entitlements from config-center, but it has NO checkpoint store " +
61
+ note: "this deployment resolves per-principal entitlements from web-admin, but it has NO checkpoint store " +
62
62
  (input.durableApproval
63
63
  ? "(DURABLE_APPROVAL=true, yet the configured backend exposes no checkpoint face)"
64
64
  : "(DURABLE_APPROVAL is off)") +
package/dist/audit.d.ts CHANGED
@@ -48,7 +48,7 @@ export declare function buildSessionAuditLocal(root: string, sessionId: string):
48
48
  * 🔴 **会话视图的读面口 —— `GET /v1/sessions/:id` 的全部取法(整读 / `?tail` / `?before` / `?blobCap` / `?message=N`)共用这一只**。
49
49
  * 次序 = **选 → 脱 → 截**(路由 `http/routes/sessions.ts`):整条消息的选窗不切字符串,先选窗不改任何一条的脱敏读数、只把脱敏的量降到选中的那几条;
50
50
  * `blobCap` 的截断会切字符串(预览切在密钥中间,半截密钥认不出来)⇒ 截断恒在脱敏之后。
51
- * `messages` 缺席 ⇒ 不铸键;非数组 ⇒ 深扫;其余键(`owner` / `promptEpoch` / `model` …)逐字。字节没变 ⇒ **同一引用**。
51
+ * `messages` 缺席 ⇒ 不铸键;非数组 ⇒ 深扫;其余键(`owner` / `promptEpoch` / `model` …)逐字。每条消息都回同一引用 ⇒ **同一引用**(带结构叶的消息恒重建,见 {@link readByTable})。
52
52
  */
53
53
  export declare function redactSessionViewForRead<V extends object>(view: V): V;
54
54
  /** Window `messages` — `tail=N` (last N) or `before=<offset>&limit=N` ([max(0,before-N), before)).