@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 { computeEntryRev, MEMORY_ORIGIN_CAUSES, nextSyncBaseline, reconcileMemoryEntries, screenInboundEntries, } from "@sema-agent/core";
2
2
  import { exportMemoryScope } from "./memory-export.js";
3
- import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
3
+ import { declaredMemorySyncIdentityFlaw } from "./memory-sync-identity.js";
4
+ const MEMORY_SYNC_INCARNATION_MAX = 128;
4
5
  export const MEMORY_SYNC_MAX_ENTRIES = 500;
5
6
  export const MEMORY_SYNC_MAX_DELETES = 2000;
6
7
  export function parseMemorySyncRequest(body, scope) {
@@ -8,8 +9,15 @@ export function parseMemorySyncRequest(body, scope) {
8
9
  return { ok: false, error: "body must be a JSON object { peer, baseRevs, entries, deletes }" };
9
10
  const b = body;
10
11
  const peer = b["peer"];
11
- if (typeof peer !== "string" || peer.length === 0 || peer.length > MEMORY_SYNC_PEER_COLUMN_CHARS)
12
- return { ok: false, error: `peer must be a non-empty string of at most ${MEMORY_SYNC_PEER_COLUMN_CHARS} characters` };
12
+ if (typeof peer !== "string")
13
+ return { ok: false, error: "peer must be a string — the sync identity the client reaches this center at" };
14
+ const peerFlaw = declaredMemorySyncIdentityFlaw(peer);
15
+ if (peerFlaw !== undefined)
16
+ return { ok: false, peerFlaw };
17
+ const rawClaim = b["peerIncarnation"];
18
+ if (rawClaim !== undefined && (typeof rawClaim !== "string" || rawClaim.length === 0 || rawClaim.length > MEMORY_SYNC_INCARNATION_MAX)) {
19
+ return { ok: false, error: `peerIncarnation must be a non-empty string of at most ${MEMORY_SYNC_INCARNATION_MAX} characters when present — the incarnation this center answered the cursor's last round with, carried back verbatim` };
20
+ }
13
21
  const rawBase = b["baseRevs"];
14
22
  if (rawBase === null || typeof rawBase !== "object" || Array.isArray(rawBase))
15
23
  return { ok: false, error: "baseRevs must be an object mapping entry id → rev (may be empty on the first round)" };
@@ -150,7 +158,7 @@ export function parseMemorySyncRequest(body, scope) {
150
158
  }
151
159
  pull = { limit: lim };
152
160
  }
153
- return { ok: true, value: { peer, baseRevs, entries, deletes, ...(pull !== undefined ? { pull } : {}) } };
161
+ return { ok: true, value: { peer, ...(rawClaim !== undefined ? { peerIncarnation: rawClaim } : {}), baseRevs, entries, deletes, ...(pull !== undefined ? { pull } : {}) } };
154
162
  }
155
163
  function baselineStub(id, rev, scope, slug) {
156
164
  return { id, rev, scope, slug: slug ?? `sync-baseline-stub/${id}`, frontmatter: {}, body: "" };
@@ -159,7 +167,19 @@ function malformedEntryReason(err) {
159
167
  const msg = err instanceof Error ? err.message : String(err);
160
168
  return `malformed_entry: core cannot read this entry's frontmatter shape (${msg.slice(0, 200)}) — the entry is refused and never stored; fix the frontmatter (a present \`distilled\` block needs planId/at/carrierRev/inputs[]) and resend`;
161
169
  }
162
- export async function performMemorySync(backend, cursors, scope, req, now = Date.now) {
170
+ function servesClaim(req, incarnation) {
171
+ return req.peerIncarnation !== undefined ? req.peerIncarnation === incarnation : Object.keys(req.baseRevs).length === 0;
172
+ }
173
+ export class MemorySyncPartitionReset extends Error {
174
+ constructor(scope) {
175
+ super(`memory sync: the partition of scope ${JSON.stringify(scope)} was reset during this round (its incarnation changed between the comparison and the last write) — no result is answered for a state that is no longer the one compared`);
176
+ this.name = "MemorySyncPartitionReset";
177
+ }
178
+ }
179
+ export async function performMemorySync(backend, sync, scope, req, now = Date.now) {
180
+ const incarnation = await sync.partitionIncarnation(scope);
181
+ if (!servesClaim(req, incarnation))
182
+ return { refused: "baseline_incarnation", incarnation };
163
183
  const conflicts = [];
164
184
  const rejected = new Map();
165
185
  const malformed = new Set();
@@ -255,13 +275,15 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
255
275
  }
256
276
  }
257
277
  const updatedAtMs = now();
258
- await cursors.putCursor({ scope, peer: req.peer, baseRevs: nextBase, updatedAtMs });
278
+ await sync.putCursor({ scope, peer: req.peer, baseRevs: nextBase, updatedAtMs });
279
+ if ((await sync.partitionIncarnation(scope)) !== incarnation)
280
+ throw new MemorySyncPartitionReset(scope);
259
281
  return {
260
282
  applied: report.applied,
261
283
  conflicts,
262
284
  serverEntries,
263
285
  serverDeletes: plan.deleteRemote,
264
- cursor: { peer: req.peer, baseRevs: nextBase, updatedAtMs },
286
+ cursor: { peer: req.peer, incarnation, baseRevs: nextBase, updatedAtMs },
265
287
  ...(pullTruncated ? { pullTruncated: true } : {}),
266
288
  };
267
289
  }
@@ -5,24 +5,28 @@
5
5
  * 判官的输入是服务进程的事实:采纳之后 `config.model` 是不是还是那只 env 模型(对象同一性)、anthropic 路由装没装(凭据在场性)、名册布防(env 表是占位)。
6
6
  * 壳拼不出这些,只能镜像两道判(目录条目逐条合形、车道 / 端点可解析)—— 镜像会随时间与真判官漂开(同一问两份读法)。所以结论只有 server 说,壳读它。
7
7
  *
8
- * ## 四个字段,两根轴
8
+ * ## 五个字段,两根轴
9
9
  * · `servedTable` —— **谁在服**:{@link servedModelTableLeg}(此刻服务 env 表还是目录表;同一只读法,不另记旗子)。
10
- * · `catalog` —— **最近一份目录候选的结局**({@link CATALOG_STANCES}):
11
- * `refused` —— 候选被整拒:候选判官整拒(`candidateRejectionState` 座),或模型面组拒(`modelRefusalsState().plane` 在场;各码见
12
- * settings-schema `MODEL_PLANE_REFUSAL_CODES`)。没有 LKG 时服务的是 env 表,有 LKG(或上一代已采纳)时仍服目录表 —— 谁在服归 `servedTable` 答,
10
+ * · `catalog` —— **最新到达的那份目录候选的结局**({@link CATALOG_STANCES}):
11
+ * `refused` —— 候选被整拒:候选判官整拒(`candidateRejectionState` 座),或模型面组拒(`latestPlaneRefusalState()` 在场 —— 落下组拒信号的那次 apply
12
+ * 就是最新一次到达;各码见 settings-schema `MODEL_PLANE_REFUSAL_CODES`)。没有 LKG 时服务的是 env 表,有 LKG(或上一代已采纳)时仍服目录表 —— 谁在服归 `servedTable` 答,
13
13
  * 不在这一格里合并(合并 = 一个字段两个意思);逐条被拒(条目不可用,`entries`)不是整拒,那些条目之外的目录照服;
14
14
  * `adopted` —— 没有整拒、且服务的是目录表;
15
15
  * `absent` —— 没有整拒、且服务的是 env 表:没配目录源 / 配了但没有启用条目(候选不管模型面,不是拒)/ 纯 env 部署。
16
16
  * 两个座(候选判官的结论座与模型面组拒的信号座)是候选在两道判官各自留下的事实,本格只读它们,不另算第二份。
17
17
  * **不在内**:候选过了两道判官、却在装配期被引擎整份拒收(`applyEffective` 里引擎拒收 `apiKeyEnv` 声明 / Runner 拒收换装;活配置零触碰,只有日志与 `/health` 世代账的 target≠applied)——
18
18
  * 这一形今天没有座,`catalog` 仍按上一代读;`adopted` 说的是「没有被判官整拒」,不是「最新候选已落地」(那一问归世代账)。要覆盖它得先给装配期拒收立一个座(另案设计)。
19
+ * · `planeRefusalCode` —— `catalog:"refused"` 的模型面那一半的**码**(与上一格同取 `latestPlaneRefusal` 一份事实,不另算):在场 ⇔ 最新到达的目录候选被模型面组拒。
20
+ * `catalog:"refused"` 而本键缺席 ⇔ 最新到达的候选被候选判官整拒(看 `candidateRejected`)。与 `/health.modelRefusals.plane` 分两个主语:那一键说「最近一份**被 stage 判过**的
21
+ * 模型面的组拒判决」,不随更新的到达撤;本键随每一次更新的到达撤(更新的候选过了门、却在 apply 落账之前失败 ⇒ 本键缺席,那份失败归世代账)。
22
+ * 同一只路由判官的码可能同时出现在本键与 `refusalCode`:本键指最新到达的候选目录,`refusalCode` 指此刻在服的那张表。
19
23
  * · `refusalCode` / `gap` —— **起服路由判官对当前 config 的判决**:`judgeServedModelRoute` 同一只函数(装配第 2 段与 `run-local` 调的就是它),通过 ⇒ 两键缺席。
20
24
  * 进程既然起来了,正常是缺席;测试夹具 / 自建装配 / 不拒启的入口可能在场 —— 如实投,不替它们编造通过。**不带句子**(拒因句只走日志与起服错误)。
21
25
  *
22
26
  * ## 只在起服后可答
23
27
  * 读面挂在运行中的进程上;判官必须在目录应用之后调(`judgeServedModelRoute` 头注),起服前没有「将要服务的表」,所以没有起服前的预判口。
24
28
  */
25
- import type { ModelRefusalsSignal } from "@sema-agent/settings-schema";
29
+ import type { ModelPlaneRefusalCode } from "@sema-agent/settings-schema";
26
30
  import { type ModelRouteEndpointVerdict, type ModelRouteGap, type ServedModelRouteView } from "./model-route-endpoint.js";
27
31
  import { type ModelTableLeg } from "./model-select.js";
28
32
  import type { CandidateRejection } from "./web-admin-client/candidate-gate.js";
@@ -37,6 +41,8 @@ export interface ModelPlaneFace {
37
41
  readonly refusalCode?: ModelRouteEndpointVerdict["code"];
38
42
  /** 同一次判决的出路轴(缺口两词)。 */
39
43
  readonly gap?: ModelRouteGap;
44
+ /** 最新到达的目录候选被模型面组拒的码(见文件头;缺席 ⇔ 最新到达的候选没有被模型面组拒)。 */
45
+ readonly planeRefusalCode?: ModelPlaneRefusalCode;
40
46
  }
41
47
  /** {@link buildModelPlaneFace} 读的进程事实(都由调用方从各自的座取,本函数不碰任何全局)。 */
