@sema-agent/server 7.98.0 → 7.100.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 (153) hide show
  1. package/MIGRATION.md +19 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +86 -60
  5. package/deploy/sema-up/lib/envf.sh +110 -42
  6. package/deploy/sema-up/sema-up.sh +76 -34
  7. package/dist/approval-ask-machine.d.ts +10 -0
  8. package/dist/approval-ask-machine.js +3 -0
  9. package/dist/approval-card.d.ts +74 -1
  10. package/dist/approval-card.js +41 -8
  11. package/dist/approval.d.ts +44 -20
  12. package/dist/approval.js +14 -6
  13. package/dist/boot/config-center.d.ts +16 -8
  14. package/dist/boot/config-center.js +206 -103
  15. package/dist/boot/execution-env.d.ts +1 -2
  16. package/dist/boot/execution-env.js +2 -2
  17. package/dist/boot/leader.js +12 -9
  18. package/dist/boot/memory-consolidation.d.ts +38 -0
  19. package/dist/boot/memory-consolidation.js +35 -0
  20. package/dist/boot/org-memory.d.ts +2 -1
  21. package/dist/boot/org-memory.js +9 -5
  22. package/dist/boot/resolve-spec.d.ts +0 -1
  23. package/dist/boot/resolve-spec.js +4 -3
  24. package/dist/boot/runner-deps.d.ts +2 -2
  25. package/dist/boot/runtime-caps.js +4 -4
  26. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  27. package/dist/boot/stage-06-runners.d.ts +2 -2
  28. package/dist/boot/stage-06-runners.js +3 -3
  29. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  30. package/dist/boot/stage-07-capability-layer.js +6 -6
  31. package/dist/boot/stage-08-reapers.d.ts +3 -4
  32. package/dist/boot/stage-08-reapers.js +2 -2
  33. package/dist/boot/stage-09-leader.d.ts +2 -3
  34. package/dist/boot/stage-10-http-server.d.ts +2 -3
  35. package/dist/boot/stage-10-http-server.js +2 -2
  36. package/dist/capabilities/center-plugins.d.ts +14 -2
  37. package/dist/capabilities/center-plugins.js +13 -5
  38. package/dist/capabilities/center-prompts.d.ts +3 -0
  39. package/dist/capabilities/center-prompts.js +1 -1
  40. package/dist/capabilities/hands-lane.js +2 -1
  41. package/dist/capabilities/scenarios.js +18 -5
  42. package/dist/capabilities/tool-defer.d.ts +2 -2
  43. package/dist/center-credential.d.ts +112 -0
  44. package/dist/center-credential.js +131 -0
  45. package/dist/config-catalog.d.ts +2 -2
  46. package/dist/config-catalog.js +15 -15
  47. package/dist/config-center/apply-effective.d.ts +34 -39
  48. package/dist/config-center/apply-effective.js +100 -64
  49. package/dist/config-center/apply-ledger.d.ts +35 -16
  50. package/dist/config-center/apply-ledger.js +25 -18
  51. package/dist/config-center/center-request.d.ts +60 -0
  52. package/dist/config-center/center-request.js +71 -0
  53. package/dist/config-center/facade.d.ts +11 -5
  54. package/dist/config-center/facade.js +1 -1
  55. package/dist/config-center/hot-keys-registry.d.ts +11 -4
  56. package/dist/config-center/hot-keys-registry.js +11 -7
  57. package/dist/config-center/http-client.d.ts +17 -9
  58. package/dist/config-center/http-client.js +27 -36
  59. package/dist/config-center/restart-signal.d.ts +21 -14
  60. package/dist/config-center/restart-signal.js +16 -22
  61. package/dist/config-center/skills-mcp.d.ts +4 -1
  62. package/dist/config-center/skills-mcp.js +2 -2
  63. package/dist/config-center/types.d.ts +3 -3
  64. package/dist/config-invariants.d.ts +2 -2
  65. package/dist/config-invariants.js +0 -8
  66. package/dist/config-lkg.d.ts +47 -8
  67. package/dist/config-lkg.js +107 -20
  68. package/dist/config-provider.d.ts +8 -7
  69. package/dist/config-provider.js +5 -5
  70. package/dist/config-types.d.ts +30 -11
  71. package/dist/config.js +11 -13
  72. package/dist/cross-session-settings.js +1 -1
  73. package/dist/fleet-client.d.ts +8 -8
  74. package/dist/fleet-client.js +7 -7
  75. package/dist/fleet-lease.d.ts +11 -11
  76. package/dist/fleet-lease.js +17 -13
  77. package/dist/host-lsp-manager.d.ts +29 -0
  78. package/dist/host-lsp-manager.js +14 -0
  79. package/dist/hosted-posture.d.ts +49 -93
  80. package/dist/hosted-posture.js +1 -25
  81. package/dist/http/active-run-conflict.d.ts +2 -12
  82. package/dist/http/active-run-conflict.js +1 -6
  83. package/dist/http/admission.js +8 -0
  84. package/dist/http/resume-legs.d.ts +1 -1
  85. package/dist/http/resume-legs.js +14 -10
  86. package/dist/http/route-ctx.d.ts +2 -2
  87. package/dist/http/routes/approvals-assistant.d.ts +1 -1
  88. package/dist/http/routes/approvals-assistant.js +2 -2
  89. package/dist/http/routes/memory-compliance.js +5 -0
  90. package/dist/http/routes/memory-origin.js +5 -0
  91. package/dist/http/routes/sessions.js +6 -0
  92. package/dist/http/routes/tasks.js +7 -6
  93. package/dist/http/server.d.ts +6 -5
  94. package/dist/http/wire-types.d.ts +6 -3
  95. package/dist/leader/wire.d.ts +20 -12
  96. package/dist/leader/wire.js +7 -6
  97. package/dist/memory-layer-preflight.d.ts +46 -0
  98. package/dist/memory-layer-preflight.js +121 -0
  99. package/dist/memory-operator-faces.js +17 -1
  100. package/dist/observability/corrupt-read-seat.d.ts +21 -4
  101. package/dist/observability/corrupt-read-seat.js +31 -0
  102. package/dist/observability/fail-open.d.ts +8 -4
  103. package/dist/observability/fail-open.js +8 -4
  104. package/dist/observability/run-terminal.js +1 -0
  105. package/dist/observability/secret-env-scrub.d.ts +3 -26
  106. package/dist/observability/secret-env-scrub.js +3 -2
  107. package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
  108. package/dist/orchestration/workflow-completion-inbox.js +8 -3
  109. package/dist/plugins/approval-ask-store-file.d.ts +20 -2
  110. package/dist/plugins/approval-ask-store-file.js +21 -3
  111. package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
  112. package/dist/plugins/approval-ask-store-memory.js +39 -13
  113. package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
  114. package/dist/plugins/approval-ask-store-sql.js +27 -3
  115. package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
  116. package/dist/plugins/checkpoint-store-sql.js +19 -2
  117. package/dist/plugins/local-checkpoint-store.js +2 -0
  118. package/dist/plugins/mailbox-store-sql.d.ts +7 -5
  119. package/dist/plugins/mailbox-store-sql.js +6 -4
  120. package/dist/plugins/permission-rule-store-file.js +3 -2
  121. package/dist/plugins/remote-env-host.d.ts +3 -1
  122. package/dist/plugins/remote-env-host.js +3 -2
  123. package/dist/plugins/sql-json-column.d.ts +16 -3
  124. package/dist/plugins/sql-json-column.js +15 -4
  125. package/dist/plugins/web-search.d.ts +42 -22
  126. package/dist/plugins/web-search.js +141 -49
  127. package/dist/project-memory.d.ts +28 -1
  128. package/dist/project-memory.js +22 -22
  129. package/dist/run-local.js +6 -7
  130. package/dist/runs.js +6 -5
  131. package/dist/runtime-caps-resolver.d.ts +8 -8
  132. package/dist/runtime-caps-resolver.js +16 -14
  133. package/dist/runtime-governance.d.ts +50 -3
  134. package/dist/runtime-governance.js +5 -0
  135. package/dist/sealed-key.js +3 -7
  136. package/dist/server-secret-env.d.ts +43 -0
  137. package/dist/server-secret-env.js +18 -0
  138. package/dist/task-settings.d.ts +1 -1
  139. package/dist/tool-approval.d.ts +144 -9
  140. package/dist/tool-approval.js +74 -18
  141. package/dist/trace/core-keyset-guard.d.ts +5 -5
  142. package/dist/trace/ledger-sink.js +2 -2
  143. package/dist/trace/project.d.ts +13 -0
  144. package/dist/trace/project.js +22 -5
  145. package/dist/trace/projection-drop.d.ts +2 -0
  146. package/dist/trace/projection-drop.js +6 -0
  147. package/dist/trace/sema-provenance.d.ts +4 -1
  148. package/dist/trace/sema-provenance.js +1 -1
  149. package/dist/trace/task-notification-facets.d.ts +65 -0
  150. package/dist/trace/task-notification-facets.js +31 -0
  151. package/dist/trace/wire-projection-faces.d.ts +3 -3
  152. package/dist/trace/wire-projection-faces.js +3 -3
  153. package/package.json +5 -4
