@sema-agent/server 7.53.0 → 7.55.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 (72) hide show
  1. package/USAGE.md +5 -0
  2. package/dist/approval-ask-audit-store.d.ts +129 -0
  3. package/dist/approval-ask-audit-store.js +284 -0
  4. package/dist/approval-card.d.ts +18 -6
  5. package/dist/approval-card.js +2 -2
  6. package/dist/approval-reconciler.d.ts +2 -1
  7. package/dist/approval-reconciler.js +1 -1
  8. package/dist/boot/config-center.js +2 -2
  9. package/dist/boot/coordinators.d.ts +2 -0
  10. package/dist/boot/coordinators.js +18 -1
  11. package/dist/boot/leader.js +2 -0
  12. package/dist/boot/reapers.d.ts +2 -1
  13. package/dist/boot/resolve-spec.js +1 -3
  14. package/dist/boot/runner-deps.d.ts +20 -0
  15. package/dist/boot/runner-deps.js +14 -8
  16. package/dist/boot/shutdown.js +1 -1
  17. package/dist/boot/stores.js +8 -1
  18. package/dist/capabilities/team.d.ts +10 -9
  19. package/dist/config-center/apply-effective.d.ts +25 -26
  20. package/dist/config-center/apply-effective.js +28 -66
  21. package/dist/config-center/effective-keys.d.ts +30 -0
  22. package/dist/config-center/effective-keys.js +49 -0
  23. package/dist/config-center/facade.d.ts +3 -3
  24. package/dist/config-center/facade.js +1 -1
  25. package/dist/config-center/restart-signal.js +3 -9
  26. package/dist/config-center/types.d.ts +19 -40
  27. package/dist/config-lkg.js +7 -1
  28. package/dist/config-provider.js +1 -1
  29. package/dist/device-enrollment.d.ts +5 -3
  30. package/dist/device-store.d.ts +81 -3
  31. package/dist/device-store.js +37 -0
  32. package/dist/device-ws-hub.d.ts +15 -2
  33. package/dist/device-ws-hub.js +31 -5
  34. package/dist/http/routes/approvals-assistant.js +1 -0
  35. package/dist/http/routes/devices.d.ts +64 -0
  36. package/dist/http/routes/devices.js +173 -0
  37. package/dist/http/server.d.ts +14 -0
  38. package/dist/http/server.js +32 -2
  39. package/dist/http/wire-types.d.ts +19 -0
  40. package/dist/leader/wire.d.ts +12 -0
  41. package/dist/leader/wire.js +6 -5
  42. package/dist/main.js +3 -1
  43. package/dist/observability/fail-open.d.ts +20 -0
  44. package/dist/observability/fail-open.js +20 -0
  45. package/dist/plugins/approval-ask-store-memory.d.ts +11 -1
  46. package/dist/plugins/approval-ask-store-memory.js +20 -3
  47. package/dist/plugins/approval-ask-store-sql.d.ts +82 -0
  48. package/dist/plugins/approval-ask-store-sql.js +41 -10
  49. package/dist/plugins/device-store-sql.d.ts +38 -1
  50. package/dist/plugins/device-store-sql.js +82 -2
  51. package/dist/plugins/pg-pool.d.ts +29 -2
  52. package/dist/plugins/pg-pool.js +45 -2
  53. package/dist/plugins/remote-env-device.d.ts +4 -2
  54. package/dist/plugins/remote-env-device.js +12 -2
  55. package/dist/plugins/roster-store-sql.d.ts +1 -3
  56. package/dist/plugins/sql-errors.d.ts +12 -0
  57. package/dist/plugins/sql-errors.js +10 -0
  58. package/dist/plugins/store-backend.js +1 -1
  59. package/dist/plugins/tidb-pool.d.ts +13 -0
  60. package/dist/runs.js +1 -0
  61. package/dist/runtime-governance.d.ts +4 -0
  62. package/dist/runtime-governance.js +23 -1
  63. package/dist/tool-approval.d.ts +63 -39
  64. package/dist/tool-approval.js +322 -109
  65. package/dist/trace/engine-notice-wire.d.ts +1 -1
  66. package/dist/trace/engine-notice-wire.js +2 -0
  67. package/dist/trace/injection-tier.d.ts +11 -21
  68. package/dist/trace/injection-tier.js +3 -4
  69. package/dist/trace/ledger-events.d.ts +9 -0
  70. package/dist/trace/project.d.ts +1 -0
  71. package/dist/trace/project.js +1 -0
  72. package/package.json +3 -3
