@sema-agent/server 7.128.0 → 7.129.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 (96) hide show
  1. package/MIGRATION.md +118 -0
  2. package/USAGE.md +89 -18
  3. package/dist/approval-card.d.ts +53 -26
  4. package/dist/approval-card.js +40 -11
  5. package/dist/approval-crash-convergence.js +4 -4
  6. package/dist/bake-runner/main.js +2 -1
  7. package/dist/boot/memory-consolidation.d.ts +46 -16
  8. package/dist/boot/memory-consolidation.js +15 -8
  9. package/dist/boot/side-query-lane.d.ts +7 -20
  10. package/dist/boot/side-query-lane.js +0 -24
  11. package/dist/boot/stage-01-config.js +3 -0
  12. package/dist/boot/stage-06-runners.js +1 -1
  13. package/dist/boot/stores.js +2 -2
  14. package/dist/boot/web-admin.js +17 -10
  15. package/dist/boot/webfetch-summarize-lane.d.ts +10 -7
  16. package/dist/boot/webfetch-summarize-lane.js +2 -3
  17. package/dist/brain.js +6 -9
  18. package/dist/capabilities/builtin-tools.d.ts +2 -2
  19. package/dist/capabilities/engine-capabilities.d.ts +14 -0
  20. package/dist/capabilities/engine-capabilities.js +12 -0
  21. package/dist/capability-facts.d.ts +3 -1
  22. package/dist/capability-facts.js +1 -0
  23. package/dist/config-types.d.ts +4 -3
  24. package/dist/egress-redirect.d.ts +12 -0
  25. package/dist/egress-redirect.js +7 -0
  26. package/dist/entry-mode-clamp-notice.d.ts +30 -0
  27. package/dist/entry-mode-clamp-notice.js +13 -0
  28. package/dist/fleet/fleet-bus.d.ts +7 -0
  29. package/dist/fleet/fleet-bus.js +1 -0
  30. package/dist/hooks/hook-llm.js +6 -2
  31. package/dist/host-lsp-manager.d.ts +3 -3
  32. package/dist/http/admission.js +5 -2
  33. package/dist/http/resume-legs.js +46 -27
  34. package/dist/http/route-ctx.d.ts +1 -1
  35. package/dist/http/routes/approvals-assistant.js +6 -4
  36. package/dist/http/routes/capabilities.js +1 -0
  37. package/dist/http/routes/session-sync.js +3 -4
  38. package/dist/http/routes/sessions.js +4 -17
  39. package/dist/http/send.d.ts +1 -1
  40. package/dist/http/send.js +4 -3
  41. package/dist/http/wire-types.d.ts +3 -2
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/observability/fail-open.d.ts +7 -3
  45. package/dist/observability/fail-open.js +7 -3
  46. package/dist/observability/secret-env-scrub.d.ts +37 -3
  47. package/dist/observability/secret-env-scrub.js +2 -2
  48. package/dist/parked-decide.js +5 -2
  49. package/dist/plugins/memory-embedder.js +5 -0
  50. package/dist/plugins/one-time-migrations.d.ts +180 -9
  51. package/dist/plugins/one-time-migrations.js +315 -3
  52. package/dist/plugins/pg-pool.js +3 -1
  53. package/dist/plugins/remote-env-host.d.ts +17 -20
  54. package/dist/plugins/remote-env-host.js +13 -25
  55. package/dist/plugins/session-policy-store-sql.d.ts +16 -4
  56. package/dist/plugins/session-policy-store-sql.js +17 -20
  57. package/dist/plugins/session-rules-column.d.ts +16 -0
  58. package/dist/plugins/session-rules-column.js +6 -0
  59. package/dist/plugins/tidb-pool.js +4 -1
  60. package/dist/read-face-posture.d.ts +2 -1
  61. package/dist/request-value-closure.js +1 -1
  62. package/dist/retired-card-words.d.ts +17 -0
  63. package/dist/retired-card-words.js +49 -0
  64. package/dist/rule-entry-list.js +2 -0
  65. package/dist/run-local.js +10 -3
  66. package/dist/server-secret-env.d.ts +77 -85
  67. package/dist/server-secret-env.js +40 -26
  68. package/dist/session-policy-wire.d.ts +61 -124
  69. package/dist/session-policy-wire.js +59 -121
  70. package/dist/session-sync.d.ts +8 -6
  71. package/dist/session-sync.js +6 -5
  72. package/dist/spec-fields.d.ts +3 -2
  73. package/dist/standing-answer-grant.d.ts +17 -9
  74. package/dist/standing-answer-grant.js +11 -8
  75. package/dist/task-settings.d.ts +6 -1
  76. package/dist/task-settings.js +1 -1
  77. package/dist/tool-approval.d.ts +14 -7
  78. package/dist/tool-approval.js +23 -18
  79. package/dist/trace/core-keyset-guard.d.ts +3 -3
  80. package/dist/trace/engine-notice-wire.d.ts +1 -1
  81. package/dist/trace/engine-notice-wire.js +1 -0
  82. package/dist/trace/project.d.ts +7 -4
  83. package/dist/trace/project.js +6 -75
  84. package/dist/trace/tool-output-media.d.ts +2 -1
  85. package/dist/trace/tool-output-media.js +3 -25
  86. package/dist/web-admin-client/apply-effective.js +28 -11
  87. package/dist/web-admin-client/facade.d.ts +1 -1
  88. package/dist/web-admin-client/facade.js +1 -1
  89. package/dist/web-admin-client/read-face.d.ts +73 -20
  90. package/dist/web-admin-client/read-face.js +76 -36
  91. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  92. package/dist/web-admin-client/skills-mcp.js +1 -1
  93. package/dist/web-admin-client/types.d.ts +3 -3
  94. package/package.json +3 -3
  95. package/dist/mcp-tool-axes.d.ts +0 -62
  96. package/dist/mcp-tool-axes.js +0 -47
@@ -50,6 +50,7 @@
50
50
  import { type ExecutionEnv, type TaskSpec, type ThinkingLevel, type ToolPolicy, type PromptProvider, type PermissionMode } from "@sema-agent/core";
51
51
  import type { HooksConfig } from "@sema-agent/settings-schema/hooks";
52
52
  import { type KeyClosureIssue } from "./request-key-closure.js";
