@sema-agent/server 7.116.1 → 7.117.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 (68) hide show
  1. package/MIGRATION.md +98 -0
  2. package/USAGE.md +32 -27
  3. package/dist/approval-card.d.ts +43 -9
  4. package/dist/approval-card.js +36 -4
  5. package/dist/boot/budget-tracing.js +2 -3
  6. package/dist/boot/config-center.d.ts +2 -2
  7. package/dist/boot/config-center.js +5 -7
  8. package/dist/boot/resolve-spec.js +10 -3
  9. package/dist/boot/runner-deps.d.ts +5 -3
  10. package/dist/boot/runner-deps.js +4 -2
  11. package/dist/brain.d.ts +17 -8
  12. package/dist/brain.js +10 -7
  13. package/dist/config-catalog.js +3 -2
  14. package/dist/config-center/apply-effective.d.ts +6 -16
  15. package/dist/config-center/apply-effective.js +0 -6
  16. package/dist/config-center/facade.d.ts +1 -1
  17. package/dist/config-center/facade.js +1 -1
  18. package/dist/config-center/restart-signal.d.ts +1 -1
  19. package/dist/config-types.d.ts +12 -16
  20. package/dist/config.d.ts +34 -27
  21. package/dist/config.js +23 -19
  22. package/dist/deployment-governance.d.ts +11 -12
  23. package/dist/deployment-governance.js +4 -9
  24. package/dist/governance-overrides.d.ts +20 -0
  25. package/dist/governance-overrides.js +8 -0
  26. package/dist/http/admission.js +7 -7
  27. package/dist/http/approval-sweeps.js +15 -12
  28. package/dist/http/checkpoint-ctx.d.ts +83 -24
  29. package/dist/http/checkpoint-ctx.js +68 -8
  30. package/dist/http/resume-legs.js +11 -15
  31. package/dist/http/retired-skill-manifest.d.ts +1 -1
  32. package/dist/http/route-ctx.d.ts +3 -3
  33. package/dist/http/routes/capabilities.js +3 -3
  34. package/dist/http/routes/runs.js +12 -0
  35. package/dist/model-select.d.ts +16 -5
  36. package/dist/model-select.js +7 -3
  37. package/dist/namespaced-rule-name.d.ts +7 -5
  38. package/dist/observability/fail-open.d.ts +1 -1
  39. package/dist/observability/fail-open.js +1 -1
  40. package/dist/observability/permission-rule-events.d.ts +3 -0
  41. package/dist/observability/permission-rule-events.js +4 -0
  42. package/dist/observability/run-terminal.js +21 -1
  43. package/dist/outbound-dispatcher.d.ts +18 -0
  44. package/dist/outbound-dispatcher.js +96 -10
  45. package/dist/permission-rule-list.d.ts +20 -31
  46. package/dist/plugins/one-time-migrations.d.ts +20 -0
  47. package/dist/plugins/one-time-migrations.js +84 -4
  48. package/dist/router/route-orchestration.d.ts +2 -23
  49. package/dist/rule-entry-list.d.ts +99 -0
  50. package/dist/rule-entry-list.js +110 -0
  51. package/dist/run-local.d.ts +4 -2
  52. package/dist/run-local.js +3 -3
  53. package/dist/runs.js +1 -0
  54. package/dist/runtime-governance.d.ts +5 -21
  55. package/dist/runtime-governance.js +4 -8
  56. package/dist/spec-fields.d.ts +15 -24
  57. package/dist/spec-fields.js +16 -17
  58. package/dist/task-settings.d.ts +18 -18
  59. package/dist/task-settings.js +57 -30
  60. package/dist/tool-approval.d.ts +24 -33
  61. package/dist/tool-approval.js +5 -7
  62. package/dist/trace/core-keyset-guard.d.ts +3 -3
  63. package/dist/trace/engine-notice-wire.d.ts +4 -6
  64. package/dist/trace/engine-notice-wire.js +14 -1
  65. package/package.json +2 -2
  66. package/scripts/drain-check-local.mjs +31 -6
  67. package/scripts/drain-check.mysql.sql +31 -6
  68. package/scripts/drain-check.pg.sql +38 -8
package/dist/brain.d.ts CHANGED
@@ -11,7 +11,8 @@ import { type RestartSliceCtx } from "./config-center/restart-signal.js";
11
11
  */
12
12
  export declare const FAST_FAIL_MAX_RETRIES = 2;
13
13
  /**
14
- * Build the external "Brain" (LLM) from config.
14
+ * Build the external "Brain" (LLM) from config — and hand out, from the same assembly, the one reading that only the assembly knows: which
15
+ * reactive-degrade target the shell welded ({@link BrainAssembly.degradeRoute}). {@link createBrain} is the projection for callers that need only the brain.
15
16
  *
16
17
  * Composition (A1 + the 1.38 resilience stack, ordered timeout-innermost → breaker → failover-outermost):
17
18
  * - **Tiered timeouts** (1.38; re-cut by core 7.33.0 along the segments of a call) are passed into each
@@ -39,6 +40,16 @@ export declare const FAST_FAIL_MAX_RETRIES = 2;
39
40
  * transport's header / body clocks off too). The Model itself (id / contextWindow / maxTokens / provider) flows
40
41
  * per-request through `Runner({ models })`.
41
42
  */