@@ -319,7 +319,7 @@ export function createStoreBackend(config, onWarn, onError) {
319
319
  if (config.dbBackend === "pg") {
320
320
  if (!config.pg)
321
321
  return undefined;
322
- const pool = createPgPool(pgPoolOptions(config.pg, config.dbQueryTimeoutMs));
322
+ const pool = createPgPool(pgPoolOptions(config.pg, config.dbQueryTimeoutMs), onWarn);
323
323
  return new PgBackend(pool, config);
324
324
  }
325
325
  if (!config.tidb)
@@ -10,6 +10,19 @@ import type { ServiceConfig } from "../config-types.js";
10
10
  */
11
11
  /** Build the mysql2 pool options from TiDB config (pure → unit-testable apart from the live connection). */
12
12
  export declare function tidbPoolOptions(tidb: NonNullable<ServiceConfig["tidb"]>): mysql.PoolOptions;
13
+ /** ── [ref] 同查结论:mysql2 与 pg-pool **不同病**,本池不需要(也不应)挂进程保命监听 ────────────────
14
+ * pg 侧的病是「idle client 错误 emit 到 Pool 且无监听 ⇒ 未捕获 ⇒ 整进程 exit 1」(修在 pg-pool.ts
15
+ * createPgPool 头注)。mysql2@3.22.6 亲读(不信文档,以实现为准):
16
+ * · `lib/pool_connection.js:14-16` —— PoolConnection 构造器**恒挂** `once('error', () =>
17
+ * this._removeFromPool())`:每条池连接天生有 error 监听(**借出期同在**——不像 pg-pool 借出时
18
+ * 摘监听,mysql2 的 checked-out 连接也无裸奔窗),EventEmitter 不会把 idle 死转 throw,
19
+ * 坏连接就地淘汰;
20
+ * · `lib/base/connection.js` `_notifyError` —— fatal 闩(`if (this._fatalError) return`)挡同一
21
+ * 连接的二次 emit;`_handleFatalError` 摘 stream data 监听 ⇒ 闩后 `protocolError` 结构不可达;
22
+ * · `lib/base/pool.js` / `lib/promise/pool.js` —— 池层与 promise 包装层都**不**把连接 error 再
23
+ * emit 到池对象上(池层只 emit acquire/connection/enqueue/release)。
24
+ * ⇒ 空闲连接被 KILL / 故障切换杀掉 = 库内吞并淘汰,进程不死、池自愈。真 TiDB KILL 钉在
25
+ * `test/pool-idle-error-db-integration.test.ts` 第③格。升 mysql2 大版时按上面三坐标复核。 */
13
26
  export declare function createTidbPool(config: ServiceConfig): Pool;
14
27
  /**
15
28
  * Idempotent schema. `session_event` is the append-only L1 event log (positional slicing by
package/dist/runs.js CHANGED
@@ -400,6 +400,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
400
400
  abortSignal: cancelCtrl.signal,
401
401
  ...(spec.sessionId ? { sessionId: spec.sessionId } : {}),
402
402
  emitCard: createApprovalCardEmitter({ appendDurable: (f) => approvalEmit(f) }),
403
+ emitRevoke: (f) => approvalEmit(f),
403
404
  legKey: "",
404
405
  ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}),
405
406
  }, withQuestion)
@@ -126,6 +126,10 @@ export declare function governanceMandatesShellGateAlways(governance: {
126
126
  autonomy?: Autonomy;
127
127
  manualModeShellGate?: "always" | "classify";
128
128
  }): boolean;
129
+ /** spec 序里的秩(0 起,越大越严)。入参收 `string`(fail-loud 边界:schema `trustRank` 同款姿势)——词表外 ⇒
130
+ * RangeError,绝不 undefined/NaN 让比较静默成 false。导出只为对表钉(test/safety-merge-spec-consumption.test.ts,
131
+ * [ref] 件4)——运行时消费仍走上表(键类型闭集)。 */
132
+ export declare function shellGateRank(word: string): number;
129
133
  /**
130
134
  * Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate) onto a base
131
135
  * `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
@@ -1,4 +1,5 @@
1
1
  import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, DEFAULT_SUBAGENT_TOOL_NAME, parseLeadingCommandName, tightenTaskSpec, } from "@sema-agent/core";
2
+ import { SAFETY_MERGE_SPEC } from "@sema-agent/settings-schema";
2
3
  import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
3
4
  export function isDelegationToolName(name) {
4
5
  return name === DEFAULT_SUBAGENT_TOOL_NAME;
@@ -116,7 +117,28 @@ function createGovernanceShellGateMarkPolicy(marksOf, tools = COARSE_SHELL_TOOLS
116
117
  },
117
118
  };
118
119
  }
119
- const SHELL_GATE_RANK = { off: 0, classify: 1, always: 2 };
120
+ const SHELL_GATE_RANK = (() => {
121
+ const tighten = SAFETY_MERGE_SPEC.shellGate.tighten;
122
+ const rule = tighten.rule;
123
+ if (tighten.rule !== "enumRank")
124
+ throw new Error(`SAFETY_MERGE_SPEC.shellGate.tighten.rule=${rule} — 不是 enumRank,shellGate 秩无从派生`);
125
+ const table = { off: -1, classify: -1, always: -1 };
126
+ for (const [i, w] of tighten.order.entries()) {
127
+ if (!(w in table))
128
+ throw new Error(`SAFETY_MERGE_SPEC.shellGate 序里有本仓词表外的词:${w}`);
129
+ table[w] = i;
130
+ }
131
+ for (const [w, r] of Object.entries(table))
132
+ if (r < 0)
133
+ throw new Error(`SAFETY_MERGE_SPEC.shellGate 序缺词:${w}`);
134
+ return table;
135
+ })();
136
+ export function shellGateRank(word) {
137
+ const r = SHELL_GATE_RANK[word];
138
+ if (r === undefined)
139
+ throw new RangeError(`unknown shellGate word: ${word}`);
140
+ return r;
141
+ }
120
142
  export function applyRuntimeGovernance(base, governance) {
121
143
  const marksOf = () => governance.askMarks ?? currentGovernanceAskMarks();
122
144
  const overrides = { ...autonomyOverrides(governance.autonomy) };
@@ -2,7 +2,8 @@ import type { IncomingMessage } from "node:http";
2
2
  import { type AskRequest, type AskOutcome, type RuleOffer } from "@sema-agent/core";
3
3
  import { type CardRulePersisted, type RuleConsentLane } from "./rules-consent.js";
4
4
  import { type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
5
- import type { ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
5
+ import { type ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
6
+ import type { ApprovalAskAuditSink } from "./approval-ask-audit-store.js";
6
7
  import { type GovernanceAskMarks } from "./governance-ask-marks.js";
7
8
  /** [ref]:审批 gate kind 闭集(core gateMatch 的 `human`/`irreversible_ask` ↔ outcome.gate "policy_ask")。
8
9
  * 此前 7 份手写副本散在两只 checkpoint store 的数组/SQL 字面与 /decide 守卫——core 加审批味 kind 时
@@ -329,8 +330,9 @@ export interface ToolApprovalRunContext {
329
330
  owner: string | null;
330
331
  emit: (frame: ToolApprovalFrame) => void | Promise<void>;
331
332
  /**
332
- * [ref] 车6:**批级撤卡帧**(`approval_revoke`,live only —— 语义见 approval-card.ts 的
333
- * `ApprovalRevokeFrame` 顶注)的投递口。
333
+ * [ref] 车6:**批级撤卡帧**(`approval_revoke`,语义与发射面见 approval-card.ts 的
334
+ * `ApprovalRevokeFrame` 顶注 —— [ref] 起 bg/resume durable 腿的钩子写本腿账本,sync 腿仍 live)
335
+ * 的投递口。
334
336
  *
335
337
  * 🔴 为什么是**独立的可选钩子**而不是给 `emit` 的入参加宽:`emit` 是既有 `tool_approval` 活卡腿的
336
338
  * 通道,它的消费方(装配点 + 全部存量帧断言面)今天只认那两种帧;把入参改成联合会强迫每一个消费点
@@ -339,9 +341,8 @@ export interface ToolApprovalRunContext {
339
341
  * 车4 域)可以与 `approval_request` 的发射点**同批**接上。
340
342
  *
341
343
  * 缺席 ⇒ 本连接不收撤卡帧;壳侧的结构补偿恒是重连 preamble 的全量对账基准。
342
- * (原注拿「`approval_request` 今天也还没接上发射点」作同栏类比 —— 那个类比已被 60846e6 证伪:呈卡口
343
- * 三腿都接上了,而本钩子至今只有 sync 腿接 live 口。两者不再同栏,类比句因此删掉,别再据它推断
344
- * 「两族都还没接线」。)
344
+ * (接线现状:[ref] 起三腿全接 —— sync live 口(`routes/tasks.ts`),bg/resume 两腿 durable
345
+ * append(`runs.ts` / `http/server.ts`),与 `emitCard` 的三腿同批对齐。)
345
346
  */
346
347
  emitRevoke?: (frame: ApprovalRevokeFrame) => void | Promise<void>;
347
348
  /**
@@ -603,6 +604,17 @@ export declare function resolveApprovalLeg(input: {
603
604
  export declare class ToolApprovalCoordinator {
604
605
  private readonly als;
605
606
  private readonly pending;
607
+ /** [ref] codex R3-1 / R4-1:终局阶梯**在飞店调用**(claim + 让路探针读)的计数 —— 按**持久 askId** 记(不按
608
+ * 本地登记闭包:同 askId 的重复登记 `pendingByAskId` 各领一顶帽 = 帽可被登记数绕开,池位按行数才是帽的
609
+ * 本义)。原始店 promise settle(resolve/reject 皆是)归还,归零即删键;帽值 {@link MAX_INFLIGHT_TERMINAL_CLAIMS}。 */
610
+ private readonly terminalClaimsInFlight;
611
+ /** {@link terminalClaimsInFlight} 的唯一写口:申请一格(帽满 ⇒ `undefined`),返回归还闭包。 */
612
+ private admitTerminalClaim;
613
+ /** 某 askId 此刻的在飞终局店调用数(claim + 让路探针)。阶梯据它分「调度争用」与「无人在飞」(R6-1);
614
+ * 判据钩子同读此口。 */
615
+ private terminalClaimsInFlightCount;
616
+ /** 判据钩子(生产路径不调)。 */
617
+ terminalClaimsInFlightForTest(askId: string): number;
606
618
  /** [ref] 车2(codex 交叉复审 round3 抓获真 finding,round5 精化成 Set):次级索引,键 = 持久层 askId
607
619
  * (与 {@link pending} 的 wire-面 uuidv7 `id` 是两条独立的身份轴,顶注同精神)——只在 askStore 在场且
608
620
  * 这只 ask 真有 askId 时才登记。**同一 askId 下可能同时挂着不止一条本地条目**(round5 抓获:
@@ -696,6 +708,11 @@ export declare class ToolApprovalCoordinator {
696
708
  * 构造之后才装配;同款 holder 先例 = main.ts 的 `getRunDenySweep`)。取到 undefined ⇒ 该臂逐字回落
697
709
  * 修前 404,partial 部署安全。 */
698
710
  private readonly parkedRedeem;
711
+ /** [ref] 裁 (c):local 车道的 File ask **审计** sink(在场 ⇔ `DB_BACKEND=local` ∧ 审批面开着,装配点
712
+ * `boot/coordinators.ts`)。三个挂线点:铸造(askBroadcast register 后)/ 决议(`settle()` 公共咽喉)/
713
+ * 腿闭(`runWithContext` finally)。**纯观察面**:sink 自己吞写失败并 `recordFailOpen`,任何一次审计
714
+ * 写都不得改变审批终局(店头注成文)。SQL 车道恒缺席(那里有真 ask 店)。 */
715
+ private readonly askAudit;
699
716
  constructor(opts?: {
700
717
  ttlMs?: number;
701
718
  /** [ref](案A):零活流铸造的 ask 的窗(见 {@link unreachedTtlMs})。装配点在协议**上场**时才传
@@ -721,6 +738,8 @@ export declare class ToolApprovalCoordinator {
721
738
  parkTombstoneTtlMs?: number;
722
739
  /** [ref]:per-principal 子帽(同上不接 env;生效值恒 `min(本值, parkTombstoneMax)`)。 */
723
740
  parkTombstonePerPrincipalMax?: number;
741
+ /** [ref] (c):local 车道审计 sink(见 {@link ToolApprovalCoordinator.askAudit})。 */
742
+ askAudit?: ApprovalAskAuditSink;
724
743
  });
725
744
  /**
726
745
  * [ref] R-13 C —— 「**无人可答**」这一类终局的**唯一**成形口(park 路由 vs deny 政策)。
@@ -732,8 +751,8 @@ export declare class ToolApprovalCoordinator {
732
751
  * (e) 断连 `forceParkNow` / emit 全灭 / 写侧准入超限 —— 走本方法。
733
752
  *
734
753
  * **持久回放的两个口也归它管**(codex R1-F1 验真后收口,红先钉在
735
- * `test/unattended-approval-policy.test.ts`):`convergeFromDurableState` `ensureAsk` 幂等重入,
736
- * 只要回放出来的是 `"unavailable"`(行 PARKING/PARKED)就过这个口。理由:那三处与臂 (a) 是**同一件
754
+ * `test/unattended-approval-policy.test.ts`):claim 败方真相的结算([ref] `settleFromClaimLoss`,前身
755
+ * `convergeFromDurableState`)与 `ensureAsk` 幂等重入,只要回放出来的是 `"unavailable"`(行 PARKING/PARKED)就过这个口。理由:那三处与臂 (a) 是**同一件
737
756
  * 事**(本 ask 的等待以「行进了投递面」收场,赢家可能是别的副本 / 上一次调用),不统一的话同一只 ask
738
757
  * 的终局会取决于「谁赢了那把 CAS」「重试没重试」—— 比残余更坏的不可解释性。人的**真决议**(回放出
739
758
  * `true`/`false`)当然不过这个口:那不是「无人可答」。
@@ -842,30 +861,6 @@ export declare class ToolApprovalCoordinator {
842
861
  * 面)—— 把它混进本名单会让「同 askId 的重复本地注册」拿到裸 deny,而赢家拿的是 park 路由:同一条
843
862
  * 持久行、两种 live 终局。那一支改走 {@link settleSameAskIdParkRoute}。 */
844
863
  private settleVoidedSiblings;
845
- /**
846
- * [ref] codex R3-F1 —— 「行上此刻有没有一个**真决议**」的一次性窄读(窗到期臂的店报错支专用)。
847
- *
848
- * 返回 `true`/`false` = 行是 `DECIDED` 且决议是 approve/deny(**人的**裁决,或车4 端点代人落的那一次);
849
- * `undefined` = 其它一切(读失败、行不在、行是 STREAM_PENDING/PARKING/PARKED/DENIED/VOID)——调用方按
850
- * 自己的臂继续。判别复用 {@link replayTerminalAskRow}(不造第二套映射),这里只把它的**布尔臂**取出来:
851
- * `"unavailable"`(PARKING/PARKED)刻意**不**在本口消费,那属于路由面、归 `unattendedPolicy` 管。
852
- *
853
- * 一次读 + 墙钟 deadline(不重试):店已经在报错的语境里,重试只会把窗到期的收尾拖成秒级,而这一口要
854
- * 回答的问题(「有没有人已经答过了」)读一次就够 —— 读不到就按「不知道」处置,与本臂原语义一致。
855
- *
856
- * ⚠️ **已登记的残余(codex R4-F1/F2,验真后不在本批修)**:本读与随后的本地 settle 之间没有持久 CAS,
857
- * 所以「读到 STREAM_PENDING、另一副本**随后**才提交 DECIDED」这条窄窗仍会得到 park/deny 终局(行是
858
- * approve 而本腿不执行);`emit` 全灭臂与取消臂的 store-未知态 catch 有**同形**缺口(两者都早于本件、
859
- * 各自有既有判据钉着)。方向仍是 fail-closed(没有任何东西在未经人批时执行),补偿是 core 侧那张仍然
860
- * pending 的 checkpoint —— 人再批一次就走。真正的收口 = 把窗/emit-全灭/取消三条 ambiguous-CAS catch
861
- * 统一到一次**持久**恢复流程(带 CAS 与终态回读),那是店语义级改动,须由本模块属主立设计件,施工车
862
- * 不私开(本批已试过整段复用 `convergeFromDurableState`,被自己的全量跑打回,理由见其顶注)。
863
- *
864
- * ✅ **第二条残余已收口([ref],2026-08-19 登记 → 2026-08-31 [ref] 销)**:本口读出的放行此前交给
865
- * `settle` 时第三形参 `updatedInput` 恒缺席(行上没有那一列),core 落回原始实参执行 = 执行面**宽于**
866
- * 人所批准。现在行上有 `updated_input` 列,本口回 `{allowed, updatedInput?}` 两件,`settle` 拿到的就是
867
- * 人批准的那份编辑;P-DEBT tag 随列撤线(fail-open.ts 撤线备注 + census 第 31 行终态)。
868
- */
869
864
  /**
870
865
  * 🔴 session grant 短路前的**持久出处复核**([ref] 的 codex R1-[high] 补丁;[ref] 换判据)。
871
866
  *
@@ -904,8 +899,34 @@ export declare class ToolApprovalCoordinator {
904
899
  * 多半头一次到,那等于把一揽子放行整只废掉。窗的上界由本判据定死:行**一提交**窗即关。
905
900
  */
906
901
  private sessionGrantUncontestedByRow;
907
- /** [ref]:返回值从裸布尔改成「方向 + 编辑载荷」(行带 `updatedInput` ⇒ 一并交出;`undefined` = 不知道)。 */
908
- private readDecidedForRecovery;
902
+ /**
903
+ * [ref]:claim 败方读数的分类器 —— **零读**(真相已随 `claimed:false` 回来,384 §4.3-1「settle 自足」),
904
+ * 把 {@link AskTerminalClaimCurrent} 翻译成 {@link RecoveryRead} 五态。两处消费 —— `resolveLocalOutcome`
905
+ * 主阶梯与三臂的迟到回调(迟到输同样自足,律 3)—— 共用这**一份**映射(`replayTerminalAskRow` 仍是唯一的
906
+ * 三态行映射,这里只把它接到结算动作上)。
907
+ *
908
+ * 沿革:[ref] 时代这里是恢复读口 `readRowForRecovery`(一次带墙钟的 `getAsk`,七态含 pending/unreadable),
909
+ * 前身 `readDecidedForRecovery`([ref] codex R3-F1)。「CAS 与读真相是两次店往返」= [ref]-R1 残余窗的病根
910
+ * ([ref]①):CAS 迟到干净输时真相不在答案里,补读又撞店不可用 ⇒ 人类决议丢失。[ref] 把读口整个撤掉:
911
+ * 争终局与读真相并成店的一次原子 claim,分类器只剩纯函数半场。[ref](行派生 approve 丢编辑)已随
912
+ * [ref] 落列收口:`approved` 臂带行上的 `updatedInput`。
913
+ *
914
+ * 行派生 approve 的两道守卫**在这里**施加(五处消费一份判据,见 {@link guardRowDerivedApprove}):
915
+ * · 弃单墓碑在场 ⇒ `parked`(park 路由,可逆可再批 —— 绝不把一个证不出载荷的放行交出去);
916
+ * · 编辑在飞 ⇒ `edit-in-flight`(codex R4-[critical] 同族口:载荷还在别人手里的那一拍按行结算 = 用裸
917
+ * `true` 抢在它前面;持有者随后必定自己结算)。
918
+ */
919
+ /**
920
+ * [ref] 让路探针(**只读、不争、不定局**):编辑在飞标记在场时 claim 阶梯刻意**不重打** claim(顶注:本臂
921
+ * 抢赢会把持有者正在提交的带编辑批准翻成 park),但一次**已落盘**的决议不该等到标记放开才被消费 ——
922
+ * codex [ref] R3-F1 的真窗:持有者回读慢于让路预算而标记仍在看门狗内 ⇒ 让路用尽走 park = 把已落盘批准
923
+ * 翻成 unavailable。探针 = 一次带墙钟的 `getAsk`;它**不是**败方真相的来源(那是 claim 答案,律 1),
924
+ * 只在 claim 被扣住的那几拍上不干扰地看一眼行:终态 ⇒ 交分类器(带载荷 approve 当拍兑现 / 负终态、
925
+ * PARKING 当拍结算 / 裸 approve 继续让);pending、行不在、读不出 ⇒ `undefined`,继续让路(有界)。
926
+ * 它只能**提前**兑现一个已落的决议,永远不会按意图定局 —— 这是它与 [ref] 恢复读口的本质差别。
927
+ */
928
+ private probeRowWhileYielding;
929
+ private classifyClaimLoss;
909
930
  /** [ref] codex R3-F2:同一 askId 下**其余本地注册**(重复注册,见 {@link pendingByAskId} 顶注)按
910
931
  * **park 路由**收尾 —— 行已 PARKING,它们看的是同一条行,终局必须与赢家同形(`unattendedPolicy`
911
932
  * 由各自的闭包读同一个口,deny 部署上一起是 deny)。赢家自己此刻已 settle 过、已从集合里摘除,
@@ -951,11 +972,13 @@ export declare class ToolApprovalCoordinator {
951
972
  settled: number;
952
973
  };
953
974
  /**
954
- * [ref] 车6:把一张**批级撤卡帧**投给给定的一组连接(live only —— 见 `ApprovalRevokeFrame` 顶注:
955
- * 撤卡帧不进 durable tail,丢帧的结构补偿是重连 preamble 的全量对账基准)。
975
+ * [ref] 车6:把一张**批级撤卡帧**投给给定的一组连接( ctx 分发 —— 见 `ApprovalRevokeFrame` 顶注:
976
+ * [ref] durable 腿的钩子把帧落进本腿账本、sync 腿仍 live;丢帧的结构补偿是重连 preamble
977
+ * 全量对账基准)。
956
978
  *
957
979
  * 空名单不发(零信息的帧只会让壳多一次无意义的对账)。每路独立 catch:一路 emit 失败(连接刚死、
958
- * durable-append 目标抖动)**绝不回滚已经落定的持久 CAS** —— 帧是通知,行才是真源。
980
+ * durable-append 目标抖动)**绝不回滚已经落定的持久 CAS** —— 帧是通知,行才是真源。[ref] 的 durable
981
+ * append 失败同落这只 catch(不新铸失败臂;账本行 VOID 是真源,撤帧丢失由 preamble 对账补偿)。
959
982
  */
960
983
  private emitRevokeTo;
961
984
  /**
@@ -1252,8 +1275,9 @@ export declare class ToolApprovalCoordinator {
1252
1275
  *
1253
1276
  * R6 把弃单墓碑的兑现点收在 {@link notifyExternalDecision} 上,并自称「公共咽喉」—— 那句话**不成立**:
1254
1277
  * 树上还有三处**直接** `settle(true, …)` / 直接把 `true` 交回 core 的行派生回放(`convergeFromDurableState`
1255
- * 的收敛臂、`ensureAsk` 幂等命中终局行的回放、窗到期报错臂的 `readDecidedForRecovery`),它们跳过那个
1256
- * 口。于是弃单之后从这三条路上仍能放出原始命令。
1278
+ * 的收敛臂、`ensureAsk` 幂等命中终局行的回放、窗到期报错臂的 `readDecidedForRecovery` —— [ref] 起前者与
1279
+ * 后者合并成 `readRowForRecovery` 一个读口、三臂共用;[ref] 起读口撤销,同一份判据长在 claim 败方读数的
1280
+ * 分类器 `classifyClaimLoss` 里),它们跳过那个口。于是弃单之后从这三条路上仍能放出原始命令。
1257
1281
  *
1258
1282
  * 收口形 = **一个判据函数、五处消费**(上面三处 + 轮询 + HTTP 观察者):把「行说 approve」这件事
1259
1283
  * 在弃单墓碑在场时翻译成 `"unavailable"`(park 路由,可逆可再批),其余取值逐字透传。