53
+ import { type EntryPermissionMode } from "./runtime-governance.js";
53
54
  /**
54
55
  * 请求面 `permissionMode` 的闭集 = core `PERMISSION_MODES` **全集**(顶层 `permissionMode` / `settings.permissions.defaultMode` /
55
56
  * `?permissionMode=` 查询串三处同读)。本仓不持有第二份词表,也不持有「收 / 不收」的表态:判据 = core 的 `isPermissionMode`,型 = core 的
@@ -111,6 +112,7 @@ export declare function planReviewDecisionExitsPlan(decision: "approve" | "edit"
111
112
  * 入口词 = `runtime-governance.ts` `entryPermissionMode(体的生效词, 地板).effective`(「词在地板下变成什么」的唯一一只函数;秩是 core 的)。本函数只加
112
113
  * 「写进体」这一半:地板缺席 ⇒ 体**原物**交回(零字节变化);地板在场 ⇒ 新体 `{ ...body, permissionMode: 入口词 }`,调用方的对象不改;bundle 的
113
114
  * `settings.permissions.defaultMode` 不动 —— 顶层赢过它({@link effectivePermissionMode} 的优先序),再改它就是同一语义的第二个写者。
115
+ * 同时交回**这一次求值的读数**(`entry`:请求词 / 地板 / 生效词 / 折没折)—— 披露这一折的通告(`entry-mode-clamp-notice.ts`)读它,不另判一遍。
114
116
  *
115
117
  * 调用点一个:提交入口(`http/admission.ts` 的 `prepareSpec`:值形门全部判完之后、`resolveSpec` 之前)。那一份体就是首跑装配读的体,也是
116
118
  * `buildCheckpointCtx` 持久化的体 ⇒ 一处盖章同时覆盖首跑与每一次续跑重建;装配腿与续跑腿都不读地板(两处读 = 两个源)。
@@ -120,7 +122,10 @@ export declare function planReviewDecisionExitsPlan(decision: "approve" | "edit"
120
122
  export declare function stampEntryPermissionMode<B extends {
121
123
  permissionMode?: unknown;
122
124
  settings?: unknown;
123
- }>(body: B, floor: PermissionMode | undefined): B;
125
+ }>(body: B, floor: PermissionMode | undefined): {
126
+ readonly body: B;
127
+ readonly entry: EntryPermissionMode;
128
+ };
124
129
  /**
125
130
  * S-433 —— **一条已持久化的提交体的生效 permission mode**,单点。
126
131
  *
@@ -35,7 +35,7 @@ export function planReviewDecisionExitsPlan(decision) {
35
35
  }
36
36
  export function stampEntryPermissionMode(body, floor) {
37
37
  const entry = entryPermissionMode(storedBodyPermissionMode(body), floor);
38
- return entry.floor === undefined ? body : { ...body, permissionMode: entry.effective };
38
+ return { body: entry.floor === undefined ? body : { ...body, permissionMode: entry.effective }, entry };
39
39
  }
40
40
  export function storedBodyPermissionMode(body) {
41
41
  return effectivePermissionMode(body, parseTaskSettings(body.settings).settings);
@@ -58,7 +58,7 @@
58
58
  import { type ParkedGateFace } from "./approval-content-kind.js";
59
59
  import { type ApprovalDecidedBy, type HostSettledBy, type RetiredDecisionKeyFacts } from "./host-decision.js";
60
60
  import type { IncomingMessage } from "node:http";
61
- import { type AskClass, type AskRequest, type AskOutcome, type PermissionResult, type CheckpointGate, type PendingAction, type PersistedRuleMandate, type ReadRootGrantCandidate, type RuleOffer, type AutoModeFallbackCause, type BoundSkillGrants, type ToolApprovalCard } from "@sema-agent/core";
61
+ import { type AskClass, type AskRequest, type AskOutcome, type PermissionResult, type CheckpointGate, type PendingAction, type PersistedRuleMandate, type ReadRootGrantCandidate, type RuleOffer, type AutoModeFallbackCause, type BoundGrantAbsence, type BoundSkillGrants, type ToolApprovalCard } from "@sema-agent/core";
62
62
  import { type CardRulePersisted, type RuleConsentLane } from "./rules-consent.js";
63
63
  import { type AllowRuleShadowProjection, type DenialLimitFallback, type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
64
64
  import { type ApprovalAskStore, type AskRow } from "./plugins/approval-ask-store-sql.js";
@@ -67,7 +67,7 @@ import { type AskRowEndingCause, type SettleCause, type SettleOutcome } from "./
67
67
  import type { ParkCommitWatch } from "./plugins/park-commit-watch.js";
68
68
  import { type GovernanceAskMarks } from "./governance-ask-marks.js";
69
69
  import { type GovernancePosture } from "./runtime-governance.js";
70
- import { type GrantBinding, type StandingAnswerGrantCarrier } from "./standing-answer-grant.js";
70
+ import { type GrantBinding, type StandingAnswerCall } from "./standing-answer-grant.js";
71
71
  import { type ApprovalExemptionStore } from "./plugins/approval-exemption-store.js";
72
72
  /** [ref]:审批 gate kind 闭集(core gateMatch 的 `human`/`irreversible_ask` ↔ outcome.gate "policy_ask")。
73
73
  * 此前 7 份手写副本散在两只 checkpoint store 的数组/SQL 字面与 /decide 守卫——core 加审批味 kind 时
@@ -192,9 +192,10 @@ export declare function carriesDeploymentListQuestion(deploymentListAsks: boolea
192
192
  * (`task-settings.ts`,`false`)—— 它们在交出这一问之前先读按工具名记的耐久豁免店(「本会话不再询问」在本层兑现,继承链上的同一问才够得着;审批席依约不退役
193
193
  * 继承问),而**能不能读**与审批席 / 回决写侧是同一只判据({@link standingAnswerDurablyRecordable}):记得下的才代答,记不下的(带授予绑定的问 ……)不代答,
194
194
  * 存量行也不例外 —— 一事一读。事实取自**这一问本身**:类集经 core 包根唯一的类派生 `classifyAsk`({@link NO_GATE_FACTS});`requiresRealApproval` /
195
- * `mandated` 读这一问自报的成员;治理位恒假(本层不是治理层);授予绑定读调用(`grantBindingOf`)。
195
+ * `mandated` 读这一问自报的成员;治理位恒假(本层不是治理层);授予绑定读**调用**(`callGrantBindingOf` —— 本层手里还没有引擎铸的卡,core 7.51.0 不在
196
+ * `ToolCallRequest` 上盖绑定词,S-1493 载体问题见 `standing-answer-grant.ts` 头注)。
196
197
  */
