@sema-agent/server 7.100.0 → 7.101.1

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 (107) hide show
  1. package/MIGRATION.md +22 -1
  2. package/README.md +12 -1
  3. package/README.zh-CN.md +9 -1
  4. package/USAGE.md +80 -9
  5. package/dist/approval-card.d.ts +24 -12
  6. package/dist/approval-card.js +4 -3
  7. package/dist/approval-policy-settlement.d.ts +31 -0
  8. package/dist/approval-policy-settlement.js +16 -0
  9. package/dist/approval-reconciler.d.ts +8 -1
  10. package/dist/approval-reconciler.js +5 -4
  11. package/dist/boot/config-center.js +86 -20
  12. package/dist/boot/execution-env.js +20 -2
  13. package/dist/boot/parked-revive-gate.js +1 -0
  14. package/dist/boot/reapers.js +1 -0
  15. package/dist/boot/resolve-spec.js +25 -8
  16. package/dist/boot/runner-deps.js +9 -3
  17. package/dist/boot/stage-02-registry-pull.js +9 -0
  18. package/dist/boot/stage-10-http-server.js +2 -0
  19. package/dist/boot/stores.js +4 -0
  20. package/dist/config-catalog.js +5 -3
  21. package/dist/config-center/apply-effective.d.ts +25 -0
  22. package/dist/config-center/apply-effective.js +144 -69
  23. package/dist/config-center/apply-ledger.d.ts +12 -2
  24. package/dist/config-center/apply-ledger.js +4 -1
  25. package/dist/config-center/facade.d.ts +1 -1
  26. package/dist/config-center/facade.js +1 -1
  27. package/dist/config-center/http-client.d.ts +4 -1
  28. package/dist/config-center/http-client.js +15 -10
  29. package/dist/config-center/read-warnings.d.ts +115 -6
  30. package/dist/config-center/read-warnings.js +115 -5
  31. package/dist/config-center/types.d.ts +6 -0
  32. package/dist/config-invariants.d.ts +2 -2
  33. package/dist/config-invariants.js +15 -0
  34. package/dist/config-provider.d.ts +4 -0
  35. package/dist/config-provider.js +64 -9
  36. package/dist/config-types.d.ts +37 -8
  37. package/dist/config.js +87 -22
  38. package/dist/deployment-governance.d.ts +45 -12
  39. package/dist/deployment-governance.js +37 -9
  40. package/dist/env-name-allowlist-knobs.d.ts +37 -0
  41. package/dist/env-name-allowlist-knobs.js +24 -0
  42. package/dist/fleet/fleet-bus.d.ts +30 -4
  43. package/dist/fleet/fleet-bus.js +20 -4
  44. package/dist/fleet/fleet-reconciler.d.ts +29 -6
  45. package/dist/fleet/fleet-reconciler.js +109 -79
  46. package/dist/fleet-client.d.ts +2 -1
  47. package/dist/hooks/hook-llm.js +13 -4
  48. package/dist/host-lsp-manager.d.ts +14 -2
  49. package/dist/host-lsp-manager.js +7 -6
  50. package/dist/http/admission.js +22 -7
  51. package/dist/http/memory-store-refusal-reply.d.ts +24 -0
  52. package/dist/http/memory-store-refusal-reply.js +10 -0
  53. package/dist/http/routes/approvals-assistant.js +1 -0
  54. package/dist/http/routes/capabilities.js +3 -3
  55. package/dist/http/routes/memory-bundle.js +3 -0
  56. package/dist/http/routes/memory-compliance.js +3 -5
  57. package/dist/http/routes/memory-origin.js +3 -5
  58. package/dist/http/routes/sessions.js +7 -6
  59. package/dist/http/routes/side-query.js +10 -4
  60. package/dist/instruction-source-notice.d.ts +97 -0
  61. package/dist/instruction-source-notice.js +78 -0
  62. package/dist/memory-layer-legacy-lock.d.ts +14 -0
  63. package/dist/memory-layer-legacy-lock.js +74 -0
  64. package/dist/memory-operator-faces.js +1 -17
  65. package/dist/memory-store-refusal.d.ts +37 -0
  66. package/dist/memory-store-refusal.js +14 -0
  67. package/dist/model-entry-refusal.d.ts +130 -0
  68. package/dist/model-entry-refusal.js +157 -0
  69. package/dist/model-provider.d.ts +9 -3
  70. package/dist/model-route-endpoint.d.ts +1 -1
  71. package/dist/model-select.d.ts +49 -4
  72. package/dist/model-select.js +48 -17
  73. package/dist/observability/fail-open.d.ts +13 -1
  74. package/dist/observability/fail-open.js +13 -1
  75. package/dist/observability/metrics.js +1 -0
  76. package/dist/observability/secret-env-scrub.d.ts +27 -4
  77. package/dist/observability/secret-env-scrub.js +22 -7
  78. package/dist/plugins/caching-session-store.d.ts +6 -0
  79. package/dist/plugins/caching-session-store.js +6 -0
  80. package/dist/plugins/local-session-store.d.ts +20 -25
  81. package/dist/plugins/local-session-store.js +111 -64
  82. package/dist/plugins/pg-session-storage.d.ts +7 -0
  83. package/dist/plugins/pg-session-storage.js +19 -0
  84. package/dist/plugins/remote-env-host.d.ts +3 -1
  85. package/dist/plugins/remote-env-host.js +3 -6
  86. package/dist/plugins/tidb-pool.js +5 -0
  87. package/dist/plugins/tidb-session-store.d.ts +13 -0
  88. package/dist/plugins/tidb-session-store.js +14 -0
  89. package/dist/plugins/web-search.d.ts +2 -1
  90. package/dist/plugins/web-search.js +43 -24
  91. package/dist/project-memory.d.ts +10 -0
  92. package/dist/project-memory.js +28 -14
  93. package/dist/run-local.d.ts +17 -15
  94. package/dist/run-local.js +115 -15
  95. package/dist/sealed-key.d.ts +5 -2
  96. package/dist/server-secret-env.d.ts +23 -1
  97. package/dist/server-secret-env.js +9 -0
  98. package/dist/tool-approval.d.ts +74 -31
  99. package/dist/tool-approval.js +102 -49
  100. package/dist/trace/core-keyset-guard.d.ts +27 -5
  101. package/dist/trace/engine-notice-wire.d.ts +27 -1
  102. package/dist/trace/engine-notice-wire.js +8 -0
  103. package/dist/trace/wire-projection-faces.d.ts +2 -2
  104. package/dist/trace/wire-projection-faces.js +2 -2
  105. package/package.json +2 -2
  106. package/dist/memory-layer-preflight.d.ts +0 -46
  107. package/dist/memory-layer-preflight.js +0 -121
@@ -15,4 +15,13 @@ export function scrubServerSecrets(env, findings) {
15
15
  }
16
16
  return out;
17
17
  }