@@ -1,11 +1,12 @@
1
1
  import type { HostDecidedBy, HostSettledBy } from "./host-decision.js";
2
2
  import type { IncomingMessage } from "node:http";
3
- import { type AskRequest, type AskOutcome, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
3
+ import { type AskRequest, type AskOutcome, type CheckpointGate, type PendingAction, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
4
4
  import { type CardRulePersisted, type RuleConsentLane } from "./rules-consent.js";
5
5
  import { type DenialLimitFallback, type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
6
6
  import { type ApprovalAskStore, type AskRow } from "./plugins/approval-ask-store-sql.js";
7
7
  import type { ApprovalAskAuditSink } from "./approval-ask-audit-store.js";
8
8
  import { type GovernanceAskMarks } from "./governance-ask-marks.js";
9
+ import { type GovernancePosture } from "./runtime-governance.js";
9
10
  /** [ref]:审批 gate kind 闭集(core gateMatch 的 `human`/`irreversible_ask` ↔ outcome.gate "policy_ask")。
10
11
  * 此前 7 份手写副本散在两只 checkpoint store 的数组/SQL 字面与 /decide 守卫——core 加审批味 kind 时
11
12
  * 全部静默漂移。core 无导出词表(全大写导出面零命中,2026-08-09 亲验),属主落此;SQL IN 片段从
@@ -76,6 +77,87 @@ export type ParkedAskRedeem = (req: ParkedAskRedeemRequest) => Promise<{
76
77
  status: number;
77
78
  body: Record<string, unknown>;
78
79
  }>;
80
+ /**
81
+ * 一问身上与「站着的答案」有关的三个事实位 —— {@link standingAnswerMayRetire} 的全部入参。
82
+ *
83
+ * 🔴 **三格全必填布尔**,刻意不做 `?: true` 的「只记真」形:判据哪天多一位,每个读者都要当场编译红,
84
+ * 而不是漏传一格被静默读成 `false`(那是安全轴上的 fail-open)。读者各自从自己手里的形折成这三格,
85
+ * 然后交**同一只** {@link standingAnswerMayRetire}(判据只有一份,折法各归读者):
86
+ * · live 读侧取 `req` + 本地治理 peek;live 写侧取 pending 条目;
87
+ * · 耐久 `/decide` 腿(S-663 → NP-1,7.100.0)取 park 行,经 {@link parkRowStandingAnswerFacts}(三格全从行上读)。
88
+ * 本型**不绑** live 条目。
89
+ */
90
+ export interface StandingAnswerFacts {
91
+ /** 本仓治理层(`AUTONOMY` / `commandPolicy` / shellGate / `SENSITIVE_WRITE_PATTERNS`)产的门。 */
92
+ readonly governanceForced: boolean;
93
+ /** core 闸站盖的「必须真人逐次批」位(连一揽子 `onAsk:"allow"` 席也不得答)。 */
94
+ readonly requiresRealApproval: boolean;
95
+ /** core 闸站盖的强制位([ref]):规则清不掉、站着的答案退不掉,只有对**这一次调用**的应答能答。 */
96
+ readonly mandated: boolean;
97
+ }
98
+ /**
99
+ * 🔴 **S-660 —— 会话级放行的唯一判据:站着的答案能不能退掉这一问。** 纯谓词,三个读者:
100
+ * · **读侧**(`askBroadcast` 的会话授权短路):`true` 才可能由既有 grant 代答,否则往下走真出卡;
101
+ * · **写侧**(`finishRespond` 的 `grantable`):`true` 才可能把这张卡上的 `allow_session` 铸成 grant,
102
+ * 否则只放行这一次、`rememberApplied:false`;
103
+ * · **耐久 `/decide` 腿**(NP-1,`http/resume-legs.ts` 的 remember 可记性):入参由 {@link parkRowStandingAnswerFacts}
104
+ * 从 park 行铸 —— `false` ⇒ 可记工具名 `null` ⇒ 放行本次、不落豁免、回执 `rememberApplied:false`。
105
+ * 两臂读同一份事实、说同一句话:一问若不许被记住的放行答掉,它的应答也不许变成一条记住的放行 —— 否则
106
+ * 同一只卡「重跑一遍」就被自己上一次的回答代答(test [ref] U1s-off 的真形)。
107
+ *
108
+ * **为什么三位是同一类**(「站着的答案退不掉的一问」):会话授权 = **上一次**应答记下的一揽子放行,不是对
109
+ * **这一次**调用的应答 —— 与持久 allow 规则同族,只是更短命(`askBroadcast` 短路处的注早已这么定性)。
110
+ * · `mandated`:core `PermissionResult` ask 臂 `mandated` 成员的文档。7.29.0 原句「no persisted allow rule and no standing
111
+ * posture may retire it, only an answer to THIS call」;**7.30.0 起 d.ts 已落 [ref] 澄清**(S-616 在 7.30.1 装树亲核):
112
+ * core `PermissionResult` ask 臂的 `mandated` 成员文档「no persisted allow rule and no STANDING ANSWER (a release remembered
113
+ * from an earlier call, at any scope, a session included) may retire it」,`AskRequest.mandated` 孪生句同义 ——
114
+ * 「从早先某次回答记住的放行,**任何 scope、会话级也算**」都是站着的答案。core `ruleClearanceOf` 的文档
115
+ * 「Allow rules silence the classifier's questions, never a mandated one」。它**不**绑
116
+ * 这一次调用的应答者(人、hook、部署的一揽子 `onAsk:"allow"` 席、mandate 表允许的活判定者 —— 同一句 d.ts 逐一点名)——
117
+ * 那一半本仓不动(见 `mandated` 帧键顶注)。本谓词行为对 7.29.0 零改(文档到货,判据本来就是这么读的)。
118
+ * · `requiresRealApproval`([ref]):更强的那条,连一揽子席也不得答,当然也不许记住的放行答。
119
+ * · `governanceForced`([ref]):运维治理层的门,客户端表态掀不掉;持久规则车道早已同判(`buildRuleLaneMaterial`)。
120
+ * 本谓词**不重推** core 的 `ruleClearanceOf` / `ruleMayClear`(core 刻意不导出:闸外重算拿不到同一份标记快照,
121
+ * 会分叉),只读闸站**盖好**的两位 + 本仓自己的治理标。
122
+ *
123
+ * **为什么读侧的行复核(`sessionGrantUncontestedByRow`)不必为 `mandated` 加一次行读**:`mandated` 是 `req`
124
+ * **自己**的位 —— core 每一次过闸(含 failover / 重启后的重入)都由 `runGateLanes` 里的 `stampAskOrigin`
125
+ * 重新盖、经同一处的 `carryOf` 带上 `AskRequest`,读侧手里的 `req` 恒是这一次过闸的真值。`governanceForced`
126
+ * 需要行读,是因为它来自本仓的**进程内**治理标表:failover / 重启后幂等命中既有行时本地表恒空,行上的卡才
127
+ * 存着落库那一刻的判定。何况行上本来就没有 `mandated`(耐久面不造孪生座)—— 读了也读不到。
128
+ */
129
+ export declare function standingAnswerMayRetire(facts: StandingAnswerFacts): boolean;
130
+ /**
131
+ * 🔴 **S-663 → NP-1(7.100.0 合并复审 [ref] 交叉表 #2)—— 耐久 `/decide` 腿的 {@link StandingAnswerFacts}:从 park 行铸三位。**
132
+ * 纯投影,**不判**:判据是同一只 {@link standingAnswerMayRetire}(唯一读点 = `http/resume-legs.ts` 的 remember 可记性,
133
+ * `standingAnswerMayRetire(parkRowStandingAnswerFacts(…))`)。修前耐久腿自成一只谓词、只读 `ruleOffersAbsence` 一词,同一行上
134
+ * core 盖好的 `requiresRealApproval` 与治理出身都不读 ⇒ 那两类卡的 `remember:"session"` 照旧铸会话免询问,而 live 腿对同一张卡
135
+ * 答 `false`(两腿同一个问题、两份事实覆盖;两腿复审同报)。三格各自的来源(全在**这一条**已属主校验的载入行上,不另读):
136
+ * · `governanceForced` —— 行上的治理标:`gate.riskDescriptor.shellGateDoctrine` × 本部署治理姿态,经 **同一只**
137
+ * `governanceOriginOf`(`runtime-governance.ts`;运维队列 `projectPendingForWire` / 409 体同一个属主,不另判)。它在
138
+ * 本读者这里只**收窄**(失真两个方向的代价登记在 `governanceMandatesShellGateAlways` 顶注)。⚠️ 它**只覆盖 shell 门的
139
+ * `always` 档** —— live 腿的治理标来自进程内标记表(敏感写路径 / `commandPolicy` / `AUTONOMY` 内层策略 / shellGate 各档),
140
+ * park 行上唯一的取证格只有 `riskDescriptor.shellGateDoctrine`(见下方 🚧);
141
+ * · `requiresRealApproval` —— 行上 core 铸的那一位,经 `approval-card.ts` 的 {@link readRequiresRealApprovalSeat}(与运维
142
+ * 队列 `parkRowFacts` 同一只读法,`=== true`)。刻意**不**经 `parkRowFacts` 本身:那只展示投影顺手对 `ruleOffersAbsence`
143
+ * 的集外词计展示面 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 不变:一个本仓不认识的新词可能正是一种强制因由)。
147
+ *
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
+ * 行形钉着);
154
+ * ② **非 shell 治理(候 S-707)**:治理位经 `governanceOriginOf` 只覆盖 shell 门 `always` 档;敏感写路径
155
+ * (`SENSITIVE_WRITE_PATTERNS`)、`commandPolicy`、`AUTONOMY` 内层策略产的门与 shellGate `classify` 档,park 行上没有取证格
156
+ * (live 腿靠进程内标记表,跨副本 / 重启后够不着),本折法读不到它们的治理出身。
157
+ */
158
+ export declare function parkRowStandingAnswerFacts(pending: Extract<PendingAction, {
159
+ kind: "tool_approval";
160
+ }>, gate: CheckpointGate, governance: GovernancePosture | undefined): StandingAnswerFacts;
79
161
  /** A live approval frame delivered to whoever tails this run's stream. `type` IS the SSE event name (named-event
80
162
  * convention, same as question). The shell renders `tool_approval` as the CC three-choice card and dismisses on
81
163
  * `tool_approval_complete`. */
@@ -142,6 +224,8 @@ export interface ToolApprovalFrame {
142
224
  * 车道**有什么可给,而那条车道对它不说话的工具、**没接规则店的部署**(本仓的 live-only 形)、装不下规则的
143
225
  * 任务都是结构性沉默 —— 它的缺席**不是**断言。本键是这一问**自己**的属性,凡强制皆在场。读错的代价 core
144
226
  * 实测过:同一条根外读命令在 shell 工具上是强制问、在 monitor 工具上看起来是普通问。
227
+ * (唯一例外是**手里没有本键**的那条腿:耐久 `/decide` 腿只有 park 行,S-663 / NP-1 只读那一词的**在场**、从不读它的
228
+ * 缺席 —— 见 {@link parkRowStandingAnswerFacts},子集与剩余不对称写在那里。live 腿恒读本键。)
145
229
  *
146
230
  * 🔴 **它不是 `requiresRealApproval`**:强制只说「没有规则能清掉」,**不**说「没有应答者可以答」——
147
231
  * 一个人、一只 hook、一张一揽子 `onAsk:"allow"` 席照常答得了强制卡。两键别互推。
@@ -150,7 +234,10 @@ export interface ToolApprovalFrame {
150
234
  * 「不是强制」「老引擎」「这是一张从 park 行上重铸的耐久卡」三形 —— **耐久面上本仓不造孪生座**:core 的
151
235
  * 寄存行(`PendingAction`)没有这一位(KL-997a 亲读登记),行上强制表现为 `ruleOffersAbsence:"mandated"`
152
236
  * 且无 `ruleOffers`;在 `card_json` 侧自铸一个就是本仓成了这个事实的第二个源(而 park 那侧根本没有输入)。
153
- * 🔴 echo-only:server 不据它做任何裁决(强制的判定属主在 core 的闸站)。
237
+ * 🔴 **server 不判「是不是强制」**(判定属主在 core 的闸站),只**读**闸站盖好的这一位 —— 读者恰一个:
238
+ * S-660 起的会话级放行判据 {@link standingAnswerMayRetire}(会话授权是**上一次**应答记下的站着的答案,
239
+ * 不是对这一次调用的应答 ⇒ 强制卡上既不被既有 grant 代答、也不铸 grant)。上一段「一揽子 `onAsk:"allow"`
240
+ * 席照常答得了」说的是**这一次调用的应答者**,本仓不改它。
154
241
  */
155
242
  mandated?: true;
156
243
  /**
@@ -637,6 +724,29 @@ export type RespondSettledBy = Extract<HostSettledBy, HostDecidedBy>;
637
724
  * (`config.ts` 的解析腿,[ref] A 档)。
638
725
  */
639
726
  export type UnattendedApprovalPolicy = "park" | "deny";
727
+ /**
728
+ * S-689 —— `deny` 政策下「无人可答」交给 core 的形:core `AskOutcome` 对象臂的**部署政策拒**
729
+ * (`settledBy:"policy"` ⇒ core 结算 `policy_refused`、`who.party:"none"`,模型读的是策略句)。
730
+ *
731
+ * 修前这里交**裸 `false`** —— 而在 core 的 onAsk 契约里裸 `false` 就是「一个人拒了」:结算记
732
+ * `human_refused` / `who.party:"person"`,模型被告知「用户不想继续,停下来等他」,run 以
733
+ * `haltedOnUserRejection` 收束。可这台部署自己声明了没有人会来 —— 拒的是它的政策。
734
+ *
735
+ * 型从 core 的 `AskOutcome` 对象臂**收窄**而不是另写一份(`settledBy` 取 core 那一格词表里的 `"policy"`:
736
+ * core 哪天退役这个词,这里编译期先红)。`reason` 必填:core 以 `policy note` 围栏把它交给模型,内容见
737
+ * {@link unattendedRefusalReason}。
738
+ */
739
+ export type UnattendedPolicyRefusal = Extract<AskOutcome, {
740
+ allow: boolean;
741
+ }> & {
742
+ allow: false;
743
+ settledBy: Extract<HostSettledBy, "policy">;
744
+ reason: string;
745
+ };
746
+ /** 「无人可答」终局的**闭集** —— core `AskOutcome` 联合里只有这两臂由 {@link ToolApprovalCoordinator.unattendedOutcome}
747
+ * 铸:park 政策的路由答 `"unavailable"`,deny 政策的 {@link UnattendedPolicyRefusal}。没有布尔臂(S-689:裸 `false`
748
+ * 是人的拒绝,这个口从不替人说话)。 */
749
+ export type UnattendedAskOutcome = Extract<AskOutcome, "unavailable"> | UnattendedPolicyRefusal;
640
750
  /**
641
751
  * [ref] 车3 刀 3b —— 「流内审批协议到底上不上场」的**单一谓词**(设计稿 §2.3 注入 / §2.4 能力面 /
642
752
  * §8.4 park 设施自检,三处同一份判据)。
@@ -959,6 +1069,10 @@ export declare class ToolApprovalCoordinator {
959
1069
  /**
960
1070
  * [ref] R-13 C —— 「**无人可答**」这一类终局的**唯一**成形口(park 路由 vs deny 政策)。
961
1071
  *
1072
+ * 终局形(S-689):park ⇒ `"unavailable"`;deny ⇒ {@link UnattendedPolicyRefusal}
1073
+ * `{allow:false, settledBy:"policy", reason}`(core 记 `policy_refused`)。**从不**交裸 `false`:那在 core 的
1074
+ * onAsk 契约里是「一个人拒了」,而本口的全部臂都是「没有人能答」。
1075
+ *
962
1076
  * 五臂(设计稿 §1 的表)全部读它,`unattendedPolicy` 因此是一处施加、五处消费:
963
1077
  * (a) 有店窗到期 `expireAsk` 赢 CAS / (b) 无店窗到期(D1)/ (c) 有店但 `expireAsk` 报错
964
1078
  * —— 三条走 `windowRouteUnavailable` 标志,在 {@link shapeOutcome}(askBroadcast 内)成形;
@@ -976,12 +1090,17 @@ export declare class ToolApprovalCoordinator {
976
1090
  * **身份不确定**的处置(与「有没有人」无关),且背后可能正躺着一条真行。于是 `deny` 部署上仍可能落
977
1091
  * 极少数 park(店抖动期),已知且有意的残余,成文在 config-types 的域注。
978
1092
  *
979
- * ⚠️ **`deny` 部署上持久行仍会经 PARKING 记账**(codex R1-F1 的另一半,验真后**不采纳**其「给 deny
980
- * 造一条原子 deny 终态转移」的建议):那是店语义变更(新转移 + 批内兄弟连坐语义 + 真双库套件),且与
981
- * 已裁的设计稿(§2:(a) 在 deny 下只改交给 core 的终局)相左。**危害亲验为零**:收敛器判据①的
982
- * `bindBatch` 只把 PARKING 行绑到**已经存在的 core checkpoint** 上,而 `deny` 部署里 core 从不 park
983
- * ⇒ 结构上绑不上,该行按判据②/④/⑤ 自行收敛成 DENIED/VOID,不会凭空生出一张 park 或幻影 gate。
984
- * 残余 = 那类行的归因会记成 `routing_failure`(其实是政策 deny),归因精化登记为候件。
1093
+ * 🔴 **`deny` 部署上政策结算当场落行**(NP-2,7.100.0 合并复审 [ref] 交叉表 #4;codex R1-F1 当年**不采纳**的那一半
1094
+ * 「给 deny 造一条原子 deny 终态转移」在这里收了):窗到期 / emit 全灭两臂在 deny 政策下的终局 claim 是
1095
+ * `refuse` 意图(见 {@link unattendedClaimIntent})—— STREAM_PENDING→DECIDED(deny) + `settled_by = "policy"`,**按 ask
1096
+ * 作用域**(不碰批、不连坐兄弟)。落的来源词与本口交给 core 的是**同一个常量**(`POLICY_REFUSAL_DECISION`),于是
1097
+ * 同 ask 重入 / 收敛后重入由 `outcomeOfDecidedRow` 读回同一个对象。当年「危害为零」的论证(PARKING 行绑不上、
1098
+ * 自行收敛成 DENIED/VOID)在 S-689 之后不再成立:首判交 `{allow:false, settledBy:"policy"}`,行停 PARKING 的回放
1099
+ * 却交裸 `false`(收敛后 DENIED 无来源)—— core 当成人拒、可停批,两次判决第一次可被观测地分家。
1100
+ * 仍按 PARKING 记账的只剩两形(都不是本进程这一次政策结算):别的写者(park 政策副本 / 切换政策之前)推进过的行,
1101
+ * 与崩溃后收敛器孤儿腿代打 expire 的行 —— 前者重入走本口(`replayed_parked`),后者登记在收车档的未闭环项。
1102
+ * deny 政策下批仍是**单 ask 批**(见 {@link batchIdFor}):`refuse` 本来就不碰批,单 ask 批让新行与存量整腿批行
1103
+ * 的差别只剩「批 id 是谁」,结算语义一致。
985
1104
  */
986
1105
  private unattendedOutcome;
987
1106
  /** 政策**读**口(不是终局铸口)—— 回答「这台部署把无人可答翻成 park 吗」。
@@ -990,6 +1109,22 @@ export declare class ToolApprovalCoordinator {
990
1109
  * 纯粹的**政策查询**(`unattendedOutcome() !== false`)会和同一只 ask 的真终局各记一条遥测 ——
991
1110
  * 「一只 ask 一条线」当场不成立。读口不落日志,铸口落;谁也不兼职。 */
992
1111
  private get unattendedRoutesToPark();
1112
+ /**
1113
+ * 一只 ask 落在**哪只批**里 —— 批作用域随政策(S-689 codex R1-F1,判据全文见 {@link deriveAskScopedBatchId})。
1114
+ * · park 路由 ⇒ 整腿一批(`deriveBatchId`):窗到期赢家整批转投递面、兄弟连坐,core 随后 park,腿就此结束;
1115
+ * · deny 政策 ⇒ 单 ask 批:没有 park 路由,腿在一次部署政策拒之后继续跑 —— 整腿一批会让首只到期把批关掉,
1116
+ * 同腿后续的卡人批不动(`decideAsk` batch_closed ⇒ 404),兄弟还被连坐成 VOID、以裸 `false` 交 core(人拒)。
1117
+ * 读的是政策**读**口(不落遥测)。
1118
+ */
1119
+ 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();
993
1128
  /** {@link unattendedOutcome} 的**公开**口:`windowZero` 的 immediate-unavailable 闭包长在装配层
994
1129
  * (`http/routes/tasks.ts` 的 sync 腿),它必须与协调器内五臂读**同一个**旋钮值,而不是各算一遍。
995
1130
  * 出处随调用方给(那条腿知道自己是哪一只 ask)。 */
@@ -997,7 +1132,7 @@ export declare class ToolApprovalCoordinator {
997
1132
  taskId?: string;
998
1133
  owner?: string | null;
999
1134
  toolName?: string;
1000
- }): "unavailable" | false;
1135
+ }): UnattendedAskOutcome;
1001
1136
  /**
1002
1137
  * [ref] 车3 刀 3b —— [ref] §3.3 / 设计稿 §0 X-2 的**写侧准入门**。
1003
1138
  *
@@ -7,10 +7,11 @@ import { hasBidiControls } from "./text-bidi.js";
7
7
  import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerprint.js";
8
8
  import { createLogger } from "./observability/logger.js";
9
9
  import { recordFailOpen } from "./observability/fail-open.js";
10
- import { deriveAskId, 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, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
12
- import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
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";
12
+ import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
13
13
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
14
+ import { governanceOriginOf } from "./runtime-governance.js";
14
15
  const defaultLogger = createLogger();
15
16
  export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
16
17
  export function isApprovalGateKind(k) {
@@ -79,6 +80,27 @@ function sessionAllowCategory(toolName) {
79
80
  function sessionAllowKey(owner, sessionId, category) {
80
81
  return JSON.stringify([owner, sessionId, category]);
81
82
  }
83
+ export function standingAnswerMayRetire(facts) {
84
+ return !facts.governanceForced && !facts.requiresRealApproval && !facts.mandated;
85
+ }
86
+ export function parkRowStandingAnswerFacts(pending, gate, governance) {
87
+ const riskDescriptor = "riskDescriptor" in gate ? gate.riskDescriptor : undefined;
88
+ return {
89
+ governanceForced: governanceOriginOf({ riskDescriptor }, governance) === true,
90
+ requiresRealApproval: readRequiresRealApprovalSeat(pending),
91
+ mandated: mandatedFromSeat(readRuleOffersAbsenceSeat(pending)),
92
+ };
93
+ }
94
+ function mandatedFromSeat(seat) {
95
+ switch (seat.state) {
96
+ case "word":
97
+ return seat.word === "mandated";
98
+ case "unreadable":
99
+ return true;
100
+ case "absent":
101
+ return false;
102
+ }
103
+ }
82
104
  export const PERSIST_RULE_TEXT_ERROR = `persistRule.rule must be a non-empty string of at most ${MAX_RULE_TEXT_CHARS} characters`;
83
105
  export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a boolean when present";
84
106
  export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
@@ -149,6 +171,10 @@ export function parseToolApprovalResponse(body) {
149
171
  }
150
172
  return { ok: false, error: 'decision must be one of "allow" | "allow_session" | "deny"' };
151
173
  }
174
+ function unattendedRefusalReason(arm) {
175
+ return ("UNATTENDED_APPROVAL_POLICY=deny: this deployment declares that no one attends its approvals, so an approval " +
176
+ `request no one can answer is refused here instead of being parked for a person (arm: ${arm}).`);
177
+ }
152
178
  function isChildAsk(req) {
153
179
  return req.fromSubagent === true;
154
180
  }
@@ -318,11 +344,26 @@ export class ToolApprovalCoordinator {
318
344
  this.askAudit = opts?.askAudit;
319
345
  }
320
346
  unattendedOutcome(site) {
321
- const outcome = this.unattendedPolicy === "deny" ? false : "unavailable";
347
+ let outcome;
348
+ let outcomeWord;
349
+ switch (this.unattendedPolicy) {
350
+ case "park":
351
+ outcome = "unavailable";
352
+ outcomeWord = "unavailable";
353
+ break;
354
+ case "deny":
355
+ outcome = { allow: false, settledBy: POLICY_REFUSAL_DECISION.settledBy, reason: unattendedRefusalReason(site.arm) };
356
+ outcomeWord = "deny";
357
+ break;
358
+ default: {
359
+ const unreachable = this.unattendedPolicy;
360
+ throw new Error(`unattendedOutcome: unknown UNATTENDED_APPROVAL_POLICY word ${String(unreachable)}`);
361
+ }
362
+ }
322
363
  defaultLogger.warn("approver_unavailable", {
323
364
  arm: site.arm,
324
365
  policy: this.unattendedPolicy,
325
- outcome: outcome === false ? "deny" : "unavailable",
366
+ outcome: outcomeWord,
326
367
  owner: site.owner ?? null,
327
368
  taskId: site.taskId ?? null,
328
369
  toolName: site.toolName ?? null,
@@ -333,6 +374,12 @@ export class ToolApprovalCoordinator {
333
374
  get unattendedRoutesToPark() {
334
375
  return this.unattendedPolicy !== "deny";
335
376
  }
377
+ batchIdFor(askId, sourceTaskId, runId, legKey) {
378
+ return this.unattendedRoutesToPark ? deriveBatchId(sourceTaskId, runId, legKey) : deriveAskScopedBatchId(askId);
379
+ }
380
+ get unattendedClaimIntent() {
381
+ return this.unattendedRoutesToPark ? "expire" : "refuse";
382
+ }
336
383
  unattendedAskOutcome(site) {
337
384
  return this.unattendedOutcome({
338
385
  arm: "window_zero",
@@ -829,8 +876,7 @@ export class ToolApprovalCoordinator {
829
876
  }
830
877
  const primary = aliveCtxs[0];
831
878
  const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
832
- if (!governanceForced &&
833
- req.requiresRealApproval !== true &&
879
+ if (standingAnswerMayRetire({ governanceForced, requiresRealApproval: req.requiresRealApproval === true, mandated: req.mandated === true }) &&
834
880
  origin.sessionId &&
835
881
  this.allowAllSessions.has(sessionAllowKey(origin.owner, origin.sessionId, sessionAllowCategory(req.toolName))) &&
836
882
  (await this.sessionGrantUncontestedByRow(origin, req))) {
@@ -915,7 +961,7 @@ export class ToolApprovalCoordinator {
915
961
  const runId = origin.taskId;
916
962
  const legKey = origin.legKey;
917
963
  askId = deriveAskId(sourceTaskId, runId, req.toolCallId, legKey, req.delegation?.parentToolCallId);
918
- batchId = deriveBatchId(sourceTaskId, runId, legKey);
964
+ batchId = this.batchIdFor(askId, sourceTaskId, runId, legKey);
919
965
  try {
920
966
  const existingOrCreated = await this.withStoreDeadline(this.askStore.ensureAsk({
921
967
  askId,
@@ -938,6 +984,7 @@ export class ToolApprovalCoordinator {
938
984
  if (abandonedEnsure && lateEnsuredAskId !== undefined)
939
985
  voidAbandonedRow(lateEnsuredAskId);
940
986
  });
987
+ batchId = existingOrCreated.row.batchId;
941
988
  if (existingOrCreated.row.state !== "STREAM_PENDING") {
942
989
  releaseAdmission();
943
990
  const replayed = replayTerminalAskRow(existingOrCreated.row);
@@ -999,7 +1046,10 @@ export class ToolApprovalCoordinator {
999
1046
  let indeterminate = true;
1000
1047
  for (let attempt = 0; attempt <= DURABLE_CONVERGE_BACKOFF_MS.length; attempt++) {
1001
1048
  try {
1002
- confirmedRowExists = (await this.withStoreDeadline(this.askStore.getAsk(askId), "getAsk(ensureAsk-confirm)")) !== null;
1049
+ const confirmedRow = await this.withStoreDeadline(this.askStore.getAsk(askId), "getAsk(ensureAsk-confirm)");
1050
+ confirmedRowExists = confirmedRow !== null;
1051
+ if (confirmedRow !== null)
1052
+ batchId = confirmedRow.batchId;
1003
1053
  indeterminate = false;
1004
1054
  break;
1005
1055
  }
@@ -1042,7 +1092,7 @@ export class ToolApprovalCoordinator {
1042
1092
  let selfEntry;
1043
1093
  let hostSettled;
1044
1094
  let denyReasonSettled;
1045
- const shapeDeny = () => {
1095
+ const shapeDeny = (bare) => {
1046
1096
  if (hostSettled !== undefined || (denyReasonSettled !== undefined && denyReasonSettled !== "")) {
1047
1097
  return {
1048
1098
  allow: false,
@@ -1050,18 +1100,17 @@ export class ToolApprovalCoordinator {
1050
1100
  ...(denyReasonSettled !== undefined && denyReasonSettled !== "" ? { reason: denyReasonSettled } : {}),
1051
1101
  };
1052
1102
  }
1053
- return false;
1103
+ return bare;
1054
1104
  };
1055
1105
  const shapeOutcome = (settled) => {
1056
1106
  if (windowRouteUnavailable) {
1057
- return this.unattendedOutcome({ arm: "window_expired", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability }) === false
1058
- ? shapeDeny()
1059
- : "unavailable";
1107
+ const unattended = this.unattendedOutcome({ arm: "window_expired", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1108
+ return unattended === "unavailable" ? unattended : shapeDeny(unattended);
1060
1109
  }
1061
1110
  if (settled.allowed) {
1062
1111
  return settled.updatedInput !== undefined ? { allow: true, updatedInput: settled.updatedInput } : true;
1063
1112
  }
1064
- return shapeDeny();
1113
+ return shapeDeny(false);
1065
1114
  };
1066
1115
  const settle = (allowed, outcome, updatedInput, hostSettledBy, denyReason) => {
1067
1116
  if (done)
@@ -1266,10 +1315,11 @@ export class ToolApprovalCoordinator {
1266
1315
  const store = this.askStore;
1267
1316
  const pinnedAskId = askId;
1268
1317
  const pinnedBatchId = batchId;
1318
+ const claimIntent = this.unattendedClaimIntent;
1269
1319
  await resolveLocalOutcome({
1270
1320
  intent: "expire",
1271
1321
  storeLabel: "claimTerminal(window)",
1272
- claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "expire"), "claimTerminal(window)", timeoutMs, (late) => {
1322
+ claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, claimIntent), "claimTerminal(window)", timeoutMs, (late) => {
1273
1323
  if (!late.claimed) {
1274
1324
  lateClaimLossSettle(late.current, "expire");
1275
1325
  return;
@@ -1385,6 +1435,7 @@ export class ToolApprovalCoordinator {
1385
1435
  ...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
1386
1436
  ...(effectiveGovernanceForced ? { governanceForced: true } : {}),
1387
1437
  ...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
1438
+ ...(req.mandated === true ? { mandated: true } : {}),
1388
1439
  ...(askId !== undefined && batchId !== undefined ? { askId, batchId } : {}),
1389
1440
  };
1390
1441
  selfEntry = pendingEntry;
@@ -1447,6 +1498,7 @@ export class ToolApprovalCoordinator {
1447
1498
  const store = this.askStore;
1448
1499
  const pinnedAskId = askId;
1449
1500
  const pinnedBatchId = batchId;
1501
+ const claimIntent = this.unattendedClaimIntent;
1450
1502
  const settleUnreachable = () => {
1451
1503
  if (done)
1452
1504
  return;
@@ -1456,7 +1508,7 @@ export class ToolApprovalCoordinator {
1456
1508
  await resolveLocalOutcome({
1457
1509
  intent: "unreachable",
1458
1510
  storeLabel: "claimTerminal(emit-failed)",
1459
- claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "expire"), "claimTerminal(emit-failed)", timeoutMs, (late) => {
1511
+ claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, claimIntent), "claimTerminal(emit-failed)", timeoutMs, (late) => {
1460
1512
  if (!late.claimed) {
1461
1513
  lateClaimLossSettle(late.current, "unreachable");
1462
1514
  return;
@@ -1962,7 +2014,11 @@ export class ToolApprovalCoordinator {
1962
2014
  }
1963
2015
  finishRespond(id, entry, parsed, opts) {
1964
2016
  let rememberApplied;
1965
- const grantable = entry.requiresRealApproval !== true && entry.governanceForced !== true;
2017
+ const grantable = standingAnswerMayRetire({
2018
+ governanceForced: entry.governanceForced === true,
2019
+ requiresRealApproval: entry.requiresRealApproval === true,
2020
+ mandated: entry.mandated === true,
2021
+ });
1966
2022
  if (parsed.value === "allow_session")
1967
2023
  rememberApplied = Boolean(entry.sessionId) && grantable;
1968
2024
  if (parsed.value === "allow_session" && entry.sessionId && grantable) {
@@ -25,7 +25,7 @@ import type { ToolApprovalFrame } from "../tool-approval.js";
25
25
  * `type X<T extends never> = T`(抄一份就是第二个写者,两处哪天分岔谁都发现不了)。
26
26
  * 本文件只有类型、不产 JS,所以被 `import type` 引用不新增任何运行期边。 */
27
27
  export type AssertAllKeysHandled<T extends never> = T;
28
- type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure" | "agentMessage" | "_sema_provenance";
28
+ type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure" | "agentMessage" | "_sema_provenance" | "spawnName" | "handback" | "compaction" | "resupplied" | "attempt";
29
29
  type NotificationExcluded = (typeof import("./wire-projection-faces.js").WIRE_PROJECTION_FACES)["task_notification"]["excluded"][number];
30
30
  type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected | NotificationExcluded>>;
31
31
  type _GuardNotificationReverse = AssertAllKeysHandled<Exclude<NotificationProjected | NotificationExcluded, keyof TaskNotificationPayload>>;
@@ -59,8 +59,8 @@ type _GuardMailboxReverse = AssertAllKeysHandled<Exclude<MailboxProjected, keyof
59
59
  type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
60
60
  type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
61
61
  type _GuardMailboxAppendReverse = AssertAllKeysHandled<Exclude<MailboxAppendProjected, keyof MailboxAppendMessage>>;
62
- type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools" | "hooks" | "lsp" | "writeProtection";
63
- type WiringSectionExcluded = "governance" | "configFingerprint" | "autoConsolidation" | "readDeny";
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";
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]>;
@@ -135,10 +135,10 @@ type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "prev
135
135
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
136
136
  type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
137
137
  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";
138
+ type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence";
139
139
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
140
140
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
141
- 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";
141
+ 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";
142
142
  type TaskResultStripped = never;
143
143
  type _GuardTaskResult = AssertAllKeysHandled<Exclude<keyof TaskResult, TaskResultForwarded | TaskResultStripped>>;
144
144
  type _GuardTaskResultReverse = AssertAllKeysHandled<Exclude<TaskResultForwarded | TaskResultStripped, keyof TaskResult>>;
@@ -1,7 +1,7 @@
1
1
  import {} from "@sema-agent/core";
2
2
  import { outwardPlane } from "../terminal.js";
3
3
  import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
4
- import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
4
+ import { NotifiedKeys, taskNotificationStreamKeyOf } from "../orchestration/workflow-completion-inbox.js";
5
5
  import { isParkLedgerEventType } from "./ledger-events.js";
6
6
  import { isModelSegmentBoundary, reasoningDeltaBytes, reasoningSegmentEventId } from "./model-segment.js";
7
7
  export function createSerialLedgerAppend(appendEvent, startSeq) {
@@ -119,7 +119,7 @@ export function createLedgerSink(opts) {
119
119
  break;
120
120
  case "task_notification": {
121
121
  const tn = ev.notification;
122
- const tnKey = tn?.task_id ? taskNotificationStreamKey({ task_type: tn.task_type, task_id: tn.task_id, status: String(tn.status), seq: tn.seq }) : undefined;
122
+ const tnKey = taskNotificationStreamKeyOf(tn);
123
123
  const tnPrior = tnKey !== undefined ? keys.get(tnKey) : undefined;
124
124
  if (tnPrior === undefined || !(await tnPrior)) {
125
125
  const write = append("task_notification", taskNotificationEventData(ev));
@@ -422,6 +422,17 @@ export declare function taskNotificationEventData(ev: {
422
422
  * `kind` 过闭集(`trace/sema-provenance.ts` 单点,park 半场同一判据);`agentType` 本 mount 恒缺席(工具无
423
423
  * agent-type 席),在场即透传(fleet spawn 帧按 taskId 可 join 时上游给)。 */
424
424
  _sema_provenance?: SemaProvenanceInput;
425
+ /** core 7.30.0 [ref] S1 / #1034 / #1036(S-616):委派终态帧三枚 facet + 重供两键 —— 判据单点在
426
+ * `trace/task-notification-facets.ts`(与 park 半场同一只;入参按 `unknown` 收、判过形才成 typed)。 */
427
+ spawnName?: string;
428
+ handback?: string;
429
+ compaction?: {
430
+ passes: number;
431
+ breakerOpen: boolean;
432
+ kind?: string;
433
+ };
434
+ resupplied?: boolean;
435
+ attempt?: number;
425
436
  };
426
437
  eventId?: string;
427
438
  parentToolCallId?: string;
@@ -840,6 +851,8 @@ export declare function compactedEventData(ev: {
840
851
  * 的数、post 恒是结构和 + 固定提示词开销,两者的差混坐标,实测在普通 run 上是负数)。本仓的处置因此
841
852
  * 是**三条都逐字透传、一条都不合成**:谁要画上下文表就读 `freedTokens`,读不到就渲染 unknown。 */
842
853
  freedTokens?: number;
854
+ /** core 7.30.0 #1036(S-616):这一次压缩**摘要掉**了几条之前已交给模型的跨会话消息(在场 iff ≥1)。 */
855
+ summarizedCrossSessionMessages?: number;
843
856
  durationMs?: number;
844
857
  phaseDurations?: {
845
858
  prepareMs?: number;
@@ -1,10 +1,12 @@
1
1
  import { redactErrorMessage, redactModelText } from "../observability/run-terminal.js";
2
- import { MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, screenGateOutcome, ToolRoster, ToolRosterDelta, WRITE_PROTECTION_TARGET_VIEWS } from "@sema-agent/core";
2
+ import { MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, screenGateOutcome, TEAM_ROLES, ToolRoster, ToolRosterDelta, WRITE_PROTECTION_TARGET_VIEWS } from "@sema-agent/core";
3
3
  import { Value } from "typebox/value";
4
4
  import { recordFailOpen } from "../observability/fail-open.js";
5
5
  import { redactSecrets, redactDeep } from "./redact.js";
6
6
  import { injectionTierOrAbsent } from "./injection-tier.js";
7
7
  import { semaProvenanceOrAbsent } from "./sema-provenance.js";
8
+ import { resupplyFields, terminalFrameFacetFields } from "./task-notification-facets.js";
9
+ import { dropProjectedFace } from "./projection-drop.js";
8
10
  export async function appendModelUsageDelta(append, modelUsage, taskId) {
9
11
  const delta = modelUsage?.drain(taskId);
10
12
  if (delta) {
@@ -229,10 +231,6 @@ export function toolEndEventData(ev) {
229
231
  ...identityFields(ev),
230
232
  };
231
233
  }
232
- const dropProjectedFace = (what) => {
233
- recordFailOpen("server.trace.projection-input-malformed", what);
234
- return undefined;
235
- };
236
234
  export function toolProgressEventData(ev) {
237
235
  const id = typeof ev.toolCallId === "string" && ev.toolCallId !== "" ? ev.toolCallId : undefined;
238
236
  const name = typeof ev.toolName === "string" && ev.toolName !== "" ? ev.toolName : undefined;
@@ -326,6 +324,8 @@ export function taskNotificationEventData(ev) {
326
324
  const p = semaProvenanceOrAbsent(n._sema_provenance);
327
325
  return p !== undefined ? { _sema_provenance: p } : {};
328
326
  })(),
327
+ ...terminalFrameFacetFields(n),
328
+ ...resupplyFields(n),
329
329
  ...(() => {
330
330
  const tier = injectionTierOrAbsent(ev.priority);
331
331
  return tier !== undefined ? { priority: tier } : {};
@@ -535,9 +535,17 @@ export function wiringManifestEventData(ev) {
535
535
  return { targetView };
536
536
  })();
537
537
  const mcp = projectWiringManifestMcpRows(m.mcp);
538
+ const teamRole = (() => {
539
+ if (m.teamRole === undefined)
540
+ return undefined;
541
+ if (!TEAM_ROLES.includes(m.teamRole))
542
+ return dropProjectedFace("wiring_manifest.teamRole is outside core TEAM_ROLES");
543
+ return m.teamRole;
544
+ })();
538
545
  return {
539
546
  ...(num(m.schemaVersion) !== undefined ? { schemaVersion: m.schemaVersion } : {}),
540
547
  ...(str(legIn.kind) !== undefined ? { leg: { kind: legIn.kind } } : {}),
548
+ ...(teamRole !== undefined ? { teamRole } : {}),
541
549
  ...(ask !== undefined ? { ask } : {}),
542
550
  ...(question !== undefined ? { question } : {}),
543
551
  ...(str(interactionIn.posture) !== undefined ? { interaction: { posture: interactionIn.posture } } : {}),
@@ -676,6 +684,15 @@ export function compactedEventData(ev) {
676
684
  ...(num(ev.tokensAfter) !== undefined ? { tokensAfter: num(ev.tokensAfter) } : {}),
677
685
  ...(num(ev.triggerTokensBefore) !== undefined ? { triggerTokensBefore: num(ev.triggerTokensBefore) } : {}),
678
686
  ...(num(ev.freedTokens) !== undefined ? { freedTokens: num(ev.freedTokens) } : {}),
687
+ ...(() => {
688
+ const v = ev.summarizedCrossSessionMessages;
689
+ if (v === undefined)
690
+ return {};
691
+ if (Number.isInteger(v) && v >= 1)
692
+ return { summarizedCrossSessionMessages: v };
693
+ dropProjectedFace("compacted.summarizedCrossSessionMessages is not an integer >= 1");
694
+ return {};
695
+ })(),
679
696
  ...(num(ev.durationMs) !== undefined ? { durationMs: num(ev.durationMs) } : {}),
680
697
  ...(phaseDurations !== undefined ? { phaseDurations } : {}),
681
698
  };
@@ -0,0 +1,2 @@
1
+ export declare function dropProjectedFace(what: string): undefined;
2
+ //# sourceMappingURL=projection-drop.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { recordFailOpen } from "../observability/fail-open.js";
2
+ export function dropProjectedFace(what) {
3
+ recordFailOpen("server.trace.projection-input-malformed", what);
4
+ return undefined;
5
+ }
6
+ //# sourceMappingURL=projection-drop.js.map
@@ -26,8 +26,11 @@ export declare const SEMA_PROVENANCE_KINDS: readonly ["agent_message"];
26
26
  * (core-keyset-guard.ts ① 的同名排除键)一同候 [ref] 表态。放进 EXCLUDED 而不是 PROJECTED,是**沿用**上一行的
27
27
  * 裁定而不是新增特判:表态未出前放行一个 lane 词,等于替它预先声明一种 cli 渲染形。表态落定那天三处(本簿、
28
28
  * 载体键、①-nested 的 `peerMeta`)同批翻。
29
+ * · `inbox_notice` —— core 7.30.0 #1036(S-616 提货;本闭集当拍真红出它)。**同一条车道的第三个载体**(收件箱通告,
30
+ * `crossSessionMessage` / `crossSessionNotice` 之后),同一个 session-box drain 铸;载体键 `inboxNotice` 在
31
+ * core-keyset-guard.ts ① 同批进 EXCLUDED。沿用上两行的裁定,不是新判据:[ref] 表态落定那天四处同批翻。
29
32
  */
30
- export declare const SEMA_PROVENANCE_KINDS_EXCLUDED: readonly ["cross_session_message", "cross_session_notice"];
33
+ export declare const SEMA_PROVENANCE_KINDS_EXCLUDED: readonly ["cross_session_message", "cross_session_notice", "inbox_notice"];
31
34
  /** 本仓两条腿入参的**形**(不是 core 类型:入参是运行期载荷,`kind` 先当 string 收、经闭集才成 typed)。 */
32
35
  export interface SemaProvenanceInput {
33
36
  kind: string;