@sema-agent/server 7.114.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 +73 -1
- package/USAGE.md +35 -2
- 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/resolve-spec.js +4 -1
- 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 +2 -1
- package/dist/config-center/apply-effective.js +11 -11
- package/dist/config-provider.d.ts +16 -0
- package/dist/config-provider.js +11 -2
- package/dist/config-types.d.ts +20 -1
- package/dist/config.d.ts +41 -18
- package/dist/config.js +36 -13
- package/dist/deployment-governance.d.ts +15 -4
- package/dist/deployment-governance.js +20 -2
- package/dist/fleet/fleet-bus.d.ts +1 -22
- package/dist/hooks/hook-runner.d.ts +29 -3
- package/dist/hooks/hook-runner.js +30 -17
- package/dist/http/admission.js +7 -2
- 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 +1 -0
- 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/fail-open.d.ts +5 -8
- package/dist/permission-rule-list.d.ts +81 -0
- package/dist/permission-rule-list.js +17 -0
- 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/task-settings.d.ts +6 -5
- package/dist/task-settings.js +16 -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
|
@@ -90,13 +90,21 @@ export interface ModelRefusalVerdict {
|
|
|
90
90
|
* 名 → 码,**值只码**(拒因事实里的上游 id / provider 词 / 坏字段键 / 出路句都不上 wire —— `entry_invalid` 行是没人验过的行,id / provider
|
|
91
91
|
* 可能就是坏行原样;句子是给本机运维的散文,不是契约)。**名筛在这里、只在这里**:键过 settings-schema `isReportableRefusalName`(唯一判据,
|
|
92
92
|
* import 不复制;6.3.0 起 = 标识符白名单 ∧ 无密钥形 ∧ 无 URL userinfo)—— 秘密形 / 空串 / `__proto__` / 不是标识符的串这类出不了门的名不进键、
|
|
93
|
-
*
|
|
93
|
+
* 不计数;引擎内部的判决不受影响;本机日志 / 拒因句里的写名同样先问这一只(S-805,{@link disclosableRefusalName}:不可披露的名只写码)。
|
|
94
94
|
* 逐条空且无组拒 ⇒ `undefined`(键缺席:「没有」只有一种拼法)。
|
|
95
95
|
*/
|
|
96
96
|
export declare function buildModelRefusalsSignal(v: ModelRefusalVerdict | undefined): ModelRefusalsSignal | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* 一个被拒条目名的**日志字段**:可披露 ⇒ `{ [key]: name }`;不可披露 ⇒ `{ [`${key}NameWithheld`]: true }`(缺 `key` 不是无声的:读日志的人知道是被扣下、
|
|
99
|
+
* 不是漏写)。**所有**把被拒条目名写进结构化日志字段的站点调这一只(`refusalWarnFields` / `apply-effective.ts` 的角色与档位绑定 warn、缺省悬空 warn),
|
|
100
|
+
* 不各写一份 `isReportableRefusalName` 判断。
|
|
101
|
+
*/
|
|
102
|
+
export declare function refusedNameLogField(key: string, name: string): Record<string, unknown>;
|
|
103
|
+
/** 一串被拒条目名的日志字段(`sema_registry_models` 行的 `unavailable`):只列可披露的名,被扣下的名**只数个数**(`<key>NamesWithheld`,0 个时键缺席)。 */
|
|
104
|
+
export declare function refusedNamesLogFields(key: string, names: readonly string[]): Record<string, unknown>;
|
|
97
105
|
/** 成因 → 它那一条 warn 的事件名(**穷举**)。两个名字是票面点名的既有/新事件,运维的告警规则按名挂。 */
|
|
98
106
|
export declare function refusalEventOf(reason: ModelEntryRefusalReason): "sema_registry_model_no_brain" | "sema_registry_model_entry_invalid";
|
|
99
|
-
/** warn 字段(不含值):`field` = 第一处坏字段(告警规则最常按它分组),`fields` = 全部。 */
|
|
107
|
+
/** warn 字段(不含值):`field` = 第一处坏字段(告警规则最常按它分组),`fields` = 全部。S-805:`model` 只在名可披露时写。 */
|
|
100
108
|
export declare function refusalWarnFields(name: string, r: ModelEntryRefusal): Record<string, unknown>;
|
|
101
109
|
/**
|
|
102
110
|
* 一次**显式选择**命中了不可用条目时的拒因句(HTTP `request.model_unavailable` 的 `error`、run-local 的 stderr、
|
|
@@ -94,6 +94,22 @@ export function buildModelRefusalsSignal(v) {
|
|
|
94
94
|
return undefined;
|
|
95
95
|
return v.plane === undefined ? { entries } : { entries, plane: v.plane };
|
|
96
96
|
}
|
|
97
|
+
function disclosableRefusalName(name) {
|
|
98
|
+
return isReportableRefusalName(name) ? name : undefined;
|
|
99
|
+
}
|
|
100
|
+
export function refusedNameLogField(key, name) {
|
|
101
|
+
const shown = disclosableRefusalName(name);
|
|
102
|
+
return shown !== undefined ? { [key]: shown } : { [`${key}NameWithheld`]: true };
|
|
103
|
+
}
|
|
104
|
+
export function refusedNamesLogFields(key, names) {
|
|
105
|
+
const shown = names.filter((n) => disclosableRefusalName(n) !== undefined);
|
|
106
|
+
const withheld = names.length - shown.length;
|
|
107
|
+
return { [key]: shown, ...(withheld > 0 ? { [`${key}NamesWithheld`]: withheld } : {}) };
|
|
108
|
+
}
|
|
109
|
+
function nameSlot(name) {
|
|
110
|
+
const shown = disclosableRefusalName(name);
|
|
111
|
+
return shown === undefined ? "(name withheld)" : `"${shown}"`;
|
|
112
|
+
}
|
|
97
113
|
export function refusalEventOf(reason) {
|
|
98
114
|
switch (reason) {
|
|
99
115
|
case "no_brain":
|
|
@@ -104,7 +120,7 @@ export function refusalEventOf(reason) {
|
|
|
104
120
|
}
|
|
105
121
|
export function refusalWarnFields(name, r) {
|
|
106
122
|
return {
|
|
107
|
-
model
|
|
123
|
+
...refusedNameLogField("model", name),
|
|
108
124
|
provider: r.provider,
|
|
109
125
|
code: r.code,
|
|
110
126
|
...(r.fields.length > 0 ? { field: r.fields[0], fields: [...r.fields] } : {}),
|
|
@@ -118,7 +134,7 @@ export function modelUnavailableMessage(field, requested, name, r) {
|
|
|
118
134
|
export function defaultModelRefusal(view, leg) {
|
|
119
135
|
if (Object.hasOwn(view.models, DEFAULT_MODEL_REF) && view.models[DEFAULT_MODEL_REF] !== undefined)
|
|
120
136
|
return undefined;
|
|
121
|
-
const refused = Object.entries(view.modelRefusals).map(([n, r]) =>
|
|
137
|
+
const refused = Object.entries(view.modelRefusals).map(([n, r]) => `${nameSlot(n)} [${r.code}]`);
|
|
122
138
|
const unavailable = refused.length > 0 ? ` (every enabled catalog entry is unavailable on this host: ${refused.join(", ")})` : "";
|
|
123
139
|
switch (leg) {
|
|
124
140
|
case "env":
|
|
@@ -163,7 +179,7 @@ export function refusedNameMintedRefusal(view) {
|
|
|
163
179
|
const minted = mintedRefusedNames(view);
|
|
164
180
|
if (minted.length === 0)
|
|
165
181
|
return undefined;
|
|
166
|
-
const pairs = minted.map((m) =>
|
|
182
|
+
const pairs = minted.map((m) => `${nameSlot(m.name)} [${m.code}] → ${nameSlot(m.target)} (${m.via})`);
|
|
167
183
|
return (`unavailable catalog entries would be re-minted onto other entries by the tier/alias expansion [${REFUSED_NAME_MINTED_CODE}]: ${pairs.join(", ")} — ` +
|
|
168
184
|
"an explicit pick of that name (or a role bound to it) would silently run on the other entry's endpoint and account; " +
|
|
169
185
|
"rename the unavailable entry, fix it (see its own warn), or rebind / drop the tier binding that mints the name.");
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { Logger } from "./logger.js";
|
|
2
2
|
import type { Metrics } from "./metrics.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
readonly note: string;
|
|
9
|
-
}
|
|
10
|
-
/** 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。 */
|
|
3
|
+
/**
|
|
4
|
+
* 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。
|
|
5
|
+
* 每项 `{ cls, note }`:`cls` 是判据表的分类(见模块头——只有这两类允许走本封装):`F`=纯体验/缓存回退,放行最坏后果只是"难看/不够好";
|
|
6
|
+
* `P-DEBT`=保护型欠账,方向明知不对、显式记债。`note` 说清这条兜底放行了什么、最坏后果是什么(写给未来读遥测的人,不写来历)。
|
|
7
|
+
*/
|
|
11
8
|
export declare const FAIL_OPEN_TAGS: {
|
|
12
9
|
readonly "server.approval-ask-journal.compaction-failed": {
|
|
13
10
|
readonly cls: "F";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **规则表的唯一编译口** —— 一张工具名规则表(`allow` / `ask` / `deny` 之一)编成一只 `ToolPolicy`。
|
|
3
|
+
*
|
|
4
|
+
* 两类读者,同一只编译口(编译与校验各只有一份,不在别处另写第二份文法):
|
|
5
|
+
* · 请求腿的 `settings.permissions.{allow,ask,deny}` 三桶(`task-settings.ts` 的 `deriveSettingsPolicy`);
|
|
6
|
+
* · 运维的 allow 规则 `APPROVAL_GRANT`(`runtime-governance.ts` 的 `applyRuntimeGovernance`,经部署治理链的单一构造口
|
|
7
|
+
* `deployment-governance.ts` 交进来 —— HTTP 提交 / durable 续跑 / 停驻后台 agent 的跨副本赎回 / run-local 四条腿同装)。
|
|
8
|
+
*
|
|
9
|
+
* 为什么住在一只叶模块里(不留在 `task-settings.ts`):运维那一侧的启动校验在 `config.ts` 里跑(坏值 = 拒启),而
|
|
10
|
+
* `task-settings.ts` 值引 `config.ts` —— 编译口留在那里,`config.ts` 要调它就闭合一只 ESM 装载环。本模块只 import core 与
|
|
11
|
+
* `text-bidi.ts`(纯叶),三处读者引它都不成环。
|
|
12
|
+
*/
|
|
13
|
+
import { type PathRuleBases, type RuleBehavior, type ToolPolicy } from "@sema-agent/core";
|
|
14
|
+
/** 一张表里规则条数的上限 —— 请求腿 wire 门(`task-settings.ts` 的三桶判形)读的也是这一个名字:core 的缺省帽是 256,
|
|
15
|
+
* 不对齐的话第 257 条起编译当场抛,等于服务端自己收下的东西自己反悔。 */
|
|
16
|
+
export declare const MAX_PERMISSION_RULES_PER_LIST = 1000;
|
|
17
|
+
/** A name-keyed rule-list ToolPolicy: a tool named by `names` gets `behavior` (an `allow` hit states the allow with
|
|
18
|
+
* `decisionReason: "rule"`; `ask` tightens to an approval gate; `deny` hard-blocks); every other tool is out of
|
|
19
|
+
* scope (`defaultAction: "allow"` = this policy has no opinion about anything it does not name). Composed deny-wins
|
|
20
|
+
* with the rest of the fold, so `ask` / `deny` only ever tighten and an `allow` never overrides a deny or an ask.
|
|
21
|
+
*
|
|
22
|
+
* `source` = 这张表**写在哪**(请求腿 `settings.permissions.<桶>`;运维腿 `APPROVAL_GRANT`)。它盖进每条规则(core 的 deny / ask
|
|
23
|
+
* 句里点名它:`flagged by permission rule "mcp__figma__*" (settings.permissions.ask)`),编不出来时的错误句也点名它;只是出处,
|
|
24
|
+
* 不带任何优先级(层序在各自的折叠里)。
|
|
25
|
+
*
|
|
26
|
+
* 🔴 [ref] / core [ref] —— 本函数曾是 `new Set(askNames).has(req.toolName)` 的**逐字**比对,那是
|
|
27
|
+
* **安全轴上的静默 fail-open**:CC 自家 settings validator 推荐的 covering 拼法
|
|
28
|
+
* (`"MCP rules do not support patterns in parentheses … use mcp__srv__*"`)在逐字比对下永远配不上真工具名
|
|
29
|
+
* `mcp__figma__get_file` ⇒ 一次不问、直跑、审计静音。两道兜底也各自放行:`unmatchableToolNamesRefusal`
|
|
30
|
+
* 对 `mcp__<peer>__*` 判「合法 MCP 全名」(server 不持挂载表,动态宇宙不能按名单否定),`nameSets` 审计面
|
|
31
|
+
* 报的是「没匹配上的名字」而 covering 名在 core 眼里**是**匹配得上的。同一份 settings 里
|
|
32
|
+
* `deny:["mcp__figma__*"]` 却**真拦** —— deny 走 `createAllowDenyPolicy`,它用的是 core 的
|
|
33
|
+
* `namespacedCoveringEntries/Hit`。同族分家,差的只是解析器。
|
|
34
|
+
*
|
|
35
|
+
* ⇒ **规则表的 policy 面现在同源**:三种态度都由 `createPermissionRulePolicy` 的 covering 车道解,底下是
|
|
36
|
+
* core 的**同一只** `namespacedRuleNameCovers`(协议无关:`mcp__`/`a2a__` 同解;peer 段精确比;工具段 `*`/glob 覆盖)。
|
|
37
|
+
* 本仓**不自写第二套 glob** —— 那正是当年分家的成因。
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ 「同源」的**射程**说准了免得下一个人当成全仓结论(codex 交叉轮 finding-1 追出来的边):
|
|
40
|
+
* · 同源的是**本编译口**认的 covering。表**进门前**还有一道更外层的判据(`config.ts` 的 `unmatchableToolNamesRefusal`,
|
|
41
|
+
* 请求腿经 `task-settings.ts` 的 `rejectUnmatchableSettingsNames`、运维腿经 `config.ts` 的 `approvalGrantRefusal`),它把
|
|
42
|
+
* `<ns>__<peer>` **两段形**(`mcp__figma`)判成「缺段名」拒掉 —— 而 core 三只 policy 都认这个形(实测:
|
|
43
|
+
* `createAllowDenyPolicy`/`createPermissionRulePolicy`/`createApprovalPolicy` 对 `mcp__figma` 一律解成 covering)。
|
|
44
|
+
* 所以各表收 `mcp__figma__*`(等价、且被推荐)、拒 `mcp__figma`。
|
|
45
|
+
* · **同源的射程已扩到 operator 名单腿**([ref] 孪生修,[ref]):`createDurableAskPolicy`(`src/approval.ts`)的
|
|
46
|
+
* deny/neverAuto/require 三集合过去是逐字 `Set.has`,自 core 5.60.1 谓词根导出后改成「精确 ∪ covering」,用的是
|
|
47
|
+
* **同一只** `namespacedRuleNameCovers`。⇒ env `APPROVAL_*` 腿 / 中心下发 `runtime.approvalRequire` 腿 / 规则表三家
|
|
48
|
+
* 现在对 covering 拼法的解读逐字一致,`mcp__figma__*` 在哪条腿上都是真判。
|
|
49
|
+
* · **两段形为什么仍不放开**(理由已换,别再引「approval.ts 还不认」):它一名两义——「整台服务器」与「三段名写漏一段」
|
|
50
|
+
* 在 boot 期不可分辨,所以各条腿一律响亮拒 + 指引给无歧义的 `mcp__figma__*`。放宽是各腿同时变宽的 behavior-facing
|
|
51
|
+
* 改动,走独立批的三问。详见 `unmatchableToolNamesRefusal` 顶注。
|
|
52
|
+
*
|
|
53
|
+
* 三处刻意的取舍,都是亲读 core dist 后定的:
|
|
54
|
+
* · `onInvalidRule:"throw"`(缺省)—— 一条编不出来的规则整表拒(请求腿 → resolve-spec 翻 422;运维腿 → 拒启),抛之前补上
|
|
55
|
+
* 出处名(core 的原文只说 "createPermissionRulePolicy: …",不说是哪张表出的错)。[ref] 的括号形在**更外层**已被拒,够不着
|
|
56
|
+
* 这里;真正新增的是「名字里带反斜杠」「单条 > 512 字符」这两族永不匹配的拼法。
|
|
57
|
+
* ✅ **不对称已收口([ref]①,[ref] 销)**:codex finding-2 当年如实登记的「同样两族拼法在 `deny`
|
|
58
|
+
* 上仍是静默 no-op」已修 —— 判据搬进单点成文的 `unmatchableToolNamesRefusal`,它拿 core 自己的 `validatePermissionRules`
|
|
59
|
+
* 对同一串跑一遍文法(不自铸第二套判词),于是三张表对这两族说同一句话。S-654 (c) 起 `allow` 也收。
|
|
60
|
+
* ⚠️ 存量 resume:带这两族拼法的**已存 body** 在 resume 腿会 422(resolve-spec 翻)。这**不是**新造的姿态——A3
|
|
61
|
+
* (`rejectUnmatchableSettingsNames`)早就对存量 body 里的退役名/短名同样抛,且 `applyTaskSettings` 腿有专门的回归钉。
|
|
62
|
+
* 判据同一条:一个**静默不存在的审批门**比一次响亮失败更贵。codex 建议的「给非法存量条目保留旧的逐字比对」被**驳回**:
|
|
63
|
+
* 那正是把 fail-open 请回来。
|
|
64
|
+
* · `caps.maxRules` 对齐 {@link MAX_PERMISSION_RULES_PER_LIST}(理由见那只常量)。core 的 set-level cap 连 `skip` 都照抛,
|
|
65
|
+
* 所以运维腿的启动校验还要**试编一次**(`approvalGrantRefusal`)才拦得住超帽 —— 判据仍是本函数这一只。
|
|
66
|
+
* · 不再自铸 `decisionReason:"rule"` —— core 的规则策略本就不铸这个键(allow 命中由 core 自己铸 `"rule"`),且 core 侧唯一区分它的
|
|
67
|
+
* 两处(prepare-task 的继承 grant 复用判据、park 的 rule-suggestion 门)对 `"rule"` 与 `undefined` **同待**(只把 `"hook"`
|
|
68
|
+
* 排除在外),dist 亲读在案。审计归因改由 `matchedAskRule` + `source` 承担,后者比前者强:它还让 core 的 auto-mode 分类器
|
|
69
|
+
* **拒绝**替人清掉这条 ask(「运维写下的『每次问我』不是分类器的犹豫」),方向是收紧。
|
|
70
|
+
*
|
|
71
|
+
* 🔴 **S-654 (c)(7.115.0,core 7.40.0 #1065):三种态度同一个编译口,`allow` 也走这里** —— `behavior: "allow"` 的规则命中 = core 带
|
|
72
|
+
* `decisionReason: "rule"` 的放行 —— allow 规则给出「有人表态」—— 它回答的是没人表态的服务器工具 / A2A 技能调用;内置工具上引擎自己的写门、shell 检查照问,deny / ask 照赢(与参照实现的 `permissions.allow` 在内置工具上**不同**:那边 `allow: ["Edit"]` 跳过编辑确认,这里不放掉引擎的写门);未命中 = `defaultAction: "allow"` 的裸 allow
|
|
73
|
+
* = 不表态、照常判。修前请求腿的 allow 编成 `createAllowDenyPolicy({ allow })` 的**名单**(名单在场 ⇒ 名单外的工具一律拒),与写它的人要的
|
|
74
|
+
* 「替这些工具表态」方向相反;终端因此从不投这只桶。core 7.40.0 起没人表态的服务器工具调用要问,allow 规则就是那张卡的常设答案
|
|
75
|
+
* (`mcp__<server>__*` 一条覆盖整台服务器)。allow 规则**不越过** deny / ask:折叠 deny > ask > allow,任何一层的 deny / ask 照赢;
|
|
76
|
+
* 它只在没人反对时给出那句表态。core 的 allow 文法只收工具名形(精确名 / `mcp__`·`a2a__` covering 形),内容形放行(`Bash(npm:*)` /
|
|
77
|
+
* `Edit(src/**)` / 参数级)core 拒编,两条腿与 deny / ask 同一个出口(判据 `unmatchableToolNamesRefusal`)。
|
|
78
|
+
* 7.115.0 起运维的 `APPROVAL_GRANT` 也编在这里(无人值守腿没有请求体,那是运维为自己接的服务器表态的唯一位置)。 */
|
|
79
|
+
/** `behavior` = 这张表的态度 —— 型直接是 core 的 `RuleBehavior`(不手抄:core 加一种态度,这里与它同步、读者 tsc 上见到)。 */
|
|
80
|
+
export declare function createRuleListPolicy(names: readonly string[], behavior: RuleBehavior, source: string, pathBases: PathRuleBases | undefined): ToolPolicy;
|
|
81
|
+
//# sourceMappingURL=permission-rule-list.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createPermissionRulePolicy } from "@sema-agent/core";
|
|
2
|
+
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
3
|
+
export const MAX_PERMISSION_RULES_PER_LIST = 1_000;
|
|
4
|
+
export function createRuleListPolicy(names, behavior, source, pathBases) {
|
|
5
|
+
try {
|
|
6
|
+
return createPermissionRulePolicy(names.map((name) => ({ rule: name, behavior, source })), {
|
|
7
|
+
defaultAction: "allow",
|
|
8
|
+
caps: { maxRules: MAX_PERMISSION_RULES_PER_LIST },
|
|
9
|
+
...(pathBases !== undefined ? { pathBases } : {}),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
const coreMsg = (e instanceof Error ? e.message : String(e)).replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
14
|
+
throw new Error(`${source.replace("settings.", "")} has rule(s) core cannot compile — ${coreMsg}`, { cause: e });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=permission-rule-list.js.map
|
package/dist/run-local.js
CHANGED
|
@@ -12,7 +12,7 @@ import { singleUserAutoAcceptBaselineApplies } from "./approval.js";
|
|
|
12
12
|
import { withUnattendedLeg } from "./unattended-permission-mode.js";
|
|
13
13
|
import { buildUnattendedPolicyRefusal } from "./tool-approval.js";
|
|
14
14
|
import { createBrain } from "./brain.js";
|
|
15
|
-
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
15
|
+
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, buildApprovalGrantNotice, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
16
16
|
import { applyRuntimeGovernance } from "./runtime-governance.js";
|
|
17
17
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
18
18
|
import { taskWallClockSec } from "./task-workflow.js";
|
|
@@ -334,6 +334,9 @@ export async function runLocal(argv, deps = {}) {
|
|
|
334
334
|
});
|
|
335
335
|
if (ungatedCompensation)
|
|
336
336
|
logger.warn(ungatedCompensation.event, ungatedCompensation.fields);
|
|
337
|
+
const approvalGrantNotice = buildApprovalGrantNotice(config);
|
|
338
|
+
if (approvalGrantNotice)
|
|
339
|
+
logger.info(approvalGrantNotice.event, approvalGrantNotice.fields);
|
|
337
340
|
if (!deps.brain) {
|
|
338
341
|
const missing = missingGatewayRequirements(config);
|
|
339
342
|
if (missing.length > 0) {
|
|
@@ -97,6 +97,19 @@ export declare function validateCommandRules(rules: CommandRule[]): string[];
|
|
|
97
97
|
* 闭集本身则由契约 zod 派生 ⇒ 契约加一个判词,`RANK` 与桶分配的穷尽 switch 双双**编译红**。
|
|
98
98
|
*/
|
|
99
99
|
export declare function compileCommandPolicyLayers(rules: CommandRule[] | undefined): ToolPolicy[];
|
|
100
|
+
/** 运维 allow 规则表的出处名 = 它的 env 名(盖进每条规则;编不出来 / 不合文法时拒启句的主语)。 */
|
|
101
|
+
export declare const APPROVAL_GRANT_SOURCE = "APPROVAL_GRANT";
|
|
102
|
+
/**
|
|
103
|
+
* 7.115.0 —— 运维的 allow 规则(`APPROVAL_GRANT`)编成治理层的**叶**(空 / 缺席 ⇒ `[]`,本层一片叶都不铸,折叠逐字与修前同)。
|
|
104
|
+
*
|
|
105
|
+
* 编译口是**请求腿 `settings.permissions.allow` 的同一只**(`permission-rule-list.ts` `createRuleListPolicy`,`behavior: "allow"`):命中 = core 带
|
|
106
|
+
* `decisionReason: "rule"` 的放行(折叠把这句表态带到终局,引擎据此不再为没人表态的服务器工具调用出 `unstated` 卡);未命中 = 裸 allow =
|
|
107
|
+
* 不表态。它是一片**不收紧**的叶 —— 与 commandPolicy / 守卫集同住一个折叠点,只因为三者是同一个「部署说的话」:折叠 deny > ask > allow,
|
|
108
|
+
* 同折叠里任何一片叶的 deny / ask、`tightenTaskSpec` 之下的审批基线、之上的请求 settings 与会话规则,照旧压过它。
|
|
109
|
+
*
|
|
110
|
+
* 启动校验(`config.ts` `approvalGrantRefusal`)试编的也是本函数 —— 拒启判据与运行期编译是同一只,不会出现「启动收下、任务里编不出来」。
|
|
111
|
+
*/
|
|
112
|
+
export declare function compileApprovalGrantLayers(grant: readonly string[] | undefined): ToolPolicy[];
|
|
100
113
|
/**
|
|
101
114
|
* Expand an autonomy mode into TIGHTEN-ONLY `TaskSpec` safety overrides (the primitives — TRAP #2):
|
|
102
115
|
* - `read-only` / `plan` → `handsReadOnly: true` **+ `writeFace: "roots"`** (read + propose, never mutate; the
|
|
@@ -203,13 +216,18 @@ export declare function governanceOriginOf(gate: {
|
|
|
203
216
|
};
|
|
204
217
|
} | undefined, governance: GovernancePosture | undefined): true | undefined;
|
|
205
218
|
/**
|
|
206
|
-
* Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate
|
|
219
|
+
* Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate + the guard set + the
|
|
220
|
+
* `APPROVAL_GRANT` allow rules) onto a base
|
|
207
221
|
* `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
|
|
208
222
|
* override (combined onto the baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to
|
|
209
223
|
* `handsReadOnly` / `shellGate` overrides (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
|
|
210
224
|
* override would LOOSEN a stricter base safety field — surfacing a misconfiguration loudly instead of silently
|
|
211
225
|
* weakening the gate. Returns `base` untouched when there is nothing to apply.
|
|
212
226
|
*
|
|
227
|
+
* 7.115.0:`APPROVAL_GRANT` 是这里唯一一片**不收紧**的叶 —— 它只给出 allow 的那句表态(core 7.40.0 起没人表态的服务器工具调用要问,
|
|
228
|
+
* 运维的规则就是那句答),从不清掉任何 deny / ask:折叠 deny > ask > allow,所以「部署 ⊇ 操作员 ⊇ 客户端只能收窄」的不变量逐字成立。
|
|
229
|
+
* 编译口与请求腿的 allow 桶同一只({@link compileApprovalGrantLayers})。
|
|
230
|
+
*
|
|
213
231
|
* [ref]([ref] server 分单②):`MANUAL_MODE_SHELL_GATE` 从 settings 折叠搬到这里**无条件施加**。旧家
|
|
214
232
|
* (task-settings deriveSettingsPolicy 的 default/auto/acceptEdits 三臂)让一个**部署级 tighten-only 旋钮
|
|
215
233
|
* 的生死由客户端表态在场性决定**:body 无 settings/permissionMode(cli print/headless 腿按设计不 stamp)
|
|
@@ -242,6 +260,8 @@ export declare function applyRuntimeGovernance(base: TaskSpec, governance: {
|
|
|
242
260
|
/** 守卫集的**叶**(`deployment-governance.ts` `createDeploymentGovernanceInputs` 预铸;S-958 起是叶的列表,不是一只预折的策略)。
|
|
243
261
|
* 空列表与缺席同义。 */
|
|
244
262
|
sensitivePathLayers?: readonly ToolPolicy[];
|
|
263
|
+
/** 7.115.0:运维的 allow 规则(`APPROVAL_GRANT` 原串;本函数经 {@link compileApprovalGrantLayers} 编成叶)。空列表与缺席同义。 */
|
|
264
|
+
approvalGrant?: readonly string[];
|
|
245
265
|
/** [ref]/[ref] `governanceForced` 的标记表(缺省 = **当前 run 腿的 ALS 表**,见
|
|
246
266
|
* `governance-ask-marks.ts` 顶注「作用域」段)。注入口只为测试与将来的多实例形。 */
|
|
247
267
|
askMarks?: GovernanceAskMarks;
|
|
@@ -2,6 +2,7 @@ import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createC
|
|
|
2
2
|
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
3
3
|
import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
|
|
4
4
|
import { createDeploymentAuthoredPolicy, operatorAsk } from "./operator-ask.js";
|
|
5
|
+
import { createRuleListPolicy } from "./permission-rule-list.js";
|
|
5
6
|
import { heldRuleName } from "./tool-rule-names.js";
|
|
6
7
|
const COMMAND_RULE_SHAPE = GovernanceConfig.shape.commandPolicy.unwrap().element.shape;
|
|
7
8
|
const COMMAND_DECISIONS = COMMAND_RULE_SHAPE.decision.options;
|
|
@@ -100,6 +101,10 @@ export function compileCommandPolicyLayers(rules) {
|
|
|
100
101
|
}
|
|
101
102
|
return policies;
|
|
102
103
|
}
|
|
104
|
+
export const APPROVAL_GRANT_SOURCE = "APPROVAL_GRANT";
|
|
105
|
+
export function compileApprovalGrantLayers(grant) {
|
|
106
|
+
return grant !== undefined && grant.length > 0 ? [createRuleListPolicy(grant, "allow", APPROVAL_GRANT_SOURCE, undefined)] : [];
|
|
107
|
+
}
|
|
103
108
|
export function autonomyOverrides(autonomy) {
|
|
104
109
|
switch (autonomy) {
|
|
105
110
|
case "read-only":
|
|
@@ -143,7 +148,7 @@ function createGovernanceShellGateMarkPolicy(marksOf, tools = COARSE_SHELL_TOOLS
|
|
|
143
148
|
export function applyRuntimeGovernance(base, governance) {
|
|
144
149
|
const marksOf = () => governance.askMarks ?? currentGovernanceAskMarks();
|
|
145
150
|
const overrides = { ...autonomyOverrides(governance.autonomy) };
|
|
146
|
-
const layers = [...compileCommandPolicyLayers(governance.commandPolicy), ...(governance.sensitivePathLayers ?? [])];
|
|
151
|
+
const layers = [...compileCommandPolicyLayers(governance.commandPolicy), ...(governance.sensitivePathLayers ?? []), ...compileApprovalGrantLayers(governance.approvalGrant)];
|
|
147
152
|
const governancePolicy = layers.length > 0 ? createGovernanceAskMarkingPolicy(combinePolicies(...layers.map(createDeploymentAuthoredPolicy)), marksOf) : undefined;
|
|
148
153
|
if (governancePolicy !== undefined)
|
|
149
154
|
overrides.toolPolicy = governancePolicy;
|
package/dist/security.js
CHANGED
|
@@ -65,7 +65,7 @@ export function createAuthorizer(config, sessionStore) {
|
|
|
65
65
|
let owner = await ownerOf(requested);
|
|
66
66
|
if (owner === undefined) {
|
|
67
67
|
if (requireExisting) {
|
|
68
|
-
throw
|
|
68
|
+
throw createCodedHttpError(404, "resume.session_not_found", "requireExistingSession: session does not exist (resume.session_not_found)");
|
|
69
69
|
}
|
|
70
70
|
if (register) {
|
|
71
71
|
await register(requested, principal ?? null);
|
|
@@ -89,7 +89,7 @@ export function createAuthorizer(config, sessionStore) {
|
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
91
|
if (requireExisting) {
|
|
92
|
-
throw
|
|
92
|
+
throw createCodedHttpError(422, "request.field_conflict", "requireExistingSession requires a sessionId — there is no existing session to resume");
|
|
93
93
|
}
|
|
94
94
|
sessionId = uuidv7();
|
|
95
95
|
if (register)
|
|
@@ -66,10 +66,11 @@ export type ToolNameRuleField = {
|
|
|
66
66
|
* 「照 #1156 未随本版」);同版新导出的 `rosterToolName` / `ruleNamesOf`(#1194)是**别名归一**(一个名字在名册上落到哪只行的线名 /
|
|
67
67
|
* 一次调用可被规则拼成的名字集),不回答「这个名字会不会让准备阶段拒启」,不能替本函数 ⇒ 本函数照留,等 #1156。
|
|
68
68
|
*
|
|
69
|
-
* 🔴 **为什么不复用 `config.ts` 的 `
|
|
70
|
-
*
|
|
71
|
-
* fail-closed
|
|
72
|
-
*
|
|
69
|
+
* 🔴 **为什么不复用 `config.ts` 的 `unmatchableToolNamesRefusal`**(本仓另一只按名字判的单点;读过实现后裁):两只回答的
|
|
70
|
+
* 不是同一个问题。那一只问「这张表里这条条目会不会**永远不生效**」(匹配不上活工具,或编不成一条规则)—— 7.115.0 起它对 `allow` 也照判
|
|
71
|
+
* (修前豁免 `allow`:那时 allow 是白名单,匹配不上 = fail-closed 且当场可见;allow 改成 allow 规则后豁免臂已删),并且拒两段 MCP 名
|
|
72
|
+
* `mcp__srv`(一名两义)与 core 文法编不出的串。本函数问的是「引擎见到这条会话规则会不会**拒启**」:引擎准备阶段的名审计对会话规则的
|
|
73
|
+
* `allow` 桶照样抛,对 `mcp__srv` 却
|
|
73
74
|
* **不**抛(会话层自 core 5.55 / 5.56 起把它当整对端覆盖形真命中)。判据只能逐字跟 core 那两臂走,不能借另一只的口径。
|
|
74
75
|
* 两只在「退役名」「无前缀 `__` 名」两臂上同源(都读 core 导出的同两件),#1156 到货的那一批一并换调 core 的导出函数。
|
|
75
76
|
*/
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
* idempotent (the same tighten), so no special resume handling is needed (mirrors runtime-governance).
|
|
13
13
|
*
|
|
14
14
|
* 🔒 SECURITY INVARIANTS (SDK settings.ts §3.3):
|
|
15
|
-
* - permissions are
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* - permissions are folded via `tightenTaskSpec` (deny-wins `combinePolicies`); it THROWS on any override that would
|
|
16
|
+
* LOOSEN a stricter base safety field. The client can ADD denies/asks/handsReadOnly on top of the deployment +
|
|
17
|
+
* operator baseline, never widen it. Its `allow` rules (S-654 (c), 7.115.0) STATE an allow for the tools they name —
|
|
18
|
+
* the answer core 7.40.0 wants before it runs an MCP / A2A call nobody stated — and never override a deny or an ask
|
|
19
|
+
* from any layer (deny > ask > allow); a miss has no opinion. They release no ask a layer raises itself: on built-in
|
|
20
|
+
* tools the engine's own write gate and shell checks still ask (unlike the reference implementation's `permissions.allow`).
|
|
18
21
|
* - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode` 收**五**模式
|
|
19
22
|
* (default | acceptEdits | plan | bypassPermissions | auto),bypass/auto 被 HONOR 为 gate-shape 选择
|
|
20
23
|
* ——但深层不变量未变:bypass=「不加 ask 门」,恒不越部署基线(tightenTaskSpec deny-wins,基线 deny/
|
|
@@ -224,8 +227,6 @@ export interface ParseTaskSettingsResult {
|
|
|
224
227
|
* posture (an uncapped prompt body is a per-turn token-cost hole; cross review). Exported so the HTTP layer
|
|
225
228
|
* (prepareSpec) can 400 fail-loud on submit with the SAME bound this defensive parse enforces on every path. */
|
|
226
229
|
export declare const MAX_SETTINGS_OUTPUT_STYLE_CHARS = 16384;
|
|
227
|
-
/** Cap on the element count of each permission list (allow/deny/ask) — bounds a pathological rule blast. */
|
|
228
|
-
export declare const MAX_SETTINGS_PERMISSION_RULES = 1000;
|
|
229
230
|
/** Caps on `settings.env` — bound a pathological env blast (count + per-value length; values land in the shell env). */
|
|
230
231
|
export declare const MAX_SETTINGS_ENV_VARS = 256;
|
|
231
232
|
export declare const MAX_SETTINGS_ENV_VALUE_CHARS = 32768;
|
package/dist/task-settings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { combinePolicies,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { combinePolicies, createFsWriteGatePolicy, parseRuleText, tightenTaskSpec, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, PERMISSION_MODES, ruleToolGrammarOf, } from "@sema-agent/core";
|
|
2
|
+
import { unmatchableToolNamesRefusal } from "./config.js";
|
|
3
|
+
import { MAX_PERMISSION_RULES_PER_LIST, createRuleListPolicy } from "./permission-rule-list.js";
|
|
4
4
|
import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "./request-key-closure.js";
|
|
5
5
|
import { parseHooksConfig } from "./hooks/hook-runner.js";
|
|
6
6
|
import { heldRuleName } from "./tool-rule-names.js";
|
|
@@ -61,7 +61,6 @@ export function skillListingMaxDescOk(v) {
|
|
|
61
61
|
return typeof v === "number" && Number.isSafeInteger(v) && v > 0;
|
|
62
62
|
}
|
|
63
63
|
export const MAX_SETTINGS_OUTPUT_STYLE_CHARS = 16_384;
|
|
64
|
-
export const MAX_SETTINGS_PERMISSION_RULES = 1_000;
|
|
65
64
|
export const MAX_SETTINGS_ENV_VARS = 256;
|
|
66
65
|
export const MAX_SETTINGS_ENV_VALUE_CHARS = 32_768;
|
|
67
66
|
export const MAX_SETTINGS_ENV_KEY_CHARS = 256;
|
|
@@ -106,8 +105,8 @@ export function settingsPermissionBucketIssue(perms) {
|
|
|
106
105
|
let fields;
|
|
107
106
|
if (!Array.isArray(v))
|
|
108
107
|
fields = { field, reason: "bad_entry" };
|
|
109
|
-
else if (v.length >
|
|
110
|
-
fields = { field, reason: "over_cap", count: v.length, cap:
|
|
108
|
+
else if (v.length > MAX_PERMISSION_RULES_PER_LIST)
|
|
109
|
+
fields = { field, reason: "over_cap", count: v.length, cap: MAX_PERMISSION_RULES_PER_LIST };
|
|
111
110
|
else {
|
|
112
111
|
const index = v.findIndex((x) => typeof x !== "string" || x.length === 0);
|
|
113
112
|
if (index >= 0)
|
|
@@ -266,20 +265,7 @@ export function acceptAppendSystemPrompt(v, warn, packDropsAppend) {
|
|
|
266
265
|
}
|
|
267
266
|
return v;
|
|
268
267
|
}
|
|
269
|
-
const RULE_SOURCE_BY_BEHAVIOR = { ask: "settings.permissions.ask", deny: "settings.permissions.deny" };
|
|
270
|
-
function createRuleListPolicy(names, behavior, pathBases) {
|
|
271
|
-
try {
|
|
272
|
-
return createPermissionRulePolicy(names.map((name) => ({ rule: name, behavior, source: RULE_SOURCE_BY_BEHAVIOR[behavior] })), {
|
|
273
|
-
defaultAction: "allow",
|
|
274
|
-
caps: { maxRules: MAX_SETTINGS_PERMISSION_RULES },
|
|
275
|
-
...(pathBases !== undefined ? { pathBases } : {}),
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
catch (e) {
|
|
279
|
-
const coreMsg = (e instanceof Error ? e.message : String(e)).replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
280
|
-
throw new Error(`${RULE_SOURCE_BY_BEHAVIOR[behavior].replace("settings.", "")} has rule(s) core cannot compile — ${coreMsg}`, { cause: e });
|
|
281
|
-
}
|
|
282
|
-
}
|
|
268
|
+
const RULE_SOURCE_BY_BEHAVIOR = { allow: "settings.permissions.allow", ask: "settings.permissions.ask", deny: "settings.permissions.deny" };
|
|
283
269
|
const WORKFLOW_ASK_NAMES = [RUN_WORKFLOW_TOOL_NAME];
|
|
284
270
|
const isWorkflowName = (name) => name === RUN_WORKFLOW_TOOL_NAME;
|
|
285
271
|
function createWorkflowAskPolicy(isExempt) {
|
|
@@ -327,16 +313,16 @@ function rejectUnmatchableSettingsNames(perms, pathBases) {
|
|
|
327
313
|
}
|
|
328
314
|
}
|
|
329
315
|
}
|
|
330
|
-
for (const [field, names, behavior
|
|
331
|
-
["permissions.ask", perms.ask, "ask"
|
|
332
|
-
["permissions.deny", perms.deny, "deny"
|
|
333
|
-
["permissions.allow", perms.allow, "allow"
|
|
316
|
+
for (const [field, names, behavior] of [
|
|
317
|
+
["permissions.ask", perms.ask, "ask"],
|
|
318
|
+
["permissions.deny", perms.deny, "deny"],
|
|
319
|
+
["permissions.allow", perms.allow, "allow"],
|
|
334
320
|
]) {
|
|
335
321
|
if (!names)
|
|
336
322
|
continue;
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
339
|
-
throw new Error(
|
|
323
|
+
const unmatchable = unmatchableToolNamesRefusal(field, names, { consumer: "rule-policy", behavior, ...(pathBases !== undefined ? { pathBases } : {}) });
|
|
324
|
+
if (unmatchable !== undefined)
|
|
325
|
+
throw new Error(unmatchable.message);
|
|
340
326
|
}
|
|
341
327
|
}
|
|
342
328
|
export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
@@ -350,11 +336,11 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
|
350
336
|
rejectUnmatchableSettingsNames(perms, pathBases);
|
|
351
337
|
const policies = [];
|
|
352
338
|
if (allow)
|
|
353
|
-
policies.push(
|
|
339
|
+
policies.push(createRuleListPolicy(allow, "allow", RULE_SOURCE_BY_BEHAVIOR.allow, pathBases));
|
|
354
340
|
if (deny)
|
|
355
|
-
policies.push(createRuleListPolicy(deny, "deny", pathBases));
|
|
341
|
+
policies.push(createRuleListPolicy(deny, "deny", RULE_SOURCE_BY_BEHAVIOR.deny, pathBases));
|
|
356
342
|
if (ask)
|
|
357
|
-
policies.push(createRuleListPolicy(ask, "ask", pathBases));
|
|
343
|
+
policies.push(createRuleListPolicy(ask, "ask", RULE_SOURCE_BY_BEHAVIOR.ask, pathBases));
|
|
358
344
|
const fsWriteGate = (acceptCwd) => {
|
|
359
345
|
if (!gate)
|
|
360
346
|
return undefined;
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -452,10 +452,7 @@ export interface ToolApprovalFrame {
|
|
|
452
452
|
* 耐久路的对偶 = `gate.riskDescriptor.probeCause`(同值,core `buildRiskDescriptor` 铸),走 checkpoint
|
|
453
453
|
* 行的**整体透传**到 `GET /v1/approvals` —— 键集零施工,钉在同一个文件的 §4。
|
|
454
454
|
*
|
|
455
|
-
* 姊妹键 `
|
|
456
|
-
* 拼法,而 core 的边界哲学(交 code + 数组、不交句子)与「一张卡上不放两份同义解释」都指向只投结构化
|
|
457
|
-
* 那一份;散文那份还无法被消费端本地化。照 `preview` / `requiresRealApproval` 先例 = 候消费端(cli)
|
|
458
|
-
* 提渲染需求再翻(登记在 `trace/core-keyset-guard.ts` 的 EXCLUDED 栏)。
|
|
455
|
+
* 姊妹键 `probeReason`(同一件事的**散文**兄弟)见下一键 —— 7.115.0 起投影(S-733),与本键**并列不替代**。
|
|
459
456
|
*/
|
|
460
457
|
probeCause?: {
|
|
461
458
|
code: string;
|
|
@@ -468,6 +465,21 @@ export interface ToolApprovalFrame {
|
|
|
468
465
|
total: number;
|
|
469
466
|
};
|
|
470
467
|
};
|
|
468
|
+
/**
|
|
469
|
+
* S-733(7.115.0;**ADDITIVE**,`"tool_approval"` only)—— 这次 ask 被收紧时探针给出的那一句**散文**因由
|
|
470
|
+
* (`AskRequest.probeReason`:core 在 maybe 档收紧点 `inlineUntrusted(…, 200)` 过的一句话)。与
|
|
471
|
+
* {@link probeCause}(结构化因由)**并列、不替代**:引擎自己铸的因由走结构位,**部署自写的探针**只会写这一句
|
|
472
|
+
* —— 一只只给 reason 不给 cause 的探针,此前其审批人在活卡上读不到任何因由(耐久面
|
|
473
|
+
* `gate.riskDescriptor.probeReason` 早就带着这同一句,两张脸只有一张有话说)。
|
|
474
|
+
*
|
|
475
|
+
* 🔴 **在场才带**(缺席绝不编空串;缺席 ≠「没有原因」);窄读单点 = `approval-card.ts` 的 `readProbeReason`
|
|
476
|
+
* (非串 / 空白 ⇒ 缺席;超信封 ⇒ **整只丢不截**;内容族 ⇒ 脱敏,与耐久面同一规则 ⇒ 对同一句话两面同值)。
|
|
477
|
+
* 🔴 **只在本帧上**:呈卡帧 / `card_json` 不携它(`ApprovalCardSchema` 是 `.strict()` 的耐久信封,加键要另行裁定)。
|
|
478
|
+
* 🔴 **展示 / 分诊元数据,永不是裁决输入**;永不进 `message`(那是分类器 / 模型的输入面)。
|
|
479
|
+
* `\p{Cf}` 展示重排类字符(方向 / 零宽标记):core 在这个座上**不剥**(同值契约下 core 自己披露的残余,[ref]);本仓脱敏器
|
|
480
|
+
* (`redactSecrets`)入口剥全部 `\p{Cf}`(7.70.0 起),活卡帧与耐久面**都过它** ⇒ 两面同值、且都是剥后字节(`test/approval-probe-reason-wire.test.ts` 钉)。
|
|
481
|
+
*/
|
|
482
|
+
probeReason?: string;
|
|
471
483
|
/**
|
|
472
484
|
* [ref] 件 G2(core 5.35.0 [ref] G-2;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 背后的
|
|
473
485
|
* **规则出处证据**(`AskRequest.ruleEvidence`,引擎在 gate 内盖章):org 快照 `orgRevision` / 命中的
|
package/dist/tool-approval.js
CHANGED
|
@@ -8,7 +8,7 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
|
|
|
8
8
|
import { createLogger } from "./observability/logger.js";
|
|
9
9
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
10
10
|
import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
11
|
-
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
11
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
12
12
|
import { MAX_SETTLED_BY_CHARS, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, settledByFitsColumn } from "./plugins/approval-ask-store-sql.js";
|
|
13
13
|
import { settleByPolicy } from "./approval-policy-settlement.js";
|
|
14
14
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
@@ -1099,6 +1099,7 @@ export class ToolApprovalCoordinator {
|
|
|
1099
1099
|
...((f) => (f !== undefined ? { denialLimitFallback: f } : {}))(readDenialLimitFallback(req)),
|
|
1100
1100
|
...((g) => (g !== undefined ? { readRootCandidate: g } : {}))(readReadRootCandidate(req)),
|
|
1101
1101
|
...((c) => (c !== undefined ? { probeCause: c } : {}))(readProbeCause(req)),
|
|
1102
|
+
...((r) => (r !== undefined ? { probeReason: r } : {}))(readProbeReason(req)),
|
|
1102
1103
|
...((c) => (c !== undefined ? { ruleEvidence: c } : {}))(readRuleEvidence(req)),
|
|
1103
1104
|
...(bounded.omitted ? { argsOmitted: true } : { args: bounded.args }),
|
|
1104
1105
|
...(bounded.hasBidi || req.hasBidiControls === true ? { inputHasBidi: true } : {}),
|
|
@@ -43,8 +43,8 @@ type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNo
|
|
|
43
43
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
44
44
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
45
45
|
type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
|
|
46
|
-
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate" | "askClass" | "classes";
|
|
47
|
-
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "
|
|
46
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "probeReason" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate" | "askClass" | "classes";
|
|
47
|
+
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "hasBidiControls" | "previewWithheld" | "execCwd" | "authoredBy";
|
|
48
48
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
49
49
|
type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
|
|
50
50
|
type AskFrameServerMinted = "type" | "approvalId" | "argsOmitted" | "outcome" | "governanceForced" | "expiresAtMs" | "expiresInMs" | "serverNowMs" | "parked" | "inputHasBidi";
|
|
@@ -88,10 +88,13 @@ type _GuardWiringSessionReverse = AssertAllKeysHandled<Exclude<WiringSessionHand
|
|
|
88
88
|
type WiringToolsHandled = "schemaVersion" | "count" | "entries" | "digest";
|
|
89
89
|
type _GuardWiringTools = AssertAllKeysHandled<Exclude<keyof WiringSection<"tools">, WiringToolsHandled>>;
|
|
90
90
|
type _GuardWiringToolsReverse = AssertAllKeysHandled<Exclude<WiringToolsHandled, keyof WiringSection<"tools">>>;
|
|
91
|
-
type
|
|
92
|
-
type WiringFleetHandled = "backgroundAgentStore" | "hostChildEventSink" | FleetExcluded;
|
|
91
|
+
type WiringFleetHandled = "backgroundAgentStore" | "hostChildEventSink" | "subagentTranscripts";
|
|
93
92
|
type _GuardWiringFleet = AssertAllKeysHandled<Exclude<keyof WiringSection<"fleet">, WiringFleetHandled>>;
|
|
94
93
|
type _GuardWiringFleetReverse = AssertAllKeysHandled<Exclude<WiringFleetHandled, keyof WiringSection<"fleet">>>;
|
|
94
|
+
type SubagentTranscriptTier = WiringSection<"fleet">["subagentTranscripts"];
|
|
95
|
+
type SubagentTranscriptTierWords = "none" | "rows" | "full";
|
|
96
|
+
type _GuardSubagentTranscriptTierWords = AssertAllKeysHandled<Exclude<SubagentTranscriptTier, SubagentTranscriptTierWords>>;
|
|
97
|
+
type _GuardSubagentTranscriptTierWordsReverse = AssertAllKeysHandled<Exclude<SubagentTranscriptTierWords, SubagentTranscriptTier>>;
|
|
95
98
|
type WiringMcpEntry = WiringSection<"mcp">[number];
|
|
96
99
|
type WiringMcpExcluded = "error";
|
|
97
100
|
type WiringMcpHandled = "name" | "source" | "status" | "errorCode" | "delivered" | "httpStatus" | "toolCount" | "liveness" | WiringMcpExcluded;
|
|
@@ -288,6 +291,8 @@ export type CoreKeysetGuards = [
|
|
|
288
291
|
_GuardWiringToolsReverse,
|
|
289
292
|
_GuardWiringFleet,
|
|
290
293
|
_GuardWiringFleetReverse,
|
|
294
|
+
_GuardSubagentTranscriptTierWords,
|
|
295
|
+
_GuardSubagentTranscriptTierWordsReverse,
|
|
291
296
|
_GuardWiringMcp,
|
|
292
297
|
_GuardWiringMcpReverse,
|
|
293
298
|
_GuardWiringRules,
|
package/dist/trace/project.js
CHANGED
|
@@ -499,9 +499,18 @@ export function wiringManifestEventData(ev, instructions) {
|
|
|
499
499
|
...(Array.isArray(parkIn.reasons) ? { reasons: parkIn.reasons.filter((r) => typeof r === "string") } : {}),
|
|
500
500
|
...(str(parkIn.checkpointDurability) !== undefined ? { checkpointDurability: parkIn.checkpointDurability } : {}),
|
|
501
501
|
});
|
|
502
|
+
const subagentTranscripts = (() => {
|
|
503
|
+
if (fleetIn.subagentTranscripts === undefined)
|
|
504
|
+
return undefined;
|
|
505
|
+
const word = str(fleetIn.subagentTranscripts);
|
|
506
|
+
if (word === undefined)
|
|
507
|
+
return dropProjectedFace("wiring_manifest.fleet.subagentTranscripts is not a non-empty string");
|
|
508
|
+
return word;
|
|
509
|
+
})();
|
|
502
510
|
const fleet = nonEmpty({
|
|
503
511
|
...(bool(fleetIn.backgroundAgentStore) !== undefined ? { backgroundAgentStore: fleetIn.backgroundAgentStore } : {}),
|
|
504
512
|
...(bool(fleetIn.hostChildEventSink) !== undefined ? { hostChildEventSink: fleetIn.hostChildEventSink } : {}),
|
|
513
|
+
...(subagentTranscripts !== undefined ? { subagentTranscripts } : {}),
|
|
505
514
|
});
|
|
506
515
|
const permissionRules = nonEmpty({
|
|
507
516
|
...(bool(rulesIn.storeWired) !== undefined ? { storeWired: rulesIn.storeWired } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.115.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"!dist/run-local-linux-x64",
|
|
29
29
|
"!dist/run-local-darwin-arm64",
|
|
30
30
|
"skills",
|
|
31
|
-
"scripts/
|
|
31
|
+
"scripts/drain-check-local.mjs",
|
|
32
32
|
"deploy/sema-up",
|
|
33
33
|
"docs/DEVICE-EXECUTOR-PROTOCOL.md",
|
|
34
34
|
"USAGE.md",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@sema-agent/core": "7.
|
|
66
|
-
"@sema-agent/settings-schema": "
|
|
65
|
+
"@sema-agent/core": "7.40.1",
|
|
66
|
+
"@sema-agent/settings-schema": "8.0.0",
|
|
67
67
|
"e2b": "^2.28.0",
|
|
68
68
|
"libsodium-wrappers": "^0.8.4",
|
|
69
69
|
"mysql2": "^3.22.4",
|