18
+ export function allowlistedEnv(names, env) {
19
+ const out = {};
20
+ for (const name of names) {
21
+ const value = env[name];
22
+ if (value !== undefined)
23
+ out[name] = value;
24
+ }
25
+ return out;
26
+ }
18
27
  //# sourceMappingURL=server-secret-env.js.map
@@ -124,7 +124,8 @@ export interface StandingAnswerFacts {
124
124
  * **自己**的位 —— core 每一次过闸(含 failover / 重启后的重入)都由 `runGateLanes` 里的 `stampAskOrigin`
125
125
  * 重新盖、经同一处的 `carryOf` 带上 `AskRequest`,读侧手里的 `req` 恒是这一次过闸的真值。`governanceForced`
126
126
  * 需要行读,是因为它来自本仓的**进程内**治理标表:failover / 重启后幂等命中既有行时本地表恒空,行上的卡才
127
- * 存着落库那一刻的判定。何况行上本来就没有 `mandated`(耐久面不造孪生座)—— 读了也读不到。
127
+ * 存着落库那一刻的判定。(那张「行」是本仓的 ask 账本行,不是 core 的 park 行;core 7.31.0 #1079 起 park 行上的
128
+ * `mandated` 孪生座由**耐久** `/decide` 腿读,见 {@link parkRowStandingAnswerFacts}。)
128
129
  */
129
130
  export declare function standingAnswerMayRetire(facts: StandingAnswerFacts): boolean;
130
131
  /**
@@ -138,19 +139,19 @@ export declare function standingAnswerMayRetire(facts: StandingAnswerFacts): boo
138
139
  * 本读者这里只**收窄**(失真两个方向的代价登记在 `governanceMandatesShellGateAlways` 顶注)。⚠️ 它**只覆盖 shell 门的
139
140
  * `always` 档** —— live 腿的治理标来自进程内标记表(敏感写路径 / `commandPolicy` / `AUTONOMY` 内层策略 / shellGate 各档),
140
141
  * park 行上唯一的取证格只有 `riskDescriptor.shellGateDoctrine`(见下方 🚧);
141
- * · `requiresRealApproval` —— 行上 core 铸的那一位,经 `approval-card.ts` 的 {@link readRequiresRealApprovalSeat}(与运维
142
+ * · `requiresRealApproval` —— 行上 core 铸的那一位,经 `approval-card.ts` 的 {@link readParkRowTrueSeat}(与运维
142
143
  * 队列 `parkRowFacts` 同一只读法,`=== true`)。刻意**不**经 `parkRowFacts` 本身:那只展示投影顺手对 `ruleOffersAbsence`
143
144
  * 的集外词计展示面 F 类 tag,而本读者对同一格是 fail-closed(S-663 codex r1 钉「权限面不计展示面 tag」);
144
- * · `mandated` —— 行上**没有**这一位(core 7.30.1 `PendingAction` 无 `mandated`,#1079 候 7.31.0),只读得到强制的子集:
145
- * `ruleOffersAbsence` 经三态窄读 {@link readRuleOffersAbsenceSeat},`"mandated"` **或读不出**(集外词 / 非串值,世代差)
146
- * ⇒ `true`(fail-closed 不变:一个本仓不认识的新词可能正是一种强制因由)。
145
+ * · `mandated` —— **S-663 二段(core #1079,7.31.0 / S-723)**:行上 core 铸的孪生座 `PendingAction.tool_approval.mandated`,经
146
+ * **同一只** {@link readParkRowTrueSeat}(`=== true`);**或**强制子集 `ruleOffersAbsence` 经三态窄读
147
+ * {@link readRuleOffersAbsenceSeat} 读成 `"mandated"` / 读不出(集外词 / 非串值,世代差)⇒ `true`。两臂是**两个事实**的析取,
148
+ * 不是同一个事实的新旧两种读法:core 对 `"mandated"` 这一词的定义就是「no persisted rule could ever clear this ask」(强制的
149
+ * 充分条件),而 #1079 之前铸的行(7.30.x 引擎 park、升级后才被 `/decide`)没有孪生座 —— 只读孪生座会让这类在飞行照旧落豁免,
150
+ * 是对上一版的回归;读不出按强制是 fail-closed(一个本仓不认识的新词可能正是一种强制因由)。
147
151
  *
148
- * 🚧 **剩余不对称两处**(耐久腿读得到的事实比 live 腿少,读不到的一律照旧可记):
149
- * ① **`mandated` 孪生位(候 core #1079)**:一张强制卡若行上词为 `shadowed` / 缺席、又无 `requiresRealApproval` 与治理出身
150
- * (非 Bash 工具 / 车道未武装 / 匿名部署上的规则强制 —— core 只在「车道武装 ∧ `Bash` ∧ 有 principal」时铸那一词,且
151
- * `ruleClearanceOf` 的词序让 `persistedRuleShadowed` / `matchedAskRule` 先得 `"shadowed"`),本折法读不到它的强制。#1079 到货后
152
- * 改的**只是 `mandated` 这一格**(改读行上那一位),谓词与调用点零改(`test/approval-exemption-http.test.ts` 的 🚧 格按 #1079
153
- * 行形钉着);
152
+ * 🚧 **剩余不对称一处**(耐久腿读得到的事实比 live 腿少,读不到的一律照旧可记):
153
+ * ① (已关,S-663 二段)`mandated` 孪生位 —— core #1079 到货,行上词为 `shadowed` / 缺席的强制卡由孪生座读到
154
+ * (`test/approval-exemption-http.test.ts` 原 🚧 格已按钉翻成「false + 零行」);
154
155
  * ② **非 shell 治理(候 S-707)**:治理位经 `governanceOriginOf` 只覆盖 shell 门 `always` 档;敏感写路径
155
156
  * (`SENSITIVE_WRITE_PATTERNS`)、`commandPolicy`、`AUTONOMY` 内层策略产的门与 shellGate `classify` 档,park 行上没有取证格
156
157
  * (live 腿靠进程内标记表,跨副本 / 重启后够不着),本折法读不到它们的治理出身。
@@ -224,16 +225,19 @@ export interface ToolApprovalFrame {
224
225
  * 车道**有什么可给,而那条车道对它不说话的工具、**没接规则店的部署**(本仓的 live-only 形)、装不下规则的
225
226
  * 任务都是结构性沉默 —— 它的缺席**不是**断言。本键是这一问**自己**的属性,凡强制皆在场。读错的代价 core
226
227
  * 实测过:同一条根外读命令在 shell 工具上是强制问、在 monitor 工具上看起来是普通问。
227
- * (唯一例外是**手里没有本键**的那条腿:耐久 `/decide` 腿只有 park 行,S-663 / NP-1 只读那一词的**在场**、从不读它的
228
- * 缺席 —— 见 {@link parkRowStandingAnswerFacts},子集与剩余不对称写在那里。live 腿恒读本键。)
228
+ * (耐久 `/decide` 腿手里只有 park 行:core 7.31.0 #1079 起行上有本键的孪生座 `PendingAction.tool_approval.mandated`,
229
+ * S-663 二段读它;#1079 之前铸的行没有孪生座,那条腿仍读那一词的**在场**(`"mandated"` ⇒ 强制)、从不读它的缺席 —— 见
230
+ * {@link parkRowStandingAnswerFacts}。live 腿恒读本键。)
229
231
  *
230
232
  * 🔴 **它不是 `requiresRealApproval`**:强制只说「没有规则能清掉」,**不**说「没有应答者可以答」——
231
233
  * 一个人、一只 hook、一张一揽子 `onAsk:"allow"` 席照常答得了强制卡。两键别互推。
232
234
  *
233
- * 🔴 **真才带,缺席绝不编 `false`**(`governanceForced` / `requiresRealApproval` 同形)。缺席同时覆盖
234
- * 「不是强制」「老引擎」「这是一张从 park 行上重铸的耐久卡」三形 —— **耐久面上本仓不造孪生座**:core 的
235
- * 寄存行(`PendingAction`)没有这一位(KL-997a 亲读登记),行上强制表现为 `ruleOffersAbsence:"mandated"`
236
- * 且无 `ruleOffers`;在 `card_json` 侧自铸一个就是本仓成了这个事实的第二个源(而 park 那侧根本没有输入)。
235
+ * 🔴 **真才带,缺席绝不编 `false`**(`governanceForced` / `requiresRealApproval` 同形)。缺席覆盖「不是强制」「老引擎」
236
+ * 两形。**本仓从不自铸本键**:活卡帧(与 `livePending` 行顶层,T4①)只读 `AskRequest.mandated`;耐久面(运维队列行 / inbox 行)
237
+ * 自 core 7.31.0 #1079 起读 core 在 park 行上铸的孪生座(`PendingAction.tool_approval.mandated` / `CheckpointSummary.mandated`,
238
+ * S-663 二段),同样只投 core 给的那一位 —— 自铸一个就是本仓成了这个事实的第二个源。⚠️ **`card_json` 卡面不带本键**
239
+ * (`ApprovalCardSchema` 没有这一位,`.strict()`)⇒ 断连重放的 `approval_request` 帧(`buildReplayFrame` 只回放卡)上它恒缺席,
240
+ * 消费方读队列行 / 活卡帧 / `livePending` 行(8D-6:此前这里写的「`card_json` 只读 `AskRequest.mandated`」是错句,T4② 车OC 改真)。
237
241
  * 🔴 **server 不判「是不是强制」**(判定属主在 core 的闸站),只**读**闸站盖好的这一位 —— 读者恰一个:
238
242
  * S-660 起的会话级放行判据 {@link standingAnswerMayRetire}(会话授权是**上一次**应答记下的站着的答案,
239
243
  * 不是对这一次调用的应答 ⇒ 强制卡上既不被既有 grant 代答、也不铸 grant)。上一段「一揽子 `onAsk:"allow"`
@@ -522,6 +526,10 @@ export interface LivePendingRow {
522
526
  sessionId?: string;
523
527
  requiresRealApproval?: true;
524
528
  governanceForced?: true;
529
+ /** T4①(八病审计 8D-5,车OC / 7.101.0):强制位([ref],`AskRequest.mandated`)—— 与上两位**同层同形**(真才带,缺席不编 false),
530
+ * 值 = 条目上那一位(`askBroadcast` 铸条目时取自这一次过闸的 `req.mandated`,与 {@link frame} 上的同名键同源)。此前只在
531
+ * `frame` 里:同一个 `GET /v1/approvals` 响应的 durable `pending[]` 行顶层带它(S-663 二段),live 行却要进帧里读。 */
532
+ mandated?: true;
525
533
  /** 发起者为委派子代(originTaskId 在场)。 */
526
534
  fromSubagent?: true;
527
535
  /** [ref] C2(additive):委派子代的 **canonical taskId**。值域精确成文(codex R1-F3 问询后按 core dist
@@ -1086,19 +1094,23 @@ export declare class ToolApprovalCoordinator {
1086
1094
  * 的终局会取决于「谁赢了那把 CAS」「重试没重试」—— 比残余更坏的不可解释性。人的**真决议**(回放出
1087
1095
  * `true`/`false`)当然不过这个口:那不是「无人可答」。
1088
1096
  *
1089
- * 🔴 **不**归它管的一类(刻意,不是遗漏):`ensureAsk` **身份未定 / 坏行**两臂 —— 那里的 park 路由是对
1090
- * **身份不确定**的处置(与「有没有人」无关),且背后可能正躺着一条真行。于是 `deny` 部署上仍可能落
1091
- * 极少数 park(店抖动期),已知且有意的残余,成文在 config-types 的域注。
1097
+ * **`ensureAsk` 的两条兜底臂也归它管**(S-702,7.100.0 合并复审 opus 腿 E1;此前两臂直接交裸 `"unavailable"`,是这句
1098
+ * 「唯一成形口」的例外):行不可用(`persisted_row_unusable`:信封形不合 / deadline 越界)与身份不定(`ensure_indeterminate`:
1099
+ * ensure 失败且复核读全失败)。两臂的**处置**不变 —— 不编造 wire 身份、不在身份未定时呈卡收人决(fail-closed)—— 变的只是
1100
+ * 「不呈卡之后交什么」:它们同样是「这只 ask 没有人能答」,于是同样过这个口(park ⇒ `"unavailable"` 逐字不变;deny ⇒
1101
+ * 部署政策拒对象),交出去的是**结算**时再经 {@link landUnattendedOutcome} 把同一个结算落在行上。修前 deny 部署在这两形下
1102
+ * 仍 durable park(与「不积压挂起 run」的自声明相反)。
1092
1103
  *
1093
1104
  * 🔴 **`deny` 部署上政策结算当场落行**(NP-2,7.100.0 合并复审 [ref] 交叉表 #4;codex R1-F1 当年**不采纳**的那一半
1094
1105
  * 「给 deny 造一条原子 deny 终态转移」在这里收了):窗到期 / emit 全灭两臂在 deny 政策下的终局 claim 是
1095
- * `refuse` 意图(见 {@link unattendedClaimIntent})—— STREAM_PENDING→DECIDED(deny) + `settled_by = "policy"`,**按 ask
1106
+ * `refuse` 意图(政策→意图的唯一映射在 `approval-policy-settlement.ts` 的 `settleByPolicy`)—— STREAM_PENDING→DECIDED(deny) + `settled_by = "policy"`,**按 ask
1096
1107
  * 作用域**(不碰批、不连坐兄弟)。落的来源词与本口交给 core 的是**同一个常量**(`POLICY_REFUSAL_DECISION`),于是
1097
1108
  * 同 ask 重入 / 收敛后重入由 `outcomeOfDecidedRow` 读回同一个对象。当年「危害为零」的论证(PARKING 行绑不上、
1098
1109
  * 自行收敛成 DENIED/VOID)在 S-689 之后不再成立:首判交 `{allow:false, settledBy:"policy"}`,行停 PARKING 的回放
1099
1110
  * 却交裸 `false`(收敛后 DENIED 无来源)—— core 当成人拒、可停批,两次判决第一次可被观测地分家。
1100
- * 仍按 PARKING 记账的只剩两形(都不是本进程这一次政策结算):别的写者(park 政策副本 / 切换政策之前)推进过的行,
1101
- * 与崩溃后收敛器孤儿腿代打 expire 的行 —— 前者重入走本口(`replayed_parked`),后者登记在收车档的未闭环项。
1111
+ * S-702 / S-706 把同一只 claim 接到了另外三处:两条兜底臂({@link landUnattendedOutcome})与崩溃恢复腿(收敛器孤儿段,
1112
+ * 它是窗到期竞争者在崩溃后的补位)—— 四处调的是**一只** `settleByPolicy`,谁也不自己挑意图。deny 部署上仍按 PARKING
1113
+ * 记账的只剩一形:别的写者(park 政策副本 / 切换政策之前)推进过的行,重入走本口(`replayed_parked`)。
1102
1114
  * deny 政策下批仍是**单 ask 批**(见 {@link batchIdFor}):`refuse` 本来就不碰批,单 ask 批让新行与存量整腿批行
1103
1115
  * 的差别只剩「批 id 是谁」,结算语义一致。
1104
1116
  */
@@ -1117,14 +1129,6 @@ export declare class ToolApprovalCoordinator {
1117
1129
  * 读的是政策**读**口(不落遥测)。
1118
1130
  */
1119
1131
  private batchIdFor;
1120
- /**
1121
- * 窗到期 / emit 全灭两臂的**终局 claim 意图**(NP-2)—— 与 {@link batchIdFor} 读同一个政策读口:
1122
- * · park 路由 ⇒ `expire`(行进投递面、批转 ROUTING_UNBOUND、兄弟连坐,core 随后 park);
1123
- * · deny 政策 ⇒ `refuse`(按 ask 作用域当场落 DECIDED(deny) + `settled_by="policy"`,与 {@link unattendedOutcome}
1124
- * 交给 core 的那一只同一个来源词 —— 回放 / 收敛后重入读回同一个对象;不碰批、不连坐兄弟,存量整腿批行同判)。
1125
- * 两臂之外的终局 claim(取消臂 `cancel`)与政策无关,不读本口。
1126
- */
1127
- private get unattendedClaimIntent();
1128
1132
  /** {@link unattendedOutcome} 的**公开**口:`windowZero` 的 immediate-unavailable 闭包长在装配层
1129
1133
  * (`http/routes/tasks.ts` 的 sync 腿),它必须与协调器内五臂读**同一个**旋钮值,而不是各算一遍。
1130
1134
  * 出处随调用方给(那条腿知道自己是哪一只 ask)。 */
@@ -1133,6 +1137,29 @@ export declare class ToolApprovalCoordinator {
1133
1137
  owner?: string | null;
1134
1138
  toolName?: string;
1135
1139
  }): UnattendedAskOutcome;
1140
+ /**
1141
+ * S-702 —— 两条兜底臂的「无人可答」终局的**落行**半场。交给 core 的值已由 {@link unattendedOutcome} 一处铸好,这里只回答
1142
+ * 「这条持久行上落什么」,判据是「交出去的是不是**结算**」(行 = 真源:交出去的结算必须同时是行上的结算,否则同 ask 重入 /
1143
+ * 收敛后重入回放出来的是另一个答案 —— NP-2 在 live 腿上收掉的同一种分家):
1144
+ * · park 路由(`"unavailable"`)不是结算 ⇒ 行**不动**:它留在 STREAM_PENDING,投递面由既有机器接(崩溃恢复腿按政策代打),
1145
+ * 与两臂修前逐字同形 —— park 部署行为不变;
1146
+ * · 部署政策拒(deny)是结算 ⇒ 经 {@link settleByPolicy}(live 腿 / 崩溃恢复腿同一只政策感知终态 claim)把同一个结算落在行上。
1147
+ *
1148
+ * 🔴 **只对「本次调用插下的」行调用**(调用方的义务;codex 对抗复审 F1 + R2-2 / [ref] 件2 同一条纪律「放弃收尾只许动本次插入的
1149
+ * 行」):兜底臂发生在**窗开之前** —— 一条幂等命中的 STREAM_PENDING 行此刻可能正被别的登记(另一副本 / 另一版本 / 重入)呈卡、
1150
+ * 有人正要按;当场 refuse 等于把一张活卡在它自己的窗走完之前作废。所以归属证不出(幂等命中、ensure 报错、迟到结果
1151
+ * `inserted:false`)⇒ 本方法不被调用、行不动:它的属主按自己的窗收场,属主不在了由收敛器孤儿段(同一只 claim,S-706)在
1152
+ * 窗过 + 宽限之后落同一个政策结算。`row.batchId` 恒取**行上**的批(claim 的 scope;本地派生的批可能与存量行不同,
1153
+ * 错 scope 的 claim 答 absent,不证明「行上没有决议」—— codex F1)。
1154
+ *
1155
+ * 答案处置(律 1 答案自足,与 live 腿同一个分类器 {@link classifyClaimLoss} + 同一个同 askId 分发口 {@link dispatchClaimTruth}):
1156
+ * - 赢 ⇒ 交政策拒;同 askId 的其余本地登记随行收尾(与 live 腿赢臂同形:park 路由口,deny 下它们同样成形为政策拒);
1157
+ * - 输 ⇒ 行真相:真决议 ⇒ 交它并分发给同 askId 的其余登记(codex F2:此前只交本调用方,另一条登记可按自己的店错落成相反终局);
1158
+ * 行已进投递面(别的写者推进的 PARKING/PARKED)⇒ 分发 park 路由、本调用交政策拒本身(与 `replayed_parked` 在 deny 下同答);
1159
+ * - absent / 编辑在飞 / 店抛错 ⇒ 交政策拒本身(fail-closed 方向不变);迟到回来的 claim 答案同样分发(赢 ⇒ park 路由口;输 ⇒ 真相)。
1160
+ * 本方法从不抛错(店错只进 `noteStoreError`)—— 迟到落盘行的收尾对它 fire-and-forget。
1161
+ */
1162
+ private landUnattendedOutcome;
1136
1163
  /**
1137
1164
  * [ref] 车3 刀 3b —— [ref] §3.3 / 设计稿 §0 X-2 的**写侧准入门**。
1138
1165
  *
@@ -1307,6 +1334,13 @@ export declare class ToolApprovalCoordinator {
1307
1334
  */
1308
1335
  private hasOtherSuspendedOwner;
1309
1336
  private settleSameAskIdParkRoute;
1337
+ /**
1338
+ * claim 败方真相的**同 askId 分发口**(codex [ref] R6-2 / R7-1 立在 live 腿的闭包里;S-702 codex F2 起两条兜底臂的落行半场
1339
+ * {@link landUnattendedOutcome} 也调它 —— 一个分发口,不写第二份):真相是**行**的,不是某一条登记的 ⇒ 真决议经
1340
+ * {@link notifyExternalDecision}、投递面经 {@link settleSameAskIdParkRoute} 交给同 askId 的全部本地登记;各登记的 settle 自带
1341
+ * 单赢者卫兵,对已落的恒无操作。absent / 编辑在飞没有可分发的真相。
1342
+ */
1343
+ private dispatchClaimTruth;
1310
1344
  /** [ref]:落一条 park 墓碑(容量帽按**插入序**逐出最旧,与 `allowAllSessions` 同款有界纪律)。
1311
1345
  * 调用点唯一 = `settle` 里的 park 路由支(见那处注);真终局绝不调它。
1312
1346
  *
@@ -1554,6 +1588,15 @@ export declare class ToolApprovalCoordinator {
1554
1588
  * 宁少列不超范围;`"__none__"` 未鉴权哨兵自然恒空)。行的 `approvalId` 就是 `respond()` 收的 id
1555
1589
  * (同源可达,[ref] 判据);`expiresAtMs` 照抄登记时单点铸定的窗死线([ref] 三键同一个数)。 */
1556
1590
  listLivePending(scope: string | undefined): LivePendingRow[];
1591
+ /**
1592
+ * S-751:某只委派子代此刻有没有**待决**的活卡 —— fleet 对账腿「子代活性由属主说了算」的属主②读口(另一只是
1593
+ * bg registry 账)。子代卡在一张没人答的卡上时当然不出帧,只看「静默」就会把它当孤儿扫下面板(报案的真形)。
1594
+ * 只答是非、不交出 Map(与 `isRunLiveOnThisReplica` 同姿势);纯读,零锁零结算语义。
1595
+ * 键 = ask 发起者域(`originTaskId` = core `AskRequest.sourceTaskId` = 子代自己的 core session id),**不是**
1596
+ * fleet 行 id —— 换算在 `FleetEventBus.childOwnerKeys`。只看本副本的内存登记:子代跑在本副本,它的活卡就登记在
1597
+ * 本副本;耐久 park 的卡由 bg registry 的 `parked` 态作保(见 fleet-reconciler.ts `bgAgentVouches`)。
1598
+ */
1599
+ hasLivePendingFrom(originTaskId: string): boolean;
1557
1600
  /**
1558
1601
  * 🔴 [ref](案A)追记 F4 —— **跨副本决议的执行通知**(悬挂行的一次终态轮询)。
1559
1602
  *
@@ -8,8 +8,9 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
8
8
  import { createLogger } from "./observability/logger.js";
9
9
  import { recordFailOpen } from "./observability/fail-open.js";
10
10
  import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
11
- import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readRequiresRealApprovalSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
11
+ import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
12
12
  import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
13
+ import { settleByPolicy } from "./approval-policy-settlement.js";
13
14
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
14
15
  import { governanceOriginOf } from "./runtime-governance.js";
15
16
  const defaultLogger = createLogger();
@@ -87,8 +88,8 @@ export function parkRowStandingAnswerFacts(pending, gate, governance) {
87
88
  const riskDescriptor = "riskDescriptor" in gate ? gate.riskDescriptor : undefined;
88
89
  return {
89
90
  governanceForced: governanceOriginOf({ riskDescriptor }, governance) === true,
90
- requiresRealApproval: readRequiresRealApprovalSeat(pending),
91
- mandated: mandatedFromSeat(readRuleOffersAbsenceSeat(pending)),
91
+ requiresRealApproval: readParkRowTrueSeat(pending, "requiresRealApproval"),
92
+ mandated: readParkRowTrueSeat(pending, "mandated") || mandatedFromSeat(readRuleOffersAbsenceSeat(pending)),
92
93
  };
93
94
  }
94
95
  function mandatedFromSeat(seat) {
@@ -377,9 +378,6 @@ export class ToolApprovalCoordinator {
377
378
  batchIdFor(askId, sourceTaskId, runId, legKey) {
378
379
  return this.unattendedRoutesToPark ? deriveBatchId(sourceTaskId, runId, legKey) : deriveAskScopedBatchId(askId);
379
380
  }
380
- get unattendedClaimIntent() {
381
- return this.unattendedRoutesToPark ? "expire" : "refuse";
382
- }
383
381
  unattendedAskOutcome(site) {
384
382
  return this.unattendedOutcome({
385
383
  arm: "window_zero",
@@ -388,6 +386,32 @@ export class ToolApprovalCoordinator {
388
386
  ...(site.toolName !== undefined ? { toolName: site.toolName } : {}),
389
387
  });
390
388
  }
389
+ async landUnattendedOutcome(outcome, row) {
390
+ const store = this.askStore;
391
+ if (outcome === "unavailable" || !store)
392
+ return outcome;
393
+ const dispatch = (res, where) => {
394
+ if (res.claimed) {
395
+ this.settleSameAskIdParkRoute(row.askId);
396
+ return undefined;
397
+ }
398
+ const read = this.classifyClaimLoss(row.askId, res.current, where);
399
+ this.dispatchClaimTruth(row.askId, read);
400
+ return read;
401
+ };
402
+ let res;
403
+ try {
404
+ res = await this.withStoreDeadline(settleByPolicy(store, row, this.unattendedPolicy), "claimTerminal(unattended-fallback)", DURABLE_CALL_TIMEOUT_MS, (late) => {
405
+ dispatch(late, "late:unattended-fallback");
406
+ });
407
+ }
408
+ catch (err) {
409
+ this.noteStoreError(err, "claimTerminal(unattended-fallback)");
410
+ return outcome;
411
+ }
412
+ const read = dispatch(res, "claim:unattended-fallback");
413
+ return read?.kind === "decided" ? read.outcome : outcome;
414
+ }
391
415
  admit(taskId, owner) {
392
416
  if (!this.askStore)
393
417
  return () => undefined;
@@ -701,6 +725,23 @@ export class ToolApprovalCoordinator {
701
725
  for (const entry of [...entries])
702
726
  entry.settleParkRoute();
703
727
  }
728
+ dispatchClaimTruth(askId, read) {
729
+ switch (read.kind) {
730
+ case "decided":
731
+ this.notifyExternalDecision(askId, read.outcome);
732
+ return;
733
+ case "parked":
734
+ this.settleSameAskIdParkRoute(askId);
735
+ return;
736
+ case "absent":
737
+ case "edit-in-flight":
738
+ return;
739
+ default: {
740
+ const unreachable = read;
741
+ throw new Error(`dispatchClaimTruth: unknown recovery read ${String(unreachable)}`);
742
+ }
743
+ }
744
+ }
704
745
  recordParkTombstone(approvalId, tomb) {
705
746
  const existing = this.parkTombstones.get(approvalId);
706
747
  if (existing !== undefined) {
@@ -945,16 +986,31 @@ export class ToolApprovalCoordinator {
945
986
  let wireApprovalId = id;
946
987
  let cardForFrame = card;
947
988
  let persistedExpiresAtMs = expiresAtMs;
948
- let lateEnsuredAskId;
949
- let abandonedEnsure = false;
989
+ let lateEnsuredRow;
990
+ let lateTerminalRow;
991
+ let abandonedAs;
950
992
  const attemptCreatedAtMs = Date.now();
951
- const voidAbandonedRow = (orphanAskId) => {
993
+ const finishAbandonedRow = (orphan) => {
952
994
  const store = this.askStore;
953
- if (!store)
954
- return;
955
- void store
956
- .transitionAsk(orphanAskId, "STREAM_PENDING", "VOID", { updatedAtMs: Date.now() })
957
- .catch((err) => this.noteStoreError(err, "transitionAsk(abandon-late-ensure)"));
995
+ if (!store || abandonedAs === undefined)
996
+ return undefined;
997
+ if (abandonedAs === "void") {
998
+ void store
999
+ .transitionAsk(orphan.askId, "STREAM_PENDING", "VOID", { updatedAtMs: Date.now() })
1000
+ .catch((err) => this.noteStoreError(err, "transitionAsk(abandon-late-ensure)"));
1001
+ return undefined;
1002
+ }
1003
+ return this.landUnattendedOutcome(abandonedAs, orphan);
1004
+ };
1005
+ const abandonRow = (handed) => {
1006
+ abandonedAs = handed === undefined || handed === "unavailable" ? "void" : handed;
1007
+ return lateEnsuredRow !== undefined ? finishAbandonedRow(lateEnsuredRow) : undefined;
1008
+ };
1009
+ const replayTerminalRow = (row) => {
1010
+ const replayed = replayTerminalAskRow(row);
1011
+ return replayed === "unavailable"
1012
+ ? this.unattendedOutcome({ arm: "replayed_parked", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability })
1013
+ : replayed;
958
1014
  };
959
1015
  if (this.askStore && card) {
960
1016
  const sourceTaskId = req.sourceTaskId ?? origin.taskId;
@@ -980,17 +1036,19 @@ export class ToolApprovalCoordinator {
980
1036
  expiresAtMs,
981
1037
  createdAtMs: attemptCreatedAtMs,
982
1038
  }), "ensureAsk", DURABLE_CALL_TIMEOUT_MS, (late) => {
983
- lateEnsuredAskId = late.inserted && late.row.state === "STREAM_PENDING" ? late.row.askId : undefined;
984
- if (abandonedEnsure && lateEnsuredAskId !== undefined)
985
- voidAbandonedRow(lateEnsuredAskId);
1039
+ if (late.row.state !== "STREAM_PENDING") {
1040
+ lateTerminalRow = late.row;
1041
+ this.dispatchClaimTruth(late.row.askId, this.classifyClaimLoss(late.row.askId, projectAskTerminalClaimTruth(late.row), "late:ensureAsk"));
1042
+ return;
1043
+ }
1044
+ lateEnsuredRow = late.inserted ? { askId: late.row.askId, batchId: late.row.batchId } : undefined;
1045
+ if (lateEnsuredRow !== undefined)
1046
+ void finishAbandonedRow(lateEnsuredRow);
986
1047
  });
987
1048
  batchId = existingOrCreated.row.batchId;
988
1049
  if (existingOrCreated.row.state !== "STREAM_PENDING") {
989
1050
  releaseAdmission();
990
- const replayed = replayTerminalAskRow(existingOrCreated.row);
991
- return replayed === "unavailable"
992
- ? this.unattendedOutcome({ arm: "replayed_parked", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability })
993
- : replayed;
1051
+ return replayTerminalRow(existingOrCreated.row);
994
1052
  }
995
1053
  const persistedEnvelope = ApprovalCardEnvelopeSchema.safeParse(existingOrCreated.row.cardJson);
996
1054
  const maxMintableWindowMs = Math.max(effectiveWindowMs, this.unreachedTtlMs);
@@ -999,7 +1057,8 @@ export class ToolApprovalCoordinator {
999
1057
  this.noteStoreError(new Error(`persisted approval row is unusable (envelopeOk=${persistedEnvelope.success}, deadlineOk=${sanePersistedDeadline}, ` +
1000
1058
  `origin=${existingOrCreated.inserted ? "row-this-call-just-inserted" : "row-hit-idempotently"})`), "ensureAsk(persisted-row-unusable)");
1001
1059
  releaseAdmission();
1002
- return "unavailable";
1060
+ const unattended = this.unattendedOutcome({ arm: "persisted_row_unusable", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1061
+ return existingOrCreated.inserted ? this.landUnattendedOutcome(unattended, existingOrCreated.row) : unattended;
1003
1062
  }
1004
1063
  persistedExpiresAtMs = existingOrCreated.row.expiresAtMs;
1005
1064
  wireApprovalId = persistedEnvelope.data.approvalId;
@@ -1059,23 +1118,26 @@ export class ToolApprovalCoordinator {
1059
1118
  await new Promise((r) => setTimeout(r, DURABLE_CONVERGE_BACKOFF_MS[attempt]).unref?.());
1060
1119
  }
1061
1120
  }
1121
+ if (lateTerminalRow !== undefined) {
1122
+ releaseAdmission();
1123
+ return replayTerminalRow(lateTerminalRow);
1124
+ }
1062
1125
  if (indeterminate) {
1063
- abandonedEnsure = true;
1064
- if (lateEnsuredAskId !== undefined)
1065
- voidAbandonedRow(lateEnsuredAskId);
1126
+ const unattended = this.unattendedOutcome({ arm: "ensure_indeterminate", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1127
+ const landed = abandonRow(unattended);
1066
1128
  releaseAdmission();
1067
- return "unavailable";
1129
+ return landed ?? unattended;
1068
1130
  }
1069
1131
  if (!confirmedRowExists) {
1070
1132
  askId = undefined;
1071
1133
  batchId = undefined;
1072
- abandonedEnsure = true;
1073
- if (lateEnsuredAskId !== undefined)
1074
- voidAbandonedRow(lateEnsuredAskId);
1075
1134
  if (reachability === "unreached") {
1135
+ const unattended = this.unattendedOutcome({ arm: "unreached_no_row", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1136
+ const landed = abandonRow(unattended);
1076
1137
  releaseAdmission();
1077
- return this.unattendedOutcome({ arm: "unreached_no_row", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1138
+ return landed ?? unattended;
1078
1139
  }
1140
+ void abandonRow(undefined);
1079
1141
  }
1080
1142
  }
1081
1143
  }
@@ -1151,21 +1213,7 @@ export class ToolApprovalCoordinator {
1151
1213
  const broadcastClaimTruth = (read) => {
1152
1214
  if (askId === undefined)
1153
1215
  return;
1154
- switch (read.kind) {
1155
- case "decided":
1156
- this.notifyExternalDecision(askId, read.outcome);
1157
- return;
1158
- case "parked":
1159
- this.settleSameAskIdParkRoute(askId);
1160
- return;
1161
- case "absent":
1162
- case "edit-in-flight":
1163
- return;
1164
- default: {
1165
- const unreachable = read;
1166
- throw new Error(`broadcastClaimTruth: unknown recovery read ${String(unreachable)}`);
1167
- }
1168
- }
1216
+ this.dispatchClaimTruth(askId, read);
1169
1217
  };
1170
1218
  const settleFromClaimLoss = (read) => {
1171
1219
  switch (read.kind) {
@@ -1315,11 +1363,10 @@ export class ToolApprovalCoordinator {
1315
1363
  const store = this.askStore;
1316
1364
  const pinnedAskId = askId;
1317
1365
  const pinnedBatchId = batchId;
1318
- const claimIntent = this.unattendedClaimIntent;
1319
1366
  await resolveLocalOutcome({
1320
1367
  intent: "expire",
1321
1368
  storeLabel: "claimTerminal(window)",
1322
- claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, claimIntent), "claimTerminal(window)", timeoutMs, (late) => {
1369
+ claim: (timeoutMs) => boundedClaim(() => settleByPolicy(store, { askId: pinnedAskId, batchId: pinnedBatchId }, this.unattendedPolicy), "claimTerminal(window)", timeoutMs, (late) => {
1323
1370
  if (!late.claimed) {
1324
1371
  lateClaimLossSettle(late.current, "expire");
1325
1372
  return;
@@ -1498,7 +1545,6 @@ export class ToolApprovalCoordinator {
1498
1545
  const store = this.askStore;
1499
1546
  const pinnedAskId = askId;
1500
1547
  const pinnedBatchId = batchId;
1501
- const claimIntent = this.unattendedClaimIntent;
1502
1548
  const settleUnreachable = () => {
1503
1549
  if (done)
1504
1550
  return;
@@ -1508,7 +1554,7 @@ export class ToolApprovalCoordinator {
1508
1554
  await resolveLocalOutcome({
1509
1555
  intent: "unreachable",
1510
1556
  storeLabel: "claimTerminal(emit-failed)",
1511
- claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, claimIntent), "claimTerminal(emit-failed)", timeoutMs, (late) => {
1557
+ claim: (timeoutMs) => boundedClaim(() => settleByPolicy(store, { askId: pinnedAskId, batchId: pinnedBatchId }, this.unattendedPolicy), "claimTerminal(emit-failed)", timeoutMs, (late) => {
1512
1558
  if (!late.claimed) {
1513
1559
  lateClaimLossSettle(late.current, "unreachable");
1514
1560
  return;
@@ -2060,6 +2106,7 @@ export class ToolApprovalCoordinator {
2060
2106
  ...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
2061
2107
  ...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
2062
2108
  ...(e.governanceForced ? { governanceForced: true } : {}),
2109
+ ...(e.mandated ? { mandated: true } : {}),
2063
2110
  ...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
2064
2111
  ...(contentKind !== undefined ? { contentKind } : {}),
2065
2112
  frame: e.frame.approvalId === id ? e.frame : { ...e.frame, approvalId: id },
@@ -2067,6 +2114,12 @@ export class ToolApprovalCoordinator {
2067
2114
  }
2068
2115
  return rows;
2069
2116
  }
2117
+ hasLivePendingFrom(originTaskId) {
2118
+ for (const e of this.pending.values())
2119
+ if (e.originTaskId === originTaskId)
2120
+ return true;
2121
+ return false;
2122
+ }
2070
2123
  async pollSuspendedDecisions() {
2071
2124
  const store = this.askStore;
2072
2125
  if (!store)
@@ -17,7 +17,7 @@
17
17
  * ⑨⑩([ref],2026-08-31)把两个最大的 wire 面也纳入:TaskSpec 按「server 有无写点」记 Wired/Unwired,
18
18
  * TaskResult 按「转发/摘除」记 Forwarded/Stripped —— 谁改 resolve-spec.ts / runs.ts 的发射点谁同步本文件。
19
19
  */
20
- import type { TaskNotificationPayload, SemaProvenance, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxAppendMessage, TaskSpec, TaskResult, WiringManifest, GateOutcome, GateDisposition, Settlement, PausedCause, WorkflowRun, ReadRootGrantCandidate, McpReconnectResult, McpServerStatus } from "@sema-agent/core";
20
+ import type { TaskNotificationPayload, SemaProvenance, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, PendingAction, TaskEvent, TraceEvent, MailboxMessage, MailboxAppendMessage, TaskSpec, TaskResult, WiringManifest, GateOutcome, GateDisposition, Settlement, PausedCause, WorkflowRun, ReadRootGrantCandidate, McpReconnectResult, McpServerStatus, NodeLspManagerOptions, RunnerDeps } from "@sema-agent/core";
21
21
  import type { ToolApprovalFrame } from "../tool-approval.js";
22
22
  /** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。
23
23
  * 🔴 **导出**([ref] U1/U2):本仓「词表闭集,未处置成员=编译红」的钉法只许有**一个**形 ——
@@ -60,7 +60,7 @@ type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peer
60
60
  type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
61
61
  type _GuardMailboxAppendReverse = AssertAllKeysHandled<Exclude<MailboxAppendProjected, keyof MailboxAppendMessage>>;
62
62
  type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools" | "hooks" | "lsp" | "writeProtection" | "teamRole";
63
- type WiringSectionExcluded = "governance" | "configFingerprint" | "autoConsolidation" | "readDeny" | "peerPresence";
63
+ type WiringSectionExcluded = "governance" | "configFingerprint" | "autoConsolidation" | "readDeny" | "peerPresence" | "fileHistory";
64
64
  type _GuardWiringSections = AssertAllKeysHandled<Exclude<keyof WiringManifest, WiringSectionProjected | WiringSectionExcluded>>;
65
65
  type _GuardWiringSectionsReverse = AssertAllKeysHandled<Exclude<WiringSectionProjected | WiringSectionExcluded, keyof WiringManifest>>;
66
66
  type WiringSection<K extends keyof WiringManifest> = NonNullable<WiringManifest[K]>;
@@ -130,12 +130,20 @@ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTrace
130
130
  /** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
131
131
  type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
132
132
  type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
133
- type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable";
133
+ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "mandated";
134
134
  type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
135
135
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
136
136
  type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
137
+ type ParkArmKeys = keyof Extract<PendingAction, {
138
+ kind: "tool_approval";
139
+ }>;
140
+ type ParkArmQueueProjected = "toolName" | "toolCallId" | "boundInputHash" | "ruleOffers" | "hasBidiControls" | keyof import("../approval-card.js").ParkRowFacts;
141
+ type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable";
142
+ type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueueProjected | ParkArmQueueExcluded>>;
143
+ type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
144
+ type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
137
145
  type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "approverPosture" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools";
138
- type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence";
146
+ type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence" | "fileHistory";
139
147
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
140
148
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
141
149
  type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles" | "responseModel" | "compaction";
@@ -197,6 +205,13 @@ type McpListingIncompleteKeys = keyof NonNullable<McpReconnectResult["listingInc
197
205
  type McpListingIncompleteProjected = "reason" | "pages" | "budgetMs" | "error";
198
206
  type _GuardMcpListingIncomplete = AssertAllKeysHandled<Exclude<McpListingIncompleteKeys, McpListingIncompleteProjected>>;
199
207
  type _GuardMcpListingIncompleteReverse = AssertAllKeysHandled<Exclude<McpListingIncompleteProjected, McpListingIncompleteKeys>>;
208
+ type LspOptionsWired = "env" | "log";
209
+ type LspOptionsUnwired = "onNotice" | "servers" | "spawn" | "readText" | "resolveRoot" | "warmup" | "maxSessions" | "idleTtlMs" | "diagnostics";
210
+ type _GuardLspOptions = AssertAllKeysHandled<Exclude<keyof NodeLspManagerOptions, LspOptionsWired | LspOptionsUnwired>>;
211
+ type _GuardLspOptionsReverse = AssertAllKeysHandled<Exclude<LspOptionsWired | LspOptionsUnwired, keyof NodeLspManagerOptions>>;
212
+ type _NoDoubleBookLspOptions = AssertAllKeysHandled<Extract<LspOptionsWired, LspOptionsUnwired>>;
213
+ type RunnerDepsDroppedPendingS724 = "fileHistoryCapture";
214
+ type _GuardFacelessRunnerDepsReverse = AssertAllKeysHandled<Exclude<RunnerDepsDroppedPendingS724, keyof RunnerDeps>>;
200
215
  export type CoreKeysetGuards = [
201
216
  _GuardNotification,
202
217
  _GuardNotificationReverse,
@@ -259,6 +274,9 @@ export type CoreKeysetGuards = [
259
274
  _GuardPermissionTraceNonEmpty,
260
275
  _GuardInboxSummary,
261
276
  _GuardInboxSummaryReverse,
277
+ _GuardParkArmQueue,
278
+ _NoDoubleBookParkArmQueue,
279
+ _GuardParkArmQueueReverse,
262
280
  _GuardTaskSpec,
263
281
  _GuardTaskSpecReverse,
264
282
  _GuardTaskResult,
@@ -294,7 +312,11 @@ export type CoreKeysetGuards = [
294
312
  _GuardMcpListingIncomplete,
295
313
  _GuardMcpListingIncompleteReverse,
296
314
  _GuardWiringMcpLiveness,
297
- _GuardWiringMcpLivenessReverse
315
+ _GuardWiringMcpLivenessReverse,
316
+ _GuardLspOptions,
317
+ _NoDoubleBookLspOptions,
318
+ _GuardLspOptionsReverse,
319
+ _GuardFacelessRunnerDepsReverse
298
320
  ];
299
321
  export {};
300
322
  //# sourceMappingURL=core-keyset-guard.d.ts.map
@@ -16,8 +16,34 @@
16
16
  * 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
17
17
  * 刚立,立完就在下一次加码时自己咬住了)。
18
18
  */
19
- export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed"];
19
+ export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "instructions.source_changed"];
20
20
  export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
