@sema-agent/settings-schema 14.4.0 → 14.6.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/CHANGELOG.md +303 -0
- package/README.md +22 -1
- package/conformance/VERSION +1 -1
- package/dist/api/scopes.d.ts +1 -1
- package/dist/api/scopes.js +1 -1
- package/dist/atomic-write.d.ts +11 -0
- package/dist/atomic-write.js +38 -5
- package/dist/bundle.d.ts +1 -1
- package/dist/bundle.js +1 -1
- package/dist/config-d-file.d.ts +4 -0
- package/dist/config-d-file.js +11 -4
- package/dist/config-dir-io.js +12 -2
- package/dist/config-dir-migration.d.ts +44 -0
- package/dist/config-dir-migration.js +91 -36
- package/dist/config-fns.js +3 -9
- package/dist/conformance/canonical.js +4 -7
- package/dist/conformance/corpus.js +3 -3
- package/dist/conformance/derive.js +6 -5
- package/dist/conformance/run.js +2 -1
- package/dist/definition-copy.d.ts +23 -0
- package/dist/definition-copy.js +52 -0
- package/dist/file-store.d.ts +1 -31
- package/dist/file-store.js +3 -30
- package/dist/fleet.js +2 -1
- package/dist/fs-failure.d.ts +17 -2
- package/dist/fs-failure.js +55 -15
- package/dist/hooks-config.d.ts +2 -2
- package/dist/hooks-config.js +112 -31
- package/dist/http-hook-allowlists.d.ts +7 -3
- package/dist/http-hook-allowlists.js +102 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/mcp-request-entry.d.ts +9 -6
- package/dist/mcp-request-entry.js +225 -93
- package/dist/node.d.ts +2 -1
- package/dist/node.js +2 -1
- package/dist/one-time-migrations.js +36 -41
- package/dist/plain-json-object.d.ts +31 -0
- package/dist/plain-json-object.js +52 -0
- package/dist/portable-domains.d.ts +31 -0
- package/dist/portable-domains.js +30 -0
- package/dist/safety-merge-spec.d.ts +88 -6
- package/dist/safety-merge-spec.js +233 -17
- package/dist/scheduler-store.js +3 -2
- package/dist/settings-units.d.ts +85 -0
- package/dist/settings-units.js +175 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +61 -53
- package/dist/url-credential.d.ts +41 -0
- package/dist/url-credential.js +95 -0
- package/dist/url-shape.d.ts +11 -5
- package/dist/url-shape.js +11 -5
- package/dist/workers-rows.d.ts +2 -1
- package/dist/workers-rows.js +5 -4
- package/docs/API-SURFACE.md +30 -4
- package/docs/CONSUMER-INTEGRATION.md +2 -0
- package/llms-full.txt +24 -2
- package/llms.txt +6 -2
- package/package.json +2 -3
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* 判官出结构不出句:放行 ⇒ `undefined`;拦下 ⇒ {@link HttpHookUrlBlock}(哪张名单 / 为什么 / 出自哪几层),用户面那一句由各宿主按它铸。
|
|
16
16
|
*/
|
|
17
17
|
import { z } from "zod";
|
|
18
|
+
import { captureStringArray } from "./definition-copy.js";
|
|
19
|
+
import { jsonShapeOf } from "./plain-json-object.js";
|
|
18
20
|
import { layerAdmission } from "./settings-sources.js";
|
|
19
21
|
import { urlAllowPatternMatches } from "./url-pattern.js";
|
|
20
22
|
/** 两键(闭集,声明序)。 */
|
|
@@ -30,26 +32,107 @@ export const HttpHookAllowlistSettings = z
|
|
|
30
32
|
})
|
|
31
33
|
.passthrough();
|
|
32
34
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
+
* 一张名单的**唯一读法**(读器与判官共用,两边永远同答):**取值 + 复制 + 检视整段在一个 try 里**,名单键只读一次;名单本身走包内的串数组捕获读法
|
|
36
|
+
* `captureStringArray`(`./definition-copy`:按下标 / `hasOwn` / 一次读 / 非串即坏 / 定义语义写入,不走迭代协议,交副本)—— 与请求面 `args` 同一只读法。
|
|
37
|
+
* 键没写(取到 `undefined`)⇒ `undefined`(不设);是串数组 ⇒ 一份已校验的捕获副本;其余一切 ⇒ 读成 `[]` 并标 `malformed`(fail-closed:一个都不许)——
|
|
38
|
+
* 取值抛(键本身是会抛的 getter)、不是串数组(含稀疏数组的洞 —— 不顺原型链读;`length` 不是安全非负整数;读元素时抛),一个也不抛出去。
|
|
39
|
+
*/
|
|
40
|
+
function captureAllowlist(holder, key) {
|
|
41
|
+
try {
|
|
42
|
+
const v = holder[key];
|
|
43
|
+
if (v === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
const list = captureStringArray(v); // 串数组的唯一读法(`./definition-copy`):按下标 / hasOwn / 一次读 / 非串即坏 / 定义语义写入,交副本
|
|
46
|
+
return list === undefined ? { list: [], malformed: true } : { list, malformed: false };
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { list: [], malformed: true };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function presenceOf(bag, key) {
|
|
53
|
+
try {
|
|
54
|
+
return Object.hasOwn(bag, key) ? "own" : key in bag ? "inherited" : "absent";
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return "unreadable";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 从一层的原始设置对象读出两键(唯一读法)。键缺席(或值为 `undefined`)⇒ 不设;字符串数组 ⇒ 原样(过 `captureAllowlist`〔包内私有〕,与判官同一只读法);其余一切(非数组、含非串条目或洞、取值或读元素时抛、`null`)
|
|
62
|
+
* ⇒ **读成 `[]`**(CC 同向:坏形名单 = 一个都不许)并在 `malformed` 点名。整份设置不是对象(`null` / 原始值 / 数组)⇒ 两键都缺。
|
|
63
|
+
* **是对象但不朴素**(类实例 / `Map` / 建在别的对象原型上的记录 / 函数 —— JSON 解析不出来,只能是嵌入方程序拼的)⇒ 同样 fail-closed:两键都读成 `[]` 并点名 `malformed`。
|
|
64
|
+
* 把它当「没有设置」读,等于把一层带名单的托管设置静默丢掉、方向放行(与本模块「坏形 = 一个都不许」的失败方向相反)。
|
|
65
|
+
* 名单键**只靠继承可见**(不是自有键)⇒ 同样 fail-closed:该键读成 `[]` 并点名 `malformed`(自有键盖过继承键;自有键值为 `undefined` 仍是「不设」)。
|
|
66
|
+
* 原型是带成员的 null 原型对象的对象(`Object.create(<null 原型的名单袋>)`)是一例:它按座位的判据读成朴素、按**自有**键读,名单键只在原型上 ⇒ 坏形。它只能由程序造出,JSON 解析不出来(座位头注 `./plain-json-object` 同写)。
|
|
35
67
|
*/
|
|
36
68
|
export function readHttpHookAllowlistLayer(source, settings) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
const shape = jsonShapeOf(settings);
|
|
70
|
+
let bag = Object.create(null); // 空袋没有原型:不存在 / 非对象的层看不见 `Object.prototype` 上的污染键,仍「两键都缺」
|
|
71
|
+
let refuseBoth = false; // 是对象但不朴素 ⇒ 两键都读成 [](fail-closed)
|
|
72
|
+
switch (shape) {
|
|
73
|
+
case "plain":
|
|
74
|
+
bag = settings;
|
|
75
|
+
break;
|
|
76
|
+
case "non_plain_object":
|
|
77
|
+
case "uninspectable": // 读形状就抛的对象(已撤销的 Proxy)同样 fail-closed:读不出就不能当「没有设置」放行
|
|
78
|
+
refuseBoth = true;
|
|
79
|
+
break;
|
|
80
|
+
case "array":
|
|
81
|
+
case "null":
|
|
82
|
+
case "primitive":
|
|
83
|
+
break; // 整份设置不是对象 ⇒ 两键都缺
|
|
84
|
+
default: {
|
|
85
|
+
const unhandled = shape;
|
|
86
|
+
throw new Error(`readHttpHookAllowlistLayer: unhandled shape ${String(unhandled)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const bad = Object.create(null); // 读成 [] 的坏形键(零原型的标记袋;`malformed` 数组出口只用定义语义造,见下)
|
|
90
|
+
// 累加器也零原型:`{}` 继承 `Object.prototype`,原型上被污染成只读(只有 getter)的同名键会让下面的赋值抛(读器该 fail-closed,不是抛)
|
|
91
|
+
const lists = Object.create(null);
|
|
40
92
|
for (const key of HTTP_HOOK_ALLOWLIST_KEYS) {
|
|
41
|
-
if (
|
|
42
|
-
continue;
|
|
43
|
-
const v = bag[key];
|
|
44
|
-
if (Array.isArray(v) && v.every((x) => typeof x === "string"))
|
|
45
|
-
lists[key] = [...v];
|
|
46
|
-
else {
|
|
93
|
+
if (refuseBoth) {
|
|
47
94
|
lists[key] = [];
|
|
48
|
-
|
|
95
|
+
bad[key] = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// 对袋的每一次触碰(`in` / `hasOwn` / 取值)都经守护:先问这个键在袋里怎么在场(一只守护的探针),再取值(`captureAllowlist`,自带守护)
|
|
99
|
+
const presence = presenceOf(bag, key);
|
|
100
|
+
switch (presence) {
|
|
101
|
+
case "absent":
|
|
102
|
+
continue;
|
|
103
|
+
case "inherited": // 名单键只靠继承可见 ⇒ 不能当「没有名单」放行
|
|
104
|
+
case "unreadable": // 袋读不出(`has` / `getOwnPropertyDescriptor` 陷阱抛)⇒ 同样 fail-closed
|
|
105
|
+
lists[key] = [];
|
|
106
|
+
bad[key] = true;
|
|
107
|
+
continue;
|
|
108
|
+
case "own": {
|
|
109
|
+
const read = captureAllowlist(bag, key); // 取值也在 try 里、只读一次:键是会抛的自有 getter ⇒ 坏形,不是抛出读器
|
|
110
|
+
if (read === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
lists[key] = read.list;
|
|
113
|
+
if (read.malformed)
|
|
114
|
+
bad[key] = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
default: {
|
|
118
|
+
const unhandled = presence;
|
|
119
|
+
throw new Error(`readHttpHookAllowlistLayer: unhandled presence ${String(unhandled)}`);
|
|
120
|
+
}
|
|
49
121
|
}
|
|
50
122
|
}
|
|
123
|
+
// `malformed` 用 filter 造(定义语义)—— `push` 会命中 `Object.prototype` 上继承的数字键 setter、吞掉点名;层是普通对象(返回值的运行期形状与 v14.5.0 同),`...lists` 是定义语义拷贝,污染下不抛
|
|
124
|
+
const malformed = HTTP_HOOK_ALLOWLIST_KEYS.filter((key) => bad[key] === true);
|
|
51
125
|
return { layer: { source, ...lists }, malformed };
|
|
52
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* 一层的某张名单:按 `[[Get]]` 读(名单放在原型上的手造层 —— 类 getter、`Object.create(defaults)` —— 照旧生效,与 v14.5.0 同答),
|
|
129
|
+
* 过 `captureAllowlist`〔包内私有〕—— 与读器**同一只读法**,产出一份已校验的捕获副本,判官只读这份副本(元素只读一次)。
|
|
130
|
+
* 读到 `undefined` ⇒ 没写(`undefined`);取值时 getter 抛、或读到的不是串数组(污染出非数组、含非串条目或洞、读 `length` / 元素的 getter 或 Proxy 陷阱抛)⇒ 读成 `[]`(fail-closed:一个都不许),不抛。
|
|
131
|
+
* 原型污染出来的名单只会让准入名单取交后更窄 —— 方向是拦,不是放;读器造的缺席层也是普通对象,污染下同样被收紧(fail-closed,判决的 `sources` 点名该层)。
|
|
132
|
+
*/
|
|
133
|
+
function listOf(layer, key) {
|
|
134
|
+
return captureAllowlist(layer, key)?.list;
|
|
135
|
+
}
|
|
53
136
|
/** 进判决的那几层(入参序):`layerAdmission` 答 `admitted` 的层。每一层都问 —— 闭集外的源 / 工作区读数在这里响亮抛,哪怕那一层没写名单。 */
|
|
54
137
|
function admittedLayers(layers, workspace) {
|
|
55
138
|
return layers.filter((l) => layerAdmission(l.source, workspace, "httpHookAllowlists") === "admitted");
|
|
@@ -59,15 +142,18 @@ function admittedLayers(layers, workspace) {
|
|
|
59
142
|
* 没有准入层写这张名单 ⇒ 不限(`undefined`)。`workspace` = 宿主对工作区信任的读数(读口抛或闭集外按 `declined` 交),与 `layerAdmission` 同一个入参。
|
|
60
143
|
*/
|
|
61
144
|
export function httpHookUrlVerdict(url, layers, workspace) {
|
|
62
|
-
const refusing = admittedLayers(layers, workspace).
|
|
145
|
+
const refusing = admittedLayers(layers, workspace).flatMap((l) => {
|
|
146
|
+
const list = listOf(l, "allowedHttpHookUrls");
|
|
147
|
+
return list !== undefined && !list.some((p) => urlAllowPatternMatches(url, p)) ? [{ source: l.source, empty: list.length === 0 }] : [];
|
|
148
|
+
});
|
|
63
149
|
if (refusing.length === 0)
|
|
64
150
|
return undefined;
|
|
65
|
-
return { list: "allowedHttpHookUrls", rule: refusing.some((
|
|
151
|
+
return { list: "allowedHttpHookUrls", rule: refusing.some((r) => r.empty) ? "empty" : "url", sources: refusing.map((r) => r.source) };
|
|
66
152
|
}
|
|
67
153
|
/** 一只 http hook 实际可插值进请求头的环境变量:它自己的 `allowedEnvVars` 与每一张准入的 `httpHookAllowedEnvVars` 取交(保持 hook 自己的序)。 */
|
|
68
154
|
export function httpHookEnvVarsAllowed(hookVars, layers, workspace) {
|
|
69
155
|
const lists = admittedLayers(layers, workspace)
|
|
70
|
-
.
|
|
71
|
-
.
|
|
156
|
+
.map((l) => listOf(l, "httpHookAllowedEnvVars"))
|
|
157
|
+
.filter((list) => list !== undefined);
|
|
72
158
|
return hookVars.filter((v) => lists.every((list) => list.includes(v)));
|
|
73
159
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export * from "./types.js";
|
|
6
6
|
export * from "./execution-lanes.js";
|
|
7
7
|
export * from "./url-shape.js";
|
|
8
|
+
export * from "./url-credential.js";
|
|
8
9
|
export * from "./default-model.js";
|
|
9
10
|
export * from "./loopback.js";
|
|
10
11
|
export * from "./hash.js";
|
|
@@ -17,6 +18,7 @@ export * from "./reader.js";
|
|
|
17
18
|
export * from "./cross-domain.js";
|
|
18
19
|
export * from "./remote-exec.js";
|
|
19
20
|
export * from "./safety-merge-spec.js";
|
|
21
|
+
export * from "./settings-units.js";
|
|
20
22
|
export * from "./scheduler-store.js";
|
|
21
23
|
export * from "./cross-session.js";
|
|
22
24
|
export * from "./hooks.js";
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export * from "./types.js";
|
|
6
6
|
export * from "./execution-lanes.js"; // 7.0.0 M-R2: the ONE lane / store vocabulary (EXECUTION_LANE_WORDS / EXECUTION_LANES / STORE_BACKENDS / SESSION_BACKENDS / MEMORY_ENGINE_BACKENDS) — pure data, browser-safe
|
|
7
7
|
export * from "./url-shape.js"; // 11.0.0: THE URL shape judge (httpUrlFlaws / httpUrlRule / isHierarchicalUrl), pure/browser-safe
|
|
8
|
+
export * from "./url-credential.js"; // 14.5.0: THE "does this URL carry a credential" judge (findUrlCredential / findUrlUserinfo / URL_CREDENTIAL_REASONS / CREDENTIAL_PARAM_NAMES …), pure/browser-safe
|
|
8
9
|
export * from "./default-model.js"; // 11.0.0: THE catalog-default resolver (roles.default → active tier ladder → fallback), pure/browser-safe
|
|
9
10
|
export * from "./loopback.js"; // 11.0.0: the two「本机」predicates (auth relaxation — narrow / connection routing — wide), pure/browser-safe
|
|
10
11
|
export * from "./hash.js";
|
|
@@ -16,7 +17,8 @@ export * from "./config-fns.js";
|
|
|
16
17
|
export * from "./reader.js";
|
|
17
18
|
export * from "./cross-domain.js"; // pure half only (no fleet placement)
|
|
18
19
|
export * from "./remote-exec.js";
|
|
19
|
-
export * from "./safety-merge-spec.js"; // #14 per-category safety merge-spec (settings resolver authority)
|
|
20
|
+
export * from "./safety-merge-spec.js"; // #14 per-category safety merge-spec (settings resolver authority) + 14.6.0 THE comparator tightenVerdict / TIGHTEN_VERDICTS and the member comparator memberTighten / resolveMemberTighten (derived from a member row's value class)
|
|
21
|
+
export * from "./settings-units.js"; // 14.6.0: SETTINGS_UNITS — the settings-document unit table (path / category / addressing / reading) + unitTighten (a unit's comparator) + readSettingsUnit (a unit's raw-value reading; spellings normalized, everything else unreadable), pure/browser-safe
|
|
20
22
|
export * from "./scheduler-store.js"; // R7 self-wake scheduler store CONTRACT (pure: type + parse/serialize + SchedulerStore interface; fs binding in ./node)
|
|
21
23
|
export * from "./cross-session.js"; // CC-parity `settings.json` cross-session keys (crossSessionInbound / dialogExpiry) — pure/browser-safe
|
|
22
24
|
export * from "./hooks.js"; // CC-parity hooks contract (33-event set + owner map + per-event input/output shapes + the config surface: classifyHooksConfig and its zod faces HooksConfig / HookEntry …) & stop attribution (stoppedBy) — pure/browser-safe
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type McpContentOrigin } from "./types.js";
|
|
2
|
-
/** 值的**种类**(不回显值本身)。 */
|
|
2
|
+
/** 值的**种类**(不回显值本身)。总(不抛):经 {@link inspectSafely}。 */
|
|
3
3
|
export declare function describeValueShape(v: unknown): string;
|
|
4
|
-
/** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。 */
|
|
4
|
+
/** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。总(不抛):经 {@link inspectSafely}。 */
|
|
5
5
|
export declare function describeNotStringArray(v: unknown): string;
|
|
6
|
-
/** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。 */
|
|
6
|
+
/** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。总(不抛):经 {@link inspectSafely}。 */
|
|
7
7
|
export declare function describeNotStringRecord(v: unknown): string;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* 字符串数组(空数组合法)。与传输读器读 `args` 是**同一只读法**(`./definition-copy` 的 `captureStringArray`:按下标、自有下标、一次读、非串即坏、不走迭代协议):
|
|
10
|
+
* 稀疏数组(洞)、`length` 读不出安全非负整数的数组、读元素时抛的数组都不是 —— 洞不顺原型链读到 `Object.prototype` / `Array.prototype` 上的数字键。
|
|
11
|
+
*/
|
|
9
12
|
export declare function isStringArray(v: unknown): v is string[];
|
|
10
13
|
/**
|
|
11
14
|
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、自有 `__proto__` 成员、有非串成员的对象都不是)。与传输读器读 `env` / `headers` 是**同一个**定义
|
|
@@ -58,7 +61,7 @@ export type RequestMcpTransport = {
|
|
|
58
61
|
* 条目内一处坏形:`path` = 条目内的键路径(`transport.url`),`reason` = 判官的原因句(只说形与词表,不回显值)。
|
|
59
62
|
*
|
|
60
63
|
* `env` / `headers` 这两张「名 → 串」记录上的坏形另带两格机读位,调用方分流不必解析句子:`fault` = 闭集词 ——
|
|
61
|
-
* · `not_plain_record`:这个键的值不是纯数据记录(不是对象 / 数组 / `null
|
|
64
|
+
* · `not_plain_record`:这个键的值不是纯数据记录(不是对象 / 数组 / `null`,或不是朴素 JSON 对象 —— 原型链比座位的判据长:`Map`、`Set`、类实例);整张记录不交;
|
|
62
65
|
* · `member_not_string`:有一个成员的值不是串(或访问器抛了);整张记录不交,`member` 点名它;
|
|
63
66
|
* · `member_name_reserved`:有一个成员叫 `__proto__`(引擎的声明收据拒这个名)。成员的值是串 ⇒ 只剔这个成员、其余照交,`path` 是成员级(`transport.env.__proto__`);
|
|
64
67
|
* 成员的值也不是串 ⇒ 整张记录不交,`path` 是键级,句子一次说全两件事 —— 名坏优先、值坏并列,所以词仍是 `member_name_reserved`。
|
|
@@ -84,7 +87,7 @@ export type RequestMcpTransportRead = {
|
|
|
84
87
|
* · 硬坏形(`transport` 不是对象 / `kind` 出闭集 / stdio 缺 `command` / http 的 `url` 不是 http(s))⇒ `ok:false`,`path` 是读器点名的键路径;
|
|
85
88
|
* · 软坏形(`args` 不是串数组 / `env` `headers` 不是纯数据的串记录 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
|
|
86
89
|
* (受理新提交的门据它响亮拒)。`assertionHeader` 不在 `lost` 里:两个身份头的键组合与 `assertionHeader` 的值形由调用方在读之前对原始传输判。
|
|
87
|
-
* · `env` / `headers` 只收**纯数据**的记录({@link readPlainStringRecord})
|
|
90
|
+
* · `env` / `headers` 只收**纯数据**的记录({@link readPlainStringRecord}):必须是朴素 JSON 对象(`./plain-json-object` 的判据;`Map`、`Set`、类实例整张不收),每个自有成员的值都是串;
|
|
88
91
|
* 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)。名叫 `__proto__` 的成员(JSON 文本铸得出这个自有键,对象字面量写不出)不进副本、单独记进 `lost`。
|
|
89
92
|
* 坏形的 `lost` 条目带机读位(见 {@link McpEntryKeyIssue})。
|
|
90
93
|
* url 的拒句不回显值:它常规地带凭据(userinfo)。
|