43
+ export declare function createBrainAssembly(config: ServiceConfig, deps?: {
44
+ fetchImpl?: typeof fetch;
45
+ breakerState?: BreakerState;
46
+ }): BrainAssembly;
47
+ /** {@link createBrainAssembly} 的产物:组好的 brain + 它的降级壳席(只有组装本身知道壳焊的是哪一只)。 */
48
+ export interface BrainAssembly {
49
+ readonly brain: Brain;
50
+ readonly degradeRoute: DegradeRouteWatch;
51
+ }
52
+ /** 只要 brain 的调用方(`run-local` / 旁路 lane / 测试台架)的投影 —— 与 {@link createBrainAssembly} 同一次组装。 */
42
53
  export declare function createBrain(config: ServiceConfig, deps?: {
43
54
  fetchImpl?: typeof fetch;
44
55
  breakerState?: BreakerState;
@@ -84,16 +95,14 @@ export declare function degradeCredentialIdentityOf(config: Pick<ServiceConfig,
84
95
  * 没有焊壳(车道没开 / 没有目标)⇒ `null`。在场 ↔ 缺席的翻转因此自然是一次变化。
85
96
  */
86
97
  export declare function degradeRouteFingerprint(config: DegradeRoutePlane): unknown;
87
- /** `degrade-route` 片的席:boot 焊进去的那一只 vs 活配置此刻的同一只目标(`restartReasons` 的 `degradeRoute` 两侧读数)。 */
98
+ /**
99
+ * `degrade-route` 片的席:boot 焊进去的那一只 vs 活配置此刻的同一只目标(`restartReasons` 的 `degradeRoute` 两侧读数)。`baked` 在 {@link createBrainAssembly}
100
+ * 焊壳的那一处算好、随装配产物交出 —— 不再有「紧随 createBrain 再捕获一次」的第二个出处。与 boot/memory-consolidation.ts `createConsolidationValveWatch` 同形
101
+ * (`{ read(): { baked, current } }`)。
102
+ */
88
103
  export interface DegradeRouteWatch {
89
104
  read(): NonNullable<RestartSliceCtx["degradeRoute"]>;
90
105
  }
91
- /**
92
- * `create*`:返回带捕获状态(组壳那一刻的指纹)的活对象。**须在 {@link createBrain} 的同一时刻、同一份 config 上调**
93
- * (生产:boot/budget-tracing.ts 紧随 createBrain)—— 否则 `baked` 就不是壳真焊进去的那一只。与 boot/memory-consolidation.ts
94
- * `createConsolidationValveWatch` 同形(`{ read(): { baked, current } }`)。
95
- */
96
- export declare function createDegradeRouteWatch(config: DegradeRoutePlane): DegradeRouteWatch;
97
106
  /**
98
107
  * S-753 复审(opus F2 + codex C1)—— **failover 拓扑 × 首字节段没有部署窗**的启动期披露(纯数据,`build*`;告警由 boot 出)。
99
108
  *
package/dist/brain.js CHANGED
@@ -38,7 +38,7 @@ function constructRouteBrain(label, sourceUrls, construct) {
38
38
  }
39
39
  }
40
40
  const DEFAULT_RESILIENCE = buildDefaultResilience();
41
- export function createBrain(config, deps = {}) {
41
+ export function createBrainAssembly(config, deps = {}) {
42
42
  const { fetchImpl, breakerState } = deps;
43
43
  const r = config.resilience ?? DEFAULT_RESILIENCE;
44
44
  const timeouts = {
@@ -95,11 +95,13 @@ export function createBrain(config, deps = {}) {
95
95
  let brain = anthropicBrain
96
96
  ? createRoutingBrain({ anthropic: anthropicBrain }, { fallback: gatewayBrain })
97
97
  : gatewayBrain;
98
+ let degradeBaked = null;
98
99
  if (config.degrade?.reactive) {
99
100
  const degradeTo = config.degrade.to;
100
101
  const fallbackModel = degradeTargetOf(config);
101
102
  if (fallbackModel) {
102
103
  const credential = degradeCredentialIdentityOf(config, fallbackModel);
104
+ degradeBaked = degradeWeldFingerprint(config, fallbackModel, credential);
103
105
  const ownEnvRef = credential.apiKeyEnv !== undefined ? { [fallbackModel.name]: credential.apiKeyEnv } : {};
104
106
  const ownSealed = credential.sealed !== undefined ? { [fallbackModel.name]: credential.sealed } : {};
105
107
  const fbOwnApiKey = () => resolveModelApiKey(fallbackModel.name, ownEnvRef, process.env, ownSealed);
@@ -139,7 +141,10 @@ export function createBrain(config, deps = {}) {
139
141
  });
140
142
  }
141
143
  }
142
- return createModelReachBrain(brain);
144
+ return { brain: createModelReachBrain(brain), degradeRoute: { read: () => ({ baked: degradeBaked, current: degradeRouteFingerprint(config) }) } };
145
+ }
146
+ export function createBrain(config, deps = {}) {
147
+ return createBrainAssembly(config, deps).brain;
143
148
  }
144
149
  export function degradeTargetOf(config) {
145
150
  return config.degrade?.reactive ? config.models?.[config.degrade.to] : undefined;
@@ -159,7 +164,9 @@ export function degradeRouteFingerprint(config) {
159
164
  const target = degradeTargetOf(config);
160
165
  if (target === undefined || config.degrade === undefined)
161
166
  return null;
162
- const credential = degradeCredentialIdentityOf(config, target);
167
+ return degradeWeldFingerprint(config, target, degradeCredentialIdentityOf(config, target));
168
+ }
169
+ function degradeWeldFingerprint(config, target, credential) {
163
170
  return {
164
171
  model: catalogEntryFingerprint(target),
165
172
  endpoint: openaiRouteBaseUrl(target.baseUrl, config.gatewayBaseUrl) ?? null,
@@ -174,10 +181,6 @@ function sealedIdentityFingerprint(sealed) {
174
181
  return { poisoned: modelKeyPoisonIdentity(sealed), reason: sealed.reason };
175
182
  return { digest: createHash("sha256").update(sealed, "utf8").digest("hex").slice(0, 16) };
176
183
  }
177
- export function createDegradeRouteWatch(config) {
178
- const baked = degradeRouteFingerprint(config);
179
- return { read: () => ({ baked, current: degradeRouteFingerprint(config) }) };
180
- }
181
184
  export function buildFailoverFirstByteNotice(config) {
182
185
  const r = config.resilience ?? DEFAULT_RESILIENCE;
183
186
  const fallbacks = config.gatewayFallbackUrls.length;
@@ -11,6 +11,7 @@ import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
11
11
  import { buildDisplaySafeUrl } from "./trace/redact.js";
12
12
  import { describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
13
13
  import { buildOutboundProxyPosture } from "./outbound-dispatcher.js";
14
+ import { modelSupportsImages } from "./model-select.js";
14
15
  export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
15
16
  export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
16
17
  export const CATALOG_DANGER_AXES = ["security", "billing", "data"];
@@ -58,7 +59,7 @@ export const CONFIG_CATALOG = [
58
59
  r("ANTHROPIC_VERSION", "modelPlane", "string", "Anthropic API version 头(缺省由 SDK 定)"),
59
60
  r("APPROVAL_AUTO_BUDGET", "approval", "number", "每 run-leg 自动放行的普通门控工具调用数上限(0=关;防审批循环的熔断,不是安全 ask 的帽)", { staticDefault: "0", danger: SEC, configKey: "approvalAutoBudget" }),
60
61
  r("APPROVAL_DENY", "approval", "csv", "始终拒绝的工具名单(装配期拒不可匹配名)", { danger: SEC, configKey: "approvalDeny" }),
61
- r("APPROVAL_GRANT", "approval", "csv", "运维的 allow 规则(工具名规则:精确名 / mcp__<server>__* / a2a__<peer>__*;命中 = 带理由放行,不再为这些调用出「没人表态」卡(不放掉任何一层自己的问);HTTP 提交 / durable 续跑 / 停驻后台 agent 赎回 / run-local 四腿同装(leader worker 首跑与修复腿到不了);不越过任何一层的 deny / ask;不是白名单;坏值拒启)", { danger: SEC, configKey: "approvalGrant", derivedDefaultNote: "缺席 = 空表(本层不表态,折叠与未设逐字同)" }),
62
+ r("APPROVAL_GRANT", "approval", "csv", "运维的 allow 表(引擎 allow 层的部署成员 RunnerDeps.allowedTools,每只 Runner 同一席:HTTP 提交 / 续跑 / 停驻赎回 / run-local / leader 一律;规则文法 = 引擎的:工具名 / mcp__<server>__* / a2a__<peer>__* / Bash(<命令>) / Bash(<前缀>:*) / Read(//<dir>/**);命中 = 免问,清普通问与没人表态两类,内置工具也在内;清不掉任何一层的 deny / ask 规则、APPROVAL_NEVER_AUTO、shell 地板、写保护、治理、hook 的问、plan;不是白名单;引擎读成空的条目 / 服务器段铸不出 ⇒ 拒启)", { danger: SEC, configKey: "approvalGrant", derivedDefaultNote: "缺席 = 空表(本层不表态,折叠与未设逐字同)" }),
62
63
  r("APPROVAL_HMAC_KEYS", "approval", "secret", "直连门(D-G)HMAC 完整性密钥集(空=直连门不激活)", { danger: SEC }),
63
64
  r("APPROVAL_NEVER_AUTO", "approval", "csv", "永不被 AUTO_BUDGET 自动放行的工具集(不可逆动作恒要人)", { danger: SEC, configKey: "approvalNeverAuto" }),
64
65
  r("APPROVAL_REQUIRE", "approval", "csv", "必须人审的工具名单(F4 门;center 腿热:refresh 拍整表换代,无 checkpoint 店的部署发非空名单 ⇒ 审批组整组拒)", { danger: SEC, configKey: "approvalRequire", center: cGov("approvalRequire") }),
@@ -318,7 +319,7 @@ export const CONFIG_CATALOG = [
318
319
  },
319
320
  }),
320
321
  r("MODEL_VERIFIER", "modelPlane", "string", "verify 门的异构 verifier(须命中目录;typo 回 default 并 boot warn=自评自批)", { derivedDefaultNote: "缺席/不在目录 ⇒ default(=implementer,去相关性失效警告)" }),
321
- r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(false 让 model_no_vision 预检真拦)", { staticDefault: "true", resolve: (c) => (Array.isArray(c.model.input) ? c.model.input.includes("image") : true), center: cModels("models[].vision") }),
322
+ r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(false = 明说不收图:带图请求的图被换成占位句交给模型,提交时计数并告警)", { staticDefault: "true", resolve: (c) => modelSupportsImages(c.model), center: cModels("models[].vision") }),
322
323
  r("MYSQL_DATABASE", "store", "string", "MySQL 协议库名(选中该引擎时必填)", { danger: DATA }),
323
324
  r("MYSQL_HOST", "store", "string", "MySQL 协议主机(在场即算 SQL 信号,参与 DB_BACKEND 默认推导)", { danger: DATA }),
324
325
  r("MYSQL_PASSWORD", "store", "secret", "MySQL 协议口令", { danger: SEC }),
@@ -81,7 +81,7 @@ export declare function applyEffective(config: ServiceConfig, eff: EffectiveConf
81
81
  * 读者一律从这里派生,禁手抄:
82
82
  * · stage:{@link stageModelPlane} 产出组内**每个**域的产物({@link ModelPlaneStages} 按本表映射),组被拒 ⇒ 一个域都没有;
83
83
  * · 世代账:整组拒的 `keys` 与落地的 `applied` 都由 {@link MODEL_PLANE_SURFACES} 按本表派生({@link modelPlaneGroupReport});
84
- * · boot 按组回落 LKG 的合成文档与 restart 基线:{@link withModelPlaneOf}(`boot/config-center.ts`)。
84
+ * · boot 按组回落 LKG 的合成文档:{@link withModelPlaneOf}(`boot/config-center.ts`)。
85
85
  * (与 `config.ts` 的配置组视图 `MODEL_PLANE_GROUP_KEYS` 不是一回事:那是 `ServiceConfig` 的分节视图;这里是「中心热应用的
86
86
  * 模型面这一组包含哪些 effective 域」。)
87
87
  */
@@ -91,29 +91,19 @@ export type ModelPlaneDomain = (typeof MODEL_PLANE_DOMAINS)[number];
91
91
  export type ModelPlaneOf = Pick<EffectiveConfig, ModelPlaneDomain>;
92
92
  /**
93
93
  * `target` 换上 `source` 的**整个模型面**(组内每个域都取 `source` 的;`source` 缺席的域在结果里同样缺席,不留 `target` 的),
94
- * 其余域原样是 `target` 的。boot 按组回落 LKG 的两处(回落 apply 的合成文档、restart 对照基线)都走这一只 —— 禁在调用点
94
+ * 其余域原样是 `target` 的。boot 按组回落 LKG 的合成文档(回落 apply 的入参)走这一只 —— 禁在调用点
95
95
  * 手写 `{ ...x, models, collab }`(漏一个域正是 V1 的病形)。纯数据:不改两个入参。
96
96
  */
97
97
  export declare function withModelPlaneOf<E extends EffectiveConfig>(target: E, source: ModelPlaneOf): E;
98
- /**
99
- * **boot 快照**(restart 片的对照基线,`boot/config-center.ts` 的 `effective`)= 本次 boot 读到的那份(`read`:拉到的候选,或读回的盘上 LKG)
100
- * 换上**真落地**的模型面 —— `appliedPlane` 是模型面组真落地的那一代(`read` 自己 / 按组回落的 LKG),`undefined` = 组被拒且没有可用的上一代
101
- * (进程服务的是 env 表 ⇒「没有中心模型面」)。其余域恒是 `read` 的。
102
- *
103
- * boot 的两条落地路径(候选拉取、整份 LKG 读回)都经这一只,禁在调用点各写一份:S-789 的病形正是这句只在候选路径上成立(2d09c8e6),
104
- * LKG 读回路径把被拒的 LKG 目录记成基线 ⇒ 修好的目录换掉降级目标时零 `degrade-route`,boot 焊死的降级壳永远指着 env 端点。
105
- * S-794 起 `degrade-route` 片不再读快照的目录(改读降级壳 boot 焊的那只目标 vs 活配置,brain.ts `createDegradeRouteWatch`)——今天没有
106
- * 重启片读快照的模型面;本函数保证的是快照本身说真话(进程真在服务的那一代),不让下一个读它的片重演 S-789。
107
- * 纯数据:不改入参;模型面就是 `read` 自己那一代 ⇒ 原样交回(同一引用)。
108
- */
109
- export declare function bootSnapshotOf<E extends EffectiveConfig>(read: E, appliedPlane: ModelPlaneOf | undefined): E;
110
98
  /**
111
99
  * E-1(7.103.1 修复轮,opus 腿):boot 读到的这一代**整套落地**了吗 —— 只有整套落地的那一代才可以成为 LKG(`latestEffective` /
112
100
  * 盘上 LKG 候选)、才让它的 ETag 做条件请求(§9.5-2 + codex R2-F1)。三件同判,缺一不算:读腿没报坏域(`domainErrors`)、
113
101
  * prompts 面过候选门同一只判形、apply 报告没有被整拒的组(`fullyApplied`;报告缺席 = 没 apply,dry-run 只判前两件)。
114
- * boot 的两条落地路径(候选拉取、整份 LKG 读回)都调这一只,禁在调用点各写一份(此前候选路径三件、LKG 路径只抄了一件)。
102
+ * boot 的两条落地路径(候选拉取、整份 LKG 读回)与 refresh / 迟到 boot 两腿的 apply 之后都调这一只,禁在调用点各写一份(此前候选路径三件、LKG 路径只抄了一件,
103
+ * refresh / 迟到 boot 两腿各内联了第三件)。
115
104
  * LKG 读回路径上前两件在上游已有门(`loadLkg` 的载荷复判拒坏 prompts;读回不产 `domainErrors`),这里同判是同一条规则的同一只谓词,
116
- * 不是第二道门。refresh / 迟到 boot 两腿的候选门在 apply **之前**整拒坏域与坏 prompts(拒的是整份候选,不只是 LKG 推进),不走本谓词。
105
+ * 不是第二道门。refresh / 迟到 boot 两腿的候选门在 apply **之前**整拒坏域与坏 prompts(拒的是整份候选,不只是 LKG 推进),所以到 apply 之后那两腿调本谓词时
106
+ * 前两件恒真、实判的是 `fullyApplied` 一件 —— 调它是为了「干净落地」只有这一个读法,不是再加一道门。
117
107
  */
118
108
  export declare function fullyLandedForLkg(read: {
119
109
  readonly effective: EffectiveConfig;
@@ -207,12 +207,6 @@ function takeModelPlaneDomain(out, source, d) {
207
207
  else
208
208
  out[d] = v;
209
209
  }
210
- function buildEnvOnlyModelPlane() {
211
- return { models: { models: [], roles: {} } };
212
- }
213
- export function bootSnapshotOf(read, appliedPlane) {
214
- return appliedPlane === read ? read : withModelPlaneOf(read, appliedPlane ?? buildEnvOnlyModelPlane());
215
- }
216
210
  export function fullyLandedForLkg(read, report) {
217
211
  const prompts = read.effective.prompts;
218
212
  return (read.domainErrors ?? []).length === 0 && (prompts === undefined || validatePromptsDomain(prompts).ok) && report?.fullyApplied !== false;
@@ -48,7 +48,7 @@
48
48
  * importers to update, which was done in the same commit.
49
49
  */
50
50
  export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
51
- export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, MODEL_PLANE_DOMAINS, type ModelPlaneDomain, type ModelPlaneOf, withModelPlaneOf, bootSnapshotOf, fullyLandedForLkg, } from "./apply-effective.js";
51
+ export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, 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 { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
54
54
  export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
@@ -1,5 +1,5 @@
1
1
  export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
2
- export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, MODEL_PLANE_DOMAINS, withModelPlaneOf, bootSnapshotOf, fullyLandedForLkg, } from "./apply-effective.js";
2
+ export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, MODEL_PLANE_DOMAINS, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
3
3
  export { restartReasons, modelPlaneChanged, } from "./restart-signal.js";
4
4
  export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
5
5
  export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
@@ -6,7 +6,7 @@ export type RestartSlice = "skills" | "mcp" | "a2a" | "scenarios" | "degrade-rou
6
6
  * EffectiveConfig. Absent/empty = that lane is off ⇒ its slice is inert (null on both sides). */
7
7
  export interface RestartSliceCtx {
8
8
  /** S-794:反应式降级壳的两侧读数 —— `baked` = createBrain 组壳那一刻焊进去的目标路由指纹,`current` = 同一只
9
- * 指纹函数(brain.ts `degradeRouteFingerprint`)对本拍活配置再读一次(读法见 brain.ts `createDegradeRouteWatch`)。
9
+ * 指纹函数(brain.ts `degradeRouteFingerprint`)对本拍活配置再读一次(`baked` 的出处见 brain.ts `createBrainAssembly`)。
10
10
  * 缺席 = 调用方没有递席(单元调用)⇒ `degrade-route` 片恒不签;车道没开 / 没焊壳在席内表达为两侧同为 null。
11
11
  * (此前的 `reactiveDegradeTo` 位只剩「阀门开着」一义,已并进本席 —— 一位不留两义。) */
12
12
  degradeRoute?: {
@@ -1325,22 +1325,18 @@ export interface ServiceConfigFlat {
1325
1325
  * 从未进入任何 policy。判定现在单源在 `hasOperatorGateIntent()`(与 inv#2 同文件)。 */
1326
1326
  approvalNeverAuto: string[];
1327
1327
  /**
1328
- * 7.115.0 —— **运维的 allow 规则**(`APPROVAL_GRANT`,逗号分隔的工具名规则:精确名 / `mcp__<server>__*` / `a2a__<peer>__*`)。命中 = 带理由的
1329
- * 放行(core `decisionReason: "rule"`,运维替这些调用表了态 —— 回答没人表态的服务器工具 / A2A 技能调用;内置工具上引擎的写门、shell 检查照问);未命中 = 不表态。与请求腿的 `settings.permissions.allow` 是**同一种东西**:
1330
- * 同一只编译口(`permission-rule-list.ts` `createRuleListPolicy`)、同一只校验(`config.ts` `approvalGrantRefusal` → `unmatchableToolNamesRefusal`,
1331
- * 坏值拒启)。施加点 = 部署治理链的单一构造口(`deployment-governance.ts` → `runtime-governance.ts` `applyRuntimeGovernance` 的一片叶),
1332
- * 所以 HTTP 提交 / durable 续跑(含 leader worker 停驻后经 `/decide` 续跑的那一段)/ 停驻后台 agent 的跨副本赎回 / run-local 四条腿同装(leader worker 首跑与修复腿到不了),每次现取(不 memoize)。**不越过**任何一层的 deny / ask(折叠 deny > ask > allow)。
1333
- *
1334
- * ⚠️ 不是白名单:名字不叫 `…_ALLOW`,与会话规则的 `toolAllow`(「只许这些」)刻意分开 —— 本键只给出表态,不收窄任何东西。
1335
- *
1336
- * 三问:谁需要 = 在没有请求体的腿上接了 MCP 服务器的运维 —— `run-local`,以及 durable 续跑(每一次从持久化体重建,含 leader worker 停驻后经 `/decide`
1337
- * 续跑的那一段;A2A 不在射程:运维声明的对端恒带外发标记,那一问本键不放) —— core 7.40.0 起没人表态的服务器工具调用要问,无席 ⇒ 拒、有 durable 席 ⇒ 每次停驻等人,
1338
- * 这里是运维为自己接的服务器表态的唯一位置。**到不了**:leader worker 的首跑与修复腿(工具策略在 leader 编排里自己折出来 —— `leader/wire.ts` 的
1339
- * `combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy(…))` —— 不经上面那只构造口);这两条腿今天不挂 MCP / A2A,本键在那里没有对象。谁受伤 = 缺省空表,无人受伤 —— 设了宽规则
1340
- * (`mcp__<server>__*`,含那台服务器将来新增的工具)= 对**每一个租户、每一条腿**的那些调用都不再因「没人表态」而问人,风险随规则的宽度走
1341
- * (射程只到这一问:任何一层自己的问照旧压过它 —— 内置工具的精确名写进来,引擎的写门 / 形状层照问);补偿 = 本键就是
1342
- * core 7.40.0 那条收紧的补偿,deny / ask(`APPROVAL_DENY` / `APPROVAL_REQUIRE` / 请求 settings / 会话规则)照旧压过它。
1343
- * v1 只有 env 一个写入通道(无 config-center 座位,不进 settings-schema)。空表 = 本层不铸叶,折叠逐字与修前同。
1328
+ * **运维的 allow 表**(`APPROVAL_GRANT`,逗号分隔的 allow 规则)—— 7.117.0(core 7.42.0)起是引擎 allow 层的**部署成员** `RunnerDeps.allowedTools`:
1329
+ * 经部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats`)交给本进程造的每一只 Runner(boot / run-local / leader),Runner 构造时读一次。
1330
+ * 命中 = 免问(core `decisionReason: "deployment_rule"`),清「普通问 + 没人表态」两类,内置工具也在内;清不掉任何一层的 deny、ask 规则、运维的强制类
1331
+ * (`APPROVAL_NEVER_AUTO` / `MANUAL_MODE_SHELL_GATE=always` / 写保护 / 治理)、hook 的问、plan 的问。文法 = 引擎的(工具名 / 协议名覆盖形 / `Bash(...)` 命令规则 /
1332
+ * `Read(//dir/**)`);坏值拒启(`config.ts` `approvalGrantRefusal`:不是字符串表 / 协议名对端段铸不出 / 引擎读成空的条目)。
1333
+ *
1334
+ * ⚠️ 不是白名单:名字不叫 `…_ALLOW`,与会话规则的 `toolAllow`(「只许这些」)刻意分开 —— 本键只放,不收窄任何东西。
1335
+ *
1336
+ * 三问(7.117.0 语义变宽):谁需要 = 没有请求体的腿(`run-local` / leader)上接了 MCP 服务器、或想一句话放掉自家某只工具的运维;谁受伤 = 表里写了内置工具名的部署
1337
+ * (7.115.0–7.116.x 本键只回答「没人表态」,`Bash` / `Write` 写进来什么也不放;本版起这两只不再问);补偿 = 起服行 `approval_grant_in_force` 的 note 写着当前效果、
1338
+ * `GET /v1/diagnostics/wiring` 的 `serverGates.approvalGrant`、`MIGRATION.md` 升级前必读一段;要某只工具必须问,用上面那几只本键清不掉的旋钮。
1339
+ * v1 只有 env 一个写入通道(无 config-center 座位,不进 settings-schema)= 进程常量,与 Runner 席位的寿命一致。空表 = 席位缺席(引擎读作「部署没有 allow 规则」)。
1344
1340
  */
1345
1341
  approvalGrant: string[];
1346
1342
  /** [ref] D-G (HMAC = integrity, not identity; ✅ WIRED — the "SCAFFOLD, not yet wired" note here was STALE,
package/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Model, type PathRuleBases, type RuleBehavior } from "@sema-agent/core";
1
+ import { type Model, type PathRuleBases, type PermissionRule } from "@sema-agent/core";
2
2
  import { type Autonomy } from "./runtime-governance.js";
3
3
  import { type RunnerEnvNameAllowlistKnob, type ServerEnvNameAllowlistKnob } from "./env-name-allowlist-knobs.js";
4
4
  import type { ServiceConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js";
@@ -285,8 +285,9 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
285
285
  * ① env 腿(`APPROVAL_REQUIRE`/`DENY`/`NEVER_AUTO`,下方)—— boot 拒启;
286
286
  * ② 中心下发腿(`runtime.approvalRequire`,config-center/apply-effective.ts)—— 域级拒绝+保前值+warn
287
287
  * (常驻进程不 crash);
288
- * ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts;S-654 (c) 起 `allow` 同判)—— 该请求 422;
289
- * ④ env 腿 `APPROVAL_GRANT`(7.115.0,运维的 allow 规则,与 ③ 的 allow 桶同一个消费者 `rule-policy`)—— boot 拒启({@link approvalGrantRefusal})。
288
+ * ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts)—— 该请求 422。
289
+ * (两张 allow 表 —— 请求的 `settings.permissions.allow` 与运维的 `APPROVAL_GRANT` —— 自 7.117.0 / core 7.42.0 起不经这里:它们整份交给引擎的
290
+ * allow 层,本仓只判形与协议名对端段,判据在 `rule-entry-list.ts`,与技能两只清单同一份。)
290
291
  *
291
292
  * 五种拼法:
292
293
  * - CC 形规则条目(`Bash(ps:*)`/`Edit(src/**)`,[ref] [ref]§七 装机实测):CC 的权限规则 DSL 写法。这些
@@ -328,28 +329,33 @@ export interface UnmatchableToolName {
328
329
  guidance: string;
329
330
  }
330
331
  /**
331
- * S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。两个消费者形态:
332
+ * S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。三个消费者形态:
332
333
  *
333
334
  * · `"tool-name-table"` —— 消费者按**整串工具名**比对(精确 ∪ covering):env `APPROVAL_*` 三键与
334
335
  * 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`)。带括号的内容形
335
336
  * 在这里永远匹配不上任何活工具名 —— 与引擎的规则文法认不认得它**无关**,是这条腿的比对方式决定的,
336
337
  * 所以内容形照旧响亮拒 + 指路。
337
- * · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):
338
- * `settings.permissions.deny` / `.ask`(S-177 / core 7.9.0 起)与 `.allow`(S-654 (c) / 7.115.0 起;修前是
339
- * `createAllowDenyPolicy` 的名单位)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` /
340
- * `Edit(~/.sema/**)`),所以本仓不许再替它拒 —— 判据整条交回 core 的 `validatePermissionRules`,按这张表
341
- * 自己的 behavior 问(allow 侧 core 只收工具名形,内容形由 core 判拒)。
342
- *
343
- * S-654 (c) 前这里还有一条「`allow` 豁免名字形与文法两族」的不对称,前提是 allow 为白名单(匹配不上 ⇒ 全拒,
344
- * fail-closed 且当场可见);allow 改成规则后那条前提不在了(匹配不上 ⇒ 什么也不放行、照常问,写的人收不到信号),豁免随之删除。
338
+ * · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):`settings.permissions.deny` / `.ask`
339
+ * (S-177 / core 7.9.0 起)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` / `Edit(~/.sema/**)`),所以本仓不许再替它拒 ——
340
+ * 判据整条交回 core 的 `validatePermissionRules`,按这张表自己的 behavior 问。core 7.42.0 起策略只说 deny / ask(allow 表不再是本消费者)。
341
+ * · `"allow-layer"` —— 消费者是引擎的 **allow 层**(`TaskSpec.allowedTools`):`settings.permissions.allow`(7.117.0 起)。本仓**只判名字**
342
+ * (退役名 / 无前缀短名 / 协议名够不着挂载工具 —— 与另两张桶同一只判、同一句),**不判内容**:条目的文法、匹配、读成空都归引擎
343
+ * (读成空的引擎在开跑时通告)。没有 behavior 一问(引擎的 allow 层不经规则编译器)。
344
+ * 翻转条件:引擎的 allow 层对非现行名也出通告(core 7.43.0 起在条目编译拿到工具名册时给「不是工具名 / 近似名」这类原因)—— 提到那一版、
345
+ * 并把名册交给条目编译之后,本消费者的名字判退役(allow 侧整只交引擎)。
345
346
  */
346
- export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
347
- /** {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器)、按哪种态度问 core 的文法、路径基。 */
348
- export interface UnmatchableToolNameJudgment {
349
- readonly consumer: RuleEntryConsumer;
350
- readonly behavior: RuleBehavior;
347
+ export type RuleEntryConsumer = "tool-name-table" | "rule-policy" | "allow-layer";
348
+ /**
349
+ * {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器 / allow 层);前两种按哪种态度问 core 的文法、路径基 ——
350
+ * allow 层没有文法一问(见 {@link RuleEntryConsumer}),型上就不带这两位。
351
+ */
352
+ export type UnmatchableToolNameJudgment = {
353
+ readonly consumer: "tool-name-table" | "rule-policy";
354
+ readonly behavior: PermissionRule["behavior"];
351
355
  readonly pathBases?: PathRuleBases;
352
- }
356
+ } | {
357
+ readonly consumer: "allow-layer";
358
+ };
353
359
  /**
354
360
  * 「这张表里有永远不会生效的条目」的**判据与拒因句,同一只函数**(返回 `undefined` = 通过;拒 ⇒ 句子 + 被点名的条目)。
355
361
  * 五条腿全调它:env `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO`(拒启)、中心下发 `governance.approvalRequire`(整组拒)、
@@ -365,16 +371,17 @@ export declare function unmatchableToolNamesRefusal(source: string, names: reado
365
371
  readonly names: readonly string[];
366
372
  } | undefined;
367
373
  /**
368
- * 7.115.0 —— 运维 allow 规则 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:
369
- * 返回句子 = 拒,`undefined` = 通过)。判据与请求腿的 `settings.permissions.allow` 是**同一只**:
370
- * ① 名字形与 core 文法 —— {@link unmatchableToolNamesRefusal},消费者 `rule-policy`、behavior `allow`(退役名 / 无前缀短名 / 缺段或对端段铸不出的协议名 /
371
- * 反斜杠 / 超长 / 内容形与参数级的 allow;句子点名哪一条、为什么、怎么改);
372
- * ② 整表试编一次 —— 编的就是治理折叠里那一只(`runtime-governance.ts` {@link compileApprovalGrantLayers}),于是 core 的整表帽这类
373
- * 只在编译时才抛的判据也在启动时响亮,而不是每个任务一次 500。
374
- * 没有 `pathBases`:运维的规则不挂在任何一条任务的根上,而 allow 文法本来只收工具名形(路径形的 allow core 拒编,在 ① 里就拒了)。
375
- * 坏值**不许**静默丢掉那一条后照常起:运维以为放行了,无人值守腿照旧拒 / 停驻,没有任何信号指回这里。
374
+ * 运维 allow 表 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:返回句子 = 拒,`undefined` = 通过)。
375
+ * 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据改成**引擎的读法**,与请求的两张
376
+ * allow 表同一份(`rule-entry-list.ts`):
377
+ * ① 值不是字符串表 ⇒ 拒(env 的 CSV 恒给字符串表;判在这里是为了「值一旦换了来源,第一道判仍在」);
378
+ * ② 协议名对端段铸不出 / 缺段 / 对端段写通配 ⇒ 拒(与技能清单、请求 allow 桶同一只函数);
379
+ * ③ 引擎读成空的条目(`compileAllowList(names, "allow").inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字)⇒ 拒,
380
+ * 句子点名条目、引擎给的原因词、该怎么写。请求的 allow 侧对 ③ 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,
381
+ * 而引擎在 Runner 构造期对读成空的部署条目只发一条无会话的通告。
382
+ * 文法随引擎:工具名、协议名覆盖形、`Bash(...)` 命令规则、`Read(//dir/**)` 读目录都收(内容形里带逗号的写不进 CSV —— 这张表的分隔符就是逗号)。
376
383
  */
377
- export declare function approvalGrantRefusal(names: readonly string[]): string | undefined;
384
+ export declare function approvalGrantRefusal(names: unknown): string | undefined;
378
385
  /**
379
386
  * [ref] 件B/C/D —— 三条**部署治理声明**的 env 解析(auth 域的一段,fail-loud 全在这里)。
380
387
  *
package/dist/config.js CHANGED
@@ -22,7 +22,8 @@ import { describeUnreachableNamespacedRuleName, ruleEntryToolName, unreachableNa
22
22
  import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
23
23
  import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
24
24
  import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
25
- import { APPROVAL_GRANT_SOURCE, AUTONOMY_MODES, compileApprovalGrantLayers } from "./runtime-governance.js";
25
+ import { AUTONOMY_MODES } from "./runtime-governance.js";
26
+ import { allowLayerEntryCountIssue, describeInertRuleEntries, describeRetiredRuleEntry, describeUnreachableRuleEntry, inertRuleEntriesOf, retiredRuleEntryOf, ruleEntryListShapeOk, unreachableRuleEntryOf } from "./rule-entry-list.js";
26
27
  import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
27
28
  import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
28
29
  import { modelProviderRefusal } from "./model-provider.js";
@@ -1227,11 +1228,6 @@ function uncompilableRuleGuidance(name, behavior, pathBases) {
1227
1228
  const issue = issues[0];
1228
1229
  if (issue === undefined)
1229
1230
  return undefined;
1230
- if (behavior === "allow" && (issue.code === "unsupported.bash_prefix" || issue.code === "unsupported.file_glob" || issue.code === "invalid.allow_param_rule")) {
1231
- return (`${issue.code} — ${issue.message}. An allow rule list compiles tool-name rules only (an exact name, or mcp__<server>__* / ` +
1232
- `a2a__<peer>__* for every tool on that peer); a content-form standing approval lives in the persisted-rule store ` +
1233
- `(/v1/rules/cc-import, or keep the rule when you answer the call's card)`);
1234
- }
1235
1231
  return (`${issue.code} — core's rule grammar cannot compile this name (${issue.message}), so a table keyed by it matches nothing. ` +
1236
1232
  `Tool names carry no backslashes and are far shorter than the 512-character rule cap; check the spelling, and use ` +
1237
1233
  `mcp__<server>__* / a2a__<peer>__* for "every tool on that peer"`);
@@ -1252,10 +1248,12 @@ function findUnmatchableToolNames(names, opts) {
1252
1248
  out.push({ name, guidance: verdict.guidance });
1253
1249
  continue;
1254
1250
  }
1255
- const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
1256
- if (uncompilable !== undefined) {
1257
- out.push({ name, guidance: uncompilable });
1258
- continue;
1251
+ if (opts.consumer !== "allow-layer") {
1252
+ const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
1253
+ if (uncompilable !== undefined) {
1254
+ out.push({ name, guidance: uncompilable });
1255
+ continue;
1256
+ }
1259
1257
  }
1260
1258
  if (verdict.kind === "unprefixed_namespace") {
1261
1259
  out.push({ name, guidance: `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentToolName(name)}`).join(" or ")}` });
@@ -1277,16 +1275,22 @@ export function unmatchableToolNamesRefusal(source, names, judgment) {
1277
1275
  bad.map((b) => `${presentToolName(b.name)} → ${b.guidance}`).join("; ")).replace(CONTROL_AND_BIDI_STRIP_RE, "");
1278
1276
  return { message, names: bad.map((b) => b.name) };
1279
1277
  }
1278
+ const APPROVAL_GRANT_ENV = "APPROVAL_GRANT";
1280
1279
  export function approvalGrantRefusal(names) {
1281
- const unmatchable = unmatchableToolNamesRefusal(APPROVAL_GRANT_SOURCE, names, { consumer: "rule-policy", behavior: "allow" });
1282
- if (unmatchable !== undefined)
1283
- return unmatchable.message;
1284
- try {
1285
- compileApprovalGrantLayers(names);
1286
- }
1287
- catch (e) {
1288
- return e instanceof Error ? e.message : String(e);
1289
- }
1280
+ if (!ruleEntryListShapeOk(names))
1281
+ return `${APPROVAL_GRANT_ENV} must be a list of permission-rule strings — refusing to start rather than running without it`;
1282
+ const over = allowLayerEntryCountIssue(APPROVAL_GRANT_ENV, names);
1283
+ if (over !== undefined)
1284
+ return `${over.message} — refusing to start rather than running with a truncated list`;
1285
+ const retired = retiredRuleEntryOf(names);
1286
+ if (retired !== undefined)
1287
+ return `${describeRetiredRuleEntry(APPROVAL_GRANT_ENV, retired)} — refusing to start rather than running without it`;
1288
+ const unreachable = unreachableRuleEntryOf(names);
1289
+ if (unreachable !== undefined)
1290
+ return `${describeUnreachableRuleEntry(APPROVAL_GRANT_ENV, unreachable)} — refusing to start rather than running without it`;
1291
+ const inert = inertRuleEntriesOf(names, "allow");
1292
+ if (inert.length > 0)
1293
+ return `${describeInertRuleEntries(APPROVAL_GRANT_ENV, inert)} — the engine would grant nothing for ${inert.length === 1 ? "it" : "them"}; refusing to start rather than running with an allow rule that never takes effect`;
1290
1294
  return undefined;
1291
1295
  }
1292
1296
  function streamApprovalConfig(gates) {
@@ -11,9 +11,6 @@ export interface DeploymentGovernanceConfigView {
11
11
  readonly commandPolicy?: ServiceConfig["commandPolicy"];
12
12
  readonly manualModeShellGate?: ServiceConfig["manualModeShellGate"];
13
13
  readonly sensitiveWritePatterns: ServiceConfig["sensitiveWritePatterns"];
14
- /** 7.115.0:运维的 allow 规则(`APPROVAL_GRANT`)—— 原串交给折叠属主编成叶(`runtime-governance.ts` `compileApprovalGrantLayers`)。
15
- * 必填:三条消费腿的切面都要点名它,漏了是编译错,不是一条腿上静默没有这只旋钮。 */
16
- readonly approvalGrant: ServiceConfig["approvalGrant"];
17
14
  /**
18
15
  * 本部署的**本地店根**(`LOCAL_DATA_ROOT → CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent`)—— 在本口只喂
19
16
  * **誊本门**(`<localDataRoot>/sessions`)。S-133 起它**不再**当守卫集的 `dataRoot`(那是引擎记忆根,见
@@ -233,17 +230,16 @@ export interface GateSeatView {
233
230
  * 此前是「这个部署一个门都没接」这个 misconfig 的**唯一**提示,而守卫集只挡那二十来个路径段、其余写
234
231
  * 目标照旧无裁决 —— 信号不能因为我们铺了基线就静默消失,所以由我们自己按同一判据说一次。
235
232
  *
236
- * 判据(与信号消失的条件逐字互补):**非门的部署叶在场**(守卫集,或 7.115.0 起运维的 allow 规则 `APPROVAL_GRANT` —— 两者都让
237
- * `spec.toolPolicy` 在场、又都不是门:一个只拒守卫段,一个只表态 allow)∧ 这条腿的审批基线**没有席**(`absent`)∧ 单用户
238
- * auto-accept 基线不适用。事件名 `tool_policy_only_sensitive_baseline` 是allow 规则之前定的,保留不改(运维的日志告警按它匹配),
239
- * 哪一片叶在场写在 `detail` 与 `guardedPatterns` / `grantRules` 两个计数里。三个量都是部署常量 ⇒ 消费腿在 boot 期说一次,不是每任务一次。
233
+ * 判据(与信号消失的条件逐字互补):**守卫集在场**(它让 `spec.toolPolicy` 在场、又不是门:只拒守卫段)∧ 这条腿的审批基线**没有席**
234
+ * (`absent`)∧ 单用户 auto-accept 基线不适用。三个量都是部署常量 ⇒ 消费腿在 boot 期说一次,不是每任务一次。
235
+ * (7.115.0–7.116.x 运维的 allow 表 `APPROVAL_GRANT` 也是这里的一片非门叶;7.117.0 起它住在 Runner 的部署席、不再是策略,随之出列。)
240
236
  * S-694:席读的是喂审批基线的**同一只**({@link ApprovalSeat})—— `durable` / `live` 两形都有人服 ask、名单
241
237
  * 进策略,「守卫集是唯一的策略」这句话在它们上面不成立。今天真发的只剩 HTTP 腿「durable 关 ∧ 多租户」那一形。
242
238
  *
243
239
  * ⚠️ 单一属主([ref] 件三):HTTP 腿与 run-local 腿共用本判据与文案。两处各写一份 = 一处改了另一处
244
240
  * 没改,而两份都长得像对的 —— 那正是本文件存在的理由。
245
241
  */
246
- export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns" | "approvalGrant">, seat: GateSeatView): OperatorWarning | undefined;
242
+ export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">, seat: GateSeatView): OperatorWarning | undefined;
247
243
  /**
248
244
  * `applyRuntimeGovernance` 的 governance 实参预铸([ref] 件一)。
249
245
  *
@@ -259,11 +255,14 @@ export declare function buildOnlySensitiveBaselineWarning(config: Pick<Deploymen
259
255
  */
260
256
  export declare function createDeploymentGovernanceInputs(config: DeploymentGovernanceConfigView, pathAdjudication: PathAdjudication, roots: DeploymentGovernanceRoots): DeploymentGovernanceInputs;
261
257
  /**
262
- * 7.115.0 —— 运维 allow 规则的**起服通告**载荷(纯数据 ⇒ `build*`;HTTP 腿与 run-local 腿各在装配时说一次,用 info 级)。
263
- * 规则名不是秘密(它们是工具名),所以原样列出;空表 ⇒ `undefined`(不打)。运维只读面上同一个事实另有一处:
264
- * `GET /v1/diagnostics/wiring` 的 `serverGates.approvalGrant`。
258
+ * 运维 allow 表 `APPROVAL_GRANT` 的**起服通告**载荷(纯数据 ⇒ `build*`;HTTP 腿与 run-local 腿各在装配时说一次,用 info 级)。规则不是秘密
259
+ * (它们是工具名 / 命令规则),所以原样列出;空表 ⇒ `undefined`(不打)。运维只读面上同一个事实另有一处:`GET /v1/diagnostics/wiring` 的
260
+ * `serverGates.approvalGrant`。`note` 是这只旋钮**本版的效果**的真话(7.117.0 起语义变宽,三问在 CHANGELOG / MIGRATION):清什么、永远清不掉什么。
261
+ * 不在本口读席位:表经部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats`)交给每只 Runner,本口只说它在场。
265
262
  */
266
- export declare function buildApprovalGrantNotice(config: Pick<DeploymentGovernanceConfigView, "approvalGrant">): OperatorWarning | undefined;
263
+ export declare function buildApprovalGrantNotice(config: {
264
+ readonly approvalGrant: ServiceConfig["approvalGrant"];
265
+ }): OperatorWarning | undefined;
267
266
  /**
268
267
  * 审批基线 —— `applyRuntimeGovernance` 那个 base 的 `toolPolicy` 座([ref] 件一)。**恒非
269
268
  * `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
@@ -61,7 +61,7 @@ export function assertGuardPatternsUsable(config) {
61
61
  }
62
62
  }
63
63
  export function buildOnlySensitiveBaselineWarning(config, seat) {
64
- if ((config.sensitiveWritePatterns.length === 0 && config.approvalGrant.length === 0) || seat.singleUserAutoAcceptBaseline)
64
+ if (config.sensitiveWritePatterns.length === 0 || seat.singleUserAutoAcceptBaseline)
65
65
  return undefined;
66
66
  switch (seat.approvalSeat) {
67
67
  case "durable":
@@ -74,17 +74,12 @@ export function buildOnlySensitiveBaselineWarning(config, seat) {
74
74
  throw new Error(`approval seat "${String(unhandled)}" has no arm in the only-sensitive-baseline warning`);
75
75
  }
76
76
  }
77
- const nonGateLayers = [
78
- ...(config.sensitiveWritePatterns.length > 0 ? ["the sensitive-path DENY baseline (SENSITIVE_WRITE_PATTERNS)"] : []),
79
- ...(config.approvalGrant.length > 0 ? ["the APPROVAL_GRANT allow rules"] : []),
80
- ];
81
77
  return {
82
78
  event: "tool_policy_only_sensitive_baseline",
83
79
  fields: {
84
- detail: `${nonGateLayers.join(" and ")} ${nonGateLayers.length > 1 ? "are" : "is"} this deployment's ONLY tool policy and not a gate — writes outside the guarded segments are unadjudicated, and core's own UNGATED warning no longer fires because a policy is now always present`,
80
+ detail: "the sensitive-path DENY baseline (SENSITIVE_WRITE_PATTERNS) is this deployment's ONLY tool policy and not a gate — writes outside the guarded segments are unadjudicated, and core's own UNGATED warning no longer fires because a policy is now always present",
85
81
  remedy: "wire an approval face (DURABLE_APPROVAL / APPROVAL_REQUIRE with a store) or accept the single-user auto-accept baseline",
86
82
  guardedPatterns: config.sensitiveWritePatterns.length,
87
- grantRules: config.approvalGrant.length,
88
83
  },
89
84
  };
90
85
  }
@@ -113,7 +108,6 @@ export function createDeploymentGovernanceInputs(config, pathAdjudication, roots
113
108
  commandPolicy: config.commandPolicy,
114
109
  ...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
115
110
  ...(sensitivePathLayers ? { sensitivePathLayers } : {}),
116
- ...(config.approvalGrant.length > 0 ? { approvalGrant: config.approvalGrant } : {}),
117
111
  };
118
112
  }
119
113
  export function buildApprovalGrantNotice(config) {
@@ -124,7 +118,8 @@ export function buildApprovalGrantNotice(config) {
124
118
  fields: {
125
119
  rules: config.approvalGrant.length,
126
120
  names: [...config.approvalGrant],
127
- note: "APPROVAL_GRANT states the allow for the tools these rules name on every leg it is installed on (HTTP submit, durable resume, cross-replica revival of parked background agents, run-local) — those calls are no longer asked for want of a statement; any layer's own deny or ask (approval lists, request settings, session rules, the engine's mode gates) still wins",
121
+ note: "APPROVAL_GRANT is the deployment's member of the engine's allow layer (RunnerDeps.allowedTools on every Runner this process builds): a call these rules cover is no longer asked for an ordinary question (a file write, a shell command, a server tool nobody stated) — built-in tools included. " +
122
+ "It never clears a deny from any layer, an APPROVAL_REQUIRE / APPROVAL_NEVER_AUTO / request ask rule, the MANUAL_MODE_SHELL_GATE=always shell floor, a write-protected path, a sensitive-path deny, governance, a PreToolUse hook's ask, or the plan mode's question; a rule that matches and cannot clear is disclosed on the card (allowRuleShadowed member \"deployment\")",
128
123
  },
129
124
  };
130
125
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * **收紧折叠的入参型与唯一折叠口** —— 本仓三处收紧(治理层 `applyRuntimeGovernance`、请求 settings 的 `applyTaskSettings`、SUP 姿态)都经
3
+ * {@link tightenGoverned} 调 core 的 `tightenTaskSpec`,入参型是 {@link GovernanceOverrides}。
4
+ *
5
+ * 🔴 为什么入参型上**没有** `allowedTools`:core 的 `tightenTaskSpec(base, overrides)` 对 `allowedTools` 不做「收紧」,而是**用覆盖值替换**
6
+ * base 上的表(本仓探针亲证)。请求的 allow 表有且只有一个写点(`task-settings.ts` 的 `applyTaskSettings`,在折叠之外);一只往收紧入参里
7
+ * 塞 `allowedTools` 的调用点会静默改写请求的表。型上禁掉它(`allowedTools?: never`),写出来就是编译红 —— 比文本扫描的静态门(方括号写法、
8
+ * 展开形会漏)更硬。
9
+ *
10
+ * 🔴 型禁有一个洞([ref] 合并复审 opus 腿):本仓没开 `exactOptionalPropertyTypes`,`{ allowedTools: undefined }` 过型检,而 core 的替换会把 base 上的表
11
+ * 抹成 `undefined`。所以折叠口再加一道**运行期**守卫:入参对象自带 `allowedTools` 键(任何值,含 `undefined`)⇒ 抛(fail-closed:宁多问一次)。
12
+ */
13
+ import { type TaskSpec } from "@sema-agent/core";
14
+ /** 收紧折叠的入参:`TaskSpec` 的字段,`allowedTools` 除外(型上禁)。 */
15
+ export type GovernanceOverrides = Partial<Omit<TaskSpec, "allowedTools">> & {
16
+ readonly allowedTools?: never;
17
+ };
18
+ /** 本仓唯一的收紧折叠口(deny-wins 合并;core 在放宽时抛)。 */
19
+ export declare function tightenGoverned(base: TaskSpec, overrides: GovernanceOverrides): TaskSpec;
20
+ //# sourceMappingURL=governance-overrides.d.ts.map
@@ -0,0 +1,8 @@
1
+ import { tightenTaskSpec } from "@sema-agent/core";
2
+ export function tightenGoverned(base, overrides) {
3
+ if (Object.hasOwn(overrides, "allowedTools")) {
4
+ throw new Error("tightenGoverned: `allowedTools` is not a tightening input — the engine replaces the base list with the override instead of narrowing it; the request's allow list has exactly one write point (applyTaskSettings)");
5
+ }
6
+ return tightenTaskSpec(base, overrides);
7
+ }
8
+ //# sourceMappingURL=governance-overrides.js.map