21
+ /**
22
+ * S-690 —— **本仓自铸**的通告码与它们的受众(码 / 铸点 / 受众三者的属主都是本仓)。
23
+ *
24
+ * 为什么可以有这一族:core 的 `EngineNotice.code` 刻意是开放 `string`,顶注逐字「a host forwarding its own notices
25
+ * through the same sink is a supported shape」—— 部署经同一只 `onNotice` 转发体投自己的通告,白名单 / 归属 / 消毒 /
26
+ * 隔离四件判据一个不绕。为什么受众写在这里:core 的 `NOTICE_AUDIENCE` 只收录 core 自己铸的码,对本仓的码
27
+ * `noticeAudienceOf` 只会读保守默认 `"operator"` —— 那是「不认识」,不是判决。顶注的「本仓不许自造第二份受众语义」
28
+ * 说的是 **core 的码**;本表只收 core 码册里**没有**的码,这里是它们唯一的一份受众语义。
29
+ *
30
+ * 🔴 机器门(`test/engine-notice-wire.test.ts` 的 server 自铸码属主门):① 本表的码与 core 运行期码册 `ENGINE_NOTICE_CODES`
31
+ * **零交集**(core 哪天铸了同名码 ⇒ 属主变成两个,当场红:销本表的行、改走 core 的受众与工厂);② 判 `"user"` 的码必须
32
+ * 入白名单;③ 白名单里的码,受众一律由「本表 ∪ core 注册表」给,判出来必须是 `"user"`。
33
+ */
34
+ export declare const SERVER_NOTICE_AUDIENCE: {
35
+ readonly "instructions.source_changed": "user";
36
+ /**
37
+ * O2(7.101.0 合并复审)—— boot 期探测到 ≤7.30.x 引擎留下的锁住的记忆层(唯一铸点 `src/memory-layer-legacy-lock.ts`,
38
+ * 调用点 = `boot/stores.ts` 的 file 记忆引擎装配段)。受众 **operator**:恢复动词是运维在数据根上执行一次 `chmod -R u+w`,
39
+ * 而且铸在 boot 期、没有任何会话 ⇒ 不上 wire、不进白名单,只走转发体的日志终点(`engine_notice` warn)。
40
+ * `detail: { layers, remedy }` —— 层名(挂载面的直接子目录名)与那一句补救;条目名 / 内容不进。
41
+ */
42
+ readonly "memory.layer_locked_legacy": "operator";
43
+ };
44
+ export type ServerNoticeCode = keyof typeof SERVER_NOTICE_AUDIENCE;
45
+ /** 该码是否本仓自铸(属主判据单点:门与投递面都读这一个)。 */
46
+ export declare function isServerNoticeCode(code: string): code is ServerNoticeCode;
21
47
  /** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
22
48
  export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
23
49
  /**
@@ -23,7 +23,15 @@ export const ENGINE_NOTICE_WIRE_CODES = [
23
23
  "mcp.injection_dropped",
24
24
  "delegation.ask_unresolvable",
25
25
  "task.interrupt_unconsumed",
26
+ "instructions.source_changed",
26
27
  ];
28
+ export const SERVER_NOTICE_AUDIENCE = {
29
+ "instructions.source_changed": "user",
30
+ "memory.layer_locked_legacy": "operator",
31
+ };
32
+ export function isServerNoticeCode(code) {
33
+ return Object.hasOwn(SERVER_NOTICE_AUDIENCE, code);
34
+ }
27
35
  const WIRE_CODES = new Set(ENGINE_NOTICE_WIRE_CODES);
28
36
  export function isEngineNoticeWireCode(code) {
29
37
  return WIRE_CODES.has(code);