42
48
  export interface ModelPlaneFacts {
@@ -44,8 +50,8 @@ export interface ModelPlaneFacts {
44
50
  readonly envTableIsPlaceholder: boolean;
45
51
  /** 候选判官最近一次整拒的结论座(`WebAdminRuntime.candidateRejectionState()`);座空 / 没有配置管道 ⇒ `undefined`。 */
46
52
  readonly candidateRejection: CandidateRejection | undefined;
47
- /** 模型拒因信号(`WebAdminRuntime.modelRefusalsState()`);`plane` 在场 ⇒ 最近一份候选的模型面被整组拒。 */
48
- readonly modelRefusals: Pick<ModelRefusalsSignal, "plane"> | undefined;
53
+ /** 最新到达的目录候选被模型面组拒的码(`WebAdminRuntime.latestPlaneRefusalState()`);没有 / 没有配置管道 ⇒ `undefined`。 */
54
+ readonly latestPlaneRefusal: ModelPlaneRefusalCode | undefined;
49
55
  }
50
56
  export declare function buildModelPlaneFace(config: ServedModelRouteView, facts: ModelPlaneFacts): ModelPlaneFace;
51
57
  //# sourceMappingURL=model-plane-face.d.ts.map
@@ -13,12 +13,13 @@ function catalogStanceOf(served, wholeRefused) {
13
13
  }
14
14
  export function buildModelPlaneFace(config, facts) {
15
15
  const servedTable = servedModelTableLeg(config);
16
- const wholeRefused = facts.candidateRejection !== undefined || facts.modelRefusals?.plane !== undefined;
16
+ const wholeRefused = facts.candidateRejection !== undefined || facts.latestPlaneRefusal !== undefined;
17
17
  const verdict = judgeServedModelRoute(config, { envTableIsPlaceholder: facts.envTableIsPlaceholder });
18
18
  return {
19
19
  servedTable,
20
20
  catalog: catalogStanceOf(servedTable, wholeRefused),
21
21
  ...(verdict !== undefined ? { refusalCode: verdict.code, gap: verdict.gap } : {}),
22
+ ...(facts.latestPlaneRefusal !== undefined ? { planeRefusalCode: facts.latestPlaneRefusal } : {}),
22
23
  };
23
24
  }
24
25
  //# sourceMappingURL=model-plane-face.js.map
@@ -1,4 +1,4 @@
1
- import type { Logger } from "./logger.js";
1
+ import { type Logger } from "./logger.js";
2
2
  import type { Metrics } from "./metrics.js";
3
3
  /**
4
4
  * 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。
@@ -31,13 +31,17 @@ export declare const FAIL_OPEN_TAGS: {
31
31
  readonly cls: "F";
32
32
  readonly note: "S-136 合并重扫确认项(`terminal.ts persistedPlaneOf`,持久 blob 的平面读面):盘上一条 `terminal` 因由形不合(`paused` 缺 gate / 词出闭集)让 core 的 `terminalProjection` 抛 ⇒ 本读面对这一行答**全格缺席**而不是整只 500。放行的最坏后果=一条坏行在列表/舰队读面上显示为无终局词;计数 + probe 带原句,便于按行回溯。";
33
33
  };
34
+ readonly "server.terminal.stored-result-code-folded": {
35
+ readonly cls: "F";
36
+ readonly note: "`terminal.ts` 的 `sealStoredResult`(出口封口的**读入口**,读座必填;调用点:`GET /v1/runs/:id` 终态臂的读面记忆腿与非终态臂 `sealStoredRunRowResult` = 读座 `run.result`,账本 `done` 行回放 = 读座 `ev.done.result`,`GET /v1/runs/:id/events` / `GET /v1/tasks/:id/turns` / trace SSE 三条耐久读腿共用):一条更早落盘的存量结果,失败码不合本服务的对外码形(小写、`.` 分族;例:驱动原码 `ER_*` / SQLSTATE `42703`)⇒ 读面上照写口同一只判官折成 `internal.store_error`(原码进 `message` 尾注,随结果过汇点脱敏)。每读一次记一次;detail = 定形字段 `original=<码 | <not-a-code>> outward=<对外词> messageBytes=<失败句字节数> at=<读座>`(失败句只报字节数;码只在它是码时报原值 —— 短 token 且秘密脱敏器原样放过它)。存量行的读面改写(与 `server.approval-card.retired-card-word-read` 同类):放行的最坏后果 = 无 —— 这一折与结果第一次离开引擎时的那一折同判同形;登记在这里是为了**响亮**且不随读次放大 —— 读面每次轮询 / 每个观看者 / 每次重连都重放同一批行,计数数的就是「旧失败被读了多少次」,warn 每进程一次;结果第一次离开引擎(写入口 `sealResultForExit`)那一折不走本 tag,每折一行 `[terminal] outward_failure_code_folded … at=exit`(每次都是一件新的失败)。计数持续非零 = 有升级前落盘的、码不合码形的失败行还在被读(随行收割 / 留存期满而归零)。";
37
+ };
34
38
  readonly "server.mcp-panel.last-leg-unreadable": {
35
39
  readonly cls: "F";
36
40
  readonly note: "S-297(`http/routes/sessions.ts lastLegMcpFor`,`/mcp` 面板的可选键 `lastLegMcp`):按会话取最近 run 的索引读 / 那条 run 的账本读**抛错或超期** ⇒ 该键缺席,面板本体照常 200。放行的最坏后果=这一屏少一格「上一次真挂上了什么」(壳退回翻账本),不是门、不是权限、不是凭证 —— 而反方向(让附加键的读失败打掉整张面板)会给一口此前零 MCP 配置下根本不碰账本的**既有**读面新加一条挂掉的理由。缺席本就是本键 wire 上的一等状态(消费端必须会读),所以这条兜底不产生任何新的消费端形态;计数让「某台部署的账本一直读不出来」显形。";
37
41
  };
38
42
  readonly "server.ledger.forward-append-failed": {
39
43
  readonly cls: "F";
40
- readonly note: "durable 腿 forward 道的写失败(`trace/ledger-sink.ts` 的 `createForwardAppendFailedNote`:账本汇 `onForwardEvent` —— bg 腿、sync detach 车道、续跑腿 verify 臂 —— 与续跑腿自己的 forward 回调同一只出口)。forward 道是同步回调里的 fire-and-forget 写(序号同步分配,结构上不能 await),写失败 ⇒ 这一帧(子代的 `task_started` / `task_progress` / 工具卡)永远不在 `GET /v1/runs/:id/events` 的回放面,run 照跑。放行的最坏后果 = 回放面少一帧子代事实(起跑帧每周期一只,丢了就补不回 `agentType` / `rootSessionId` / `startedAt`),不是门、不是凭据、不改任何裁决;不重试 —— 晚到的重试行会落在读方游标之后(单写串行链存在的理由)。计数非零 = 这台部署的账本写口在出错(主流的 awaited 写多半同时在报错),去看库。detail = `type=<帧型> err=<原句,经记录器脱敏>`。";
44
+ readonly note: "durable 腿 forward 道的写失败(`trace/ledger-sink.ts` 的 `createForwardAppendFailedNote`:账本汇 `onForwardEvent` —— bg 腿、sync detach 车道、续跑腿 verify 臂 —— 与续跑腿自己的 forward 回调同一只出口)。forward 道是同步回调里的 fire-and-forget 写(序号同步分配,结构上不能 await),写失败 ⇒ 这一帧(子代的 `task_started` / `task_progress` / 工具卡)永远不在 `GET /v1/runs/:id/events` 的回放面,run 照跑。放行的最坏后果 = 回放面少一帧子代事实(起跑帧每周期一只,丢了就补不回 `agentType` / `rootSessionId` / `startedAt`),不是门、不是凭据、不改任何裁决;不重试 —— 晚到的重试行会落在读方游标之后(单写串行链存在的理由)。计数非零 = 这台部署的账本写口在出错(主流的 awaited 写多半同时在报错),去看库。detail = `type=<帧型> err=<原句,经记录器脱敏>`。同一出口还收 sync 腿 `file_link` 的账本孪生写失败(`type=file_link`:回放面缺这张文件卡,工具面不 fault;流还活着时 live 帧已发,流已断时这张卡就是整张没送达 —— 该腿有账本口,死直写不另记丢帧,丢与不丢只由这一笔判;修前裸吞)。";
41
45
  };
42
46
  readonly "server.tool-end-media.live-backlog-degraded": {
43
47
  readonly cls: "F";
@@ -105,7 +109,7 @@ export declare const FAIL_OPEN_TAGS: {
105
109
  };
106
110
  readonly "server.stream.frame-dropped-stream-closed": {
107
111
  readonly cls: "F";
108
- readonly note: "非 HITL-open 的运行流帧写向已断连接 ⇒ 丢弃。连接都没了,这一帧本就无人可看;单独立项是为了不让它挤进上面那条保护型计数。";
112
+ readonly note: "非 HITL-open 的运行流帧写向已断连接 ⇒ 丢弃。连接都没了,这一帧本就无人可看;单独立项是为了不让它挤进上面那条保护型计数。⚠️ `frame=engine_notice` 与 `frame=file_link` **只在本腿没有账本口的部署上计**(无 durable run 行):配了账本口的腿,这一帧在账本里,账本孪生即送达,死直写不再另记 —— 丢没丢由账本那一笔判(通告 `server.engine-notice.durable-append-failed`、文件卡 `server.ledger.forward-append-failed`),所以本计数里的这两型就是真丢。读遥测时别拿本 tag 当「壳在通告到达前断流」的旁证:有账本口的腿上那件事要从账本行时间与接入日志的 499 对出来。";
109
113
  };
110
114
  readonly "server.question.open-frame-undelivered": {
111
115
  readonly cls: "P-DEBT";
@@ -203,6 +207,10 @@ export declare const FAIL_OPEN_TAGS: {
203
207
  readonly cls: "F";
204
208
  readonly note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。 第五面:回决门族的 200 体(`/decide` / `/resume` / `/plan_review` 执行之前从选中的那一行捕获一次,`http/resume-legs.ts` 三处;detail `face=decision-receipt`)—— 选中那一行的门身份没过信任边界 ⇒ 结清证据照铸、体上 `checkpointId` 键缺席(决断方只是对不上身份;位、决定、续跑都照常)。每次走到捕获点的决断计一次(计的是选中那一行的事实,与这次决断随后走哪条车道无关)。";
205
209
  };
210
+ readonly "server.peer-inbound.notice-coordinates-unreadable": {
211
+ readonly cls: "F";
212
+ readonly note: "S-1370(`peer-inbound-face.ts` 的 `observe`,部署级通告转发席喂它):core 的 `peer.inbound_disposition{disposition:\"held\"}` / `peer.held_settled{heldCount:0}` 通告的 `detail` 读不出坐标(`sessionId` 不是串或不合 core 的会话地址文法 / `box` 既不是那只会话的箱也不是 principal 收件箱)⇒ 这一条不进(或不出)收信方决断面的坐标表。按构造不可达:core 的两只铸点恒给这两位。放行(忽略这一条)的最坏后果:进表那一形 = 那张被扣的信不出现在 `GET /v1/approvals` 的 `pending[]` 上 —— 它仍在 core 的队列里,按 `dialogExpiry` 带 `expired` 回执结清(core 的安全默认),也仍可经 core 自己的通告面看到;出表那一形 = 坐标表多留一行(读时多查一次空队列)。不放行(抛)会把异常回抛进引擎的通告投递(转发席的隔离契约禁止)。故 F 类;计数非零 = core 改了这两条通告的 detail 形,对 core 的 d.ts 重核坐标读法。";
213
+ };
206
214
  readonly "server.approvals.queue-row-gate-kind-missing": {
207
215
  readonly cls: "F";
208
216
  readonly note: "S-1681(`plugins/checkpoint-store-sql.ts` 的 `queueRowGateKind`,7.132.0;SQL 与 local 两只孪生的 `listPending` 同调这一只判官):运维待批队列(`GET /v1/approvals` / `/stream`)读到一行 pending 的门型(SQL 孪生:`checkpoint.gate_kind` 列;local 孪生:快照上的门)是 NULL 或不是 core 登记的门型 ⇒ 这一行**不列**。按构造不可达:每次停驻都把门型反范式进这一列,一次性迁移 M21 把没有它的存量行从门子树补齐(读不出登记门型的置 expired)。放行(不列、队列其余照常)的最坏后果 = 一行谁也决不了的行不出现在队列上 —— 它本来就不可决:每一条决断口按门型绑定取行(`gate_kind IN (…)`),证不出门型就不当它是这道门;不放行(整只拒)的代价是一行坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧)。也不按缺键投出去:消费方按 `gateKind` 路由、缺键即 fail-closed,队列行的契约是 `gateKind` 恒在场。故 F 类;计数非零 = 写腿或手改出了错,`SELECT session_id FROM checkpoint WHERE status = 'pending' AND gate_kind IS NULL` 查那一行(置 expired 或删会话)。";
@@ -277,7 +285,7 @@ export declare const FAIL_OPEN_TAGS: {
277
285
  };
278
286
  readonly "server.engine-notice.durable-append-failed": {
279
287
  readonly cls: "F";
280
- readonly note: "#310(codex 对抗复审 R1-[medium],验真后修):三条 run 腿把一条白名单通告写进 durable 账本时,那次 `appendEvent` **异步 reject**(store 抖动 / 连接断 / 行冲突)。结构上不能 await —— `onNotice` 是 core 的同步回调,拿一次账本写去阻塞引擎是更坏的交易 —— 所以写口只能 fire-and-forget,`route` 的同步 try 结构性观察不到这次失败 —— 「投出去了」与「真落盘了」不是同一件事。放行的最坏后果按腿分档,如实登记:**sync stream 腿**还有 live SSE 那一份(在线的人看得见,丢的是断连补看);**bg / resume 两腿账本是唯一的用户可见终点** ⇒ 那条通告对用户永久消失。仍判 F 而不是 P:通告是**披露**面不是执法面(不参与任何门/裁决),且运维面那一份(结构化日志 `engine_notice`)在分流之前就已经落定、一条不丢。此前是裸 `.catch(() => undefined)` —— 同文件 resume 腿的子代 forward 写口早在 C2/C5 批2 就因为「裸吞 = 事件从 durable log 永久消失且零信号」补过 `warnAppend`,本 tag 是同一条纪律在通告口的落实(warn 留细节 + 计数让「系统性在漏」显形)。不做重试队列是刻意的:账本里没有任何一族行做有界重试,单给通告开一条会造出第二套持久化语义。";
288
+ readonly note: "#310(codex 对抗复审 R1-[medium],验真后修):三条 run 腿把一条白名单通告写进 durable 账本时,那次 `appendEvent` **异步 reject**(store 抖动 / 连接断 / 行冲突)。结构上不能 await —— `onNotice` 是 core 的同步回调,拿一次账本写去阻塞引擎是更坏的交易 —— 所以写口只能 fire-and-forget,`route` 的同步 try 结构性观察不到这次失败 —— 「投出去了」与「真落盘了」不是同一件事。放行的最坏后果按腿分档,如实登记:**sync stream 腿**还有 live SSE 那一份(在线的人看得见,丢的是断连补看;写失败时 live 可以多于账本 —— 用户看见了、回放里没有,本 tag 计一次);**bg / resume 两腿账本是唯一的用户可见终点** ⇒ 那条通告对用户永久消失。仍判 F 而不是 P:通告是**披露**面不是执法面(不参与任何门/裁决),且运维面那一份(结构化日志 `engine_notice`)在分流之前就已经落定、一条不丢。此前是裸 `.catch(() => undefined)` —— 同文件 resume 腿的子代 forward 写口早在 C2/C5 批2 就因为「裸吞 = 事件从 durable log 永久消失且零信号」补过 `warnAppend`,本 tag 是同一条纪律在通告口的落实(warn 留细节 + 计数让「系统性在漏」显形)。不做重试队列是刻意的:账本里没有任何一族行做有界重试,单给通告开一条会造出第二套持久化语义。";
281
289
  };
282
290
  readonly "server.memory.locked-layer-probe-partial": {
283
291
  readonly cls: "F";
@@ -357,11 +365,11 @@ export declare const FAIL_OPEN_TAGS: {
357
365
  };
358
366
  readonly "server.approval-ask-audit.append-failed": {
359
367
  readonly cls: "F";
360
- readonly note: "S-38 裁 (c) 审计半场:local 车道 File ask 审计店(`approval-ask-audit-store.ts`)的**运行期** append(铸造/决议/腿闭/收敛四类行)失败(盘满/只读/fd 失效)⇒ 丢**这一条**审计行,审批路径照常。方向刻意 fail-open:审批可用性不押在审计盘上 —— 盘满时人还得能批,把一次审计写失败升级成审批失败是把「痕迹缺一条」换成「门整个不可用」,方向更坏。boot 期(mkdir/重放)失败则**拒启**(File 店族同律,store 头注成文),不经本 tag。放行的最坏后果 = 该 ask 在崩溃收敛读面上缺席或分臂失真(与 A-075.55 的零痕迹病同向)—— 所以必须留痕:「审计一直在漏写」与「没有审批发生」在读面上同形,不计数则病灶永不显形。detail 带行类与异常文案。";
368
+ readonly note: "S-38 裁 (c) 审计半场:local 车道 File ask 审计店(`approval-ask-audit-store.ts`)的**运行期** append(铸造/决议/腿闭/有序关停/迟到结算各类行)失败(盘满/只读/fd 失效)⇒ 丢**这一条**审计行,审批路径照常。起服收敛器写的收敛行**不在其列**:它是那一组的幂等锚与成因上界、是 boot 期写,写不进即拒启(S-2480 起)。方向刻意 fail-open:审批可用性不押在审计盘上 —— 盘满时人还得能批,把一次审计写失败升级成审批失败是把「痕迹缺一条」换成「门整个不可用」,方向更坏。boot 期(mkdir/重放)失败则**拒启**(File 店族同律,store 头注成文),不经本 tag。放行的最坏后果 = 该 ask 在崩溃收敛读面上缺席或分臂失真(与 A-075.55 的零痕迹病同向)—— 所以必须留痕:「审计一直在漏写」与「没有审批发生」在读面上同形,不计数则病灶永不显形。detail 带行类与异常文案。";
361
369
  };
362
370
  readonly "server.run-terminal.redact-threw": {
363
371
  readonly cls: "F";
364
- readonly note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役)+ 出口座(S-485 / RH-1:`trace/exit-seat.ts` 的 `seatText`,probe 前缀 `seat=<座表行>`;trace 帧自由文本位上抛 ⇒ 这一位换同一枚占位、投影不抛):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形由 S-219 的 atLeast 铸点闭掉;JSON 秘密字段臂的转义对循环与 PEM 栅栏臂的标签交替循环由 S-265 换成手写线性扫描器闭掉 —— 已知形全部闭掉,本臂留作结构性兜底,计数恒零是证据、不归零即新形)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。";
372
+ readonly note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役)+ 出口座(S-485 / RH-1:`trace/exit-seat.ts` 的 `seatText`,probe 前缀 `seat=<座表行>`;trace 帧自由文本位上抛 ⇒ 这一位换同一枚占位、投影不抛)+ 活面放行窗(`trace/model-segment.ts` 的 `createLiveModelText`,probe 前缀 `live-hold`:逐块放行口抛 ⇒ 这一拍不放、字节留在窗里;段末整段读法抛 ⇒ 余下的字节整段换同一枚占位):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形由 S-219 的 atLeast 铸点闭掉;JSON 秘密字段臂的转义对循环与 PEM 栅栏臂的标签交替循环由 S-265 换成手写线性扫描器闭掉 —— 已知形全部闭掉,本臂留作结构性兜底,计数恒零是证据、不归零即新形)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。";
365
373
  };
366
374
  readonly "server.run-terminal.provider-echo-shaped": {
367
375
  readonly cls: "F";
@@ -391,14 +399,26 @@ export declare const FAIL_OPEN_TAGS: {
391
399
  readonly cls: "F";
392
400
  readonly note: "S-107(全量门 silent-degradation 棘轮抓出的五处静默 `catch {}`):run 终局链里**账本腿**(flush 缓冲 text/reasoning、append `failed` 行)与终态翻转(setTerminal)已不共命运——账本 INSERT 抛不再让 run 卡 running 到 reaper;但那条抛掉的账本行之前是**静默**吞的。detail `arm=` 四臂(flush-clean-cancel / append-clean-cancel / flush-catch / append-thrown-cancel / append-failed);S-947 起续跑腿(`http/resume-legs.ts`)两座同 tag:resume-append-resume-aborted / resume-append-thrown-cancel(取消抛出两臂的 `failed` 行,修前那两臂根本不落账本终局行):缺的是 run_events 尾巴(缓冲文本或 failed 事件),run 行本身仍是 durable truth,读面/reaper 容忍缺失,故 F 类;计数=多少次终局账本写失败。";
393
401
  };
402
+ readonly "server.live-text.hold-diverged": {
403
+ readonly cls: "P-DEBT";
404
+ readonly note: "live 面模型文本的流式口(`trace/model-segment.ts` 的 `createLiveModelText`,三条 live 腿共用;流式口 = `trace/redact.ts` 的 `createRedactionStream`:喂入 / 取已判定前缀 / 收尾冲刷):一段冲刷时,已取出去的逐块字节与整段读法在那一截上的渲染对不上,或有一条整段读法的跨度横跨放行位 —— 流式口的律被打破(某条臂的判定在脱敏器读出的未决尾上界 `PENDING_TAIL_UNITS` 之外落定,而它没有把那一支标成未决尾)。处置:余下的字节照整段读法出(横跨放行位的跨度整枚换记号,不再出它的原文字节),段末帧照发整段脱敏的正文、消费端按它整段替换;但**已经出门**的那几块收不回 —— 其中可能有整段读法会遮的字节(这就是 P 族的理由)。债:未决尾上界是候选池成员判定距离的最大值,正则臂从正则源读出,手写扫描器的那一项是成员处的陈述(它起头要读全的字面锚),不是从扫描器的代码机械读出;已知一形是多遮方向(方案词锚的头值在文本末尾被读成「收在行尾」,其后隔着长过上界的行内空白又跟了词 ⇒ 整段读法不遮、活流已出记号)。钉 = 基线语料逐条按块流过流式口拼接 = 整段脱敏 + 判定距离最远的几形逐码元。detail `family=<text|reasoning> len=<段长>`;计数 = 多少段的律被打破(期望恒 0)。";
405
+ };
394
406
  readonly "server.redact.ppk-eat-to-end": {
395
407
  readonly cls: "F";
396
408
  readonly note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;出口座有界行的 head 读法(只扫一段头)上「窗内无合格终止」是终止可能在窗外的未决 —— 照样遮到窗尾,但不是兜底,不计;计数=多少次整段吃尾。";
397
409
  };
410
+ readonly "server.redact.forensic-level-cap": {
411
+ readonly cls: "F";
412
+ readonly note: "脱敏器取证相(redact.ts armPool 第二相)跑满级数界 FORENSIC_LEVEL_MAX 仍没收敛(证据链里层被认出、外层才认得出,刻意交替嵌套的私钥块 / URL 每层多一级,深过 31 层才够得着;现役语料最深 3 级)⇒ 下一级本会重跑的取证臂各自从它读证据的起点遮到窗尾(fail-closed:多遮可见、不少遮),不再升级;每次调用至多一条;影子扫不计;detail from=/len= 是视图坐标。";
413
+ };
398
414
  readonly "server.approval-card.closed-word-out-of-set": {
399
415
  readonly cls: "F";
400
416
  readonly note: "S-249(`approval-card.ts` 的边界窄读族;今天四个成员:`readRuleOffersAbsence`、S-934 ② 起的 `readAskClassPair`(core 7.37.0 #1214 的类对 `askClass` / `classes`,活卡帧 / 呈卡帧的卡 / 运维队列行同一只,判法 = core 屏 `screenAskClassMembers`(S-955 ⑥ / core 7.38.0 #1249,本仓零自写对判),两键同在同缺,半对 / 集外 / 首员不符 / 重复员 = 屏拒 = 读不出;detail 前缀 `askClass=` / `classes=`),与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛)、S-1170 起的 `classifierNoRuling`(core 7.43.0 #1353 的成因词,活卡帧读 `AskRequest.classifierNoRuling`,闭集筛 = core `isAutoModeFallbackCause`)与 `differs`(core 7.44.0 #1374,载体是 `runs.ts` 的 409 `steering.duplicate_input_id` 体读的引擎拒错 `detail.differs`,闭集筛 = core `STEER_REPLAY_FACETS` 的非空子集;集外成员 / 空 / 非数组 ⇒ 不铸 `detail`,码仍是契约);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` / `classifierNoRuling=` / `differs=` 判别是哪一座;第四个成员是 `approval-content-kind.ts` 的 `readApprovalCard` —— 卡词 `approvalCard` 自己的闭集窄读,活 ask / LOCAL 店停驻门 / SQL 店读出的门子树三个载体共用一只,闭集 = core `TOOL_APPROVAL_CARDS`,detail 前缀 `approvalCard=`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。";
401
417
  };
418
+ readonly "server.approval-card.retired-card-key-read": {
419
+ readonly cls: "F";
420
+ readonly note: "一次性迁移 M37 的读侧(`approval-card.ts` 的 `renameRetiredSourceKey`,存卡的唯一读者入口 `readStoredCardEnvelope` 先调它;窗与删法见 `plugins/one-time-migrations.ts` 登记表 M37 —— 窗关随 M37 删本 tag):存下的审批卡信封(`approval_ask.card_json` / local 问账的同一份信封)上,委派出处键还是引擎 7.61.0 之前的旧名 `card.sourceTaskId` ⇒ 读时改名 `sourceSessionId`(值不变),整卡照读。这不是放行:改名前后是同一个值、同一个事实,只是不改名 `.strict()` 的卡 schema 就整卡读不出(断连重放跳过、回决口按要人、幂等重入走 park)。计数非零 = 有本版之前铸的存卡还在被读(滚动升级窗里旧副本新写的、升级前已结仍被幂等重入 / 兑付读的);窗关前应归零。";
421
+ };
402
422
  readonly "server.approval-card.retired-card-word-read": {
403
423
  readonly cls: "F";
404
424
  readonly note: "S-1562(core 7.51.0 #1515 / #1702;一次性迁移 M18 的**读侧半场**,`approval-card.ts` 的 `readStoredCardEnvelope` / `currentParkArm`,窗与删法见 `plugins/one-time-migrations.ts` 登记表 M18 —— 窗关随 M18 删本 tag):存下的审批卡(`approval_ask.card_json`)或停驻行(`pendingAction` 的 `tool_approval` 臂)上带着引擎 7.51.0 退役的词 —— `ruleOffersAbsence: \"shadowed\"` 或类对里的 `governance_unavailable` —— 读时按 M18 那一只改写读(前者删键 = 合法缺席,后者换成 `rule_unreadable`、类集去重按引擎序)。每改写一次记一次;detail = 载体(`card_json` / `parked row`)与座名,不带值。放行的最坏后果 = 无:这一读与存卡的起服改写对同一份字节给同一个答案(一只函数);它不是兜底,它登记在这里是为了**响亮** —— 载体 `card_json` 计数非零 = 有起服改写够不着的卡(滚动升级窗里 7.128.x 副本新写的卡、local 文件 ask 账);载体 `parked row` 计数非零 = 读到了带旧词的停驻行(引擎写的文档,本服务从不改写它 —— 行摘要覆盖类对 `askClass` / `classes`,改写 = 续跑判 `damaged`;所以这一格在窗内随每次读到那样的行而涨,直到那些行被决掉 / 收割),运维据它判断旧副本是否都已换下、窗关删码前是否还有这种行在飞。反方向(不改写)= 修前:整张存卡读不出(断连重放跳过、幂等重入走 park、回决口按要人)、停驻行的 `shadowed` 读成「读不出 ⇒ 按强制」(拿不到「本会话放行」)。";
@@ -417,7 +437,7 @@ export declare const FAIL_OPEN_TAGS: {
417
437
  };
418
438
  readonly "server.run-store.corrupt-record-skipped": {
419
439
  readonly cls: "F";
420
- readonly note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;三个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。";
440
+ readonly note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;四个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。前三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。第四个盘面 `run-blobs/<会话>.jsonl`(`wiring_manifest` 行容器段的块账本,`plugins/wiring-section-codec.ts`)同一只座、两个检测点:① 重放时一条块记录形不合(`digest` 不是串 / `body` 不是容器)⇒ 跳过;② 读口(`getEvents`)装回时清单行引用的块不在本会话账本里(块缺:账本行被删 / 被跳过、断电丢尾、删会话时 run 目录删不掉留下的僵尸 run)⇒ 那一帧这一次不交出(永不交出缺段的清单),每个 `(taskId, seq)` 每进程计一次,`maxSeq` / `retainedFrom` 照旧按存位;明细 `kind=section-ref taskId= seq= sections=<段:摘要,…>`。块账本整份读不出(权限 / 挂载)不走本 tag,与 `events.jsonl` 读不出同一条路(strict 拒启,否则原样抛)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。";
421
441
  };
422
442
  readonly "server.resume-anchor.corrupt-record-skipped": {
423
443
  readonly cls: "F";
@@ -481,7 +501,7 @@ export declare const FAIL_OPEN_TAGS: {
481
501
  };
482
502
  readonly "server.approval-crash-convergence.cause-unreadable": {
483
503
  readonly cls: "F";
484
- readonly note: "7.116.0 修复轮(`approval-crash-convergence.ts` 的 `convergeCrashedAsksAtBoot`;local 车道启动收敛):前世留下的一条 `STREAM_PENDING` 问行,它的卡信封(`card_json`)读不出 ⇒ 读不出 wire `approvalId`、无从向审计账要成因词 ⇒ 成因**回落**成 `crashed_before_park`。行照样收 VOID(决断口照样 410 —— 可决性 fail-closed,不受影响);最坏后果 = 一张其实是被有序关停截断的卡,在 410 体的 `deniedReason` 上说成「进程崩了」(`crashConverged` 读面若有它的审计组,那里的成因仍按审计账判,两处可能不同词)。计数非零 = 问账里有读不出卡信封的行(位腐 / 手改 / 版本错位),去看探针 detail 点名的 askId。读数另在启动日志 `approval_ask_audit_crash_converged` 的 `causeUnreadable` 格单列。";
504
+ readonly note: "7.116.0 修复轮(`approval-crash-convergence.ts` 的 `convergeCrashedAsksAtBoot`;local 车道启动收敛):前世留下的一条 `STREAM_PENDING` 问行,它的卡信封(`card_json`)读不出 ⇒ 读不出 wire `approvalId`、无从向审计账要成因词 ⇒ 成因**回落**成 `crashed_before_park`。行照样收 VOID(决断口照样 410 —— 可决性 fail-closed,不受影响);最坏后果 = 一张其实是被有序关停截断的卡,在 410 体的 `cause` 上说成「进程崩了」(`crashConverged` 读面若有它的审计组,那里的成因仍按审计账判,两处可能不同词)。计数非零 = 问账里有读不出卡信封的行(位腐 / 手改 / 版本错位),去看探针 detail 点名的 askId。读数另在启动日志 `approval_ask_audit_crash_converged` 的 `causeUnreadable` 格单列。";
485
505
  };
486
506
  readonly "server.outbound.hop-member-unreadable": {
487
507
  readonly cls: "F";
@@ -579,11 +599,11 @@ export declare const FAIL_OPEN_TAGS: {
579
599
  };
580
600
  readonly "server.park.held-card-void-failed": {
581
601
  readonly cls: "F";
582
- readonly note: "停驻没落定就以失败收场的腿(`runs.ts` 的 `voidHeldParkCard`;两处:后台腿 catch 的失败臂 `leg=bg`、续跑腿 catch 放认领的取消 / 普通异常两臂 `leg=resume`):手里的结果是宿主层停驻(引擎交回之前已把卡落进 checkpoint 店,卡在本 run 的会话上),而停驻行 / 用量行 / 结果到手之后的账本写抛 ⇒ 腿以 `failed` 收场、放认领;放认领之前先按卡自己的 scope 店级 `expire` 作废那张卡(动词 = `card-void.ts` 的结算作废,与验证器自己的停驻同一只)。作废没做成 —— 店读 / 作废抛错(`store_error`)、卡的会话与本 run 的会话对不上(`card_mismatch`,只作废那一张,对不上就不动)、本部署没接 checkpoint 店(`no_checkpoint_store`)—— 照样以 `failed` 收场、放认领,每次记一次。放行的最坏后果 = 那张卡回到修前的形:留在待决面上,兑现它的 run 已经 `failed`、认领已放,决定口照样受理、续跑在没有认领的会话上驱动(`resume-legs.ts` 认领缺席成因 ②),由审批兜底收割收走;前提是停驻簿记写失败,并且这张卡没作废成(三个 reason 任一:checkpoint 店这一笔也失败 / 卡与本 run 的会话对不上 / 没接店 —— 后两者不需要第二次写失败)。不是门、不是凭据、不改 run 的判词。计数非零 = 有一次停驻没落定的失败收场留下了一张卡:按 reason 看 —— `store_error` = checkpoint 店在同一拍也出错,`card_mismatch` = 结果里的卡与本 run 的会话对不上,`no_checkpoint_store` = 这台部署没接 checkpoint 店;去看那只会话。detail = `reason=<词> leg=<bg|resume> [err=<原句>]`(不含凭据)。";
602
+ readonly note: "停驻没落定就以失败收场的腿(`runs.ts` 的 `voidHeldParkCard`;两处:后台腿 catch 的失败臂 `leg=bg`、续跑腿 catch 放认领的取消 / 普通异常两臂 `leg=resume`):手里的结果是宿主层停驻(引擎交回之前已把卡落进 checkpoint 店,卡在本 run 的会话上),而停驻行 / 用量行 / 结果到手之后的账本写抛 ⇒ 腿以 `failed` 收场、放认领;放认领之前先按卡自己的 scope 店级 `expire` 作废那张卡(动词 = `card-void.ts` 的结算作废)。作废没做成 —— 店读 / 作废抛错(`store_error`)、卡的会话与本 run 的会话对不上(`card_mismatch`,只作废那一张,对不上就不动)、本部署没接 checkpoint 店(`no_checkpoint_store`)—— 照样以 `failed` 收场、放认领,每次记一次。放行的最坏后果 = 那张卡回到修前的形:留在待决面上,兑现它的 run 已经 `failed`、认领已放,决定口照样受理、续跑在没有认领的会话上驱动(`resume-legs.ts` 认领缺席成因 ②),由审批兜底收割收走;前提是停驻簿记写失败,并且这张卡没作废成(三个 reason 任一:checkpoint 店这一笔也失败 / 卡与本 run 的会话对不上 / 没接店 —— 后两者不需要第二次写失败)。不是门、不是凭据、不改 run 的判词。计数非零 = 有一次停驻没落定的失败收场留下了一张卡:按 reason 看 —— `store_error` = checkpoint 店在同一拍也出错,`card_mismatch` = 结果里的卡与本 run 的会话对不上,`no_checkpoint_store` = 这台部署没接 checkpoint 店;去看那只会话。detail = `reason=<词> leg=<bg|resume> [err=<原句>]`(不含凭据)。";
583
603
  };
584
604
  readonly "server.verifier-pause.card-void-failed": {
585
605
  readonly cls: "F";
586
- readonly note: "验证门结算(`verifier-pause-card.ts` 的 `voidVerifierPauseCard`;三处结算点:bg 组合腿、同步提交腿、续跑 verify 臂):验证器自己的停驻卡(结果上 `verification.verifierPause`)是本服务兑现不了的卡(它在验证器现铸的会话上,`/decide` 读不到续跑输入),结算时按卡自己的 scope 走店级 `expire` 当场作废;作废没做成 —— 店读 / 作废抛错(`store_error`)、卡的会话与停驻报的验证器会话不一致(`card_mismatch`,只作废那一张,对不上就不动)、本部署没接 checkpoint 店却收到了带凭据的验证器停驻(`no_checkpoint_store`)。每次记一次。放行的最坏后果 = 那张卡回到修前的形:留在待决面上点不动(`/decide` 答 409),由审批兜底收割收走;不是门、不是凭据(凭据由出口封口摘,与本臂无关)、不改 run 的判词。计数非零 = 这台部署的 checkpoint 店在出错,或引擎交回的停驻与店里的卡对不上(去看那只会话)。detail = `reason=<词> [err=<原句>]`(不含凭据)。引擎给出「作废验证器卡并释放其会话」的动词后本臂改调它,本 tag 随臂删或改写。";
606
+ readonly note: "验证门结算(`verifier-pause-card.ts` 的 `voidVerifierPauseCard`;四处结算点:bg 组合腿、同步提交腿、续跑 verify 臂、leader 的 worker 结算):验证器自己的停驻卡(结果上 `verification.verifierPause`)是本服务兑现不了的卡(它在验证器现铸的会话上,`/decide` 读不到续跑输入),结算时交跑过这次验证门的那只 Runner 的动词 `voidVerifierPause` 当场作废(引擎读卡上的组合腿,只收组合腿 `verify` 的卡;卡 `expired` + 停驻环境销毁 + 验证器会话放钉)。动词答 `false`(别的结算者先到)不计;动词抛 ⇒ 结算照常往下走,每次记一次:卡上记的不是验证器的腿(`resume.composition_mismatch`)、卡不在 / 没接店(`checkpoint.not_found`)、作废了但会话钉没放掉(`checkpoint.void_unpin_failed`,引擎同时报 `onError`)、店读 / 作废抛错(引擎原样抛,码读不出记 `thrown`)。放行的最坏后果 = 那张卡回到修前的形:留在待决面上点不动(`/decide` 答 409),由审批兜底收割收走;`void_unpin_failed` 时卡已作废、只是验证器会话留在副本内存里直到进程重启;不是门、不是凭据(凭据由出口封口摘,与本臂无关)、不改 run 的判词。计数非零 = 按码看:`thrown` = checkpoint 店在出错;`composition_mismatch` = 结果上的验证器停驻指向一张不是验证器腿铸的卡(去看那只会话);`void_unpin_failed` = 会话店放钉出错。detail = `code=<码 | thrown> err=<原句>`(不含凭据)。";
587
607
  };
588
608
  readonly "server.run-permission-mode.write-back-unpersisted": {
589
609
  readonly cls: "F";
@@ -599,8 +619,8 @@ export type FailOpenTag = keyof typeof FAIL_OPEN_TAGS;
599
619
  * 断连会让保护型计数涨两次(open 一次、complete 又一次),真正的 open 失败率被面包屑的假阳性盖住。
600
620
  * 只有 open 帧是"人在环这道门被跳过"的证据,其余都是体验损失。
601
621
  *
602
- * ⚠️ **两条 question 臂在生产上已不可达**([ref] 之后,2026-08-07):本函数唯一的生产调用点是
603
- * `routes/tasks.ts` 的 `emitAsk` 断流臂,而 question 与 question_complete 两型帧的直写口
622
+ * ⚠️ **两条 question 臂在生产上已不可达**([ref] 之后,2026-08-07):本函数的生产调用点有两处,都在 `routes/tasks.ts` ——
623
+ * `emitAsk` 断流臂与通告(`engine_notice`)的 live 口(后者只在本腿没有账本口时调,default 臂);question 与 question_complete 两型帧的直写口
604
624
  * (同文件 `writeQuestionLive`)先过断流守卫(两道守卫同步相邻、其间无 await)并在断流时 throw,永不抵达这里;
605
625
  * 另一条 question 路(`runs.ts` 的 durable append)根本不经 emitAsk。elicitation 族自账本孪生起也只在**无 durable
606
626
  * run 行**的部署上抵达这里(有孪生的腿直写面已死时孪生即送达,不碰死连接)。在产的只剩那一形与 default。
@@ -608,6 +628,16 @@ export type FailOpenTag = keyof typeof FAIL_OPEN_TAGS;
608
628
  * 留着的臂是正确落点而不是死码。读遥测时按上面这段判在产分布。
609
629
  */
610
630
  export declare function failOpenTagForDroppedFrame(frameType: string): FailOpenTag;
631
+ /**
632
+ * 一次性 warn 的契约文字 `[sema] fail-open: <tag>` —— 跨仓黑盒锚(`audits/failopen-governance-176.md` §3 的 stderr 外部面),**唯一铸点**。
633
+ * 它是 JSON 日志行的 `msg`(默认落点)与注入口收到的裸文本行(测试落点)共用的那一句,两处不各拼一遍。
634
+ */
635
+ export declare function buildFailOpenAnchor(tag: FailOpenTag): string;
636
+ /** 一次性 warn 发声那一拍的事实:哪个 tag、此刻为止的累计次数。日志行字段闭集就是这两个。 */
637
+ export interface FailOpenAnnouncement {
638
+ readonly tag: FailOpenTag;
639
+ readonly count: number;
640
+ }
611
641
  /** {@link createFailOpenRecorder} 返回的活对象(有行为、有状态 ⇒ `create*` 而非 `build*`)。 */
612
642
  export interface FailOpenRecorder {
613
643
  /** 走到一条登记过的兜底臂。总不抛——观测本身绝不能变成故障(见 reapers 同族判据)。 */
@@ -643,8 +673,11 @@ export interface FailOpenRecorderDeps {
643
673
  * 用注入而不是直接 import:脱敏器自己 import 了本模块(它有一条 fail-open 留痕臂),直接 import 会成环。
644
674
  */
645
675
  redactDetail?: (detail: string) => string;
646
- /** 测试注入:替掉 stderr 契约行的落点。缺省直写 `process.stderr`。 */
647
- writeStderr?: (line: string) => void;
676
+ /**
677
+ * 测试注入:替掉一次性 warn 的落点;收到旧的裸文本行(`[sema] fail-open: <tag>\n`,契约文字 + 换行)与发声那一拍的事实。缺省 = {@link announceAsJsonLine}(stderr 上的一行 JSON 日志;
678
+ * 注入口名字沿用「stderr 契约行」时代,落点已是日志行 —— 一百多处测试用它静音这一件,不为改名牵动)。
679
+ */
680
+ writeStderr?: (line: string, facts: FailOpenAnnouncement) => void;
648
681
  /** 测试注入:替掉探针文件追加。缺省 `appendFileSync`(同步=进程猝死也不丢已记的行)。 */
649
682
  appendProbe?: (path: string, line: string) => void;
650
683
  }
@@ -1,4 +1,5 @@
1
1
  import { appendFileSync } from "node:fs";
2
+ import { createLogger } from "./logger.js";
2
3
  export const FAIL_OPEN_TAGS = {
3
4
  "server.approval-ask-journal.compaction-failed": {
4
5
  cls: "F",
@@ -12,13 +13,17 @@ export const FAIL_OPEN_TAGS = {
12
13
  cls: "F",
13
14
  note: "S-136 合并重扫确认项(`terminal.ts persistedPlaneOf`,持久 blob 的平面读面):盘上一条 `terminal` 因由形不合(`paused` 缺 gate / 词出闭集)让 core 的 `terminalProjection` 抛 ⇒ 本读面对这一行答**全格缺席**而不是整只 500。放行的最坏后果=一条坏行在列表/舰队读面上显示为无终局词;计数 + probe 带原句,便于按行回溯。",
14
15
  },
16
+ "server.terminal.stored-result-code-folded": {
17
+ cls: "F",
18
+ note: "`terminal.ts` 的 `sealStoredResult`(出口封口的**读入口**,读座必填;调用点:`GET /v1/runs/:id` 终态臂的读面记忆腿与非终态臂 `sealStoredRunRowResult` = 读座 `run.result`,账本 `done` 行回放 = 读座 `ev.done.result`,`GET /v1/runs/:id/events` / `GET /v1/tasks/:id/turns` / trace SSE 三条耐久读腿共用):一条更早落盘的存量结果,失败码不合本服务的对外码形(小写、`.` 分族;例:驱动原码 `ER_*` / SQLSTATE `42703`)⇒ 读面上照写口同一只判官折成 `internal.store_error`(原码进 `message` 尾注,随结果过汇点脱敏)。每读一次记一次;detail = 定形字段 `original=<码 | <not-a-code>> outward=<对外词> messageBytes=<失败句字节数> at=<读座>`(失败句只报字节数;码只在它是码时报原值 —— 短 token 且秘密脱敏器原样放过它)。存量行的读面改写(与 `server.approval-card.retired-card-word-read` 同类):放行的最坏后果 = 无 —— 这一折与结果第一次离开引擎时的那一折同判同形;登记在这里是为了**响亮**且不随读次放大 —— 读面每次轮询 / 每个观看者 / 每次重连都重放同一批行,计数数的就是「旧失败被读了多少次」,warn 每进程一次;结果第一次离开引擎(写入口 `sealResultForExit`)那一折不走本 tag,每折一行 `[terminal] outward_failure_code_folded … at=exit`(每次都是一件新的失败)。计数持续非零 = 有升级前落盘的、码不合码形的失败行还在被读(随行收割 / 留存期满而归零)。",
19
+ },
15
20
  "server.mcp-panel.last-leg-unreadable": {
16
21
  cls: "F",
17
22
  note: "S-297(`http/routes/sessions.ts lastLegMcpFor`,`/mcp` 面板的可选键 `lastLegMcp`):按会话取最近 run 的索引读 / 那条 run 的账本读**抛错或超期** ⇒ 该键缺席,面板本体照常 200。放行的最坏后果=这一屏少一格「上一次真挂上了什么」(壳退回翻账本),不是门、不是权限、不是凭证 —— 而反方向(让附加键的读失败打掉整张面板)会给一口此前零 MCP 配置下根本不碰账本的**既有**读面新加一条挂掉的理由。缺席本就是本键 wire 上的一等状态(消费端必须会读),所以这条兜底不产生任何新的消费端形态;计数让「某台部署的账本一直读不出来」显形。",
18
23
  },
19
24
  "server.ledger.forward-append-failed": {
20
25
  cls: "F",
21
- note: "durable 腿 forward 道的写失败(`trace/ledger-sink.ts` 的 `createForwardAppendFailedNote`:账本汇 `onForwardEvent` —— bg 腿、sync detach 车道、续跑腿 verify 臂 —— 与续跑腿自己的 forward 回调同一只出口)。forward 道是同步回调里的 fire-and-forget 写(序号同步分配,结构上不能 await),写失败 ⇒ 这一帧(子代的 `task_started` / `task_progress` / 工具卡)永远不在 `GET /v1/runs/:id/events` 的回放面,run 照跑。放行的最坏后果 = 回放面少一帧子代事实(起跑帧每周期一只,丢了就补不回 `agentType` / `rootSessionId` / `startedAt`),不是门、不是凭据、不改任何裁决;不重试 —— 晚到的重试行会落在读方游标之后(单写串行链存在的理由)。计数非零 = 这台部署的账本写口在出错(主流的 awaited 写多半同时在报错),去看库。detail = `type=<帧型> err=<原句,经记录器脱敏>`。",
26
+ note: "durable 腿 forward 道的写失败(`trace/ledger-sink.ts` 的 `createForwardAppendFailedNote`:账本汇 `onForwardEvent` —— bg 腿、sync detach 车道、续跑腿 verify 臂 —— 与续跑腿自己的 forward 回调同一只出口)。forward 道是同步回调里的 fire-and-forget 写(序号同步分配,结构上不能 await),写失败 ⇒ 这一帧(子代的 `task_started` / `task_progress` / 工具卡)永远不在 `GET /v1/runs/:id/events` 的回放面,run 照跑。放行的最坏后果 = 回放面少一帧子代事实(起跑帧每周期一只,丢了就补不回 `agentType` / `rootSessionId` / `startedAt`),不是门、不是凭据、不改任何裁决;不重试 —— 晚到的重试行会落在读方游标之后(单写串行链存在的理由)。计数非零 = 这台部署的账本写口在出错(主流的 awaited 写多半同时在报错),去看库。detail = `type=<帧型> err=<原句,经记录器脱敏>`。同一出口还收 sync 腿 `file_link` 的账本孪生写失败(`type=file_link`:回放面缺这张文件卡,工具面不 fault;流还活着时 live 帧已发,流已断时这张卡就是整张没送达 —— 该腿有账本口,死直写不另记丢帧,丢与不丢只由这一笔判;修前裸吞)。",
22
27
  },
23
28
  "server.tool-end-media.live-backlog-degraded": {
24
29
  cls: "F",
@@ -86,7 +91,7 @@ export const FAIL_OPEN_TAGS = {
86
91
  },
87
92
  "server.stream.frame-dropped-stream-closed": {
88
93
  cls: "F",
89
- note: "非 HITL-open 的运行流帧写向已断连接 ⇒ 丢弃。连接都没了,这一帧本就无人可看;单独立项是为了不让它挤进上面那条保护型计数。",
94
+ note: "非 HITL-open 的运行流帧写向已断连接 ⇒ 丢弃。连接都没了,这一帧本就无人可看;单独立项是为了不让它挤进上面那条保护型计数。⚠️ `frame=engine_notice` 与 `frame=file_link` **只在本腿没有账本口的部署上计**(无 durable run 行):配了账本口的腿,这一帧在账本里,账本孪生即送达,死直写不再另记 —— 丢没丢由账本那一笔判(通告 `server.engine-notice.durable-append-failed`、文件卡 `server.ledger.forward-append-failed`),所以本计数里的这两型就是真丢。读遥测时别拿本 tag 当「壳在通告到达前断流」的旁证:有账本口的腿上那件事要从账本行时间与接入日志的 499 对出来。",
90
95
  },
91
96
  "server.question.open-frame-undelivered": {
92
97
  cls: "P-DEBT",
@@ -184,6 +189,10 @@ export const FAIL_OPEN_TAGS = {
184
189
  cls: "F",
185
190
  note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。 第五面:回决门族的 200 体(`/decide` / `/resume` / `/plan_review` 执行之前从选中的那一行捕获一次,`http/resume-legs.ts` 三处;detail `face=decision-receipt`)—— 选中那一行的门身份没过信任边界 ⇒ 结清证据照铸、体上 `checkpointId` 键缺席(决断方只是对不上身份;位、决定、续跑都照常)。每次走到捕获点的决断计一次(计的是选中那一行的事实,与这次决断随后走哪条车道无关)。",
186
191
  },
192
+ "server.peer-inbound.notice-coordinates-unreadable": {
193
+ cls: "F",
194
+ note: "S-1370(`peer-inbound-face.ts` 的 `observe`,部署级通告转发席喂它):core 的 `peer.inbound_disposition{disposition:\"held\"}` / `peer.held_settled{heldCount:0}` 通告的 `detail` 读不出坐标(`sessionId` 不是串或不合 core 的会话地址文法 / `box` 既不是那只会话的箱也不是 principal 收件箱)⇒ 这一条不进(或不出)收信方决断面的坐标表。按构造不可达:core 的两只铸点恒给这两位。放行(忽略这一条)的最坏后果:进表那一形 = 那张被扣的信不出现在 `GET /v1/approvals` 的 `pending[]` 上 —— 它仍在 core 的队列里,按 `dialogExpiry` 带 `expired` 回执结清(core 的安全默认),也仍可经 core 自己的通告面看到;出表那一形 = 坐标表多留一行(读时多查一次空队列)。不放行(抛)会把异常回抛进引擎的通告投递(转发席的隔离契约禁止)。故 F 类;计数非零 = core 改了这两条通告的 detail 形,对 core 的 d.ts 重核坐标读法。",
195
+ },
187
196
  "server.approvals.queue-row-gate-kind-missing": {
188
197
  cls: "F",
189
198
  note: "S-1681(`plugins/checkpoint-store-sql.ts` 的 `queueRowGateKind`,7.132.0;SQL 与 local 两只孪生的 `listPending` 同调这一只判官):运维待批队列(`GET /v1/approvals` / `/stream`)读到一行 pending 的门型(SQL 孪生:`checkpoint.gate_kind` 列;local 孪生:快照上的门)是 NULL 或不是 core 登记的门型 ⇒ 这一行**不列**。按构造不可达:每次停驻都把门型反范式进这一列,一次性迁移 M21 把没有它的存量行从门子树补齐(读不出登记门型的置 expired)。放行(不列、队列其余照常)的最坏后果 = 一行谁也决不了的行不出现在队列上 —— 它本来就不可决:每一条决断口按门型绑定取行(`gate_kind IN (…)`),证不出门型就不当它是这道门;不放行(整只拒)的代价是一行坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧)。也不按缺键投出去:消费方按 `gateKind` 路由、缺键即 fail-closed,队列行的契约是 `gateKind` 恒在场。故 F 类;计数非零 = 写腿或手改出了错,`SELECT session_id FROM checkpoint WHERE status = 'pending' AND gate_kind IS NULL` 查那一行(置 expired 或删会话)。",
@@ -258,7 +267,7 @@ export const FAIL_OPEN_TAGS = {
258
267
  },
259
268
  "server.engine-notice.durable-append-failed": {
260
269
  cls: "F",
261
- note: "#310(codex 对抗复审 R1-[medium],验真后修):三条 run 腿把一条白名单通告写进 durable 账本时,那次 `appendEvent` **异步 reject**(store 抖动 / 连接断 / 行冲突)。结构上不能 await —— `onNotice` 是 core 的同步回调,拿一次账本写去阻塞引擎是更坏的交易 —— 所以写口只能 fire-and-forget,`route` 的同步 try 结构性观察不到这次失败 —— 「投出去了」与「真落盘了」不是同一件事。放行的最坏后果按腿分档,如实登记:**sync stream 腿**还有 live SSE 那一份(在线的人看得见,丢的是断连补看);**bg / resume 两腿账本是唯一的用户可见终点** ⇒ 那条通告对用户永久消失。仍判 F 而不是 P:通告是**披露**面不是执法面(不参与任何门/裁决),且运维面那一份(结构化日志 `engine_notice`)在分流之前就已经落定、一条不丢。此前是裸 `.catch(() => undefined)` —— 同文件 resume 腿的子代 forward 写口早在 C2/C5 批2 就因为「裸吞 = 事件从 durable log 永久消失且零信号」补过 `warnAppend`,本 tag 是同一条纪律在通告口的落实(warn 留细节 + 计数让「系统性在漏」显形)。不做重试队列是刻意的:账本里没有任何一族行做有界重试,单给通告开一条会造出第二套持久化语义。",
270
+ note: "#310(codex 对抗复审 R1-[medium],验真后修):三条 run 腿把一条白名单通告写进 durable 账本时,那次 `appendEvent` **异步 reject**(store 抖动 / 连接断 / 行冲突)。结构上不能 await —— `onNotice` 是 core 的同步回调,拿一次账本写去阻塞引擎是更坏的交易 —— 所以写口只能 fire-and-forget,`route` 的同步 try 结构性观察不到这次失败 —— 「投出去了」与「真落盘了」不是同一件事。放行的最坏后果按腿分档,如实登记:**sync stream 腿**还有 live SSE 那一份(在线的人看得见,丢的是断连补看;写失败时 live 可以多于账本 —— 用户看见了、回放里没有,本 tag 计一次);**bg / resume 两腿账本是唯一的用户可见终点** ⇒ 那条通告对用户永久消失。仍判 F 而不是 P:通告是**披露**面不是执法面(不参与任何门/裁决),且运维面那一份(结构化日志 `engine_notice`)在分流之前就已经落定、一条不丢。此前是裸 `.catch(() => undefined)` —— 同文件 resume 腿的子代 forward 写口早在 C2/C5 批2 就因为「裸吞 = 事件从 durable log 永久消失且零信号」补过 `warnAppend`,本 tag 是同一条纪律在通告口的落实(warn 留细节 + 计数让「系统性在漏」显形)。不做重试队列是刻意的:账本里没有任何一族行做有界重试,单给通告开一条会造出第二套持久化语义。",
262
271
  },
263
272
  "server.memory.locked-layer-probe-partial": {
264
273
  cls: "F",
@@ -338,11 +347,11 @@ export const FAIL_OPEN_TAGS = {
338
347
  },
339
348
  "server.approval-ask-audit.append-failed": {
340
349
  cls: "F",
341
- note: "S-38 裁 (c) 审计半场:local 车道 File ask 审计店(`approval-ask-audit-store.ts`)的**运行期** append(铸造/决议/腿闭/收敛四类行)失败(盘满/只读/fd 失效)⇒ 丢**这一条**审计行,审批路径照常。方向刻意 fail-open:审批可用性不押在审计盘上 —— 盘满时人还得能批,把一次审计写失败升级成审批失败是把「痕迹缺一条」换成「门整个不可用」,方向更坏。boot 期(mkdir/重放)失败则**拒启**(File 店族同律,store 头注成文),不经本 tag。放行的最坏后果 = 该 ask 在崩溃收敛读面上缺席或分臂失真(与 A-075.55 的零痕迹病同向)—— 所以必须留痕:「审计一直在漏写」与「没有审批发生」在读面上同形,不计数则病灶永不显形。detail 带行类与异常文案。",
350
+ note: "S-38 裁 (c) 审计半场:local 车道 File ask 审计店(`approval-ask-audit-store.ts`)的**运行期** append(铸造/决议/腿闭/有序关停/迟到结算各类行)失败(盘满/只读/fd 失效)⇒ 丢**这一条**审计行,审批路径照常。起服收敛器写的收敛行**不在其列**:它是那一组的幂等锚与成因上界、是 boot 期写,写不进即拒启(S-2480 起)。方向刻意 fail-open:审批可用性不押在审计盘上 —— 盘满时人还得能批,把一次审计写失败升级成审批失败是把「痕迹缺一条」换成「门整个不可用」,方向更坏。boot 期(mkdir/重放)失败则**拒启**(File 店族同律,store 头注成文),不经本 tag。放行的最坏后果 = 该 ask 在崩溃收敛读面上缺席或分臂失真(与 A-075.55 的零痕迹病同向)—— 所以必须留痕:「审计一直在漏写」与「没有审批发生」在读面上同形,不计数则病灶永不显形。detail 带行类与异常文案。",
342
351
  },
343
352
  "server.run-terminal.redact-threw": {
344
353
  cls: "F",
345
- note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役)+ 出口座(S-485 / RH-1:`trace/exit-seat.ts` 的 `seatText`,probe 前缀 `seat=<座表行>`;trace 帧自由文本位上抛 ⇒ 这一位换同一枚占位、投影不抛):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形由 S-219 的 atLeast 铸点闭掉;JSON 秘密字段臂的转义对循环与 PEM 栅栏臂的标签交替循环由 S-265 换成手写线性扫描器闭掉 —— 已知形全部闭掉,本臂留作结构性兜底,计数恒零是证据、不归零即新形)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。",
354
+ note: "三个发射座(errorMessage 终态文案 / 诊断文本 diag / 工件 artifact,probe 前缀判别;普查 56 行;补丁头 patch-head 那座随 7.83.0 / S-113 的 redactPatchHead 一并退役)+ 出口座(S-485 / RH-1:`trace/exit-seat.ts` 的 `seatText`,probe 前缀 `seat=<座表行>`;trace 帧自由文本位上抛 ⇒ 这一位换同一枚占位、投影不抛)+ 活面放行窗(`trace/model-segment.ts` 的 `createLiveModelText`,probe 前缀 `live-hold`:逐块放行口抛 ⇒ 这一拍不放、字节留在窗里;段末整段读法抛 ⇒ 余下的字节整段换同一枚占位):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(RangeError:关键字/前缀臂那一形由 S-219 的 atLeast 铸点闭掉;JSON 秘密字段臂的转义对循环与 PEM 栅栏臂的标签交替循环由 S-265 换成手写线性扫描器闭掉 —— 已知形全部闭掉,本臂留作结构性兜底,计数恒零是证据、不归零即新形)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。",
346
355
  },
347
356
  "server.run-terminal.provider-echo-shaped": {
348
357
  cls: "F",
@@ -354,11 +363,20 @@ export const FAIL_OPEN_TAGS = {
354
363
  "server.run-terminal.output-truncated": { cls: "F", note: "S-107:持久 error 脱后 UTF-8 字节 > 65,000(TiDB TEXT 65,535 字节)⇒ 脱后切并带标记(切在占位里不是泄漏);计数=多少条终态文案被字节封顶截断" },
355
364
  "server.runs.abort-cause-missing": { cls: "F", note: "S-947 候裁 ⑤(主会话裁:默认值保留,miss 臂必须响):续跑腿 `http/resume-legs.ts` 的 `checkpoint.resume_aborted` 臂 —— core 只在本腿递进去的信号**已中止**时铸该码,而那只信号唯一的写者是取消动词(快路 / 耐久旗轮询,句柄登记 `cancel_verb`),所以**只有 core 契约外的形**(桩 / 非 core Runner 直接抛该码,本腿句柄从未中止)才到得了「句柄上读不到成因」这一臂。取消终局照铸,成因取本腿唯一的中止词 `cancel_verb`(取消终局恒带成因由铸点签名保证,不让它缺);detail `task=<taskId>`;计数 = 多少次取消成因是默认值而不是句柄登记的。生产上恒零是证据,不归零 = 有一条不走 core 契约的 Runner 在跑续跑腿。" },
356
365
  "server.runs.terminal-ledger-write-failed": { cls: "F", note: "S-107(全量门 silent-degradation 棘轮抓出的五处静默 `catch {}`):run 终局链里**账本腿**(flush 缓冲 text/reasoning、append `failed` 行)与终态翻转(setTerminal)已不共命运——账本 INSERT 抛不再让 run 卡 running 到 reaper;但那条抛掉的账本行之前是**静默**吞的。detail `arm=` 四臂(flush-clean-cancel / append-clean-cancel / flush-catch / append-thrown-cancel / append-failed);S-947 起续跑腿(`http/resume-legs.ts`)两座同 tag:resume-append-resume-aborted / resume-append-thrown-cancel(取消抛出两臂的 `failed` 行,修前那两臂根本不落账本终局行):缺的是 run_events 尾巴(缓冲文本或 failed 事件),run 行本身仍是 durable truth,读面/reaper 容忍缺失,故 F 类;计数=多少次终局账本写失败。" },
366
+ "server.live-text.hold-diverged": {
367
+ cls: "P-DEBT",
368
+ note: "live 面模型文本的流式口(`trace/model-segment.ts` 的 `createLiveModelText`,三条 live 腿共用;流式口 = `trace/redact.ts` 的 `createRedactionStream`:喂入 / 取已判定前缀 / 收尾冲刷):一段冲刷时,已取出去的逐块字节与整段读法在那一截上的渲染对不上,或有一条整段读法的跨度横跨放行位 —— 流式口的律被打破(某条臂的判定在脱敏器读出的未决尾上界 `PENDING_TAIL_UNITS` 之外落定,而它没有把那一支标成未决尾)。处置:余下的字节照整段读法出(横跨放行位的跨度整枚换记号,不再出它的原文字节),段末帧照发整段脱敏的正文、消费端按它整段替换;但**已经出门**的那几块收不回 —— 其中可能有整段读法会遮的字节(这就是 P 族的理由)。债:未决尾上界是候选池成员判定距离的最大值,正则臂从正则源读出,手写扫描器的那一项是成员处的陈述(它起头要读全的字面锚),不是从扫描器的代码机械读出;已知一形是多遮方向(方案词锚的头值在文本末尾被读成「收在行尾」,其后隔着长过上界的行内空白又跟了词 ⇒ 整段读法不遮、活流已出记号)。钉 = 基线语料逐条按块流过流式口拼接 = 整段脱敏 + 判定距离最远的几形逐码元。detail `family=<text|reasoning> len=<段长>`;计数 = 多少段的律被打破(期望恒 0)。",
369
+ },
357
370
  "server.redact.ppk-eat-to-end": { cls: "F", note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;出口座有界行的 head 读法(只扫一段头)上「窗内无合格终止」是终止可能在窗外的未决 —— 照样遮到窗尾,但不是兜底,不计;计数=多少次整段吃尾。" },
371
+ "server.redact.forensic-level-cap": { cls: "F", note: "脱敏器取证相(redact.ts armPool 第二相)跑满级数界 FORENSIC_LEVEL_MAX 仍没收敛(证据链里层被认出、外层才认得出,刻意交替嵌套的私钥块 / URL 每层多一级,深过 31 层才够得着;现役语料最深 3 级)⇒ 下一级本会重跑的取证臂各自从它读证据的起点遮到窗尾(fail-closed:多遮可见、不少遮),不再升级;每次调用至多一条;影子扫不计;detail from=/len= 是视图坐标。" },
358
372
  "server.approval-card.closed-word-out-of-set": {
359
373
  cls: "F",
360
374
  note: "S-249(`approval-card.ts` 的边界窄读族;今天四个成员:`readRuleOffersAbsence`、S-934 ② 起的 `readAskClassPair`(core 7.37.0 #1214 的类对 `askClass` / `classes`,活卡帧 / 呈卡帧的卡 / 运维队列行同一只,判法 = core 屏 `screenAskClassMembers`(S-955 ⑥ / core 7.38.0 #1249,本仓零自写对判),两键同在同缺,半对 / 集外 / 首员不符 / 重复员 = 屏拒 = 读不出;detail 前缀 `askClass=` / `classes=`),与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛)、S-1170 起的 `classifierNoRuling`(core 7.43.0 #1353 的成因词,活卡帧读 `AskRequest.classifierNoRuling`,闭集筛 = core `isAutoModeFallbackCause`)与 `differs`(core 7.44.0 #1374,载体是 `runs.ts` 的 409 `steering.duplicate_input_id` 体读的引擎拒错 `detail.differs`,闭集筛 = core `STEER_REPLAY_FACETS` 的非空子集;集外成员 / 空 / 非数组 ⇒ 不铸 `detail`,码仍是契约);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` / `classifierNoRuling=` / `differs=` 判别是哪一座;第四个成员是 `approval-content-kind.ts` 的 `readApprovalCard` —— 卡词 `approvalCard` 自己的闭集窄读,活 ask / LOCAL 店停驻门 / SQL 店读出的门子树三个载体共用一只,闭集 = core `TOOL_APPROVAL_CARDS`,detail 前缀 `approvalCard=`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。",
361
375
  },
376
+ "server.approval-card.retired-card-key-read": {
377
+ cls: "F",
378
+ note: "一次性迁移 M37 的读侧(`approval-card.ts` 的 `renameRetiredSourceKey`,存卡的唯一读者入口 `readStoredCardEnvelope` 先调它;窗与删法见 `plugins/one-time-migrations.ts` 登记表 M37 —— 窗关随 M37 删本 tag):存下的审批卡信封(`approval_ask.card_json` / local 问账的同一份信封)上,委派出处键还是引擎 7.61.0 之前的旧名 `card.sourceTaskId` ⇒ 读时改名 `sourceSessionId`(值不变),整卡照读。这不是放行:改名前后是同一个值、同一个事实,只是不改名 `.strict()` 的卡 schema 就整卡读不出(断连重放跳过、回决口按要人、幂等重入走 park)。计数非零 = 有本版之前铸的存卡还在被读(滚动升级窗里旧副本新写的、升级前已结仍被幂等重入 / 兑付读的);窗关前应归零。",
379
+ },
362
380
  "server.approval-card.retired-card-word-read": {
363
381
  cls: "F",
364
382
  note: "S-1562(core 7.51.0 #1515 / #1702;一次性迁移 M18 的**读侧半场**,`approval-card.ts` 的 `readStoredCardEnvelope` / `currentParkArm`,窗与删法见 `plugins/one-time-migrations.ts` 登记表 M18 —— 窗关随 M18 删本 tag):存下的审批卡(`approval_ask.card_json`)或停驻行(`pendingAction` 的 `tool_approval` 臂)上带着引擎 7.51.0 退役的词 —— `ruleOffersAbsence: \"shadowed\"` 或类对里的 `governance_unavailable` —— 读时按 M18 那一只改写读(前者删键 = 合法缺席,后者换成 `rule_unreadable`、类集去重按引擎序)。每改写一次记一次;detail = 载体(`card_json` / `parked row`)与座名,不带值。放行的最坏后果 = 无:这一读与存卡的起服改写对同一份字节给同一个答案(一只函数);它不是兜底,它登记在这里是为了**响亮** —— 载体 `card_json` 计数非零 = 有起服改写够不着的卡(滚动升级窗里 7.128.x 副本新写的卡、local 文件 ask 账);载体 `parked row` 计数非零 = 读到了带旧词的停驻行(引擎写的文档,本服务从不改写它 —— 行摘要覆盖类对 `askClass` / `classes`,改写 = 续跑判 `damaged`;所以这一格在窗内随每次读到那样的行而涨,直到那些行被决掉 / 收割),运维据它判断旧副本是否都已换下、窗关删码前是否还有这种行在飞。反方向(不改写)= 修前:整张存卡读不出(断连重放跳过、幂等重入走 park、回决口按要人)、停驻行的 `shadowed` 读成「读不出 ⇒ 按强制」(拿不到「本会话放行」)。",
@@ -377,7 +395,7 @@ export const FAIL_OPEN_TAGS = {
377
395
  },
378
396
  "server.run-store.corrupt-record-skipped": {
379
397
  cls: "F",
380
- note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;三个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。",
398
+ note: "S-582(`plugins/file-run-store.ts`,local 车道的 File 形 run 店;四个盘面同一只座):盘上一条记录读不出形 —— 一行 `42` / 缺本店必填键的对象 / 中间的半截 JSON(撕尾不走本 tag,那是崩溃的正常落盘形)⇒ 跳过它继续。前三个盘面各自的最坏后果:`run.json` 坏 ⇒ 那条 run 读成**缺席**(`GET /v1/runs/:id` 404,listRuns 少一行;它若占着 claim,EEXIST 路按「行不在」判残骸接管);`events.jsonl` 一行坏 ⇒ 回放少一个事件(其余照读、按 seq 去重不变);claim 记录坏 ⇒ 走既有的撕裂 claim 路(有非终局行就收编,否则隔离出 activeDir)。第四个盘面 `run-blobs/<会话>.jsonl`(`wiring_manifest` 行容器段的块账本,`plugins/wiring-section-codec.ts`)同一只座、两个检测点:① 重放时一条块记录形不合(`digest` 不是串 / `body` 不是容器)⇒ 跳过;② 读口(`getEvents`)装回时清单行引用的块不在本会话账本里(块缺:账本行被删 / 被跳过、断电丢尾、删会话时 run 目录删不掉留下的僵尸 run)⇒ 那一帧这一次不交出(永不交出缺段的清单),每个 `(taskId, seq)` 每进程计一次,`maxSeq` / `retainedFrom` 照旧按存位;明细 `kind=section-ref taskId= seq= sections=<段:摘要,…>`。块账本整份读不出(权限 / 挂载)不走本 tag,与 `events.jsonl` 读不出同一条路(strict 拒启,否则原样抛)。都是**可用性**损失,不开任何门 ⇒ F。修前三面全零披露(店自己的 `typeof … continue` 与 core 的静默跳过);计数让「这台机器的 run 账在腐」在遥测里显形。⚠️ `RUN_STORE_STRICT_HYDRATE=on` 的射程是「文件读不出」,不含「文件里有坏行」—— strict 档下坏行照走本 tag(未改 strict 语义,见普查表第 96 行)。",
381
399
  },
382
400
  "server.resume-anchor.corrupt-record-skipped": {
383
401
  cls: "F",
@@ -441,7 +459,7 @@ export const FAIL_OPEN_TAGS = {
441
459
  },
442
460
  "server.approval-crash-convergence.cause-unreadable": {
443
461
  cls: "F",
444
- note: "7.116.0 修复轮(`approval-crash-convergence.ts` 的 `convergeCrashedAsksAtBoot`;local 车道启动收敛):前世留下的一条 `STREAM_PENDING` 问行,它的卡信封(`card_json`)读不出 ⇒ 读不出 wire `approvalId`、无从向审计账要成因词 ⇒ 成因**回落**成 `crashed_before_park`。行照样收 VOID(决断口照样 410 —— 可决性 fail-closed,不受影响);最坏后果 = 一张其实是被有序关停截断的卡,在 410 体的 `deniedReason` 上说成「进程崩了」(`crashConverged` 读面若有它的审计组,那里的成因仍按审计账判,两处可能不同词)。计数非零 = 问账里有读不出卡信封的行(位腐 / 手改 / 版本错位),去看探针 detail 点名的 askId。读数另在启动日志 `approval_ask_audit_crash_converged` 的 `causeUnreadable` 格单列。",
462
+ note: "7.116.0 修复轮(`approval-crash-convergence.ts` 的 `convergeCrashedAsksAtBoot`;local 车道启动收敛):前世留下的一条 `STREAM_PENDING` 问行,它的卡信封(`card_json`)读不出 ⇒ 读不出 wire `approvalId`、无从向审计账要成因词 ⇒ 成因**回落**成 `crashed_before_park`。行照样收 VOID(决断口照样 410 —— 可决性 fail-closed,不受影响);最坏后果 = 一张其实是被有序关停截断的卡,在 410 体的 `cause` 上说成「进程崩了」(`crashConverged` 读面若有它的审计组,那里的成因仍按审计账判,两处可能不同词)。计数非零 = 问账里有读不出卡信封的行(位腐 / 手改 / 版本错位),去看探针 detail 点名的 askId。读数另在启动日志 `approval_ask_audit_crash_converged` 的 `causeUnreadable` 格单列。",
445
463
  },
446
464
  "server.outbound.hop-member-unreadable": {
447
465
  cls: "F",
@@ -539,11 +557,11 @@ export const FAIL_OPEN_TAGS = {
539
557
  },
540
558
  "server.park.held-card-void-failed": {
541
559
  cls: "F",
542
- note: "停驻没落定就以失败收场的腿(`runs.ts` 的 `voidHeldParkCard`;两处:后台腿 catch 的失败臂 `leg=bg`、续跑腿 catch 放认领的取消 / 普通异常两臂 `leg=resume`):手里的结果是宿主层停驻(引擎交回之前已把卡落进 checkpoint 店,卡在本 run 的会话上),而停驻行 / 用量行 / 结果到手之后的账本写抛 ⇒ 腿以 `failed` 收场、放认领;放认领之前先按卡自己的 scope 店级 `expire` 作废那张卡(动词 = `card-void.ts` 的结算作废,与验证器自己的停驻同一只)。作废没做成 —— 店读 / 作废抛错(`store_error`)、卡的会话与本 run 的会话对不上(`card_mismatch`,只作废那一张,对不上就不动)、本部署没接 checkpoint 店(`no_checkpoint_store`)—— 照样以 `failed` 收场、放认领,每次记一次。放行的最坏后果 = 那张卡回到修前的形:留在待决面上,兑现它的 run 已经 `failed`、认领已放,决定口照样受理、续跑在没有认领的会话上驱动(`resume-legs.ts` 认领缺席成因 ②),由审批兜底收割收走;前提是停驻簿记写失败,并且这张卡没作废成(三个 reason 任一:checkpoint 店这一笔也失败 / 卡与本 run 的会话对不上 / 没接店 —— 后两者不需要第二次写失败)。不是门、不是凭据、不改 run 的判词。计数非零 = 有一次停驻没落定的失败收场留下了一张卡:按 reason 看 —— `store_error` = checkpoint 店在同一拍也出错,`card_mismatch` = 结果里的卡与本 run 的会话对不上,`no_checkpoint_store` = 这台部署没接 checkpoint 店;去看那只会话。detail = `reason=<词> leg=<bg|resume> [err=<原句>]`(不含凭据)。",
560
+ note: "停驻没落定就以失败收场的腿(`runs.ts` 的 `voidHeldParkCard`;两处:后台腿 catch 的失败臂 `leg=bg`、续跑腿 catch 放认领的取消 / 普通异常两臂 `leg=resume`):手里的结果是宿主层停驻(引擎交回之前已把卡落进 checkpoint 店,卡在本 run 的会话上),而停驻行 / 用量行 / 结果到手之后的账本写抛 ⇒ 腿以 `failed` 收场、放认领;放认领之前先按卡自己的 scope 店级 `expire` 作废那张卡(动词 = `card-void.ts` 的结算作废)。作废没做成 —— 店读 / 作废抛错(`store_error`)、卡的会话与本 run 的会话对不上(`card_mismatch`,只作废那一张,对不上就不动)、本部署没接 checkpoint 店(`no_checkpoint_store`)—— 照样以 `failed` 收场、放认领,每次记一次。放行的最坏后果 = 那张卡回到修前的形:留在待决面上,兑现它的 run 已经 `failed`、认领已放,决定口照样受理、续跑在没有认领的会话上驱动(`resume-legs.ts` 认领缺席成因 ②),由审批兜底收割收走;前提是停驻簿记写失败,并且这张卡没作废成(三个 reason 任一:checkpoint 店这一笔也失败 / 卡与本 run 的会话对不上 / 没接店 —— 后两者不需要第二次写失败)。不是门、不是凭据、不改 run 的判词。计数非零 = 有一次停驻没落定的失败收场留下了一张卡:按 reason 看 —— `store_error` = checkpoint 店在同一拍也出错,`card_mismatch` = 结果里的卡与本 run 的会话对不上,`no_checkpoint_store` = 这台部署没接 checkpoint 店;去看那只会话。detail = `reason=<词> leg=<bg|resume> [err=<原句>]`(不含凭据)。",
543
561
  },
544
562
  "server.verifier-pause.card-void-failed": {
545
563
  cls: "F",
546
- note: "验证门结算(`verifier-pause-card.ts` 的 `voidVerifierPauseCard`;三处结算点:bg 组合腿、同步提交腿、续跑 verify 臂):验证器自己的停驻卡(结果上 `verification.verifierPause`)是本服务兑现不了的卡(它在验证器现铸的会话上,`/decide` 读不到续跑输入),结算时按卡自己的 scope 走店级 `expire` 当场作废;作废没做成 —— 店读 / 作废抛错(`store_error`)、卡的会话与停驻报的验证器会话不一致(`card_mismatch`,只作废那一张,对不上就不动)、本部署没接 checkpoint 店却收到了带凭据的验证器停驻(`no_checkpoint_store`)。每次记一次。放行的最坏后果 = 那张卡回到修前的形:留在待决面上点不动(`/decide` 答 409),由审批兜底收割收走;不是门、不是凭据(凭据由出口封口摘,与本臂无关)、不改 run 的判词。计数非零 = 这台部署的 checkpoint 店在出错,或引擎交回的停驻与店里的卡对不上(去看那只会话)。detail = `reason=<词> [err=<原句>]`(不含凭据)。引擎给出「作废验证器卡并释放其会话」的动词后本臂改调它,本 tag 随臂删或改写。",
564
+ note: "验证门结算(`verifier-pause-card.ts` 的 `voidVerifierPauseCard`;四处结算点:bg 组合腿、同步提交腿、续跑 verify 臂、leader 的 worker 结算):验证器自己的停驻卡(结果上 `verification.verifierPause`)是本服务兑现不了的卡(它在验证器现铸的会话上,`/decide` 读不到续跑输入),结算时交跑过这次验证门的那只 Runner 的动词 `voidVerifierPause` 当场作废(引擎读卡上的组合腿,只收组合腿 `verify` 的卡;卡 `expired` + 停驻环境销毁 + 验证器会话放钉)。动词答 `false`(别的结算者先到)不计;动词抛 ⇒ 结算照常往下走,每次记一次:卡上记的不是验证器的腿(`resume.composition_mismatch`)、卡不在 / 没接店(`checkpoint.not_found`)、作废了但会话钉没放掉(`checkpoint.void_unpin_failed`,引擎同时报 `onError`)、店读 / 作废抛错(引擎原样抛,码读不出记 `thrown`)。放行的最坏后果 = 那张卡回到修前的形:留在待决面上点不动(`/decide` 答 409),由审批兜底收割收走;`void_unpin_failed` 时卡已作废、只是验证器会话留在副本内存里直到进程重启;不是门、不是凭据(凭据由出口封口摘,与本臂无关)、不改 run 的判词。计数非零 = 按码看:`thrown` = checkpoint 店在出错;`composition_mismatch` = 结果上的验证器停驻指向一张不是验证器腿铸的卡(去看那只会话);`void_unpin_failed` = 会话店放钉出错。detail = `code=<码 | thrown> err=<原句>`(不含凭据)。",
547
565
  },
548
566
  "server.run-permission-mode.write-back-unpersisted": {
549
567
  cls: "F",
@@ -563,6 +581,14 @@ export function failOpenTagForDroppedFrame(frameType) {
563
581
  return "server.stream.frame-dropped-stream-closed";
564
582
  }
565
583
  }
584
+ export function buildFailOpenAnchor(tag) {
585
+ return `[sema] fail-open: ${tag}`;
586
+ }
587
+ let announceEmitter;
588
+ function announceAsJsonLine(facts) {
589
+ announceEmitter ??= createLogger("warn");
590
+ announceEmitter.warn(buildFailOpenAnchor(facts.tag), { tag: facts.tag, count: facts.count });
591
+ }
566
592
  export const FAIL_OPEN_PROBE_ENV = "SEMA_FAILOPEN_PROBE";
567
593
  const FAIL_OPEN_COUNTER = "fail_open_total";
568
594
  export function createFailOpenRecorder(deps = {}) {
@@ -571,7 +597,7 @@ export function createFailOpenRecorder(deps = {}) {
571
597
  const logged = new Set(deps.seedLogged ?? []);
572
598
  const backlog = new Map(deps.seedBacklog ?? []);
573
599
  const probePath = deps.probePath ?? process.env[FAIL_OPEN_PROBE_ENV];
574
- const writeStderr = deps.writeStderr ?? ((line) => void process.stderr.write(line));
600
+ const announce = deps.writeStderr ?? ((_line, facts) => announceAsJsonLine(facts));
575
601
  const appendProbe = deps.appendProbe ?? ((path, line) => appendFileSync(path, line));
576
602
  deps.metrics?.counter(FAIL_OPEN_COUNTER, "fallback (fail-open) arms traversed, by registered tag — see docs/FAIL-OPEN-CENSUS.md");
577
603
  if (deps.metrics !== undefined && backlog.size > 0) {
@@ -630,7 +656,7 @@ export function createFailOpenRecorder(deps = {}) {
630
656
  if (warned.has(tag))
631
657
  return;
632
658
  try {
633
- writeStderr(`[sema] fail-open: ${tag}\n`);
659
+ announce(`${buildFailOpenAnchor(tag)}\n`, { tag, count: counts.get(tag) ?? 1 });
634
660
  warned.add(tag);
635
661
  }
636
662
  catch {