197
- export declare function policyAskStandingAnswerFacts(req: StandingAnswerGrantCarrier, ask: Extract<PermissionResult, {
198
+ export declare function policyAskStandingAnswerFacts(req: StandingAnswerCall, ask: Extract<PermissionResult, {
198
199
  action: "ask";
199
200
  }>, deploymentListQuestion: boolean): StandingAnswerFacts;
200
201
  /**
@@ -216,7 +217,8 @@ export interface StandingAnswerFacts {
216
217
  readonly mandated: boolean;
217
218
  /** 这一问的应答**授不授予**这一次调用之外的东西、授予的话绑定是什么(批准引擎的 `Skill` = 激活技能 + 本 run 内清单免问)。
218
219
  * `bound` ⇒ 站着的答案按「工具 + 绑定」记、只代答绑定相同的问(core 7.43.0 #1348 / #1357:活的问与停驻行同带 `boundGrantDigest`);
219
- * `unreadable` ⇒ 不可记、不被代答(fail-closed)。读者一律经 `standing-answer-grant.ts` 的 `grantBindingOf`(唯一读口)折这一位。 */
220
+ * `unreadable` ⇒ 不可记、不被代答(fail-closed)。读者一律经 `standing-answer-grant.ts` 折这一位:载体由问的属主定(`questionGrantBindingOf`)——
221
+ * 引擎铸的问读卡上引擎的词(`grantBindingOf`),部署名单自己的问与策略层的调用读 `callGrantBindingOf`(S-1493)。 */
220
222
  readonly grantBinding: GrantBinding;
221
223
  /** 引擎盖在这一问上的**类集**(`AskRequest.classes` / 停驻行孪生座;经 core 的屏 `screenAskClassMembers` 读)。`undefined` = 卡上没有类
222
224
  * (老引擎 / 不盖类的站)或屏拒 —— 读不出的类不是「无类」。 */
@@ -275,7 +277,7 @@ export declare function standingAnswerHome(facts: StandingAnswerFacts): Standing
275
277
  * `seat` 还得没有授予绑定 —— 耐久店按 `(会话, 工具名)` 记,没有绑定这一维,记下一张带绑定的答案就会代答同名工具的每一种绑定
276
278
  * (`plugins/approval-exemption-store.ts`);`none` 记不住。读者:耐久 `/decide` 腿的 remember 可记性(`http/resume-legs.ts`)与运维队列行的
277
279
  * `rememberRefused`(`http/routes/approvals-assistant.ts`)—— 两处同答;策略层的豁免读面(部署名单门、workflow 问句门)经本函数 + {@link policyAskStandingAnswerFacts}
278
- * 决定读不读店。读侧同理:耐久店不代答带绑定的问(`standingGrantSettles` 经 `answerGrantsBeyondCall`)。
280
+ * 决定读不读店。读侧同理:耐久店不代答带绑定的问(`standingGrantSettles` 读这一问的 `grantBinding`,不是 `none` 就不读店)。
279
281
  */
280
282
  export declare function standingAnswerDurablyRecordable(facts: StandingAnswerFacts): boolean;
281
283
  /**
@@ -306,7 +308,7 @@ export declare function standingAnswerDurablyRecordable(facts: StandingAnswerFac
306
308
  * (`SENSITIVE_WRITE_PATTERNS`)、`commandPolicy`、`AUTONOMY` 内层策略产的门与 shellGate `classify` 档,park 行上没有取证格
307
309
  * (live 腿靠进程内标记表,跨副本 / 重启后够不着),本折法读不到它们的治理出身。
308
310
  */
309
- export declare function parkRowStandingAnswerFacts(pending: Extract<PendingAction, {
311
+ export declare function parkRowStandingAnswerFacts(stored: Extract<PendingAction, {
310
312
  kind: "tool_approval";
311
313
  }>, gate: CheckpointGate, governance: GovernancePosture | undefined,
312
314
  /** 部署名单门对这一行的调用出不出它自己的问(`approval.ts` 的 `deploymentListAsks`,调用方现读活配置;停驻行上只有 wire 名)。 */
@@ -467,6 +469,11 @@ export interface ToolApprovalFrame {
467
469
  /** 上一位哈希的那一份值:`{ skill, allowedTools, disallowedTools }`(`skill: null` = 调用没点名任何已载技能)。与摘要同在同缺;**部署文本**,
468
470
  * 壳按上下文转义渲染(core:display metadata)。逐字过境。 */
469
471
  boundGrants?: BoundSkillGrants;
472
+ /** S-1493(core 7.51.0 #1688,**ADDITIVE**,`"tool_approval"` only):这张卡为什么没有 {@link boundGrantDigest} —— 引擎铸的每张卡恰带两者之一:
473
+ * `not_bindable`(这一问不是引擎 `Skill` 工具的激活,不绑任何授予 ⇒ 应答可按工具记)/ `unreadable`(绑着却读不出;活卡上引擎恒给摘要,这一词
474
+ * 只出现在读存量行的面上)。闭集经 `readClosedWordSeat(req, "boundGrantAbsence")` 窄读(筛 = core `BOUND_GRANT_ABSENCES`),集外 ⇒ 缺席 + 计一次。
475
+ * 与停驻行 / 运维队列行 / inbox 行的同名位同值;站着的答案的授予绑定(引擎铸的问那一家;部署名单自己的问读调用口)读它(`standing-answer-grant.ts` `grantBindingOf`)。display。 */
476
+ boundGrantAbsence?: BoundGrantAbsence;
470
477
  /** core 7.43.0 #1353:分类器没给判词的成因词(`error` / `timeout` / `parse_error`;只在类 `classifier_no_ruling` 的卡上)。闭集经 core 的
471
478
  * `readClosedWordSeat(req, "classifierNoRuling")` 窄读(筛 = core `isAutoModeFallbackCause`),集外 ⇒ 缺席 + 计一次。display:本仓不按它裁决。 */
472
479
  classifierNoRuling?: AutoModeFallbackCause;
@@ -9,13 +9,13 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
9
9
  import { createLogger } from "./observability/logger.js";
10
10
  import { recordFailOpen } from "./observability/fail-open.js";
11
11
  import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
12
- import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
12
+ import { currentParkArm, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readStoredCardEnvelope, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
13
13
  import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
14
14
  import { APPROVAL_ENDING_CAUSES, SETTLE_OUTCOME, causeOfAskRow } from "./approval-deny-reasons.js";
15
15
  import { settleByPolicy } from "./approval-policy-settlement.js";
16
16
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
17
17
  import { governanceOriginOf } from "./runtime-governance.js";
18
- import { answerGrantsBeyondCall, grantBindingOf } from "./standing-answer-grant.js";
18
+ import { callGrantBindingOf, questionGrantBindingOf } from "./standing-answer-grant.js";
19
19
  import { operatorAsk } from "./operator-ask.js";
20
20
  import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
21
21
  const defaultLogger = createLogger();
@@ -99,13 +99,14 @@ function sessionAllowKey(owner, sessionId, category, binding) {
99
99
  }
100
100
  function askStandingAnswerFacts(req, governanceForced, deploymentListAsks) {
101
101
  const classes = screenAskClassMembers(req.askClass, req.classes)?.classes;
102
+ const deploymentListQuestion = carriesDeploymentListQuestion(deploymentListAsks, classes);
102
103
  return {
103
104
  governanceForced,
104
105
  requiresRealApproval: req.requiresRealApproval === true,
105
106
  mandated: req.mandated === true,
106
- grantBinding: grantBindingOf(req),
107
+ grantBinding: questionGrantBindingOf(req, deploymentListQuestion),
107
108
  classes,
108
- deploymentListQuestion: carriesDeploymentListQuestion(deploymentListAsks, classes),
109
+ deploymentListQuestion,
109
110
  };
110
111
  }
111
112
  const NO_GATE_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} };
@@ -118,7 +119,7 @@ export function policyAskStandingAnswerFacts(req, ask, deploymentListQuestion) {
118
119
  governanceForced: false,
119
120
  requiresRealApproval: ask.requiresRealApproval === true,
120
121
  mandated: ask.mandated === true,
121
- grantBinding: grantBindingOf(req),
122
+ grantBinding: callGrantBindingOf(req),
122
123
  classes: [...classifyAsk(ask, NO_GATE_FACTS).classes],
123
124
  deploymentListQuestion,
124
125
  };
@@ -146,16 +147,18 @@ export function standingAnswerDurablyRecordable(facts) {
146
147
  }
147
148
  }
148
149
  }
149
- export function parkRowStandingAnswerFacts(pending, gate, governance, deploymentListAsks) {
150
+ export function parkRowStandingAnswerFacts(stored, gate, governance, deploymentListAsks) {
150
151
  const riskDescriptor = "riskDescriptor" in gate ? gate.riskDescriptor : undefined;
152
+ const pending = currentParkArm(stored);
151
153
  const classes = screenAskClassMembers(pending.askClass, pending.classes)?.classes;
154
+ const deploymentListQuestion = carriesDeploymentListQuestion(deploymentListAsks, classes);
152
155
  return {
153
156
  governanceForced: governanceOriginOf({ riskDescriptor }, governance) === true,
154
157
  requiresRealApproval: readParkRowTrueSeat(pending, "requiresRealApproval"),
155
158
  mandated: readParkRowTrueSeat(pending, "mandated") || mandatedFromSeat(readRuleOffersAbsenceSeat(pending)),
156
- grantBinding: grantBindingOf(pending),
159
+ grantBinding: questionGrantBindingOf(pending, deploymentListQuestion),
157
160
  classes,
158
- deploymentListQuestion: carriesDeploymentListQuestion(deploymentListAsks, classes),
161
+ deploymentListQuestion,
159
162
  };
160
163
  }
161
164
  function pendingEntryStandingAnswerFacts(entry) {
@@ -831,7 +834,7 @@ export class ToolApprovalCoordinator {
831
834
  if (!sessionId)
832
835
  return false;
833
836
  const live = this.allowAllSessions.has(sessionAllowKey(origin.owner, sessionId, sessionAllowCategory(req.toolName), facts.grantBinding));
834
- const durable = !live && !answerGrantsBeyondCall(req) && (await this.durableGrantHas(sessionId, req.toolName));
837
+ const durable = !live && facts.grantBinding.kind === "none" && (await this.durableGrantHas(sessionId, req.toolName));
835
838
  if (!live && !durable)
836
839
  return false;
837
840
  if (!(await this.sessionGrantUncontestedByRow(origin, req)))
@@ -1154,6 +1157,7 @@ export class ToolApprovalCoordinator {
1154
1157
  let ruleLaneMaterial = this.buildRuleLaneMaterial(req, origin.owner, governanceForced, origin.sessionId);
1155
1158
  const mandateWord = readClosedWordSeat(req, "mandate");
1156
1159
  const noRulingCause = readClosedWordSeat(req, "classifierNoRuling");
1160
+ const grantAbsence = readClosedWordSeat(req, "boundGrantAbsence");
1157
1161
  const cardWord = readApprovalCard(req.approvalCard);
1158
1162
  const originWord = typeof req.origin === "string" && req.origin.length > 0 ? req.origin : undefined;
1159
1163
  const askClassPair = readAskClassPair(req);
@@ -1185,6 +1189,7 @@ export class ToolApprovalCoordinator {
1185
1189
  ...(standingAnswerHome(askStandingAnswerFacts(req, governanceForced, deploymentListAsks)) === "none" ? { rememberRefused: true } : {}),
1186
1190
  ...(typeof req.boundGrantDigest === "string" ? { boundGrantDigest: req.boundGrantDigest, ...(req.boundGrants !== undefined ? { boundGrants: req.boundGrants } : {}) } : {}),
1187
1191
  ...(noRulingCause !== undefined ? { classifierNoRuling: noRulingCause } : {}),
1192
+ ...(grantAbsence !== undefined ? { boundGrantAbsence: grantAbsence } : {}),
1188
1193
  ...(cardWord !== undefined ? { approvalCard: cardWord } : {}),
1189
1194
  ...(mandateWord !== undefined ? { mandate: mandateWord } : {}),
1190
1195
  ...(originWord !== undefined ? { origin: originWord } : {}),
@@ -1273,20 +1278,20 @@ export class ToolApprovalCoordinator {
1273
1278
  releaseAdmission();
1274
1279
  return replayTerminalRow(existingOrCreated.row);
1275
1280
  }
1276
- const persistedEnvelope = ApprovalCardEnvelopeSchema.safeParse(existingOrCreated.row.cardJson);
1281
+ const persistedEnvelope = readStoredCardEnvelope(existingOrCreated.row.cardJson);
1277
1282
  const maxMintableWindowMs = Math.max(effectiveWindowMs, this.unreachedTtlMs);
1278
1283
  const sanePersistedDeadline = Number.isFinite(existingOrCreated.row.expiresAtMs) && existingOrCreated.row.expiresAtMs <= Date.now() + maxMintableWindowMs + DURABLE_CALL_TIMEOUT_MS;
1279
- if (!persistedEnvelope.success || !sanePersistedDeadline) {
1280
- this.noteStoreError(new Error(`persisted approval row is unusable (envelopeOk=${persistedEnvelope.success}, deadlineOk=${sanePersistedDeadline}, ` +
1284
+ if (persistedEnvelope === undefined || !sanePersistedDeadline) {
1285
+ this.noteStoreError(new Error(`persisted approval row is unusable (envelopeOk=${persistedEnvelope !== undefined}, deadlineOk=${sanePersistedDeadline}, ` +
1281
1286
  `origin=${existingOrCreated.inserted ? "row-this-call-just-inserted" : "row-hit-idempotently"})`), "ensureAsk(persisted-row-unusable)");
1282
1287
  releaseAdmission();
1283
1288
  const unattended = this.unattendedOutcome({ arm: "persisted_row_unusable", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1284
1289
  return existingOrCreated.inserted ? this.landUnattendedOutcome(unattended, existingOrCreated.row) : unattended;
1285
1290
  }
1286
1291
  persistedExpiresAtMs = existingOrCreated.row.expiresAtMs;
1287
- wireApprovalId = persistedEnvelope.data.approvalId;
1292
+ wireApprovalId = persistedEnvelope.approvalId;
1288
1293
  frame.approvalId = wireApprovalId;
1289
- cardForFrame = persistedEnvelope.data.card;
1294
+ cardForFrame = persistedEnvelope.card;
1290
1295
  if (cardForFrame.governanceForced === true)
1291
1296
  frame.governanceForced = true;
1292
1297
  else
@@ -1726,9 +1731,9 @@ export class ToolApprovalCoordinator {
1726
1731
  return shapeOutcome(await allowedP);
1727
1732
  }
1728
1733
  }
1729
- const entryGrantBinding = grantBindingOf(req);
1730
1734
  const entryClasses = askClassPair?.classes;
1731
1735
  const entryDeploymentListQuestion = carriesDeploymentListQuestion(deploymentListAsks, entryClasses);
1736
+ const entryGrantBinding = questionGrantBindingOf(req, entryDeploymentListQuestion);
1732
1737
  const rememberRefused = standingAnswerHome(pendingEntryStandingAnswerFacts({
1733
1738
  ...(effectiveGovernanceForced ? { governanceForced: true } : {}),
1734
1739
  ...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
@@ -2145,10 +2150,10 @@ export class ToolApprovalCoordinator {
2145
2150
  if (lane === undefined || row.owner === null)
2146
2151
  return flag(false, "rule_lane_unavailable");
2147
2152
  const owner = row.owner;
2148
- const envelope = ApprovalCardEnvelopeSchema.safeParse(row.cardJson);
2149
- if (!envelope.success)
2153
+ const envelope = readStoredCardEnvelope(row.cardJson);
2154
+ if (envelope === undefined)
2150
2155
  return flag(false, "rule_material_absent");
2151
- const card = envelope.data.card;
2156
+ const card = envelope.card;
2152
2157
  if (card.governanceForced === true)
2153
2158
  return flag(false, "rule_governance_forced");
2154
2159
  const command = row.ruleCommand;
@@ -36,14 +36,14 @@ type _GuardProvenanceReverse = AssertAllKeysHandled<Exclude<ProvenanceProjected
36
36
  type AgentMessageProjected = "from" | "body";
37
37
  type _GuardAgentMessage = AssertAllKeysHandled<Exclude<keyof NonNullable<TaskNotificationPayload["agentMessage"]>, AgentMessageProjected>>;
38
38
  type _GuardAgentMessageReverse = AssertAllKeysHandled<Exclude<AgentMessageProjected, keyof NonNullable<TaskNotificationPayload["agentMessage"]>>>;
39
- type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId" | "spawnName";
39
+ type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId" | "spawnName" | "result";
40
40
  type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "description" | "agentType" | "name" | "currentAction" | "currentTool" | "parentSessionId" | "startedAt" | "workflowRunId" | "progressTaskId" | "progressParentTaskId" | "model";
41
41
  type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
42
42
  type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNotifExcluded, keyof BackgroundChildEvent>>;
43
43
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
44
44
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
45
45
  type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
46
- type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "allowRuleShadowed" | "probeCause" | "probeReason" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate" | "askClass" | "classes" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling" | "approvalCard";
46
+ type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "allowRuleShadowed" | "probeCause" | "probeReason" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate" | "askClass" | "classes" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling" | "approvalCard" | "boundGrantAbsence";
47
47
  type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "hasBidiControls" | "previewWithheld" | "execCwd" | "authoredBy";
48
48
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
49
49
  type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
@@ -136,7 +136,7 @@ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTrace
136
136
  /** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
137
137
  type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
138
138
  type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
139
- type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling";
139
+ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling" | "boundGrantAbsence";
140
140
  type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "approvalCard" | "restoreMode" | "checkpointId" | "previewWithheld" | "authoredBy";
141
141
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
142
142
  type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
@@ -16,7 +16,7 @@
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", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped", "server.model_unreachable", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "permission.allowed_tool_not_granted", "task.tool_turn_incomplete", "route.finish_reason_unknown", "route.stream_end_ignored"];
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", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped", "server.model_unreachable", "permission.entry_mode_clamped", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "permission.allowed_tool_not_granted", "task.tool_turn_incomplete", "route.finish_reason_unknown", "route.stream_end_ignored"];
20
20
  export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
21
21
  /** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
22
22
  export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
@@ -29,6 +29,7 @@ export const ENGINE_NOTICE_WIRE_CODES = [
29
29
  "instructions.source_changed",
30
30
  "session.records_skipped",
31
31
  "server.model_unreachable",
32
+ "permission.entry_mode_clamped",
32
33
  "classifier.unavailable",
33
34
  "config.permission_mode_unavailable",
34
35
  "permission.ask_released_by_mode",
@@ -167,7 +167,8 @@ export declare function toolStartEventData(ev: {
167
167
  * `done` 永远到不了、重连恒复现同一处),而 `gate-outcome-defective` 那一格还不涨。
168
168
  *
169
169
  * ⇒ 兑现方式从「枚举」换成**结构**:任何逃出投影的异常同走既有的那一口 report + withhold。规则因此变少
170
- * (不是「守住已知的 N 种坏形」而是「坏字节一律 withhold」),上面那几道具名形门留着只为**诊断串更准**。
170
+ * (不是「守住已知的 N 种坏形」而是「坏字节一律 withhold」)。core 7.51.0 起筛本身对任意 JSON 形不抛(KL-1731-a:只覆盖 JSON ——
171
+ * 访问器 / Proxy 这类非 JSON 值仍可能抛),本仓此前叠在筛前的具名形门与成员门(第二读法)随之退役,诊断串就是筛给的缺陷句。
171
172
  * ⛔ **刻意不动**「先脱敏后截断」的次序:截断在前会把跨界的密钥切成半截明文留在输出里,而且它只治
172
173
  * `note` 一条叶(`approver` 同病)—— 那是给一个类打两个特判,正是本仓要消掉的形。
173
174
  */
@@ -188,11 +189,13 @@ export declare function screenedGate(v: unknown): GateOutcome | undefined;
188
189
  * 一律过 {@link screenedGate},零例外」。它按**型**分派而不是按调用点,所以将来第二个携带
189
190
  * `GateOutcome` 的账本型只需把 {@link GATE_BEARING_LEDGER_EVENT_TYPE} 扩成词表,读腿一行都不用改。
190
191
  *
191
- * ## 读腿与写腿现在是**同一只**筛(7.74.0 起,零双读)
192
+ * ## 读腿与写腿现在是**同一只**筛(零双读)
192
193
  * 7.72.0–7.73.0 期间读腿多一步「认本服务上一版写下的退役词旧拼法」(`unresolvable` → `ancestor_marked`),
193
194
  * 那是一条**长期双读臂**:同一份字节在写腿与读腿上有两种理解,而且没有到期日。7.74.0 把它换成**一次性
194
- * 迁移**(启动期把存量字节原地改名;窗关后已删码)⇒ 读腿退回写腿那只严格筛子,集外词一律整条 withhold + 计数,
195
- * 不再有第二种读法 —— 残留旧拼法的字节(local 车道引擎 File 店里不可改写的已决行)同走这一只筛,响亮不静默。
195
+ * 迁移**(启动期把存量字节原地改名;窗关后已删码)。S-1542(core 7.51.0 #1731)起这只筛是 core `screenGateOutcome(g)` 缺省的**档案读法**
196
+ * (每个闭集 = 今日成员 ∪ core `RETIRED_MEMBERS`),读腿与写腿同走它:残留旧拼法的字节(local 车道引擎 File 店里不可改写的已决行)
197
+ * **整条读回、原词上帧、计数 0**(档案词是当年合法铸下的事实,不是缺陷,不改写成今天的词);整条 withhold + 计数的只有真缺陷
198
+ * (集外且不在退役表的词、破不变量、形坏)。读法只有一种。
196
199
  *
197
200
  * ## 与写腿脱敏汇点的关系(别再走回头路)
198
201
  * 写腿的对偶是 `redactLedgerEventData`(三孪生 store 的 `appendEvent` 落库前),它管的是脱敏、不是判形;
@@ -1,5 +1,5 @@
1
1
  import { admitProviderEcho, redactErrorMessage, redactModelText } from "../observability/run-terminal.js";
2
- import { isAskClass, isAskOrigin, isModeReleaseReason, isPermissionMode, isPersistedRuleMandate, isSettlementParty, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, screenGateOutcome, TEAM_ROLES, 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";
@@ -111,77 +111,16 @@ export function screenedGate(v) {
111
111
  return undefined;
112
112
  }
113
113
  }
114
- function modeLedgerDefect(d) {
115
- const classesOk = (v) => Array.isArray(v) && v.every((c) => isAskClass(c));
116
- if (d.kind === "denied") {
117
- if (d.mode !== undefined && !isPermissionMode(d.mode))
118
- return "disposition.mode is outside the closed set";
119
- if (d.askClass !== undefined && !isAskClass(d.askClass))
120
- return "disposition.askClass is outside the closed set";
121
- if (d.classes !== undefined && !classesOk(d.classes))
122
- return "disposition.classes is not a list of ask classes";
123
- return false;
124
- }
125
- if (d.released === undefined)
126
- return false;
127
- const r = d.released;
128
- if (r === null || typeof r !== "object")
129
- return "disposition.released is not an object";
130
- const o = r;
131
- if (!isPermissionMode(o.mode) || !isAskClass(o.askClass) || !classesOk(o.classes))
132
- return "disposition.released.mode / askClass / classes is outside its closed set";
133
- if (o.origin !== undefined && !isAskOrigin(o.origin))
134
- return "disposition.released.origin is outside the closed set";
135
- if (o.mandate !== undefined && !isPersistedRuleMandate(o.mandate))
136
- return "disposition.released.mandate is outside the closed set";
137
- return false;
138
- }
139
- function withReleaseReasonTheEngineReads(g) {
140
- const d = g.disposition;
141
- if (d.kind !== "allowed" || d.released === undefined)
142
- return g;
143
- const r = d.released;
144
- if (r === null || typeof r !== "object")
145
- return g;
146
- const reason = d.released.reason;
147
- if (typeof reason !== "string" || isModeReleaseReason(reason))
148
- return g;
149
- const { reason: _unread, ...kept } = d.released;
150
- return { ...g, disposition: { ...d, released: kept } };
151
- }
152
114
  function screenedGateOrThrow(v) {
153
- if (v === null || typeof v !== "object") {
154
- if (v !== undefined)
155
- recordFailOpen("server.trace.gate-outcome-defective", "gate is not an object");
156
- return undefined;
157
- }
158
- const stored = v;
159
- if (stored.disposition === null || typeof stored.disposition !== "object" || (stored.disposition.kind !== "allowed" && stored.disposition.kind !== "denied")) {
160
- recordFailOpen("server.trace.gate-outcome-defective", "disposition is not one of the two arms");
161
- return undefined;
162
- }
163
- const g = withReleaseReasonTheEngineReads(stored);
164
- const d = g.disposition;
165
- const classifierFallbackRaw = d.classifier !== null && typeof d.classifier === "object" ? d.classifier.fallback : undefined;
166
- const shapeDefect = (d.kind === "denied" && typeof d.deniedBy !== "string" && "disposition.deniedBy is not a string") ||
167
- (d.kind === "denied" && d.cause !== undefined && typeof d.cause !== "string" && "disposition.cause is not a string") ||
168
- (d.classifier !== undefined && (d.classifier === null || typeof d.classifier !== "object") && "disposition.classifier is not an object") ||
169
- (classifierFallbackRaw !== undefined && (classifierFallbackRaw === null || typeof classifierFallbackRaw !== "object") && "disposition.classifier.fallback is not an object") ||
170
- (classifierFallbackRaw !== undefined && classifierFallbackRaw !== null && typeof classifierFallbackRaw === "object" && typeof classifierFallbackRaw.cause !== "string" && "disposition.classifier.fallback.cause is not a string") ||
171
- modeLedgerDefect(d) ||
172
- (g.settlement !== undefined && (g.settlement === null || typeof g.settlement !== "object") && "settlement is not an object") ||
173
- (g.settlement !== undefined && g.settlement !== null && typeof g.settlement.kind !== "string" && "settlement.kind is not a string") ||
174
- (g.origin !== undefined && typeof g.origin !== "string" && "origin is not a string") ||
175
- undefined;
176
- if (shapeDefect !== undefined) {
177
- recordFailOpen("server.trace.gate-outcome-defective", shapeDefect);
115
+ if (v === undefined)
178
116
  return undefined;
179
- }
180
- const defects = screenGateOutcome(g);
117
+ const defects = screenGateOutcome(v);
181
118
  if (defects.length > 0) {
182
119
  recordFailOpen("server.trace.gate-outcome-defective", defects.join("; "));
183
120
  return undefined;
184
121
  }
122
+ const g = v;
123
+ const d = g.disposition;
185
124
  const attribution = d.classifier;
186
125
  const classifier = attribution === undefined
187
126
  ? undefined
@@ -193,10 +132,6 @@ function screenedGateOrThrow(v) {
193
132
  : { fallback: { from: attribution.fallback.from, to: attribution.fallback.to, cause: attribution.fallback.cause } }),
194
133
  };
195
134
  const settlement = g.settlement === undefined ? undefined : projectSettlement(g.settlement);
196
- if (g.settlement !== undefined && settlement === undefined) {
197
- recordFailOpen("server.trace.gate-outcome-defective", "settlement.who is not an object with a party in the closed set");
198
- return undefined;
199
- }
200
135
  const rel = d.kind === "allowed" ? d.released : undefined;
201
136
  const released = rel === undefined
202
137
  ? undefined
@@ -242,11 +177,7 @@ export function screenedStoredEventData(type, data) {
242
177
  return { ...d, gate };
243
178
  }
244
179
  function projectSettlement(st) {
245
- const whoV = st.who;
246
- const partyV = whoV === null || typeof whoV !== "object" ? undefined : whoV.party;
247
- if (!isSettlementParty(partyV))
248
- return undefined;
249
- const who = whoV;
180
+ const who = st.who;
250
181
  return {
251
182
  kind: st.kind,
252
183
  when: st.when,
@@ -17,7 +17,8 @@ export declare function projectToolStructured(structured: unknown, redact: (valu
17
17
  * `tool_end` 载荷的媒体降级(持久面恒用;活面只在连接积压时用),**一次调用降两个座位**:
18
18
  * · `output`:带 `data` 的媒体块换成描述符,有任何一块被换 ⇒ 帧级 `truncated: true` 且帧级 `totalChars` 在场 —— core 不变量
19
19
  * 「Present iff truncated」:被界之前那个值的真实大小。帧上已有 `totalChars`(core 已截断:文本被切而媒体留着)⇒ **原样保留**
20
- * (它说的是更早的原值,本仓手里的已是切过的值,重算只会低报);没有 ⇒ 按 {@link trueSizeOf} 逐块求和(core 没截,`output` 就是原值 ——
20
+ * (它说的是更早的原值,本仓手里的已是切过的值,重算只会低报);没有 ⇒ 按引擎的逐块计数器 `toolOutputBlockChars` 求和(core 7.51.1 导出,`tool_end.totalChars` 加总的就是它;本仓 7.128.x 及以前的拷贝
21
+ * `trueSizeOf` 随之删除)(core 没截,`output` 就是原值 ——
21
22
  * 文本块是本服务脱敏后的字节,即本服务交付的内容)。已是描述符的块(无 `data`)原样 ⇒ 幂等。
22
23
  * · `structured`:媒体卡去掉 `file.base64`(core 卡形自己的「不带字节」形),其余成员逐字。帧级 `truncated` / `totalChars` 只说 `output`,
23
24
  * 单独剥卡**不动**它们(`output` 已是 core 描述符而卡仍带 ≤48K 字节的帧,和卡超 48K 时 core 自己略去 `base64` 的帧,在这两个键上读数一致)。
@@ -1,12 +1,6 @@
1
+ import { isMediaBlock, mediaBlockDescriptor, toolOutputBlockChars } from "@sema-agent/core";
1
2
  import { redactDeep } from "./redact.js";
2
3
  import { recordFailOpen } from "../observability/fail-open.js";
3
- function isMediaBlock(b) {
4
- if (b === null || typeof b !== "object" || Array.isArray(b))
5
- return false;
6
- const block = b;
7
- return (block.type === "image" || block.type === "document") && typeof block.data === "string";
8
- }
9
- const MIME_ECHO_MAX = 255;
10
4
  export function projectToolOutput(output) {
11
5
  if (!Array.isArray(output))
12
6
  return redactDeep(output);
@@ -27,15 +21,6 @@ export function projectToolStructured(structured, redact) {
27
21
  const file = Object.fromEntries(Object.entries(structured.file).map(([k, v]) => [k, k === "base64" ? v : redact(v)]));
28
22
  return Object.fromEntries(Object.entries(structured).map(([k, v]) => [k, k === "file" ? file : redact(v)]));
29
23
  }
30
- function trueSizeOf(b) {
31
- if (isMediaBlock(b))
32
- return b.data.length;
33
- if (b !== null && typeof b === "object" && !Array.isArray(b) && b.type === "text" && typeof b.text === "string") {
34
- const { type: _type, text, ...rest } = b;
35
- return text.length + Math.max(0, (JSON.stringify(rest)?.length ?? 2) - 2);
36
- }
37
- return JSON.stringify(b)?.length ?? 0;
38
- }
39
24
  export function toolEndMediaAsDescriptors(data) {
40
25
  const output = data.output;
41
26
  const outputHasMedia = Array.isArray(output) && output.some(isMediaBlock);
@@ -45,15 +30,8 @@ export function toolEndMediaAsDescriptors(data) {
45
30
  const next = { ...data };
46
31
  if (outputHasMedia) {
47
32
  const blocks = output;
48
- const totalChars = typeof data.totalChars === "number" && Number.isFinite(data.totalChars) ? data.totalChars : blocks.reduce((n, b) => n + trueSizeOf(b), 0);
49
- next.output = blocks.map((b) => isMediaBlock(b)
50
- ? {
51
- type: b.type,
52
- ...(typeof b.mimeType === "string" && b.mimeType.length <= MIME_ECHO_MAX ? { mimeType: b.mimeType } : {}),
53
- bytes: Math.floor((b.data.length * 3) / 4),
54
- truncated: true,
55
- }
56
- : b);
33
+ const totalChars = typeof data.totalChars === "number" && Number.isFinite(data.totalChars) ? data.totalChars : blocks.reduce((n, b) => n + toolOutputBlockChars(b), 0);
34
+ next.output = blocks.map((b) => (isMediaBlock(b) ? mediaBlockDescriptor(b) : b));
57
35
  next.truncated = true;
58
36
  next.totalChars = totalChars;
59
37
  }
@@ -15,6 +15,7 @@ import { judgeModelRouteEndpoint } from "../model-route-endpoint.js";
15
15
  import { gateIntentUnserviceableReason } from "../approval.js";
16
16
  import { refusedGroups } from "./apply-ledger.js";
17
17
  import { judgeConfigCandidate } from "./candidate-gate.js";
18
+ import { declareServerWithheldEnv, withdrawServerWithheldEnv } from "../server-secret-env.js";
18
19
  const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
19
20
  function envDerivedModel(config) {
20
21
  const m = config.envModel;
@@ -97,17 +98,21 @@ export function applyEffective(config, eff, logger, opts) {
97
98
  return false;
98
99
  }
99
100
  const mp = staged.modelPlaneGroup.stages.models;
100
- if (mp !== undefined && opts.swapPlane !== undefined) {
101
- try {
102
- opts.swapPlane({ models: mp.models, tiers: mp.activeTiers });
103
- }
104
- catch (err) {
105
- logger?.warn("web_admin_model_plane_swap_refused", {
106
- version: staged.version,
107
- error: err instanceof Error ? err.message : String(err),
108
- note: "the Runner refused the candidate model plane (swapDeps screens the whole candidate atomically — the current generation stays in force untouched); candidate refused whole, re-judged next poll (/health shows target≠applied while it persists)",
109
- });
110
- return false;
101
+ if (mp !== undefined) {
102
+ declareCandidateWithheldEnv(mp.modelApiKeyEnv, staged.version);
103
+ if (opts.swapPlane !== undefined) {
104
+ try {
105
+ opts.swapPlane({ models: mp.models, tiers: mp.activeTiers });
106
+ }
107
+ catch (err) {
108
+ withdrawServerWithheldEnv();
109
+ logger?.warn("web_admin_model_plane_swap_refused", {
110
+ version: staged.version,
111
+ error: err instanceof Error ? err.message : String(err),
112
+ note: "the Runner refused the candidate model plane (swapDeps screens the whole candidate atomically — the current generation stays in force untouched); candidate refused whole, re-judged next poll (/health shows target≠applied while it persists)",
113
+ });
114
+ return false;
115
+ }
111
116
  }
112
117
  }
113
118
  commitStaged(config, staged);
@@ -141,6 +146,18 @@ export function applyEffective(config, eff, logger, opts) {
141
146
  logger?.info("web_admin_projects", { projects: Object.keys(staged.projects) });
142
147
  return true;
143
148
  }
149
+ function declareCandidateWithheldEnv(modelApiKeyEnv, version) {
150
+ try {
151
+ declareServerWithheldEnv(modelApiKeyEnv);
152
+ }
153
+ catch (err) {
154
+ const entries = Object.entries(modelApiKeyEnv)
155
+ .map(([model, envName]) => `${model}→${envName}`)
156
+ .join(", ");
157
+ throw new Error(`model catalog (effective v${version}): a model entry's apiKeyEnv names a variable the engine cannot withhold from model-driven shells — ` +
158
+ `the candidate is refused whole and the current generation keeps serving; fix that entry's apiKeyEnv (entries: ${entries}). Engine: ${err instanceof Error ? err.message : String(err)}`);
159
+ }
160
+ }
144
161
  function commitStaged(config, staged) {
145
162
  const mp = staged.modelPlaneGroup.stages.models;
146
163
  if (mp) {
@@ -51,6 +51,6 @@ export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, WebAdminHt
51
51
  export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, logEffectiveDiff, MODEL_PLANE_DOMAINS, type ModelPlaneDomain, type ModelPlaneOf, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
52
52
  export { restartReasons, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type EngineRestartSignal, } from "./restart-signal.js";
53
53
  export { applySkillsManifest, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
54
- export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
54
+ export { applyCenterReadFace, buildReadFaceBaseline, buildReadFaceSwapCandidate, restoreReadFaceBaseline, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline, type ReadFaceSeat } from "./read-face.js";
55
55
  export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterModel, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
56
56
  //# sourceMappingURL=facade.d.ts.map
@@ -2,5 +2,5 @@ export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, WebAdminHt
2
2
  export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, logEffectiveDiff, MODEL_PLANE_DOMAINS, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
3
3
  export { restartReasons, modelPlaneChanged, } from "./restart-signal.js";
4
4
  export { applySkillsManifest, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
5
- export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
5
+ export { applyCenterReadFace, buildReadFaceBaseline, buildReadFaceSwapCandidate, restoreReadFaceBaseline, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
6
6
  //# sourceMappingURL=facade.js.map