@sema-agent/settings-schema 14.6.0 → 14.8.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 +475 -0
- package/README.md +89 -5
- package/conformance/VERSION +1 -1
- package/conformance/cases/c84-sandbox-git-block-carried-verbatim/expected.json +153 -0
- package/conformance/cases/c84-sandbox-git-block-carried-verbatim/input.json +23 -0
- package/conformance/cases/c84-sandbox-git-block-carried-verbatim/meta.json +5 -0
- package/conformance/cases/c85-sandbox-git-refused-block-still-carried/expected.json +153 -0
- package/conformance/cases/c85-sandbox-git-refused-block-still-carried/input.json +23 -0
- package/conformance/cases/c85-sandbox-git-refused-block-still-carried/meta.json +5 -0
- package/dist/api/auth-bridge.d.ts +16 -2
- package/dist/api/auth-bridge.js +59 -24
- package/dist/api/credentials.d.ts +281 -0
- package/dist/api/credentials.js +149 -0
- package/dist/api/scopes.js +5 -1
- package/dist/atomic-write.d.ts +15 -0
- package/dist/atomic-write.js +109 -5
- package/dist/catalog-manages.d.ts +15 -0
- package/dist/catalog-manages.js +60 -0
- package/dist/center-refs.d.ts +68 -0
- package/dist/center-refs.js +70 -0
- package/dist/config-d-file.js +33 -15
- package/dist/config-dir-migration.js +1 -0
- package/dist/config-fns.js +147 -81
- package/dist/config-root-documents.d.ts +106 -0
- package/dist/config-root-documents.js +93 -0
- package/dist/config-root-node.d.ts +117 -0
- package/dist/config-root-node.js +260 -0
- package/dist/conformance/canonical.js +4 -4
- package/dist/conformance/contract-adapter.js +26 -1
- package/dist/conformance/corpus.js +4 -4
- package/dist/conformance/derive.js +2 -2
- package/dist/conformance/run.d.ts +3 -0
- package/dist/conformance/run.js +53 -12
- package/dist/conformance/temp-roots.d.ts +31 -0
- package/dist/conformance/temp-roots.js +184 -0
- package/dist/cross-session.d.ts +4 -4
- package/dist/declared-capture.d.ts +41 -0
- package/dist/declared-capture.js +344 -0
- package/dist/default-model.d.ts +1 -1
- package/dist/default-model.js +5 -4
- package/dist/env-model-table.d.ts +163 -0
- package/dist/env-model-table.js +257 -0
- package/dist/file-edit.js +11 -15
- package/dist/fleet.d.ts +36 -7
- package/dist/fleet.js +83 -32
- package/dist/hash.d.ts +0 -9
- package/dist/hash.js +64 -7
- package/dist/hooks-config.js +18 -3
- package/dist/http-hook-allowlists.d.ts +22 -9
- package/dist/http-hook-allowlists.js +169 -91
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/mcp-request-entry.d.ts +67 -13
- package/dist/mcp-request-entry.js +132 -93
- package/dist/mcp-server-name.d.ts +17 -0
- package/dist/mcp-server-name.js +19 -0
- package/dist/migration-receipt.d.ts +10 -6
- package/dist/migration-receipt.js +5 -3
- package/dist/node.d.ts +1 -0
- package/dist/node.js +2 -0
- package/dist/one-time-migrations.d.ts +4 -2
- package/dist/one-time-migrations.js +236 -96
- package/dist/own-key-read.d.ts +247 -0
- package/dist/own-key-read.js +449 -0
- package/dist/plain-json-object.d.ts +11 -1
- package/dist/plain-json-object.js +37 -1
- package/dist/remote-exec.js +9 -1
- package/dist/retired-form.d.ts +19 -0
- package/dist/retired-form.js +24 -1
- package/dist/safety-merge-spec.d.ts +6 -6
- package/dist/safety-merge-spec.js +50 -37
- package/dist/sandbox-git.d.ts +64 -0
- package/dist/sandbox-git.js +89 -0
- package/dist/scheduler-store.js +19 -11
- package/dist/settings-units.js +14 -12
- package/dist/tier-words.d.ts +33 -0
- package/dist/tier-words.js +43 -0
- package/dist/types.d.ts +12 -2
- package/dist/types.js +35 -6
- package/dist/url-shape.d.ts +16 -0
- package/dist/url-shape.js +18 -0
- package/dist/web-admin-credential.js +5 -2
- package/dist/web-admin-session-url.js +2 -2
- package/dist/worker-row-verdict.d.ts +11 -0
- package/dist/worker-row-verdict.js +58 -2
- package/dist/workers-rows.js +38 -10
- package/dist/zod-unwrap.d.ts +6 -0
- package/dist/zod-unwrap.js +14 -7
- package/docs/API-SURFACE.md +100 -5
- package/docs/CONSUMER-INTEGRATION.md +250 -2
- package/llms-full.txt +26 -4
- package/llms.txt +11 -4
- package/package.json +2 -2
- package/dist/definition-copy.d.ts +0 -23
- package/dist/definition-copy.js +0 -52
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { type McpContentOrigin } from "./types.js";
|
|
2
|
-
/** 值的**种类**(不回显值本身)。总(不抛)
|
|
2
|
+
/** 值的**种类**(不回显值本身)。总(不抛):形状只问座位,读不出的对象说全包唯一的「检视不了」那一句。 */
|
|
3
3
|
export declare function describeValueShape(v: unknown): string;
|
|
4
|
-
/** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。总(不抛)
|
|
4
|
+
/** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。总(不抛):`isJsonArray` 是座位的总版。 */
|
|
5
5
|
export declare function describeNotStringArray(v: unknown): string;
|
|
6
|
-
/** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。总(不抛)
|
|
6
|
+
/** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。总(不抛)。 */
|
|
7
7
|
export declare function describeNotStringRecord(v: unknown): string;
|
|
8
8
|
/**
|
|
9
|
-
* 字符串数组(空数组合法)
|
|
10
|
-
*
|
|
9
|
+
* 字符串数组(空数组合法)—— 类型守卫,所以按 `./own-key-read` 的 `captureStringArray` 的**严归类**读(整容器读,自有下标各一份描述符,非串即坏、不走迭代协议):
|
|
10
|
+
* 下标是访问器或不可枚举的数组不是(getter 一次都不调 —— 守卫为真,调用方随后的普通读读到的就是判过的那一份);稀疏数组(洞)、`length` 读不出安全非负整数的数组、
|
|
11
|
+
* 读不出的数组都不是 —— 洞不顺原型链读到 `Object.prototype` / `Array.prototype` 上的数字键。传输读器读 `args` 交出去的是副本,用同一只函数的宽归类。
|
|
11
12
|
*/
|
|
12
13
|
export declare function isStringArray(v: unknown): v is string[];
|
|
13
14
|
/**
|
|
14
|
-
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、自有 `__proto__` 成员、有非串成员的对象都不是)
|
|
15
|
-
* (
|
|
15
|
+
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、自有 `__proto__` 成员、有非串成员的对象都不是)—— 类型守卫,所以是**严归类**:成员经内核的
|
|
16
|
+
* `ownEntriesOf` 读(整容器读的严归类:每个自有串键一份描述符;访问器成员或不可枚举的成员 ⇒ 不是,getter 一次都不调 —— 守卫为真,调用方随后的普通读读到的就是判过的那一份)。
|
|
17
|
+
* 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(是串、不叫 `__proto__`)两边同一条。
|
|
16
18
|
*/
|
|
17
19
|
export declare function isStringRecord(v: unknown): v is Record<string, string>;
|
|
18
20
|
/** 引擎 `TOOL_WIRE_NAME_MAX_CHARS` 的镜像:一台 MCP server(或 A2A 对端)的申报名会成为线上工具名的命名空间段,引擎对超界名是**拒**(不截断)。 */
|
|
@@ -29,6 +31,16 @@ export declare function isUsableWireName(v: unknown): v is string;
|
|
|
29
31
|
* (制表 / 换行这类控制空白在回显里被剥掉,所以看得出是空白靠这个种类词,不靠引号里的样子);两件事并存 ⇒ 一句说全(`N chars > 128, leading or trailing whitespace`)。
|
|
30
32
|
*/
|
|
31
33
|
export declare function describeBadWireName(v: unknown): string;
|
|
34
|
+
/**
|
|
35
|
+
* 这个 MCP 服务器名能用吗:{@link isUsableWireName} 再加一臂 —— 不在保留给设备执行端远程 MCP 服务器的 `remote-` 命名空间里(`./mcp-server-name`,字面前缀比较)。
|
|
36
|
+
* 只多这一臂:线上名的判据(非空、长界、首尾空白)一字不改,别的输入两只判官逐项同答。A2A 对端名只用 {@link isUsableWireName}(它的工具名前缀是 `a2a__`,不在这个命名空间里)。
|
|
37
|
+
*/
|
|
38
|
+
export declare function isUsableMcpServerName(v: unknown): v is string;
|
|
39
|
+
/**
|
|
40
|
+
* 坏 MCP 服务器名的**可打印**摘要:线上名本身就坏 ⇒ 与 {@link describeBadWireName} 逐字同(长界这类线上名的事实先说,不被保留臂盖掉);
|
|
41
|
+
* 线上名好而落在保留命名空间里 ⇒ 引号形的回显加那一句原因。
|
|
42
|
+
*/
|
|
43
|
+
export declare function describeBadMcpServerName(v: unknown): string;
|
|
32
44
|
/**
|
|
33
45
|
* 在内容出处词表({@link MCP_CONTENT_ORIGINS})内吗。严判:非串与大小写不符(`"LOCAL"`)一律不在 —— 引擎的词是字面量联合、不做规范化;
|
|
34
46
|
* 替它「宽容一下」等于自造第二份值域语义,而这条轴上宽容的方向恰好是**放宽信任**(把一个写坏的声明读成 `"local"`)。
|
|
@@ -44,7 +56,14 @@ export declare function readMcpIdentityHeaders(transport: Record<string, unknown
|
|
|
44
56
|
principalHeader?: string;
|
|
45
57
|
assertionHeader?: string;
|
|
46
58
|
};
|
|
47
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* 请求面一条 MCP 条目的传输形。与引擎传输形的关系逐键一句(处置表,对拍 = 上游漂移门 ⑧):
|
|
61
|
+
* · `kind` / `command` / `args` / `url` / `principalHeader` —— 与引擎同键同型;
|
|
62
|
+
* · `env` / `headers` —— 请求带来的**字面件**:引擎的声明不带值(一台服务器的凭据头 / 子进程环境由部署的对端座每次发送时答),
|
|
63
|
+
* 所以这两把键交给引擎之前由消费方从声明里拿走、按它自己的对端座答;
|
|
64
|
+
* · `assertionHeader` —— 本包承载,引擎的 http 传输没有这个座(交给引擎之前由消费方剥掉);
|
|
65
|
+
* · 引擎的 `credentials`(不透明的凭据引用)—— 请求面不收:读器不读它。
|
|
66
|
+
*/
|
|
48
67
|
export type RequestMcpTransport = {
|
|
49
68
|
kind: "stdio";
|
|
50
69
|
command: string;
|
|
@@ -60,18 +79,18 @@ export type RequestMcpTransport = {
|
|
|
60
79
|
/**
|
|
61
80
|
* 条目内一处坏形:`path` = 条目内的键路径(`transport.url`),`reason` = 判官的原因句(只说形与词表,不回显值)。
|
|
62
81
|
*
|
|
63
|
-
* `env` / `headers` 这两张「名 → 串」记录上的坏形另带两格机读位,调用方分流不必解析句子:`fault` = 闭集词 ——
|
|
64
|
-
* · `not_plain_record`:这个键的值不是纯数据记录(不是对象 / 数组 / `null`,或不是朴素 JSON 对象 —— 原型链比座位的判据长:`Map`、`Set
|
|
65
|
-
* · `member_not_string
|
|
82
|
+
* `env` / `headers` 这两张「名 → 串」记录上的坏形另带两格机读位,调用方分流不必解析句子:`fault` = 闭集词({@link PLAIN_STRING_RECORD_FAULTS},与公开的记录读器 {@link readPlainStringRecord} 同一张表)——
|
|
83
|
+
* · `not_plain_record`:这个键的值不是纯数据记录(不是对象 / 数组 / `null`,或不是朴素 JSON 对象 —— 原型链比座位的判据长:`Map`、`Set`、类实例;读形状就抛的对象同);整张记录不交;
|
|
84
|
+
* · `member_not_string`:有一个成员的值不是串,或它读不出(它的访问器抛了,或读它的描述符时代理陷阱抛了);整张记录不交,`member` 点名它,两种情形句子分说;
|
|
66
85
|
* · `member_name_reserved`:有一个成员叫 `__proto__`(引擎的声明收据拒这个名)。成员的值是串 ⇒ 只剔这个成员、其余照交,`path` 是成员级(`transport.env.__proto__`);
|
|
67
|
-
* 成员的值也不是串
|
|
86
|
+
* 成员的值也不是串(或读不出)⇒ 整张记录不交,`path` 是键级,句子一次说全两件事 —— 名坏优先、值坏并列,所以词仍是 `member_name_reserved`。
|
|
68
87
|
* `member` 是成员名的**回显形**(剥控制与双向字符、截 64 字加 `…`),可直接进日志与句子,不是回查原对象的键。其余的坏形(`args` / `url` / `principalHeader` / ……)两格都不带。
|
|
69
88
|
*/
|
|
70
89
|
export interface McpEntryKeyIssue {
|
|
71
90
|
readonly path: string;
|
|
72
91
|
readonly reason: string;
|
|
73
92
|
readonly member?: string;
|
|
74
|
-
readonly fault?:
|
|
93
|
+
readonly fault?: PlainStringRecordFault;
|
|
75
94
|
}
|
|
76
95
|
/** {@link readRequestMcpTransport} 的结果:`ok:false` = 硬坏形(这条 server 无从挂载);`ok:true` 的 `lost` = 读器**丢掉**的可选子键(server 照挂、少一个声明)。 */
|
|
77
96
|
export type RequestMcpTransportRead = {
|
|
@@ -93,3 +112,38 @@ export type RequestMcpTransportRead = {
|
|
|
93
112
|
* url 的拒句不回显值:它常规地带凭据(userinfo)。
|
|
94
113
|
*/
|
|
95
114
|
export declare function readRequestMcpTransport(t: unknown): RequestMcpTransportRead;
|
|
115
|
+
/**
|
|
116
|
+
* 引擎的声明收据不收的成员名(core `mcp-server-spec`:按赋值拷贝它会变成原型):JSON 文本铸得出这个**自有**键,对象字面量写不出。
|
|
117
|
+
* 公开,因为读器交出 `reservedMember: true` 时消费方要点名它 —— 名字只在这里定一次。
|
|
118
|
+
*/
|
|
119
|
+
export declare const PLAIN_STRING_RECORD_RESERVED_MEMBER = "__proto__";
|
|
120
|
+
/**
|
|
121
|
+
* 「名 → 串」记录读不交(或交时点名保留名)的**原因词**,闭集,三个词、一张表:
|
|
122
|
+
* 整张一词(`not_plain_record`)· 成员级一词(`member_not_string`:值读成了别的种类,或读成员时抛了 —— 句子分说)· 名字级一词(`member_name_reserved`)。
|
|
123
|
+
* 词的意义逐词写在 {@link McpEntryKeyIssue}。消费方按词分派,不读句子;读器的读数 {@link PlainStringRecordRead} 与传输条目的机读位 {@link McpEntryKeyIssue} 的 `fault` 都由这一张表推出。
|
|
124
|
+
* 多一个词而消费方的穷尽 `switch` 不补 ⇒ 编译红(提货成本,不是静默)。
|
|
125
|
+
*/
|
|
126
|
+
export declare const PLAIN_STRING_RECORD_FAULTS: readonly ["not_plain_record", "member_not_string", "member_name_reserved"];
|
|
127
|
+
export type PlainStringRecordFault = (typeof PLAIN_STRING_RECORD_FAULTS)[number];
|
|
128
|
+
/**
|
|
129
|
+
* {@link readPlainStringRecord} 的读数:`ok:false` = 记录不交(`fault` = 原因词;`detail` 是接在 `must be a plain object of string values — ` 后面的句子;坏在某个成员时 `member` 点名它的回显形);
|
|
130
|
+
* `ok:true` = 交出去的纯数据副本,`reservedMember` = 记录里有一个名叫 {@link PLAIN_STRING_RECORD_RESERVED_MEMBER} 的成员(它没进副本)。
|
|
131
|
+
*/
|
|
132
|
+
export type PlainStringRecordRead = {
|
|
133
|
+
readonly ok: true;
|
|
134
|
+
readonly record: Record<string, string>;
|
|
135
|
+
readonly reservedMember: boolean;
|
|
136
|
+
} | {
|
|
137
|
+
readonly ok: false;
|
|
138
|
+
readonly detail: string;
|
|
139
|
+
readonly fault: PlainStringRecordFault;
|
|
140
|
+
readonly member?: string;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* 读一份「名 → 串」记录 —— **公开读口**:传输读器读 `env` / `headers` 用的就是它,消费方读自己的「名 → 串」记录(中心名册的引用表)调同一只,不抄镜像。从不抛。记录交不交:是朴素 JSON 对象(座位的判据,见 `./plain-json-object`:原型是 `null`,或原型自己的原型是 `null`;纯数据 —— `Map`、`Set`、类实例、建在原型上的记录都不是),
|
|
144
|
+
* 且每个成员的值都是串。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算 —— 引擎的收据按全部自有属性名读),值经内核 `readKey` 读(一份描述符:数据属性取值,访问器调一次,不问 `get` 陷阱;访问器或描述符 trap 抛了同判为坏 —— 词与「值不是串」同是 `member_not_string`,句子说读不出)。
|
|
145
|
+
* 读原型与成员名时代理 trap 抛了 ⇒ 整张不交(读器对调用方递来的任何对象都不抛;`JSON` 存体里不会有代理,这条只落在直接调读器的嵌入方身上)。
|
|
146
|
+
* 交出去的是副本(原型 `Object.prototype`、成员全可枚举),调用方的对象不被交出去、也不被改;名叫 `__proto__` 的成员不进副本,`reservedMember` 记下它。
|
|
147
|
+
* 名叫 `__proto__` 而值也坏的成员,句子一次说全两件事(名坏在前、值坏并列):调用方照值改完再撞第二条是两步才改对。
|
|
148
|
+
*/
|
|
149
|
+
export declare function readPlainStringRecord(v: unknown): PlainStringRecordRead;
|
|
@@ -14,78 +14,93 @@
|
|
|
14
14
|
*
|
|
15
15
|
* ## 依赖的引擎事实(镜像 + 漂移门)
|
|
16
16
|
* · 申报名的线长界 {@link TOOL_WIRE_NAME_MAX_CHARS} = 引擎同名常量(值级对拍:上游漂移门 ⑧);
|
|
17
|
-
* · 请求面传输形 {@link RequestMcpTransport}
|
|
17
|
+
* · 请求面传输形 {@link RequestMcpTransport} 对引擎 MCP 条目的传输形是一张处置表,两边每个键恰好一行(编译期两向对拍:门 ⑧)——
|
|
18
|
+
* `kind` / `command` / `args` / `url` / `principalHeader` 与引擎同键同型;`env` / `headers` 是请求带来的字面件(引擎的声明不带值);
|
|
19
|
+
* `assertionHeader` 本包承载(引擎没有这个座);引擎的 `credentials`(不透明的凭据引用)请求面不收;
|
|
18
20
|
* · 内容出处词表 = {@link MCP_CONTENT_ORIGINS}(已有的镜像,门 ⑥);
|
|
19
21
|
* · url 前缀守卫 = `./url-shape` 的 `startsWithHttpScheme`(本包判 URL 的正则只在那一个模块)。它只看前缀,不是形判官 —— 同一个串两口可以答得不同,两张分歧表与收口形(S-2106)写在 `./url-shape` 头注「第二个答案」。
|
|
20
22
|
*
|
|
21
23
|
* ## 与配置面的关系(如实)
|
|
22
24
|
* 配置面的 MCP 条目(`McpServerSpec`,部署声明:凭据只许 env-NAME 引用)与请求面(提交体:可带字面凭据 `env` / `headers`)是两份契约,本模块只管后者。
|
|
23
25
|
* 两份契约对「名」与「url」今天给的判据不同(配置面:名按字符集、url 按 `NON_SECRET_URL`;请求面:名按长度界、url 按 http(s) 前缀),本版刻意不动
|
|
24
|
-
* (语义逐字搬)
|
|
26
|
+
* (语义逐字搬),是一项已知待收的分歧。`remote-` 保留前缀这一臂也只在请求面(`isUsableMcpServerName`):配置面 `McpServerSpec.name` 不拒它(此前合法的名被拒是校验收紧,归 major,不在 minor 里落),是又一处两面不齐的已知分歧。
|
|
25
27
|
*/
|
|
26
28
|
import { echoCallerText } from "./caller-text-echo.js";
|
|
27
|
-
import { captureStringArray, dataDescriptor } from "./
|
|
28
|
-
import { isPlainJsonObject, jsonShapeOf } from "./plain-json-object.js";
|
|
29
|
+
import { UNINSPECTABLE, captureStringArray, dataDescriptor, inspect, ownEntriesOf, ownFieldsOf, ownValueOf, readKey } from "./own-key-read.js";
|
|
30
|
+
import { isJsonArray, isPlainJsonObject, jsonShapeOf } from "./plain-json-object.js";
|
|
29
31
|
import { MCP_CONTENT_ORIGINS } from "./types.js";
|
|
32
|
+
import { RESERVED_MCP_SERVER_NAME_REASON, isReservedMcpServerName } from "./mcp-server-name.js";
|
|
30
33
|
import { startsWithHttpScheme } from "./url-shape.js";
|
|
31
34
|
// ── 值的种类描述(拒句只说「你写的是什么形」,不回显值:值可能带凭据、可能任意长)──────────────────────────────────────
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
/** 值的**种类词**(不回显值本身):`null` / `array` / `typeof` 词(`object` / `function` / `string` …);读不出的对象 ⇒ 全包唯一的那一句。形状只问座位(总,不抛):两只坏值描述器共用这一只。 */
|
|
36
|
+
function describeKindWord(v) {
|
|
37
|
+
const shape = jsonShapeOf(v);
|
|
38
|
+
switch (shape) {
|
|
39
|
+
case "null":
|
|
40
|
+
return "null";
|
|
41
|
+
case "array":
|
|
42
|
+
return "array";
|
|
43
|
+
case "uninspectable":
|
|
44
|
+
return UNINSPECTABLE;
|
|
45
|
+
case "plain":
|
|
46
|
+
case "non_plain_object":
|
|
47
|
+
case "primitive":
|
|
48
|
+
return typeof v;
|
|
49
|
+
default: {
|
|
50
|
+
const unhandled = shape;
|
|
51
|
+
throw new Error(`describeKindWord: unhandled shape ${String(unhandled)}`);
|
|
52
|
+
}
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
|
-
/**
|
|
47
|
-
function inspectSafely(describe) {
|
|
48
|
-
const read = inspect(describe);
|
|
49
|
-
return read.ok ? read.value : read.sentence;
|
|
50
|
-
}
|
|
51
|
-
/** 值的**种类**(不回显值本身)。总(不抛):经 {@link inspectSafely}。 */
|
|
55
|
+
/** 值的**种类**(不回显值本身)。总(不抛):形状只问座位,读不出的对象说全包唯一的「检视不了」那一句。 */
|
|
52
56
|
export function describeValueShape(v) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
if (v === undefined)
|
|
58
|
+
return "nothing (the key is absent)";
|
|
59
|
+
const shape = jsonShapeOf(v);
|
|
60
|
+
switch (shape) {
|
|
61
|
+
case "null":
|
|
57
62
|
return "null";
|
|
58
|
-
|
|
63
|
+
case "array":
|
|
59
64
|
return "an array";
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
case "uninspectable":
|
|
66
|
+
return UNINSPECTABLE;
|
|
67
|
+
case "plain":
|
|
68
|
+
case "non_plain_object":
|
|
69
|
+
return typeof v === "function" ? "a function" : "an object";
|
|
70
|
+
case "primitive":
|
|
71
|
+
return typeof v === "string" ? (v.length === 0 ? "an empty string" : `a string of ${v.length} characters`) : `a ${typeof v}`;
|
|
72
|
+
default: {
|
|
73
|
+
const unhandled = shape;
|
|
74
|
+
throw new Error(`describeValueShape: unhandled shape ${String(unhandled)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
66
77
|
}
|
|
67
|
-
/** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。总(不抛)
|
|
78
|
+
/** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。总(不抛):`isJsonArray` 是座位的总版。 */
|
|
68
79
|
export function describeNotStringArray(v) {
|
|
69
|
-
return
|
|
80
|
+
return isJsonArray(v) ? "an array with a non-string element" : describeValueShape(v);
|
|
70
81
|
}
|
|
71
|
-
/** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。总(不抛)
|
|
82
|
+
/** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。总(不抛)。 */
|
|
72
83
|
export function describeNotStringRecord(v) {
|
|
73
|
-
return
|
|
84
|
+
return isPlainJsonObject(v) ? "an object with a non-string value" : describeValueShape(v);
|
|
74
85
|
}
|
|
75
86
|
/**
|
|
76
|
-
* 字符串数组(空数组合法)
|
|
77
|
-
*
|
|
87
|
+
* 字符串数组(空数组合法)—— 类型守卫,所以按 `./own-key-read` 的 `captureStringArray` 的**严归类**读(整容器读,自有下标各一份描述符,非串即坏、不走迭代协议):
|
|
88
|
+
* 下标是访问器或不可枚举的数组不是(getter 一次都不调 —— 守卫为真,调用方随后的普通读读到的就是判过的那一份);稀疏数组(洞)、`length` 读不出安全非负整数的数组、
|
|
89
|
+
* 读不出的数组都不是 —— 洞不顺原型链读到 `Object.prototype` / `Array.prototype` 上的数字键。传输读器读 `args` 交出去的是副本,用同一只函数的宽归类。
|
|
78
90
|
*/
|
|
79
91
|
export function isStringArray(v) {
|
|
80
|
-
return captureStringArray(v) !== undefined;
|
|
92
|
+
return captureStringArray(v, "strict") !== undefined;
|
|
81
93
|
}
|
|
82
94
|
/**
|
|
83
|
-
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、自有 `__proto__` 成员、有非串成员的对象都不是)
|
|
84
|
-
* (
|
|
95
|
+
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、自有 `__proto__` 成员、有非串成员的对象都不是)—— 类型守卫,所以是**严归类**:成员经内核的
|
|
96
|
+
* `ownEntriesOf` 读(整容器读的严归类:每个自有串键一份描述符;访问器成员或不可枚举的成员 ⇒ 不是,getter 一次都不调 —— 守卫为真,调用方随后的普通读读到的就是判过的那一份)。
|
|
97
|
+
* 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(是串、不叫 `__proto__`)两边同一条。
|
|
85
98
|
*/
|
|
86
99
|
export function isStringRecord(v) {
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
if (!isPlainJsonObject(v))
|
|
101
|
+
return false;
|
|
102
|
+
const entries = ownEntriesOf(v);
|
|
103
|
+
return entries.ok && entries.value.every(([name, value]) => name !== PLAIN_STRING_RECORD_RESERVED_MEMBER && typeof value === "string");
|
|
89
104
|
}
|
|
90
105
|
// ── 调用方写的串的回显口径 ──────────────────────────────────────────────────────────────────────────
|
|
91
106
|
/** 句子里点名一个值的写法:{@link echoCallerText} 外加一对双引号;内部的引号与反斜杠逐字(不转义)。 */
|
|
@@ -115,7 +130,7 @@ export function isUsableWireName(v) {
|
|
|
115
130
|
*/
|
|
116
131
|
export function describeBadWireName(v) {
|
|
117
132
|
if (typeof v !== "string")
|
|
118
|
-
return
|
|
133
|
+
return describeKindWord(v);
|
|
119
134
|
if (v.length === 0)
|
|
120
135
|
return "empty";
|
|
121
136
|
const head = quotedCallerText(v);
|
|
@@ -124,6 +139,23 @@ export function describeBadWireName(v) {
|
|
|
124
139
|
return `${head} (${tooLong})`;
|
|
125
140
|
return `${head} (${v.length > TOOL_WIRE_NAME_MAX_CHARS ? `${tooLong}, ` : ""}leading or trailing whitespace)`;
|
|
126
141
|
}
|
|
142
|
+
// ── MCP 服务器名(线上名 + 保留命名空间)──────────────────────────────────────────────────────────────────
|
|
143
|
+
/**
|
|
144
|
+
* 这个 MCP 服务器名能用吗:{@link isUsableWireName} 再加一臂 —— 不在保留给设备执行端远程 MCP 服务器的 `remote-` 命名空间里(`./mcp-server-name`,字面前缀比较)。
|
|
145
|
+
* 只多这一臂:线上名的判据(非空、长界、首尾空白)一字不改,别的输入两只判官逐项同答。A2A 对端名只用 {@link isUsableWireName}(它的工具名前缀是 `a2a__`,不在这个命名空间里)。
|
|
146
|
+
*/
|
|
147
|
+
export function isUsableMcpServerName(v) {
|
|
148
|
+
return isUsableWireName(v) && !isReservedMcpServerName(v);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 坏 MCP 服务器名的**可打印**摘要:线上名本身就坏 ⇒ 与 {@link describeBadWireName} 逐字同(长界这类线上名的事实先说,不被保留臂盖掉);
|
|
152
|
+
* 线上名好而落在保留命名空间里 ⇒ 引号形的回显加那一句原因。
|
|
153
|
+
*/
|
|
154
|
+
export function describeBadMcpServerName(v) {
|
|
155
|
+
if (!isUsableWireName(v))
|
|
156
|
+
return describeBadWireName(v);
|
|
157
|
+
return `${quotedCallerText(v)} (${RESERVED_MCP_SERVER_NAME_REASON})`;
|
|
158
|
+
}
|
|
127
159
|
// ── 内容出处词 ──────────────────────────────────────────────────────────────────────────────────────
|
|
128
160
|
const CONTENT_ORIGIN_WORDS = new Set(MCP_CONTENT_ORIGINS);
|
|
129
161
|
/**
|
|
@@ -135,7 +167,7 @@ export function isContentOriginWord(v) {
|
|
|
135
167
|
}
|
|
136
168
|
/** 坏内容出处值的**可打印**摘要:串 ⇒ 引号形的回显({@link quotedCallerText}:剥控制与双向字符、截 64 字);非串只说种类。 */
|
|
137
169
|
export function describeBadContentOrigin(v) {
|
|
138
|
-
return typeof v === "string" ? quotedCallerText(v) :
|
|
170
|
+
return typeof v === "string" ? quotedCallerText(v) : describeKindWord(v);
|
|
139
171
|
}
|
|
140
172
|
// ── 两个身份头 ──────────────────────────────────────────────────────────────────────────────────────
|
|
141
173
|
/**
|
|
@@ -155,35 +187,35 @@ export function readMcpIdentityHeaders(transport) {
|
|
|
155
187
|
/**
|
|
156
188
|
* 两个身份头的**唯一读法**(公开读者与传输快照共用):只有 `principalHeader` **干净地读成缺席**时才读 `assertionHeader`;`principalHeader` 在场(不论值形)⇒ `assertionHeader` 一次不读;
|
|
157
189
|
* 任何一个读不出 ⇒ 整体读不出(`ok:false`,向上传),不折成 `undefined` —— 不然「principal 读不出」会落到 assertion、读它的 getter。
|
|
190
|
+
* **只读自有键**(身份只认自有键):键只靠继承可见(含 `Object.prototype` 被污染成数据值)⇒ 不读它的值、当缺席,但**记下**(`principalInherited` / `assertionInherited`)—— 传输读器据此点名,
|
|
191
|
+
* 身份轴上不静默;公开读者没有 `lost` 通道,只读值。
|
|
158
192
|
*/
|
|
159
193
|
function identityFieldsOf(record) {
|
|
160
|
-
const principal =
|
|
161
|
-
if (
|
|
162
|
-
return
|
|
163
|
-
if (principal.value !== undefined)
|
|
164
|
-
return { ok: true, value: { principalHeader: principal.value, assertionHeader: undefined } };
|
|
165
|
-
const assertion = ownValueOf(record, "assertionHeader");
|
|
166
|
-
if (!assertion.ok)
|
|
167
|
-
return assertion;
|
|
168
|
-
return { ok: true, value: { principalHeader: undefined, assertionHeader: assertion.value } };
|
|
169
|
-
}
|
|
170
|
-
/** 一个字段的值,**只读自有属性**(`Object.hasOwn`):原型链上的值 / 被污染的 getter 一律当缺席。**自身就是守护读**(经 `inspect`):`getOwnPropertyDescriptor` 陷阱抛、会抛的自有 getter、
|
|
171
|
-
* 已撤销的 Proxy ⇒ `ok:false`(读不出),调用方各自决定怎么办、不再各包一层。 */
|
|
172
|
-
function ownValueOf(record, key) {
|
|
173
|
-
return inspect(() => (Object.hasOwn(record, key) ? record[key] : undefined));
|
|
174
|
-
}
|
|
175
|
-
/** 按顺序读若干字段,**第一个读不出的就停**(后面的一个不读),整体读不出向上传。 */
|
|
176
|
-
function ownFieldsOf(record, keys) {
|
|
177
|
-
const out = Object.create(null);
|
|
178
|
-
for (const key of keys) {
|
|
179
|
-
const read = ownValueOf(record, key);
|
|
180
|
-
if (!read.ok)
|
|
181
|
-
return read;
|
|
182
|
-
Object.defineProperty(out, key, dataDescriptor(read.value));
|
|
194
|
+
const principal = readKey(record, "principalHeader");
|
|
195
|
+
if (principal.presence === "unreadable")
|
|
196
|
+
return { ok: false, sentence: UNINSPECTABLE };
|
|
197
|
+
if (principal.presence === "own" && principal.value !== undefined) {
|
|
198
|
+
return { ok: true, value: { principalHeader: principal.value, assertionHeader: undefined, principalInherited: false, assertionInherited: false } };
|
|
183
199
|
}
|
|
184
|
-
|
|
200
|
+
const assertion = readKey(record, "assertionHeader");
|
|
201
|
+
if (assertion.presence === "unreadable")
|
|
202
|
+
return { ok: false, sentence: UNINSPECTABLE };
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
value: {
|
|
206
|
+
principalHeader: undefined,
|
|
207
|
+
assertionHeader: assertion.presence === "own" ? assertion.value : undefined,
|
|
208
|
+
principalInherited: principal.presence === "inherited",
|
|
209
|
+
assertionInherited: assertion.presence === "inherited",
|
|
210
|
+
},
|
|
211
|
+
};
|
|
185
212
|
}
|
|
186
213
|
const transportBad = (path, reason) => ({ ok: false, issue: { path, reason } });
|
|
214
|
+
/** 身份头只靠继承可见时的 `lost` 条目:不读它的值,但点名(身份轴不静默;句子不回显值)。机读位不带(`fault` 的闭集不为它加词)。 */
|
|
215
|
+
const inheritedIdentityIssue = (key) => ({
|
|
216
|
+
path: `transport.${key}`,
|
|
217
|
+
reason: "inherited key ignored (the key is visible only through the prototype chain; a transport's identity headers are read from its own keys)",
|
|
218
|
+
});
|
|
187
219
|
/**
|
|
188
220
|
* 请求面 `transport` 的**唯一读器**:读器投影 transport 用它,发送前 / 受理时的判形读它报的 `lost` —— 同一只函数两个消费点,「读器丢了什么」与「门拒什么」不可能分岔。
|
|
189
221
|
* · 硬坏形(`transport` 不是对象 / `kind` 出闭集 / stdio 缺 `command` / http 的 `url` 不是 http(s))⇒ `ok:false`,`path` 是读器点名的键路径;
|
|
@@ -214,7 +246,7 @@ export function readRequestMcpTransport(t) {
|
|
|
214
246
|
}
|
|
215
247
|
}
|
|
216
248
|
const tr = t;
|
|
217
|
-
//
|
|
249
|
+
// 字段只读一次:经读内核把「这一种传输要读的字段」读成局部快照(描述符 / `has` 陷阱抛的 Proxy / 会抛的自有 getter ⇒ 整只不交,不抛;`get` 陷阱不被问),之后只读快照;这一种传输用不到的字段一次也不读
|
|
218
250
|
const snapshotRead = snapshotTransport(tr);
|
|
219
251
|
if (!snapshotRead.ok)
|
|
220
252
|
return transportBad("transport", `must be an object with a "kind" of "stdio" or "http" — got ${snapshotRead.sentence}`);
|
|
@@ -225,7 +257,7 @@ export function readRequestMcpTransport(t) {
|
|
|
225
257
|
if (typeof snap.command !== "string" || snap.command.length === 0)
|
|
226
258
|
return transportBad("transport.command", `must be a non-empty string (the command to run) — got ${describeValueShape(snap.command)}`);
|
|
227
259
|
const rawArgs = snap.args; // 交出去的是副本(不是调用方数组的原引用),稀疏 / 读不出的数组整键丢、进 `lost`
|
|
228
|
-
const args = rawArgs === undefined ? undefined : captureStringArray(rawArgs);
|
|
260
|
+
const args = rawArgs === undefined ? undefined : captureStringArray(rawArgs, "wide");
|
|
229
261
|
const argsIssue = rawArgs !== undefined && args === undefined ? { path: "transport.args", reason: `must be an array of strings — got ${describeNotStringArray(rawArgs)}` } : undefined;
|
|
230
262
|
const env = readTransportRecord("transport.env", snap.env);
|
|
231
263
|
return {
|
|
@@ -256,7 +288,7 @@ export function readRequestMcpTransport(t) {
|
|
|
256
288
|
...(headers.record !== undefined ? { headers: headers.record } : {}),
|
|
257
289
|
...ids,
|
|
258
290
|
},
|
|
259
|
-
lost: issuesOf(...headers.issues, principalIssue),
|
|
291
|
+
lost: issuesOf(...headers.issues, principalIssue, snap.principalInherited ? inheritedIdentityIssue("principalHeader") : undefined, snap.assertionInherited ? inheritedIdentityIssue("assertionHeader") : undefined),
|
|
260
292
|
};
|
|
261
293
|
}
|
|
262
294
|
case "other":
|
|
@@ -286,14 +318,24 @@ function snapshotTransport(tr) {
|
|
|
286
318
|
const ids = identityFieldsOf(tr);
|
|
287
319
|
if (!ids.ok)
|
|
288
320
|
return ids;
|
|
289
|
-
return { ok: true, value: { kind: "http", url: fields.value.url, headers: fields.value.headers, principalHeader: ids.value.principalHeader, assertionHeader: ids.value.assertionHeader } };
|
|
321
|
+
return { ok: true, value: { kind: "http", url: fields.value.url, headers: fields.value.headers, principalHeader: ids.value.principalHeader, assertionHeader: ids.value.assertionHeader, principalInherited: ids.value.principalInherited, assertionInherited: ids.value.assertionInherited } };
|
|
290
322
|
}
|
|
291
323
|
return { ok: true, value: { kind: "other", value: kind.value } };
|
|
292
324
|
}
|
|
293
325
|
// ── 「名 → 串」记录的读法(`env` / `headers`)────────────────────────────────────────────────────────
|
|
294
|
-
/**
|
|
295
|
-
|
|
296
|
-
|
|
326
|
+
/**
|
|
327
|
+
* 引擎的声明收据不收的成员名(core `mcp-server-spec`:按赋值拷贝它会变成原型):JSON 文本铸得出这个**自有**键,对象字面量写不出。
|
|
328
|
+
* 公开,因为读器交出 `reservedMember: true` 时消费方要点名它 —— 名字只在这里定一次。
|
|
329
|
+
*/
|
|
330
|
+
export const PLAIN_STRING_RECORD_RESERVED_MEMBER = "__proto__";
|
|
331
|
+
const RESERVED_MEMBER_FAULT = `no member of a declaration may be named "${PLAIN_STRING_RECORD_RESERVED_MEMBER}"`;
|
|
332
|
+
/**
|
|
333
|
+
* 「名 → 串」记录读不交(或交时点名保留名)的**原因词**,闭集,三个词、一张表:
|
|
334
|
+
* 整张一词(`not_plain_record`)· 成员级一词(`member_not_string`:值读成了别的种类,或读成员时抛了 —— 句子分说)· 名字级一词(`member_name_reserved`)。
|
|
335
|
+
* 词的意义逐词写在 {@link McpEntryKeyIssue}。消费方按词分派,不读句子;读器的读数 {@link PlainStringRecordRead} 与传输条目的机读位 {@link McpEntryKeyIssue} 的 `fault` 都由这一张表推出。
|
|
336
|
+
* 多一个词而消费方的穷尽 `switch` 不补 ⇒ 编译红(提货成本,不是静默)。
|
|
337
|
+
*/
|
|
338
|
+
export const PLAIN_STRING_RECORD_FAULTS = ["not_plain_record", "member_not_string", "member_name_reserved"];
|
|
297
339
|
/** 记录不交时 `detail` 的前半句:它是哪一种不朴素(null / 数组 / 原始值各一句,对象里的非朴素一句)。`shape` 穷举 —— 新词不补 = 编译红。 */
|
|
298
340
|
function notPlainDetail(shape, v) {
|
|
299
341
|
switch (shape) {
|
|
@@ -316,13 +358,13 @@ function notPlainDetail(shape, v) {
|
|
|
316
358
|
}
|
|
317
359
|
}
|
|
318
360
|
/**
|
|
319
|
-
* 读一份「名 →
|
|
320
|
-
* 且每个成员的值都是串。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算 —— 引擎的收据按全部自有属性名读)
|
|
361
|
+
* 读一份「名 → 串」记录 —— **公开读口**:传输读器读 `env` / `headers` 用的就是它,消费方读自己的「名 → 串」记录(中心名册的引用表)调同一只,不抄镜像。从不抛。记录交不交:是朴素 JSON 对象(座位的判据,见 `./plain-json-object`:原型是 `null`,或原型自己的原型是 `null`;纯数据 —— `Map`、`Set`、类实例、建在原型上的记录都不是),
|
|
362
|
+
* 且每个成员的值都是串。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算 —— 引擎的收据按全部自有属性名读),值经内核 `readKey` 读(一份描述符:数据属性取值,访问器调一次,不问 `get` 陷阱;访问器或描述符 trap 抛了同判为坏 —— 词与「值不是串」同是 `member_not_string`,句子说读不出)。
|
|
321
363
|
* 读原型与成员名时代理 trap 抛了 ⇒ 整张不交(读器对调用方递来的任何对象都不抛;`JSON` 存体里不会有代理,这条只落在直接调读器的嵌入方身上)。
|
|
322
364
|
* 交出去的是副本(原型 `Object.prototype`、成员全可枚举),调用方的对象不被交出去、也不被改;名叫 `__proto__` 的成员不进副本,`reservedMember` 记下它。
|
|
323
365
|
* 名叫 `__proto__` 而值也坏的成员,句子一次说全两件事(名坏在前、值坏并列):调用方照值改完再撞第二条是两步才改对。
|
|
324
366
|
*/
|
|
325
|
-
function readPlainStringRecord(v) {
|
|
367
|
+
export function readPlainStringRecord(v) {
|
|
326
368
|
// 形状归座位旁的分类(不在这里再推一遍;座位总 —— 读形状就抛的对象答 `uninspectable`)。成员名的读取(`ownKeys` 陷阱抛)读不出的对象也不是纯数据。
|
|
327
369
|
// 读器不抛出去 —— 重放存量提交体的腿被一次抛错砖死比整键不交糟,而「整键不交」是响亮的(进 `lost`)。检视律与句都在 `inspect`。
|
|
328
370
|
const shape = jsonShapeOf(v);
|
|
@@ -337,26 +379,23 @@ function readPlainStringRecord(v) {
|
|
|
337
379
|
const members = Object.create(null);
|
|
338
380
|
let reservedMember = false;
|
|
339
381
|
for (const name of names) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
value = d === undefined ? undefined : Object.hasOwn(d, "value") ? d.value : d.get === undefined ? undefined : Reflect.apply(d.get, v, []);
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
unreadable = true;
|
|
348
|
-
}
|
|
382
|
+
// 成员经内核读(契约见函数头注):一份描述符里取值 —— 数据属性取值、访问器调一次、从不问 Proxy 的 `get` 陷阱;成员在两次读之间被删掉 ⇒ 不是串(不读继承来的值)
|
|
383
|
+
const read = readKey(v, name);
|
|
384
|
+
const unreadable = read.presence === "unreadable";
|
|
385
|
+
const value = read.presence === "own" ? read.value : undefined;
|
|
349
386
|
if (unreadable || typeof value !== "string") {
|
|
350
387
|
const valueFault = unreadable ? "could not be read (its getter threw)" : `must be a string — got ${describeValueShape(value)}`;
|
|
351
|
-
const reserved = name ===
|
|
388
|
+
const reserved = name === PLAIN_STRING_RECORD_RESERVED_MEMBER;
|
|
389
|
+
// 词:名坏优先(句子里值坏并列说全);名没问题时,读不出与值不是串同词,句子分说
|
|
390
|
+
const fault = reserved ? "member_name_reserved" : "member_not_string";
|
|
352
391
|
return {
|
|
353
392
|
ok: false,
|
|
354
|
-
fault
|
|
393
|
+
fault,
|
|
355
394
|
member: echoCallerText(name),
|
|
356
395
|
detail: `its member "${echoCallerText(name)}" ${reserved ? `is not allowed (${RESERVED_MEMBER_FAULT}) and its value ${valueFault}` : valueFault}`,
|
|
357
396
|
};
|
|
358
397
|
}
|
|
359
|
-
if (name ===
|
|
398
|
+
if (name === PLAIN_STRING_RECORD_RESERVED_MEMBER)
|
|
360
399
|
reservedMember = true;
|
|
361
400
|
else
|
|
362
401
|
Object.defineProperty(members, name, dataDescriptor(value));
|
|
@@ -381,6 +420,6 @@ function readTransportRecord(path, raw) {
|
|
|
381
420
|
}
|
|
382
421
|
return {
|
|
383
422
|
record: read.record,
|
|
384
|
-
issues: read.reservedMember ? [{ path: `${path}.${
|
|
423
|
+
issues: read.reservedMember ? [{ path: `${path}.${PLAIN_STRING_RECORD_RESERVED_MEMBER}`, reason: RESERVED_MEMBER_FAULT, member: PLAIN_STRING_RECORD_RESERVED_MEMBER, fault: "member_name_reserved" }] : [],
|
|
385
424
|
};
|
|
386
425
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 服务器名的保留命名空间 —— 一处定义,请求面的 MCP 名判官(`isUsableMcpServerName` / `describeBadMcpServerName`)读同一个谓词、说同一句原因。
|
|
3
|
+
* 配置面 `McpServerSpec.name` **不读**它(受理以这个前缀起头的名):此前合法的名被拒是校验收紧,按发版纪律归 major。
|
|
4
|
+
* 纯数据 + 纯函数,浏览器安全(叶模块:不 import 本包别的模块)。
|
|
5
|
+
*
|
|
6
|
+
* 设备执行端(device executor)提供的远程 MCP 服务器以 `remote-<名>` 挂载,工具名 `mcp__remote-<名>__<工具>`。部署声明的、请求带来的 MCP 条目若也用这个前缀,
|
|
7
|
+
* 一台它们声明的服务器就与一台设备上的服务器同名、落进同一个工具名命名空间 —— 所以这个前缀只归设备挂载。
|
|
8
|
+
*
|
|
9
|
+
* 比较按**字面**(区分大小写、不规范化):引擎铸工具名前缀时保留大小写、只把 `[A-Za-z0-9_-]` 之外的字符换成 `_`,`remote-` 这七个字符原样保留 ——
|
|
10
|
+
* 所以「名字以 `remote-` 起头」恰好等于「铸出的命名空间段以 `remote-` 起头」;`Remote-x` 铸成 `mcp__Remote-x__`,不在这个命名空间里。
|
|
11
|
+
*/
|
|
12
|
+
/** 保留给设备执行端远程 MCP 服务器的名字前缀(挂载名 = 这个前缀 + 设备声明的服务器名)。 */
|
|
13
|
+
export declare const REMOTE_MCP_SERVER_NAME_PREFIX = "remote-";
|
|
14
|
+
/** 这个 MCP 服务器名落在保留命名空间里吗(字面前缀比较)。 */
|
|
15
|
+
export declare function isReservedMcpServerName(name: string): boolean;
|
|
16
|
+
/** 保留前缀的原因句(不回显名字 —— 回显由读它的那一面按自己的口径加)。 */
|
|
17
|
+
export declare const RESERVED_MCP_SERVER_NAME_REASON = "the \"remote-\" prefix is reserved for the remote MCP servers a device executor offers";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 服务器名的保留命名空间 —— 一处定义,请求面的 MCP 名判官(`isUsableMcpServerName` / `describeBadMcpServerName`)读同一个谓词、说同一句原因。
|
|
3
|
+
* 配置面 `McpServerSpec.name` **不读**它(受理以这个前缀起头的名):此前合法的名被拒是校验收紧,按发版纪律归 major。
|
|
4
|
+
* 纯数据 + 纯函数,浏览器安全(叶模块:不 import 本包别的模块)。
|
|
5
|
+
*
|
|
6
|
+
* 设备执行端(device executor)提供的远程 MCP 服务器以 `remote-<名>` 挂载,工具名 `mcp__remote-<名>__<工具>`。部署声明的、请求带来的 MCP 条目若也用这个前缀,
|
|
7
|
+
* 一台它们声明的服务器就与一台设备上的服务器同名、落进同一个工具名命名空间 —— 所以这个前缀只归设备挂载。
|
|
8
|
+
*
|
|
9
|
+
* 比较按**字面**(区分大小写、不规范化):引擎铸工具名前缀时保留大小写、只把 `[A-Za-z0-9_-]` 之外的字符换成 `_`,`remote-` 这七个字符原样保留 ——
|
|
10
|
+
* 所以「名字以 `remote-` 起头」恰好等于「铸出的命名空间段以 `remote-` 起头」;`Remote-x` 铸成 `mcp__Remote-x__`,不在这个命名空间里。
|
|
11
|
+
*/
|
|
12
|
+
/** 保留给设备执行端远程 MCP 服务器的名字前缀(挂载名 = 这个前缀 + 设备声明的服务器名)。 */
|
|
13
|
+
export const REMOTE_MCP_SERVER_NAME_PREFIX = "remote-";
|
|
14
|
+
/** 这个 MCP 服务器名落在保留命名空间里吗(字面前缀比较)。 */
|
|
15
|
+
export function isReservedMcpServerName(name) {
|
|
16
|
+
return name.startsWith(REMOTE_MCP_SERVER_NAME_PREFIX);
|
|
17
|
+
}
|
|
18
|
+
/** 保留前缀的原因句(不回显名字 —— 回显由读它的那一面按自己的口径加)。 */
|
|
19
|
+
export const RESERVED_MCP_SERVER_NAME_REASON = `the "${REMOTE_MCP_SERVER_NAME_PREFIX}" prefix is reserved for the remote MCP servers a device executor offers`;
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* 一次性迁移的**收据形**与两张闭集(11.0.0)—— 叶子模块(运行期零依赖;只有型导入),由 `./one-time-migrations` 再导出(不进主桶)。
|
|
3
3
|
* 单拆出来只为一件事:调度店的读口(`./scheduler-store`,轻、浏览器安全)要在告警里带收据,不该为一个型把整套域 schema 拉进来。
|
|
4
4
|
*/
|
|
5
|
+
import type { ENV_MODELS_DOCUMENT } from "./config-root-documents.js";
|
|
5
6
|
import type { REMOTE_EXEC_DOCUMENT } from "./remote-exec.js";
|
|
6
7
|
import type { DomainName, RetiredDomainName } from "./types.js";
|
|
7
8
|
/** 本版登记的一次性**数据**迁移 id(闭集;每个 id 在 `ONE_TIME_MIGRATIONS` 登记表里恰一行)。收据只许这些 id —— 同表登记的 wire 删码位
|
|
8
9
|
* (`WIRE_RETIREMENT_IDS`,`./one-time-migrations`)不改任何存储形,铸不出收据,不在本集。 */
|
|
9
|
-
export declare const ONE_TIME_MIGRATION_IDS: readonly ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9", "SS-M10"];
|
|
10
|
+
export declare const ONE_TIME_MIGRATION_IDS: readonly ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9", "SS-M10", "SS-M11"];
|
|
10
11
|
export type OneTimeMigrationId = (typeof ONE_TIME_MIGRATION_IDS)[number];
|
|
11
12
|
/**
|
|
12
13
|
* 一条收据说迁移**对一个坐标做了什么**(闭集,每词一义):
|
|
@@ -14,12 +15,15 @@ export type OneTimeMigrationId = (typeof ONE_TIME_MIGRATION_IDS)[number];
|
|
|
14
15
|
* - `rewritten` — 同一坐标上的值换成今天的生效读法(例:URL 剥掉从不上线的 fragment、调度行的 durable 键重铸、6.x 的 worker 行换新形);
|
|
15
16
|
* - `dropped-shadowed` — 删掉;它从来没生效过,因为另一个位赢了(例:`governance` 已设时 `runtime` 上的治理旧键、`sealedApiKey` 在场时的 `apiKeyEnv`);
|
|
16
17
|
* - `dropped-inert` — 删掉;今天的读法本就不让它生效(例:超界的主机条目、引擎从不读的旧字段、空的retired 域文档);
|
|
17
|
-
* - `refused` — 不改:要改就得替运维猜(或旧文档今天本就读不进);原样留着,读侧照旧响亮拒,`reason`
|
|
18
|
+
* - `refused` — 不改:要改就得替运维猜(或旧文档今天本就读不进);原样留着,读侧照旧响亮拒,`reason` 说为什么;
|
|
19
|
+
* - `minted` — 在**缺席处**写下一份新事实(一份部署决定),不改任何已有的值 —— 只有 SS-M11 铸(配置根的 `config.env-models.json`:
|
|
20
|
+
* 升级前「管模型面的目录整表顶替 env 模型」的读法,写成 `replaced`;不存在才建)。
|
|
18
21
|
*/
|
|
19
|
-
export declare const MIGRATION_APPLIED_AS: readonly ["moved", "rewritten", "dropped-shadowed", "dropped-inert", "refused"];
|
|
22
|
+
export declare const MIGRATION_APPLIED_AS: readonly ["moved", "rewritten", "dropped-shadowed", "dropped-inert", "refused", "minted"];
|
|
20
23
|
export type MigrationAppliedAs = (typeof MIGRATION_APPLIED_AS)[number];
|
|
21
|
-
/** 收据指向的那份文档:配置域名、retired 域名(`runtime`)、`config.d/remote-exec.json`(`remote-exec`)
|
|
22
|
-
|
|
24
|
+
/** 收据指向的那份文档:配置域名、retired 域名(`runtime`)、`config.d/remote-exec.json`(`remote-exec`)、调度店(`scheduler-store`),
|
|
25
|
+
* 或配置根上的 env 模型表决定(`config.env-models`,SS-M11 铸)。 */
|
|
26
|
+
export type MigrationDocument = DomainName | RetiredDomainName | typeof REMOTE_EXEC_DOCUMENT | "scheduler-store" | typeof ENV_MODELS_DOCUMENT;
|
|
23
27
|
/** 文档里的段坐标(字符串段 = 对象键,数字段 = 数组下标;与告警坐标同一形)。 */
|
|
24
28
|
export type MigrationPath = readonly (string | number)[];
|
|
25
29
|
interface ReceiptBase {
|
|
@@ -35,7 +39,7 @@ export type MigrationReceipt = (ReceiptBase & {
|
|
|
35
39
|
readonly path: MigrationPath;
|
|
36
40
|
};
|
|
37
41
|
}) | (ReceiptBase & {
|
|
38
|
-
readonly appliedAs: "rewritten" | "dropped-shadowed" | "dropped-inert";
|
|
42
|
+
readonly appliedAs: "rewritten" | "dropped-shadowed" | "dropped-inert" | "minted";
|
|
39
43
|
}) | (ReceiptBase & {
|
|
40
44
|
readonly appliedAs: "refused";
|
|
41
45
|
readonly reason: string;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/** 本版登记的一次性**数据**迁移 id(闭集;每个 id 在 `ONE_TIME_MIGRATIONS` 登记表里恰一行)。收据只许这些 id —— 同表登记的 wire 删码位
|
|
2
2
|
* (`WIRE_RETIREMENT_IDS`,`./one-time-migrations`)不改任何存储形,铸不出收据,不在本集。 */
|
|
3
|
-
export const ONE_TIME_MIGRATION_IDS = ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9", "SS-M10"];
|
|
3
|
+
export const ONE_TIME_MIGRATION_IDS = ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9", "SS-M10", "SS-M11"];
|
|
4
4
|
/**
|
|
5
5
|
* 一条收据说迁移**对一个坐标做了什么**(闭集,每词一义):
|
|
6
6
|
* - `moved` — 值搬到新位(`to`),义不变;
|
|
7
7
|
* - `rewritten` — 同一坐标上的值换成今天的生效读法(例:URL 剥掉从不上线的 fragment、调度行的 durable 键重铸、6.x 的 worker 行换新形);
|
|
8
8
|
* - `dropped-shadowed` — 删掉;它从来没生效过,因为另一个位赢了(例:`governance` 已设时 `runtime` 上的治理旧键、`sealedApiKey` 在场时的 `apiKeyEnv`);
|
|
9
9
|
* - `dropped-inert` — 删掉;今天的读法本就不让它生效(例:超界的主机条目、引擎从不读的旧字段、空的retired 域文档);
|
|
10
|
-
* - `refused` — 不改:要改就得替运维猜(或旧文档今天本就读不进);原样留着,读侧照旧响亮拒,`reason`
|
|
10
|
+
* - `refused` — 不改:要改就得替运维猜(或旧文档今天本就读不进);原样留着,读侧照旧响亮拒,`reason` 说为什么;
|
|
11
|
+
* - `minted` — 在**缺席处**写下一份新事实(一份部署决定),不改任何已有的值 —— 只有 SS-M11 铸(配置根的 `config.env-models.json`:
|
|
12
|
+
* 升级前「管模型面的目录整表顶替 env 模型」的读法,写成 `replaced`;不存在才建)。
|
|
11
13
|
*/
|
|
12
|
-
export const MIGRATION_APPLIED_AS = ["moved", "rewritten", "dropped-shadowed", "dropped-inert", "refused"];
|
|
14
|
+
export const MIGRATION_APPLIED_AS = ["moved", "rewritten", "dropped-shadowed", "dropped-inert", "refused", "minted"];
|
package/dist/node.d.ts
CHANGED
|
@@ -12,3 +12,4 @@ export * from "./scheduler-store-node.js";
|
|
|
12
12
|
export { migrateConfigDirectory, migrateConfigDocuments, // 14.5.0:同一段「迁 → 整域终判」的同步口(入参是已有的存量文档,无 io)—— 目录形调的就是它的内核
|
|
13
13
|
type ConfigDirectoryDocument, type ConfigDirectoryIo, type ConfigDirectoryMigration, type ConfigDirectoryRefusal, type ConfigDocumentsMigration, type MigrateConfigDirectoryOptions, type MigrateConfigDocumentsOptions, } from "./config-dir-migration.js";
|
|
14
14
|
export { nodeConfigDirectoryIo } from "./config-dir-io.js";
|
|
15
|
+
export { migrateEnvModelsDecision, nodeConfigRootIo, readEnvModelsDecision, readMigrationMarker, writeEnvModelsDecision, type ConfigRootIo, type EnvModelsDecisionWrite, type EnvModelsMigration, type EnvModelsMint, } from "./config-root-node.js";
|
package/dist/node.js
CHANGED
|
@@ -13,3 +13,5 @@ export * from "./scheduler-store-node.js"; // R7 self-wake scheduler store — f
|
|
|
13
13
|
export { migrateConfigDirectory, migrateConfigDocuments, // 14.5.0:同一段「迁 → 整域终判」的同步口(入参是已有的存量文档,无 io)—— 目录形调的就是它的内核
|
|
14
14
|
} from "./config-dir-migration.js";
|
|
15
15
|
export { nodeConfigDirectoryIo } from "./config-dir-io.js"; // 上面那只函数的真 I/O 绑定:node:fs + 本包唯一的原子写器(config-document 模式规则)
|
|
16
|
+
// 配置根上的决定文档(config.env-models.json)与按 id 的运行记录(config.migration.<id>.json)的两只读者、决定文档的唯一写函数(运行记录由迁移独占创建),与一次性迁移 SS-M11 的迁移体(名字与闭集答案表在包根)。显式列名。
|
|
17
|
+
export { migrateEnvModelsDecision, nodeConfigRootIo, readEnvModelsDecision, readMigrationMarker, writeEnvModelsDecision, } from "./config-root-node.js";
|