@sema-agent/server 7.113.0 → 7.115.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 +109 -2
- package/USAGE.md +36 -3
- package/deploy/sema-up/cluster-up.sh +13 -1
- package/deploy/sema-up/kube-up.sh +13 -1
- package/deploy/sema-up/preflight.sh +13 -1
- package/deploy/sema-up/sema-up.sh +13 -1
- package/deploy/sema-up/smoke.sh +13 -1
- package/dist/adoption/wire.d.ts +0 -3
- package/dist/approval-card.d.ts +21 -24
- package/dist/approval-card.js +12 -0
- package/dist/approval.js +5 -2
- package/dist/boot/exit-boundary.d.ts +3 -2
- package/dist/boot/exit-boundary.js +3 -0
- package/dist/boot/memory-boundary.d.ts +2 -1
- package/dist/boot/memory-consolidation.d.ts +1 -1
- package/dist/boot/resolve-spec.js +6 -3
- package/dist/boot/shutdown.d.ts +19 -0
- package/dist/boot/shutdown.js +12 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/config-catalog.js +8 -6
- package/dist/config-center/apply-effective.js +11 -11
- package/dist/config-invariants.d.ts +27 -0
- package/dist/config-invariants.js +54 -39
- package/dist/config-provider.d.ts +16 -0
- package/dist/config-provider.js +11 -2
- package/dist/config-types.d.ts +24 -11
- package/dist/config-types.js +0 -1
- package/dist/config.d.ts +41 -34
- package/dist/config.js +78 -81
- package/dist/deployment-governance.d.ts +15 -4
- package/dist/deployment-governance.js +20 -2
- package/dist/env-facts.d.ts +3 -1
- package/dist/env-facts.js +5 -1
- package/dist/execution-lane-caps.d.ts +54 -30
- package/dist/execution-lane-caps.js +48 -20
- package/dist/fleet/fleet-bus.d.ts +1 -22
- package/dist/hooks/hook-runner.d.ts +112 -6
- package/dist/hooks/hook-runner.js +138 -32
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +9 -4
- package/dist/http/approval-sweeps.js +20 -0
- package/dist/http/checkpoint-ctx.d.ts +68 -0
- package/dist/http/checkpoint-ctx.js +36 -0
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/resume-legs.js +9 -0
- package/dist/http/route-ctx.d.ts +7 -0
- package/dist/http/routes/a2a-serve.d.ts +1 -3
- package/dist/http/routes/a2a-serve.js +0 -2
- package/dist/http/routes/admin-memory-optout.d.ts +0 -2
- package/dist/http/routes/admin-memory-optout.js +0 -1
- package/dist/http/routes/capabilities.js +3 -2
- package/dist/http/routes/diagnostics.d.ts +5 -0
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/tasks.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +2 -1
- package/dist/local-data-root.d.ts +1 -1
- package/dist/mcp-content-origin.d.ts +0 -8
- package/dist/model-entry-refusal.d.ts +10 -2
- package/dist/model-entry-refusal.js +19 -3
- package/dist/observability/corrupt-read-seat.d.ts +3 -3
- package/dist/observability/corrupt-read-seat.js +5 -4
- package/dist/observability/fail-open.d.ts +10 -9
- package/dist/observability/fail-open.js +5 -1
- package/dist/permission-rule-list.d.ts +81 -0
- package/dist/permission-rule-list.js +17 -0
- package/dist/plugins/remote-env-host.d.ts +0 -35
- package/dist/plugins/remote-env-host.js +0 -8
- package/dist/plugins/remote-env-local-docker.d.ts +5 -13
- package/dist/plugins/remote-env-local-docker.js +2 -4
- package/dist/run-local.js +4 -1
- package/dist/runtime-governance.d.ts +21 -1
- package/dist/runtime-governance.js +6 -1
- package/dist/security.js +2 -2
- package/dist/session-policy-wire.d.ts +5 -4
- package/dist/store-posture.d.ts +7 -7
- package/dist/store-posture.js +0 -1
- package/dist/task-settings.d.ts +19 -5
- package/dist/task-settings.js +19 -30
- package/dist/tool-approval.d.ts +16 -4
- package/dist/tool-approval.js +2 -1
- package/dist/trace/core-keyset-guard.d.ts +9 -4
- package/dist/trace/project.js +9 -0
- package/package.json +4 -4
- package/scripts/{retired-mode-seed-drain-local.mjs → drain-check-local.mjs} +147 -26
package/dist/config-types.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { InfraCostRates } from "./observability/cost-taxonomy.js";
|
|
|
18
18
|
import type { Autonomy, CommandRule } from "./runtime-governance.js";
|
|
19
19
|
import type { CenterCredentialSource } from "./center-credential.js";
|
|
20
20
|
import type { GitApiKind } from "./git-api-kind.js";
|
|
21
|
+
import type { MemoryEngineBackend, StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
|
|
21
22
|
import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
|
|
22
23
|
/** [ref]:记忆向量面的 embedder 坐标(env `MEMORY_EMBEDDER_*` 解析结果;单一属主 = 本类型,
|
|
23
24
|
* `plugins/memory-embedder.ts` 的工厂直接消费它,不复制形状)。语义与三问见
|
|
@@ -54,13 +55,6 @@ export type RetentionMode = (typeof RETENTION_MODES)[number];
|
|
|
54
55
|
*/
|
|
55
56
|
export declare const CONFIG_PROVIDER_WORDS: readonly ["local", "remote"];
|
|
56
57
|
export type ConfigProviderWord = (typeof CONFIG_PROVIDER_WORDS)[number];
|
|
57
|
-
/**
|
|
58
|
-
* S-866 ① —— `DB_BACKEND` 的闭集词表(**一处**:`config.ts` 的枚举解析、配置目录的 enum 行、caps `store.declared` / `store.backend`
|
|
59
|
-
* 三处共读;S-2 设计稿 v1 写的契约包 `STORE_ENGINES` 不存在,搬进契约包是 [ref] A-5 的另票)。`mysql` = 任一 MySQL 协议服务器
|
|
60
|
-
* (MySQL / TiDB / MariaDB);`local` = 本机文件店;`memory` = 纯内存(显式 opt-out,或多租裸 boot 的缺省)。
|
|
61
|
-
*/
|
|
62
|
-
export declare const DB_BACKEND_WORDS: readonly ["mysql", "pg", "local", "memory"];
|
|
63
|
-
export type DbBackendWord = (typeof DB_BACKEND_WORDS)[number];
|
|
64
58
|
/** [ref] §3/§4 —— sweep lane 的节律与灰度档(语义与三问见 {@link ServiceConfigFlat.retentionSweep})。 */
|
|
65
59
|
export interface RetentionSweepConfig {
|
|
66
60
|
/** `audit-only` = 照跑照判、只记 `audit_only` 行,**不调任何破坏性方法**;`enforce` = 真删。默认前者。 */
|
|
@@ -702,7 +696,7 @@ export interface ServiceConfigFlat {
|
|
|
702
696
|
* pg 部署的检索**恒 lexical**(词面 jaccard),向量列空转。配上这几键 = 该部署真正拿到语义检索。
|
|
703
697
|
* ② **半配为何拒启**:三键里少任何一件,「向量面」都无从组装。静默忽略的后果不是功能缺席,而是
|
|
704
698
|
* **operator 以为开了**——检索照常返回结果(词面档),质量差异只在最终答案里,任何日志都不报。
|
|
705
|
-
* 所以照 `MEMORY_SYNC_*` / `
|
|
699
|
+
* 所以照 `MEMORY_SYNC_*` / [ref] 车道必需键(`laneRequiredEnv`)的同款姿势整拒并点名缺哪几键。
|
|
706
700
|
* ③ **维度守卫为何是拒启不是 warn**:向量列写错维度是**静默毒库**形([ref] 裁③)——错长度的向量
|
|
707
701
|
* 既不会让 DB 报错(本仓 pg 记忆表的 embedding 是 `jsonb`,无维度约束),也不会让检索报错(维度
|
|
708
702
|
* 不匹配的行只是悄悄退回词面档),只会让「向量面开着」这句话变成谎。所以坏 DIM(非正整数)在
|
|
@@ -1003,7 +997,7 @@ export interface ServiceConfigFlat {
|
|
|
1003
997
|
* MYSQL_* replacement named (see RETIRED_ENVS in config.ts). `mysql` reads MYSQL_* coords only.
|
|
1004
998
|
* TiDB itself stays fully supported (any MySQL-protocol engine works — memory no longer needs a VECTOR
|
|
1005
999
|
* column since [ref] moved it onto the file engine). Env: DB_BACKEND. */
|
|
1006
|
-
dbBackend:
|
|
1000
|
+
dbBackend: StoreBackendWord;
|
|
1007
1001
|
/** clay 拍(2026-07-27):operator 是否显式设了 DB_BACKEND。main.ts 的降级判据——**默认推导**的
|
|
1008
1002
|
* local(裸 boot)在 mkdir/ensure 失败时降级 memory + warn + gauge(auto-DB 不可达先例);
|
|
1009
1003
|
* **显式** DB_BACKEND=local 失败仍 fail-loud(operator 要了 durable,静默丢=丢数据)。 */
|
|
@@ -1330,6 +1324,25 @@ export interface ServiceConfigFlat {
|
|
|
1330
1324
|
* 「只配 APPROVAL_NEVER_AUTO」这一形被判成"零门意图" ⇒ 落进单用户 allow-all 基线 ⇒ 那张表
|
|
1331
1325
|
* 从未进入任何 policy。判定现在单源在 `hasOperatorGateIntent()`(与 inv#2 同文件)。 */
|
|
1332
1326
|
approvalNeverAuto: string[];
|
|
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)。空表 = 本层不铸叶,折叠逐字与修前同。
|
|
1344
|
+
*/
|
|
1345
|
+
approvalGrant: string[];
|
|
1333
1346
|
/** [ref] D-G (HMAC = integrity, not identity; ✅ WIRED — the "SCAFFOLD, not yet wired" note here was STALE,
|
|
1334
1347
|
* corrected 2026-07-25: consumed by `verifyDirectDoorProof` → `verifyApprovalHmac`, reached from the live decide
|
|
1335
1348
|
* routes — the two `verifyDirectDoorProof(` call sites in `http/routes/approvals-assistant.ts`): the
|
|
@@ -2053,7 +2066,7 @@ export interface ServiceConfigFlat {
|
|
|
2053
2066
|
* 404 there (P1-2: our own repos live on GitHub and were unreviewable). Unknown word = refuse boot. */
|
|
2054
2067
|
gitApiKind: GitApiKind;
|
|
2055
2068
|
/** S3-TOB 设计 §1.3:memory 持久面方言(env MEMORY_ENGINE_BACKEND,缺省 file)。boot-only。 */
|
|
2056
|
-
memoryEngineBackend:
|
|
2069
|
+
memoryEngineBackend: MemoryEngineBackend;
|
|
2057
2070
|
/** CC `workflowSizeGuideline` (advisory, injected into the Workflow tool card via
|
|
2058
2071
|
* core `RunnerDeps.workflowLimits.sizeGuideline`). env WORKFLOW_SIZE_GUIDELINE; unset = absent =
|
|
2059
2072
|
* byte-compat (core treats missing/unrestricted identically). boot-only (per-task card snapshot). */
|
|
@@ -2107,7 +2120,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
|
|
|
2107
2120
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "envModel" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelRefusals" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
|
|
2108
2121
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|
|
2109
2122
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
2110
|
-
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "autoModeClassifierFallback" | "autoModeClassifierTimeoutMs" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
2123
|
+
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalGrant" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "autoModeClassifierFallback" | "autoModeClassifierTimeoutMs" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
2111
2124
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
2112
2125
|
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
2113
2126
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
package/dist/config-types.js
CHANGED
|
@@ -2,7 +2,6 @@ import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
|
|
|
2
2
|
export const TASK_WRITE_FACE_ESCALATIONS = ["allow", "refuse"];
|
|
3
3
|
export const RETENTION_MODES = ["audit-only", "enforce"];
|
|
4
4
|
export const CONFIG_PROVIDER_WORDS = ["local", "remote"];
|
|
5
|
-
export const DB_BACKEND_WORDS = ["mysql", "pg", "local", "memory"];
|
|
6
5
|
export const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
7
6
|
export const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
8
7
|
export const AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS = { minMs: 1_000, maxMs: MAX_TIMER_DELAY_MS };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type Model, type PathRuleBases, type RuleBehavior } from "@sema-agent/core";
|
|
2
|
-
import { type RemoteExecProvider } from "./execution-lane-caps.js";
|
|
3
2
|
import { type Autonomy } from "./runtime-governance.js";
|
|
4
3
|
import { type RunnerEnvNameAllowlistKnob, type ServerEnvNameAllowlistKnob } from "./env-name-allowlist-knobs.js";
|
|
5
4
|
import type { ServiceConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js";
|
|
@@ -252,21 +251,6 @@ export declare const configLkgEnabled: () => boolean;
|
|
|
252
251
|
export declare const hostBackgroundShellEnabled: () => boolean;
|
|
253
252
|
/** The `host` exec lane's spool-file stdio form; false ⇒ the pre-1.226 pipe fallback (read per exec). */
|
|
254
253
|
export declare const hostExecSpoolEnabled: () => boolean;
|
|
255
|
-
/**
|
|
256
|
-
* 每条执行车道**必需的 env 键名**([ref] 的 fail-closed 拒启门按它点名缺件;见 `parseOrchestrationDomain`
|
|
257
|
-
* 里那处 `REMOTE_EXEC` 校验)。
|
|
258
|
-
*
|
|
259
|
-
* 键集 = `RemoteExecProvider`(判别联合派生,`execution-lane-caps.ts`):配置面加一条 provider 臂而
|
|
260
|
-
* 这里不加行 = **编译红**(codex R1-F2)。此前是 `Record<string, …>`,漏一行只表现为该词在 boot 期被判
|
|
261
|
-
* 「不是执行车道」—— fail-closed 但把「新车道」讲成「拼错了」,运维照着报文永远查不到成因。
|
|
262
|
-
*
|
|
263
|
-
* 🔴 **S-599:提到模块顶层导出**(此前是 `parseOrchestrationDomain` 的函数内局部)。零语义变,动机是
|
|
264
|
-
* 部署面需要**同一张表**:`deploy/sema-up/lib/lanes.json` 的每行必须登记它那条车道的必需键名,根 /
|
|
265
|
-
* review compose 的 environment 白名单必须把**全部**车道的必需键透传出去 —— 那两处若各手抄一份名单,
|
|
266
|
-
* 加一条车道就会静默分叉(选得到、起不来,而 config 的拒启报文里那个键在 compose 里根本不存在)。
|
|
267
|
-
* 门 = `test/deploy-lanes-contract.test.ts`,它读的是本表而不是任何副本。
|
|
268
|
-
*/
|
|
269
|
-
export declare const REQUIRED_ENVS_BY_LANE: Record<RemoteExecProvider, readonly string[]>;
|
|
270
254
|
/** 名单型 env 旋钮的**唯一**解析器(规则全文见上方 `ENV_NAME_ALLOWLIST_KNOBS` 头注)。`undefined` = 没配(未设 / 空串)。
|
|
271
255
|
* 服务侧旋钮要交本进程的姿态(③);runner 旋钮没有姿态,签名上就不收 —— 读者与行形由叶表的类型一起定,传错是编译红。 */
|
|
272
256
|
export declare function parseEnvNameAllowlist(knob: ServerEnvNameAllowlistKnob, singleUserTurnkey: boolean): readonly string[] | undefined;
|
|
@@ -301,7 +285,8 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
|
|
|
301
285
|
* ① env 腿(`APPROVAL_REQUIRE`/`DENY`/`NEVER_AUTO`,下方)—— boot 拒启;
|
|
302
286
|
* ② 中心下发腿(`runtime.approvalRequire`,config-center/apply-effective.ts)—— 域级拒绝+保前值+warn
|
|
303
287
|
* (常驻进程不 crash);
|
|
304
|
-
* ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts)—— 该请求 422
|
|
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})。
|
|
305
290
|
*
|
|
306
291
|
* 五种拼法:
|
|
307
292
|
* - CC 形规则条目(`Bash(ps:*)`/`Edit(src/**)`,[ref] [ref]§七 装机实测):CC 的权限规则 DSL 写法。这些
|
|
@@ -343,28 +328,50 @@ export interface UnmatchableToolName {
|
|
|
343
328
|
* S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。两个消费者形态:
|
|
344
329
|
*
|
|
345
330
|
* · `"tool-name-table"` —— 消费者按**整串工具名**比对(精确 ∪ covering):env `APPROVAL_*` 三键与
|
|
346
|
-
* 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`)
|
|
347
|
-
* `settings.permissions.allow`(消费者 core 的 `createAllowDenyPolicy` 的 allow 位)。带括号的内容形
|
|
331
|
+
* 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`)。带括号的内容形
|
|
348
332
|
* 在这里永远匹配不上任何活工具名 —— 与引擎的规则文法认不认得它**无关**,是这条腿的比对方式决定的,
|
|
349
333
|
* 所以内容形照旧响亮拒 + 指路。
|
|
350
334
|
* · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):
|
|
351
|
-
* `settings.permissions.deny`
|
|
352
|
-
*
|
|
353
|
-
* `
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* 写下的拼法族错,静默代价是整只 Bash 不可用。
|
|
335
|
+
* `settings.permissions.deny` / `.ask`(S-177 / core 7.9.0 起)与 `.allow`(S-654 (c) / 7.115.0 起;修前是
|
|
336
|
+
* `createAllowDenyPolicy` 的名单位)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` /
|
|
337
|
+
* `Edit(~/.sema/**)`),所以本仓不许再替它拒 —— 判据整条交回 core 的 `validatePermissionRules`,按这张表
|
|
338
|
+
* 自己的 behavior 问(allow 侧 core 只收工具名形,内容形由 core 判拒)。
|
|
339
|
+
*
|
|
340
|
+
* S-654 (c) 前这里还有一条「`allow` 豁免名字形与文法两族」的不对称,前提是 allow 为白名单(匹配不上 ⇒ 全拒,
|
|
341
|
+
* fail-closed 且当场可见);allow 改成规则后那条前提不在了(匹配不上 ⇒ 什么也不放行、照常问,写的人收不到信号),豁免随之删除。
|
|
359
342
|
*/
|
|
360
343
|
export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
344
|
+
/** {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器)、按哪种态度问 core 的文法、路径基。 */
|
|
345
|
+
export interface UnmatchableToolNameJudgment {
|
|
346
|
+
readonly consumer: RuleEntryConsumer;
|
|
347
|
+
readonly behavior: RuleBehavior;
|
|
348
|
+
readonly pathBases?: PathRuleBases;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* 「这张表里有永远不会生效的条目」的**判据与拒因句,同一只函数**(返回 `undefined` = 通过;拒 ⇒ 句子 + 被点名的条目)。
|
|
352
|
+
* 五条腿全调它:env `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO`(拒启)、中心下发 `governance.approvalRequire`(整组拒)、
|
|
353
|
+
* 请求体 `settings.permissions.{allow,ask,deny}`(422)、运维 `APPROVAL_GRANT`(拒启)。修前判据(`findUnmatchableToolNames`)与成句
|
|
354
|
+
* (`formatUnmatchableToolNames`)是两只导出,句子里那半句(「这些表都是整串逐字比对」)只对名表成立 —— 规则编译器的
|
|
355
|
+
* 三只桶(与 `APPROVAL_GRANT`)是按规则文法编、按 covering 匹配的,不是整串比对。
|
|
356
|
+
*
|
|
357
|
+
* 句子对两种消费者、三种态度都成立:每一条要么永远匹配不到活工具(退役名 / 无协议前缀的短名 / 缺段的 MCP 名 / 名表里的内容形),要么编不成
|
|
358
|
+
* 一条规则(core 文法的判词原样带出);名字按写的样子读,不做别名折叠、不补协议前缀。逐条的「为什么 / 怎么改」在 `→` 之后。
|
|
359
|
+
*/
|
|
360
|
+
export declare function unmatchableToolNamesRefusal(source: string, names: readonly string[], judgment: UnmatchableToolNameJudgment): {
|
|
361
|
+
readonly message: string;
|
|
362
|
+
readonly names: readonly string[];
|
|
363
|
+
} | undefined;
|
|
364
|
+
/**
|
|
365
|
+
* 7.115.0 —— 运维 allow 规则 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:
|
|
366
|
+
* 返回句子 = 拒,`undefined` = 通过)。判据与请求腿的 `settings.permissions.allow` 是**同一只**:
|
|
367
|
+
* ① 名字形与 core 文法 —— {@link unmatchableToolNamesRefusal},消费者 `rule-policy`、behavior `allow`(退役名 / 无前缀短名 / 缺段 MCP 名 /
|
|
368
|
+
* 反斜杠 / 超长 / 内容形与参数级的 allow;句子点名哪一条、为什么、怎么改);
|
|
369
|
+
* ② 整表试编一次 —— 编的就是治理折叠里那一只(`runtime-governance.ts` {@link compileApprovalGrantLayers}),于是 core 的整表帽这类
|
|
370
|
+
* 只在编译时才抛的判据也在启动时响亮,而不是每个任务一次 500。
|
|
371
|
+
* 没有 `pathBases`:运维的规则不挂在任何一条任务的根上,而 allow 文法本来只收工具名形(路径形的 allow core 拒编,在 ① 里就拒了)。
|
|
372
|
+
* 坏值**不许**静默丢掉那一条后照常起:运维以为放行了,无人值守腿照旧拒 / 停驻,没有任何信号指回这里。
|
|
373
|
+
*/
|
|
374
|
+
export declare function approvalGrantRefusal(names: readonly string[]): string | undefined;
|
|
368
375
|
/**
|
|
369
376
|
* [ref] 件B/C/D —— 三条**部署治理声明**的 env 解析(auth 域的一段,fail-loud 全在这里)。
|
|
370
377
|
*
|
package/dist/config.js
CHANGED
|
@@ -10,7 +10,7 @@ import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
|
10
10
|
import { isV2ScopeKey, memoryReplicationCoverage, replicationCovers } from "./memory-scope.js";
|
|
11
11
|
import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
|
|
12
12
|
import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
|
|
13
|
-
import { CONFIG_PROVIDER_WORDS,
|
|
13
|
+
import { CONFIG_PROVIDER_WORDS, RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS } from "./config-types.js";
|
|
14
14
|
import { resolveLocalDataRoot } from "./local-data-root.js";
|
|
15
15
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
16
16
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
@@ -18,8 +18,10 @@ import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-post
|
|
|
18
18
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
19
19
|
import {} from "./posture-source.js";
|
|
20
20
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
|
|
22
|
+
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
|
|
23
|
+
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
24
|
+
import { APPROVAL_GRANT_SOURCE, AUTONOMY_MODES, compileApprovalGrantLayers } from "./runtime-governance.js";
|
|
23
25
|
import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
|
|
24
26
|
import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
|
|
25
27
|
import { modelProviderRefusal } from "./model-provider.js";
|
|
@@ -484,15 +486,6 @@ function boolEnv(name, def) {
|
|
|
484
486
|
export const configLkgEnabled = () => boolEnv("CONFIG_LKG_ENABLED", true);
|
|
485
487
|
export const hostBackgroundShellEnabled = () => boolEnv("HOST_BG_ENABLED", true);
|
|
486
488
|
export const hostExecSpoolEnabled = () => boolEnv("HOST_EXEC_SPOOL_ENABLED", true);
|
|
487
|
-
export const REQUIRED_ENVS_BY_LANE = {
|
|
488
|
-
e2b: ["E2B_API_KEY"],
|
|
489
|
-
k8s: ["K8S_SANDBOX_IMAGE"],
|
|
490
|
-
ssh: ["SSH_HOST", "SSH_USER", "SSH_PRIVATE_KEY"],
|
|
491
|
-
adb: ["ADB_SERIAL"],
|
|
492
|
-
host: [],
|
|
493
|
-
"local-docker": ["DOCKER_IMAGE"],
|
|
494
|
-
device: [],
|
|
495
|
-
};
|
|
496
489
|
function parseReadDenyPatternsEnv(raw) {
|
|
497
490
|
let arr;
|
|
498
491
|
try {
|
|
@@ -862,7 +855,7 @@ function parseStoreDomain(ctx) {
|
|
|
862
855
|
!!(process.env.MYSQL_HOST || process.env.PG_HOST);
|
|
863
856
|
const bareBoot = !dbBackendSet && !sqlSignal;
|
|
864
857
|
const multiTenantBareBoot = bareBoot && requirePrincipal;
|
|
865
|
-
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql",
|
|
858
|
+
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", STORE_BACKENDS);
|
|
866
859
|
if (multiTenantBareBoot) {
|
|
867
860
|
CONFIG_NOTICES.push({
|
|
868
861
|
level: "warn",
|
|
@@ -873,7 +866,7 @@ function parseStoreDomain(ctx) {
|
|
|
873
866
|
});
|
|
874
867
|
}
|
|
875
868
|
const sqlEngineExplicit = !!process.env.DB_BACKEND && dbBackend !== "local" && dbBackend !== "memory";
|
|
876
|
-
const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory",
|
|
869
|
+
const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", SESSION_BACKENDS);
|
|
877
870
|
const sessionBackend = sessionBackendRaw === "mysql" ? "tidb" : sessionBackendRaw;
|
|
878
871
|
const localDataRoot = resolveLocalDataRoot(process.env, homedir());
|
|
879
872
|
const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
|
|
@@ -1233,14 +1226,16 @@ function uncompilableRuleGuidance(name, behavior, pathBases) {
|
|
|
1233
1226
|
const issue = issues[0];
|
|
1234
1227
|
if (issue === undefined)
|
|
1235
1228
|
return undefined;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1229
|
+
if (behavior === "allow" && (issue.code === "unsupported.bash_prefix" || issue.code === "unsupported.file_glob" || issue.code === "invalid.allow_param_rule")) {
|
|
1230
|
+
return (`${issue.code} — ${issue.message}. An allow rule list compiles tool-name rules only (an exact name, or mcp__<server>__* / ` +
|
|
1231
|
+
`a2a__<peer>__* for every tool on that peer); a content-form standing approval lives in the persisted-rule store ` +
|
|
1232
|
+
`(/v1/rules/cc-import, or keep the rule when you answer the call's card)`);
|
|
1233
|
+
}
|
|
1239
1234
|
return (`${issue.code} — core's rule grammar cannot compile this name (${issue.message}), so a table keyed by it matches nothing. ` +
|
|
1240
1235
|
`Tool names carry no backslashes and are far shorter than the 512-character rule cap; check the spelling, and use ` +
|
|
1241
1236
|
`mcp__<server>__* / a2a__<peer>__* for "every tool on that peer"`);
|
|
1242
1237
|
}
|
|
1243
|
-
|
|
1238
|
+
function findUnmatchableToolNames(names, opts) {
|
|
1244
1239
|
const out = [];
|
|
1245
1240
|
for (const name of names) {
|
|
1246
1241
|
if (opts.consumer === "tool-name-table") {
|
|
@@ -1250,8 +1245,6 @@ export function findUnmatchableToolNames(names, opts) {
|
|
|
1250
1245
|
continue;
|
|
1251
1246
|
}
|
|
1252
1247
|
}
|
|
1253
|
-
if (opts.behavior === "allow")
|
|
1254
|
-
continue;
|
|
1255
1248
|
const retired = RETIRED_TOOL_NAMES.get(name);
|
|
1256
1249
|
if (retired !== undefined) {
|
|
1257
1250
|
out.push({ name, guidance: retired });
|
|
@@ -1275,9 +1268,26 @@ export function findUnmatchableToolNames(names, opts) {
|
|
|
1275
1268
|
}
|
|
1276
1269
|
return out;
|
|
1277
1270
|
}
|
|
1278
|
-
export function
|
|
1279
|
-
|
|
1271
|
+
export function unmatchableToolNamesRefusal(source, names, judgment) {
|
|
1272
|
+
const bad = findUnmatchableToolNames(names, judgment);
|
|
1273
|
+
if (bad.length === 0)
|
|
1274
|
+
return undefined;
|
|
1275
|
+
const message = (`${source} has ${bad.length === 1 ? "an entry" : "entries"} that can never take effect — ${bad.length === 1 ? "it" : "each"} would never match a live tool or cannot be compiled into a rule ` +
|
|
1276
|
+
`(names are read exactly as written: no alias folding, no protocol prefix added): ` +
|
|
1280
1277
|
bad.map((b) => `${presentToolName(b.name)} → ${b.guidance}`).join("; ")).replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
1278
|
+
return { message, names: bad.map((b) => b.name) };
|
|
1279
|
+
}
|
|
1280
|
+
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
|
+
}
|
|
1290
|
+
return undefined;
|
|
1281
1291
|
}
|
|
1282
1292
|
function streamApprovalConfig(gates) {
|
|
1283
1293
|
const cfg = {
|
|
@@ -1344,9 +1354,9 @@ function parseApprovalDomain(ctx) {
|
|
|
1344
1354
|
const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS, announceKeySetRejection("APPROVAL_HMAC_KEYS"));
|
|
1345
1355
|
const durableApproval = postureOn("DURABLE_APPROVAL", infraReady);
|
|
1346
1356
|
const rejectUnmatchableToolNames = (envName, names) => {
|
|
1347
|
-
const
|
|
1348
|
-
if (
|
|
1349
|
-
throw new Error(
|
|
1357
|
+
const unmatchable = unmatchableToolNamesRefusal(envName, names, { consumer: "tool-name-table", behavior: "deny" });
|
|
1358
|
+
if (unmatchable !== undefined)
|
|
1359
|
+
throw new Error(unmatchable.message);
|
|
1350
1360
|
return names;
|
|
1351
1361
|
};
|
|
1352
1362
|
return {
|
|
@@ -1360,6 +1370,12 @@ function parseApprovalDomain(ctx) {
|
|
|
1360
1370
|
resourceSuspendMaxSlices: nonNegativeIntEnv("RESOURCE_SUSPEND_MAX_SLICES", "跨片 slice 数上限"),
|
|
1361
1371
|
approvalAutoBudget: clampEnvWithWarn("APPROVAL_AUTO_BUDGET", "0", 0, 10000),
|
|
1362
1372
|
approvalNeverAuto: rejectUnmatchableToolNames("APPROVAL_NEVER_AUTO", csv("APPROVAL_NEVER_AUTO")),
|
|
1373
|
+
approvalGrant: ((names) => {
|
|
1374
|
+
const refusal = approvalGrantRefusal(names);
|
|
1375
|
+
if (refusal !== undefined)
|
|
1376
|
+
throw new Error(refusal);
|
|
1377
|
+
return names;
|
|
1378
|
+
})(csv("APPROVAL_GRANT")),
|
|
1363
1379
|
approvalHmacKeys: dgHmacKeys,
|
|
1364
1380
|
directApprovalDoor,
|
|
1365
1381
|
sensitiveWritePatterns: ((raw) => {
|
|
@@ -1645,11 +1661,7 @@ function parseMemoryConsolidationDriver() {
|
|
|
1645
1661
|
function parseMemoryDomain(ctx) {
|
|
1646
1662
|
const { requirePrincipal } = ctx;
|
|
1647
1663
|
const memoryEngineEnabled = parseMemoryEngineWord(env("MEMORY_ENGINE", "on"));
|
|
1648
|
-
const
|
|
1649
|
-
if (!["file", "pg", "tidb"].includes(memoryEngineBackendRaw)) {
|
|
1650
|
-
throw new Error(`MEMORY_ENGINE_BACKEND must be file|pg|tidb, got "${memoryEngineBackendRaw}"`);
|
|
1651
|
-
}
|
|
1652
|
-
const memoryEngineBackend = memoryEngineBackendRaw;
|
|
1664
|
+
const memoryEngineBackend = enumEnv("MEMORY_ENGINE_BACKEND", "file", MEMORY_ENGINE_BACKENDS);
|
|
1653
1665
|
const memoryScope = process.env.MEMORY_SCOPE ?? (memoryEngineEnabled && !requirePrincipal ? "local" : undefined);
|
|
1654
1666
|
const memorySyncUrl = process.env.MEMORY_SYNC_URL || undefined;
|
|
1655
1667
|
const memorySyncToken = process.env.MEMORY_SYNC_TOKEN || undefined;
|
|
@@ -1865,12 +1877,12 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1865
1877
|
{
|
|
1866
1878
|
const lane = process.env.REMOTE_EXEC;
|
|
1867
1879
|
if (lane !== undefined && lane !== "") {
|
|
1868
|
-
const
|
|
1869
|
-
if (
|
|
1870
|
-
throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: ${
|
|
1880
|
+
const word = REMOTE_EXEC_PROVIDER_WORDS.find((w) => w === lane);
|
|
1881
|
+
if (word === undefined) {
|
|
1882
|
+
throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")}. ` +
|
|
1871
1883
|
`An unrecognized value used to fall through to the host/in-process lane silently; refusing to start instead (#157).`);
|
|
1872
1884
|
}
|
|
1873
|
-
const missing =
|
|
1885
|
+
const missing = laneRequiredEnv(word).filter((name) => !process.env[name]);
|
|
1874
1886
|
if (missing.length > 0) {
|
|
1875
1887
|
throw new Error(`REMOTE_EXEC=${lane} but required env ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} missing/empty — ` +
|
|
1876
1888
|
`refusing to start rather than silently downgrading to the host/in-process lane (#157; unset REMOTE_EXEC if you meant no sandbox lane).`);
|
|
@@ -2307,7 +2319,7 @@ const MODEL_PLANE_GROUP_KEYS = [
|
|
|
2307
2319
|
"modelApiKeyEnv", "modelApiKeys", "modelRefusals", "modelQuotaWeights", "tiers", "projects", "roles", "atModelAllowlist", "cascadeLadder", "degrade",
|
|
2308
2320
|
];
|
|
2309
2321
|
const APPROVAL_GROUP_KEYS = [
|
|
2310
|
-
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
2322
|
+
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto", "approvalGrant",
|
|
2311
2323
|
"approvalHmacKeys", "durableApproval", "durableApprovalSource", "streamAskWindowMsSource", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
2312
2324
|
"resourceSuspendTtlSec", "resourceSuspendTotalTokens", "resourceSuspendTotalBudgetUsd", "resourceSuspendMaxSlices", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "permissionRulesEnabled", "permissionRulesEnabledExplicit", "streamAskWindowMarginMs", "streamApproval", "unattendedApprovalPolicy", "mcpElicitation",
|
|
2313
2325
|
"sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "autoModeClassifierFallback", "autoModeClassifierTimeoutMs", "crossSessionInbound", "crossSessionDialogExpiry",
|
|
@@ -2407,54 +2419,39 @@ export function loadConfig() {
|
|
|
2407
2419
|
const integrations = parseIntegrationsDomain({ requirePrincipal, configProvider });
|
|
2408
2420
|
const { directApprovalDoor, durableApproval: dgDurable, approvalHmacKeys: dgHmacKeys } = approval;
|
|
2409
2421
|
const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, authTokens } = auth;
|
|
2410
|
-
const { enabled: imageBakesEnabled, runnerPrincipal: bakeRunnerPrincipal } = orchestration.imageBakes;
|
|
2411
2422
|
const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2423
|
+
const doorRefusal = halfOpenDirectDoorRefusal({
|
|
2424
|
+
directApprovalDoor,
|
|
2425
|
+
durableApproval: dgDurable,
|
|
2426
|
+
approvalHmacKeys: dgHmacKeys,
|
|
2427
|
+
principalJwtPubkeys,
|
|
2428
|
+
principalJwtIss,
|
|
2429
|
+
principalJwtAud,
|
|
2430
|
+
requirePrincipal,
|
|
2431
|
+
operatorPrincipals: dgOperators,
|
|
2432
|
+
});
|
|
2433
|
+
if (doorRefusal !== undefined) {
|
|
2434
|
+
throw new Error(doorRefusal);
|
|
2435
|
+
}
|
|
2436
|
+
const operatorsRefusal = operatorPrincipalsRequiredRefusal({ durableApproval: dgDurable, requirePrincipal, operatorPrincipals: dgOperators });
|
|
2437
|
+
if (operatorsRefusal !== undefined) {
|
|
2438
|
+
throw new Error(operatorsRefusal);
|
|
2439
|
+
}
|
|
2440
|
+
const bakeOperatorsRefusal = bakeOperatorPrincipalsRefusal({ imageBakes: orchestration.imageBakes, operatorPrincipals: dgOperators });
|
|
2441
|
+
if (bakeOperatorsRefusal !== undefined) {
|
|
2442
|
+
throw new Error(bakeOperatorsRefusal);
|
|
2443
|
+
}
|
|
2444
|
+
const bakeRunnerRefusal = bakeRunnerCredentialRefusal({ imageBakes: orchestration.imageBakes, authTokens });
|
|
2445
|
+
if (bakeRunnerRefusal !== undefined) {
|
|
2446
|
+
throw new Error(bakeRunnerRefusal);
|
|
2447
|
+
}
|
|
2448
|
+
const leaderStoreRefusal = leaderDurableStoreRefusal({ leaderEnabled: orchestration.leaderEnabled, dbBackend: store.dbBackend });
|
|
2449
|
+
if (leaderStoreRefusal !== undefined) {
|
|
2450
|
+
throw new Error(leaderStoreRefusal);
|
|
2431
2451
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
if (imageBakesEnabled && dgOperators.length === 0) {
|
|
2436
|
-
throw new Error("IMAGE_BAKES_ENABLED=true but OPERATOR_PRINCIPALS is empty — the bake door runs build.sh on the privileged build host; an empty operator set makes isOperator() true-for-all (world-writable RCE). Set OPERATOR_PRINCIPALS or disable bakes (IMAGE-API-DESIGN.md §P2.4a boot invariant)");
|
|
2437
|
-
}
|
|
2438
|
-
if (imageBakesEnabled && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
|
|
2439
|
-
throw new Error(`IMAGE_BAKES_ENABLED=true but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${bakeRunnerPrincipal}' — the bake-runner authenticates by its credential (token-derived source), so a '<BAKE_RUNNER_TOKEN>=${bakeRunnerPrincipal}' SERVICE_AUTH_TOKENS entry is required or claim/ingest/heartbeat all 403 (IMAGE-API-DESIGN.md §P2.12 boot invariant)`);
|
|
2440
|
-
}
|
|
2441
|
-
if (orchestration.leaderEnabled && store.dbBackend !== "mysql" && store.dbBackend !== "pg") {
|
|
2442
|
-
throw new Error(`LEADER_ENABLED=true but DB_BACKEND=${store.dbBackend} (no external SQL store) is refused: a leader run is an ` +
|
|
2443
|
-
"N-worker fan-out lasting up to 24h that ends in a REAL git push, and without a durable store its registry is " +
|
|
2444
|
-
"a per-process Map — a cross-replica GET 404s, a restart forgets the run, and a retried POST starts a SECOND " +
|
|
2445
|
-
"push. Point the service at a durable store (DB_BACKEND=mysql|pg), or turn the leader off (unset LEADER_ENABLED) " +
|
|
2446
|
-
"(#193 车4 件2 / staleness ledger P1-9+P1-10 boot invariant)");
|
|
2447
|
-
}
|
|
2448
|
-
if (orchestration.requireExecutionLane && orchestration.remoteExec === undefined) {
|
|
2449
|
-
throw new Error(`REQUIRE_EXECUTION_LANE=true but NO execution lane is named on this deployment — refusing to start. ` +
|
|
2450
|
-
`With no lane the engine falls back to its in-process stub environment, where at least the Bash tool is ` +
|
|
2451
|
-
`not mounted, yet every task still returns status=completed — indistinguishable from "the model chose ` +
|
|
2452
|
-
`not to use its tools". (What the roster actually keeps is a per-run fact: read the wiring_manifest ` +
|
|
2453
|
-
`event and count entries whose mountedBy is "hands".) ` +
|
|
2454
|
-
`Cause: REMOTE_EXEC is unset/empty AND CONFIG_PROVIDER is not "local". ` +
|
|
2455
|
-
`Fix: set REMOTE_EXEC to one of ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")} (and that lane's required env), ` +
|
|
2456
|
-
`or set CONFIG_PROVIDER=local to take the single-machine \`host\` default, ` +
|
|
2457
|
-
`or unset REQUIRE_EXECUTION_LANE to keep running without hands on purpose.`);
|
|
2452
|
+
const laneRequiredRefusal = executionLaneRequiredRefusal(orchestration);
|
|
2453
|
+
if (laneRequiredRefusal !== undefined) {
|
|
2454
|
+
throw new Error(laneRequiredRefusal);
|
|
2458
2455
|
}
|
|
2459
2456
|
{
|
|
2460
2457
|
const envLaneDispatchable = !isRosterGateArmed(integrations.configCenter);
|
|
@@ -11,6 +11,9 @@ 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"];
|
|
14
17
|
/**
|
|
15
18
|
* 本部署的**本地店根**(`LOCAL_DATA_ROOT → CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent`)—— 在本口只喂
|
|
16
19
|
* **誊本门**(`<localDataRoot>/sessions`)。S-133 起它**不再**当守卫集的 `dataRoot`(那是引擎记忆根,见
|
|
@@ -209,7 +212,7 @@ export declare class RelativeTargetLexicalEnv extends StubExecutionEnv {
|
|
|
209
212
|
* core 的原因。env 只是编译期的占位(compilePatterns 不碰它),真裁决用的是每请求按 lane 铸的那一个。
|
|
210
213
|
*/
|
|
211
214
|
export declare function assertGuardPatternsUsable(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">): void;
|
|
212
|
-
/** 一条 boot
|
|
215
|
+
/** 一条 boot 期运维告警 / 通告的**载荷**(纯数据 ⇒ `build*`;打给谁、用哪只 logger、哪一级归消费腿)。 */
|
|
213
216
|
export interface OperatorWarning {
|
|
214
217
|
readonly event: string;
|
|
215
218
|
readonly fields: Record<string, unknown>;
|
|
@@ -230,15 +233,17 @@ export interface GateSeatView {
|
|
|
230
233
|
* 此前是「这个部署一个门都没接」这个 misconfig 的**唯一**提示,而守卫集只挡那二十来个路径段、其余写
|
|
231
234
|
* 目标照旧无裁决 —— 信号不能因为我们铺了基线就静默消失,所以由我们自己按同一判据说一次。
|
|
232
235
|
*
|
|
233
|
-
* 判据(与信号消失的条件逐字互补)
|
|
234
|
-
*
|
|
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 期说一次,不是每任务一次。
|
|
235
240
|
* S-694:席读的是喂审批基线的**同一只**({@link ApprovalSeat})—— `durable` / `live` 两形都有人服 ask、名单
|
|
236
241
|
* 进策略,「守卫集是唯一的策略」这句话在它们上面不成立。今天真发的只剩 HTTP 腿「durable 关 ∧ 多租户」那一形。
|
|
237
242
|
*
|
|
238
243
|
* ⚠️ 单一属主([ref] 件三):HTTP 腿与 run-local 腿共用本判据与文案。两处各写一份 = 一处改了另一处
|
|
239
244
|
* 没改,而两份都长得像对的 —— 那正是本文件存在的理由。
|
|
240
245
|
*/
|
|
241
|
-
export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">, seat: GateSeatView): OperatorWarning | undefined;
|
|
246
|
+
export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns" | "approvalGrant">, seat: GateSeatView): OperatorWarning | undefined;
|
|
242
247
|
/**
|
|
243
248
|
* `applyRuntimeGovernance` 的 governance 实参预铸([ref] 件一)。
|
|
244
249
|
*
|
|
@@ -253,6 +258,12 @@ export declare function buildOnlySensitiveBaselineWarning(config: Pick<Deploymen
|
|
|
253
258
|
* 的读法因此不变。
|
|
254
259
|
*/
|
|
255
260
|
export declare function createDeploymentGovernanceInputs(config: DeploymentGovernanceConfigView, pathAdjudication: PathAdjudication, roots: DeploymentGovernanceRoots): DeploymentGovernanceInputs;
|
|
261
|
+
/**
|
|
262
|
+
* 7.115.0 —— 运维 allow 规则的**起服通告**载荷(纯数据 ⇒ `build*`;HTTP 腿与 run-local 腿各在装配时说一次,用 info 级)。
|
|
263
|
+
* 规则名不是秘密(它们是工具名),所以原样列出;空表 ⇒ `undefined`(不打)。运维只读面上同一个事实另有一处:
|
|
264
|
+
* `GET /v1/diagnostics/wiring` 的 `serverGates.approvalGrant`。
|
|
265
|
+
*/
|
|
266
|
+
export declare function buildApprovalGrantNotice(config: Pick<DeploymentGovernanceConfigView, "approvalGrant">): OperatorWarning | undefined;
|
|
256
267
|
/**
|
|
257
268
|
* 审批基线 —— `applyRuntimeGovernance` 那个 base 的 `toolPolicy` 座([ref] 件一)。**恒非
|
|
258
269
|
* `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 || seat.singleUserAutoAcceptBaseline)
|
|
64
|
+
if ((config.sensitiveWritePatterns.length === 0 && config.approvalGrant.length === 0) || seat.singleUserAutoAcceptBaseline)
|
|
65
65
|
return undefined;
|
|
66
66
|
switch (seat.approvalSeat) {
|
|
67
67
|
case "durable":
|
|
@@ -74,12 +74,17 @@ 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
|
+
];
|
|
77
81
|
return {
|
|
78
82
|
event: "tool_policy_only_sensitive_baseline",
|
|
79
83
|
fields: {
|
|
80
|
-
detail: "
|
|
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`,
|
|
81
85
|
remedy: "wire an approval face (DURABLE_APPROVAL / APPROVAL_REQUIRE with a store) or accept the single-user auto-accept baseline",
|
|
82
86
|
guardedPatterns: config.sensitiveWritePatterns.length,
|
|
87
|
+
grantRules: config.approvalGrant.length,
|
|
83
88
|
},
|
|
84
89
|
};
|
|
85
90
|
}
|
|
@@ -108,6 +113,19 @@ export function createDeploymentGovernanceInputs(config, pathAdjudication, roots
|
|
|
108
113
|
commandPolicy: config.commandPolicy,
|
|
109
114
|
...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
|
|
110
115
|
...(sensitivePathLayers ? { sensitivePathLayers } : {}),
|
|
116
|
+
...(config.approvalGrant.length > 0 ? { approvalGrant: config.approvalGrant } : {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function buildApprovalGrantNotice(config) {
|
|
120
|
+
if (config.approvalGrant.length === 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
return {
|
|
123
|
+
event: "approval_grant_in_force",
|
|
124
|
+
fields: {
|
|
125
|
+
rules: config.approvalGrant.length,
|
|
126
|
+
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",
|
|
128
|
+
},
|
|
111
129
|
};
|
|
112
130
|
}
|
|
113
131
|
export function createApprovalBaselinePolicy(config, seat) {
|
package/dist/env-facts.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ImageCapabilities } from "./plugins/store-contracts.js";
|
|
2
|
+
import type { RemoteExecProvider } from "./execution-lane-caps.js";
|
|
2
3
|
export interface SandboxEnvFacts {
|
|
3
4
|
profile?: string;
|
|
4
5
|
capabilities?: string[];
|
|
@@ -53,7 +54,8 @@ export declare function buildEnvFacts(opts: {
|
|
|
53
54
|
* codex 复审 medium:k8s 必须按**有效快照能力**分叉,不能只看车道名——s3Snapshot 未配=park-only,
|
|
54
55
|
* resume 是全新 pod 且**工作区不恢复**,「文件已从快照恢复」那句会让模型信赖不存在的文件(比缺席
|
|
55
56
|
* 更糟的谎)。 */
|
|
56
|
-
export declare function resumeFactsForLane(provider:
|
|
57
|
+
export declare function resumeFactsForLane(provider: RemoteExecProvider | undefined, // 车道词 = settings-schema 词表正本(经本仓读口取型,不手抄;表加词 ⇒ 下方穷举 switch 编译红)
|
|
58
|
+
caps?: {
|
|
57
59
|
k8sSnapshot?: boolean;
|
|
58
60
|
}): SandboxEnvFacts["resumeFacts"] | undefined;
|
|
59
61
|
/** Path-safe session segment: the session id can be CALLER-SUPPLIED (body.sessionId), so it must not be able to
|
package/dist/env-facts.js
CHANGED
|
@@ -42,8 +42,12 @@ export function resumeFactsForLane(provider, caps) {
|
|
|
42
42
|
case "host":
|
|
43
43
|
case undefined:
|
|
44
44
|
return { scratch: "preserved" };
|
|
45
|
-
|
|
45
|
+
case "local-docker":
|
|
46
46
|
return undefined;
|
|
47
|
+
default: {
|
|
48
|
+
const unreachable = provider;
|
|
49
|
+
return unreachable;
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
export function scratchpadSessionSegment(sessionId) {
|