@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.
- package/MIGRATION.md +98 -0
- package/USAGE.md +32 -27
- package/dist/approval-card.d.ts +43 -9
- package/dist/approval-card.js +36 -4
- package/dist/boot/budget-tracing.js +2 -3
- package/dist/boot/config-center.d.ts +2 -2
- package/dist/boot/config-center.js +5 -7
- package/dist/boot/resolve-spec.js +10 -3
- package/dist/boot/runner-deps.d.ts +5 -3
- package/dist/boot/runner-deps.js +4 -2
- package/dist/brain.d.ts +17 -8
- package/dist/brain.js +10 -7
- package/dist/config-catalog.js +3 -2
- package/dist/config-center/apply-effective.d.ts +6 -16
- package/dist/config-center/apply-effective.js +0 -6
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/restart-signal.d.ts +1 -1
- package/dist/config-types.d.ts +12 -16
- package/dist/config.d.ts +34 -27
- package/dist/config.js +23 -19
- package/dist/deployment-governance.d.ts +11 -12
- package/dist/deployment-governance.js +4 -9
- package/dist/governance-overrides.d.ts +20 -0
- package/dist/governance-overrides.js +8 -0
- package/dist/http/admission.js +7 -7
- package/dist/http/approval-sweeps.js +15 -12
- package/dist/http/checkpoint-ctx.d.ts +83 -24
- package/dist/http/checkpoint-ctx.js +68 -8
- package/dist/http/resume-legs.js +11 -15
- package/dist/http/retired-skill-manifest.d.ts +1 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/routes/capabilities.js +3 -3
- package/dist/http/routes/runs.js +12 -0
- package/dist/model-select.d.ts +16 -5
- package/dist/model-select.js +7 -3
- package/dist/namespaced-rule-name.d.ts +7 -5
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/observability/run-terminal.js +21 -1
- package/dist/outbound-dispatcher.d.ts +18 -0
- package/dist/outbound-dispatcher.js +96 -10
- package/dist/permission-rule-list.d.ts +20 -31
- package/dist/plugins/one-time-migrations.d.ts +20 -0
- package/dist/plugins/one-time-migrations.js +84 -4
- package/dist/router/route-orchestration.d.ts +2 -23
- package/dist/rule-entry-list.d.ts +99 -0
- package/dist/rule-entry-list.js +110 -0
- package/dist/run-local.d.ts +4 -2
- package/dist/run-local.js +3 -3
- package/dist/runs.js +1 -0
- package/dist/runtime-governance.d.ts +5 -21
- package/dist/runtime-governance.js +4 -8
- package/dist/spec-fields.d.ts +15 -24
- package/dist/spec-fields.js +16 -17
- package/dist/task-settings.d.ts +18 -18
- package/dist/task-settings.js +57 -30
- package/dist/tool-approval.d.ts +24 -33
- package/dist/tool-approval.js +5 -7
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +4 -6
- package/dist/trace/engine-notice-wire.js +14 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +31 -6
- package/scripts/drain-check.mysql.sql +31 -6
- 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
|
-
/**
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/config-catalog.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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 读回)
|
|
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,
|
|
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,
|
|
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`)对本拍活配置再读一次(
|
|
9
|
+
* 指纹函数(brain.ts `degradeRouteFingerprint`)对本拍活配置再读一次(`baked` 的出处见 brain.ts `createBrainAssembly`)。
|
|
10
10
|
* 缺席 = 调用方没有递席(单元调用)⇒ `degrade-route` 片恒不签;车道没开 / 没焊壳在席内表达为两侧同为 null。
|
|
11
11
|
* (此前的 `reactiveDegradeTo` 位只剩「阀门开着」一义,已并进本席 —— 一位不留两义。) */
|
|
12
12
|
degradeRoute?: {
|
package/dist/config-types.d.ts
CHANGED
|
@@ -1325,22 +1325,18 @@ export interface ServiceConfigFlat {
|
|
|
1325
1325
|
* 从未进入任何 policy。判定现在单源在 `hasOperatorGateIntent()`(与 inv#2 同文件)。 */
|
|
1326
1326
|
approvalNeverAuto: string[];
|
|
1327
1327
|
/**
|
|
1328
|
-
*
|
|
1329
|
-
*
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
* ⚠️ 不是白名单:名字不叫 `…_ALLOW`,与会话规则的 `toolAllow`(「只许这些」)刻意分开 ——
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
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
|
|
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
|
|
289
|
-
*
|
|
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
|
-
*
|
|
339
|
-
* `
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
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
|
-
/**
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* ②
|
|
373
|
-
*
|
|
374
|
-
*
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
return
|
|
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
|
-
* 判据(与信号消失的条件逐字互补)
|
|
237
|
-
*
|
|
238
|
-
*
|
|
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"
|
|
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
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* `
|
|
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:
|
|
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 (
|
|
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:
|
|
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
|
|
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
|