@sema-agent/settings-schema 14.0.0 → 14.2.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 CHANGED
@@ -6,6 +6,101 @@
6
6
  > `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
7
7
  > 包名是**历史记录**,如实保留不追改。
8
8
 
9
+ ## Unreleased
10
+
11
+ (空)
12
+
13
+ ## 14.2.0 — 2026-10-18 — minor:守卫迹象报告序改句 · 关停位任一层 tighten-only(删 managed-only 告知)· 项目层 `.claude/settings*.json` 只经 layerAdmission 读 tighten 方向并点名未读部分 · 控制 / 双向字符剥除集根导出(server / cli 同读,漂移门 ⑨ 退役)
14
+
15
+ > 档位(按发版纪律):**minor** —— 全部加性:新导出(`CONTROL_AND_BIDI_CHARS` · `stripControlAndBidi` · `hasControlOrBidi` · `layerReadsTightenOnly` · `LAYER_PARTS_UNREAD_CODE` ·
16
+ > `buildLayerPartsUnreadNotice` · 型 `LayerPartsUnreadNotice`)、三张闭集各尾插一词(`SafetyMergeCategory` + `killSwitches` · `SETTINGS_SOURCES` + `foreignProjectSettings` ·
17
+ > `LAYER_ADMISSION_VERDICTS` + `tighten_only`)、`SettingsSourceContract` 多一个可选格 `tightenOnly`;既有导出对既有输入的答案逐格不变(钉见各条);
18
+ > 一处展示串改动(`SAFETY_MERGE_SPEC.permissionMode.seam`,规则字段逐字节不变)与一处文字改真(hooks 分类器报告序)。持穷举表的消费方提货时编译红 = 通知。
19
+
20
+ - **新源 + 新判词 + 披露(写给另一个工具的项目层设置文件只读收紧方向)**。克隆来的仓库常带着写给另一个工具的 `.claude/settings.json` / `.claude/settings.local.json`;
21
+ 此前本包没有它们的源,消费方不读 —— 仓库里写好的 deny 规则在这里**静默不生效,也没有一句提示**。现在源表尾插 `foreignProjectSettings`(项目档、仓库可控、`tightenOnly: true`),
22
+ 判官仍是 `layerAdmission` 一只,律仍是一条:**只读收紧方向的层只准入方向 tighten 的成员** —— 这种层要么是本工具自己的仓库可控层在工作区未受信时(既有,判词 `trust_withheld`),
23
+ 要么是只收紧的源、不论工作区读数(新,尾插判词 `tighten_only`:接受工作区是同意本工具的文件,不是同意另一个工具的 hooks、环境变量与 allow 规则)。
24
+ 于是那两份文件里 deny / ask 规则、关停位的开、沙箱的收紧极照读;allow 规则、hooks、env、MCP 服务器与其余一律不读。MCP 名单判决
25
+ (`mcpServerPolicyVerdict`)同律:只收紧的源只贡献拒绝名单,它的允许名单不读(各层允许名单是拼接,拼进一张就是放宽别层的名单)。`layerReadsTightenOnly(source, workspace)` 是
26
+ 「这一层是不是只读收紧方向」的唯一答案(`layerAdmission` 与披露同读);`SettingsSourceContract` 多一个可选格 `tightenOnly`,既有五个源的行与答案逐格不变。
27
+ **披露**:一只事件码 `settings.layer_parts_unread`(`LAYER_PARTS_UNREAD_CODE`)+ 一行 `buildLayerPartsUnreadNotice({ source, workspace, file, parts })` →
28
+ `<file>: read for restrictions only (<起因>) — not read: <parts>`(起因 `it is written for another tool` / `this workspace is not trusted`,两种起因同一句形;
29
+ 文件与部分名剥控制与双向字符、部分名截 64 字、最多列 16 个再计数);对不是只读收紧方向的层调用 ⇒ RangeError(那句话对它不成立)。
30
+ `SETTINGS_SOURCES` / `LAYER_ADMISSION_VERDICTS` 尾插各一词:持穷举表的消费方提货时编译红 = 通知;旧消费方不传新源就收不到新判词。
31
+ 三问 —— **谁需要**:在克隆来的仓库里干活、指望那个仓库写好的 deny 规则也在这里生效的人;**谁受伤**:那份文件里的 deny / ask 现在会收紧本工具的会话(此前被忽略)
32
+ —— 一个仓库能借此拒掉一些工具调用(收紧方向,与本工具自己的项目层在未受信工作区的待遇相同);**补偿**:一行披露写明读了什么、没读什么,要放开就改那个仓库的文件
33
+ (allow 永远不从这里读,所以它放不宽任何东西)。升级说明:消费方的读法到货前,可以先只出这一行、列出文件里的全部键(只提示不读)作过渡 —— 那是消费方自己的发版说明,本包不设旋钮。
34
+ 钉:`test/foreign-project-settings-14.2.0.test.ts`(三表尾插 · 三读数 × 全类目逐格 · 既有五源与上一版律逐格对拍 · 唯一答案真值表 · 披露两种起因 / 回显 / 上限 / 响亮错 · 其余判官认新源)。
35
+
36
+ - **契约表加一行(关停位任一层可收紧,类目尾插)**。`SAFETY_MERGE_SPEC.killSwitches`:权限模式的两只关停位 `disableBypassPermissionsMode` / `disableAutoMode` 是它的成员
37
+ (方向 tighten、值向):**开**(收紧极)任一设置文件层都可写(`boolPole { strict: true, minTrust: "local" }`,与 `permissions.deny` / `ask` 同一条「收紧不需要信任」律);
38
+ **关** / 非布尔 / 不带值的问法落在行地板 `global`;合并 `replace` + `boolStricter(strict: true)` ⇒ 任何一层的开都不会被别层的关覆盖;`gate` / `closed`。
39
+ 此前表上没有这一行,`permissionMode` 行的 seam 文字写着关停位「只许托管层」,消费方据此在本地盖了一个托管层地板 —— 于是同一个消费方里判官读全部来源
40
+ (任一层写了开都算,与 CC 2.1.296 的限制性键表同形),resolver 却对非托管层告知「只能由托管层配置设置、已拒」,说的与做的相反。现在两边读同一行:
41
+ `permissionMode` 的 seam 改成指向这一行(展示文字;它的合并 / 地板 / 收紧 / 执法 / 失败方向逐字节不变),「只许托管层」的标记从表上删掉。
42
+ `SafetyMergeCategory` 随尾插加宽:持 `Record<SafetyMergeCategory, …>` / 穷举 `switch` 的消费方提货时编译红 = 通知(新词尾插按发版纪律是 minor)。
43
+ 三问 —— **谁需要**:想在用户层 / 项目层关掉 bypass 或 auto 模式的人(此前写了判官认、resolver 告知被拒);**谁受伤**:一个克隆来的仓库可以在自己的设置文件里关掉
44
+ 这两种模式(CC 同此),在那个仓库里要用它们的人看见的是被关掉;**补偿**:关停位只能收紧不能放宽是这条律本身 —— 要用就从那个仓库的设置文件里删掉这一行,没有从更低层重新打开的旋钮。
45
+ 不认识 `boolPole` 的旧消费方经它自己的 schema 读这一行时两员都是 tighten@global(仓库可控层两极都不收,fail-closed)。
46
+ 钉:`test/kill-switches-14.2.0.test.ts`(行形 · 尾插 · 值向准入 · 五源 × 三读数 · 无托管层标记)· `test/safety-merge-spec.test.ts`(1.1.0 冻结钉第四个展示串豁免 · 经 1.1.0 schema 的视图)。
47
+
48
+ - **文字改真(hooks 分类器拒绝项 `field` 的报告序;实现不变)**。13.0.1 那一节写「迹象按文档序取第一个」,与实现不符:一块里有几个迹象时,实现**先报该块自身的迹象**
49
+ (它自有的守卫事件键;条目自己就是匹配器形时取 `hooks`),**再按文档序找嵌套迹象**(匹配器下合形的条目列表不在匹配器这一层找:那里的迹象归各条目,各是一块)。所以同一块里嵌套迹象写在前、自有守卫键写在后时(`{ n: { PreToolUse: … }, PermissionRequest: …, hooks: [ … ] }`),
50
+ `field` 是 `PermissionRequest`,不是文档序第一个的 `n`。改的是句子(d.ts 头注、`llms.txt` / `llms-full.txt` 同句),不是判法:结局、码、坐标、剔点、`field` 逐字节不变。
51
+ 句子按实现改而不是反过来:`field` 先指向块自己的键,用户照它找改哪一行时先看到的就是让这一块被拒的那个键。三问 —— **谁需要**:按 `field` 告诉用户改哪个键、并依文档读报告序的消费方;
52
+ **谁受伤**:无;**补偿**:不需要。钉:`test/hooks-classify-13.0.0.test.ts` ⑥(报告序一格:自有键在后仍先报自有键 · 条目自己是匹配器形 ⇒ `hooks` · 块自身无迹象时才按文档序;随包句子一格)。
53
+
54
+ - **新导出(一行回显的剥除集,单源在本包)**。`CONTROL_AND_BIDI_CHARS`(C0 控制符 + DEL + Unicode `Bidi_Control=Yes` 全集 12 个码点的正则字符类体,`\uXXXX` 转义的源码文本)·
55
+ `stripControlAndBidi(text)`(删掉集内每一个码点,别的字符一个不动)· `hasControlOrBidi(text)`(有 ⇔ 剥了会变),主 barrel。调用方 / 仓库写的串进拒句、告警、日志、屏上一行之前剥掉的
56
+ 那一族码点,此前是请求面两只回显描述器里的私有正则,标成引擎双向码点表的镜像、由上游漂移门逐码点对拍;服务端另存一份 —— 两份表一只门。这张集合是**回显策略**,不是引擎判官词表的镜像
57
+ (引擎的审批文本拒集另含 C1 与行段分隔符,用途是拒不是剥),所以单源归本包:两只描述器改读它,对拍引擎的那一组漂移门退役。集合与此前逐码点相同(全扫钉),C1 与 U+2028 / U+2029 仍不在集里;
58
+ 今后增删码点是本包的行为变更,按发版纪律判档,不跟随任何别处的词表。
59
+ 三问 —— **谁需要**:服务端(退掉自己那份,剥与检都读这里)与任何要把外来串落成一行的消费方;**谁受伤**:无(新导出,行为零变化);**补偿**:不需要。
60
+ 钉:`test/control-and-bidi-14.2.0.test.ts`(导出面 · 逐码点全扫真值表 · 检出与剥除同一集合 · 纯函数 · 两只描述器同读)。
61
+
62
+ ## 14.1.0 — 2026-10-18 — minor:请求面 MCP 判官与服务端请求腿同严(申报名首尾空白 · `env` / `headers` 只收纯数据记录 · 两只回显描述器的回显口径)
63
+
64
+ > 档位:**minor**(型面加性:`McpEntryKeyIssue` 新增两个可选格 `member?` / `fault?`;三处两层两答的判官变严是 bug 修复);导出名单不变(请求面判官与描述器自 14.0.0 起都在主 barrel:`isUsableWireName` · `describeBadWireName` · `isContentOriginWord` ·
65
+ > `describeBadContentOrigin` · `readRequestMcpTransport` · `readMcpIdentityHeaders` · 值种类描述件与 `TOOL_WIRE_NAME_MAX_CHARS`,本版无增删,`docs/API-SURFACE.md` 不动)。
66
+ > 型面一处加性:`McpEntryKeyIssue` 多两个**可选**格 `member?` / `fault?`(不改既有的 `path` / `reason`;只有 `env` / `headers` 记录上的坏形带)。
67
+
68
+ - **行为修复(申报名首尾空白 = 坏名)**。`isUsableWireName` 对首尾带空白的名返回 `false`;此前它只判非串 / 空串 / 超 `TOOL_WIRE_NAME_MAX_CHARS`,所以 `" fs"` / `"fs\n"` 被收。
69
+ 「空白」就是 `String.prototype.trim` 剥的那一组(ECMAScript 的 WhiteSpace + LineTerminator 共 25 个码点:空格 / 制表 / 换行 / NBSP / BOM / 全角空格 / 行段分隔符等);不在这一组里的不可见字符(零宽空格 U+200B 等)不算空白,本判官不管。申报名是线上工具名 `mcp__<server>__<tool>` 的身份片:带着空白的那个名字不是
70
+ 另一个名字、是坏名 —— 所以**不 trim、不规范化**(与「不截断」同向:改写一个名字就是换一个身份,挂在原名上的 allowTools / toolFaces 收窄声明会一起对不上号)。内部空白(`"a b"`)照旧合法,本判官不扩判。
71
+ `describeBadWireName` 对这类名给一个种类词:`" a" (leading or trailing whitespace)`;又超界的一句说全:`… (129 chars > 128, leading or trailing whitespace)`;摘要的回显口径见下面「两只回显描述器」一条,
72
+ 既有的超界 / `empty` / 非串三形的句式不变。判与说共读同一个「首尾空白」定义,不会各按各的口径。
73
+ 三问 —— **谁需要**:两层同答。装配口(client-core)对首尾空白的申报名拒、而本判官收,同一个名字两层两答;判官在契约包,各层读同一份才不会再分岔。
74
+ **谁受伤**:申报名带首尾空白的配置 / 请求 —— 读本判官的服务端请求腿与声明腿(壳的发送前预检若读它亦然)此前放行(服务端受理后以带空白的名挂上),现在按坏名处理;重放存量提交体的消费方按各自对坏名的处置走
75
+ (服务端的重放腿:摘掉这条 server 并点名),所以存量里带空白名的 parked 任务续跑时会少这台 server。**补偿**:去掉空白 —— 拒句点名 `leading or trailing whitespace`(制表 / 换行这类控制空白在回显里被剥掉,看得出是空白靠这个种类词);
76
+ `"a b"` 这类内部空白无须改。
77
+ 钉:`test/mcp-request-entry-14.0.0.test.ts` ⑥(首尾空白各形,含 `trim` 那一组 25 个码点的前后位 · 组外不可见字符照旧合法 · 内部空白照旧 · 摘要既有三形不动 + 空白种类词 + 超界并存一句说全 · 判官与摘要按逐名事实表同一定义)·
78
+ ⑦(消费方切 import 要用的判官 / 描述器 / 常量与三个结果型逐名在主 barrel)。
79
+ - **行为修复(`env` / `headers` 只收纯数据的「名 → 串」记录)**。`readRequestMcpTransport` 读 `transport.env`(stdio)/ `transport.headers`(http)此前判「每个值都是串」(`Object.values` 全是串):
80
+ `new Map()`、`Set`、带自有串属性的类实例照收并原样交出(交出的就是那个 `Map` / 实例),JSON 文本铸出的自有 `__proto__` 键原样带出,交出的还是调用方的**同一个对象**,一个抛错的访问器会让读器抛出去。
81
+ 现在与服务端请求腿同严:记录交不交 = 原型是 `Object.prototype` 或 `null`(纯数据)且每个自有成员(不可枚举的也算,访问器调一次)的值都是串。
82
+ `Map` / `Set` / 类实例 / 建在原型上的记录 ⇒ 这个键整张不交,`lost` 记一处,原因句 `must be a plain object of string values — got an object that is not plain data (…)`;不是对象 / 数组 / `null` ⇒ 同一条(`got an array` 等);
83
+ 有成员值不是串(或访问器抛了)⇒ 整张不交,句子点名第一个坏成员(`its member "PORT" must be a string — got a number`);读原型与成员名时代理的 trap 抛了 ⇒ 同样整张不交(读器对递来的任何对象都不抛,这一条服务端读法是抛出去,本包更稳);
84
+ 名叫 `__proto__` 的成员(引擎的声明收据不收这个名)⇒ 记录照交、副本里没有它,`lost` 单记一处、路径 `transport.env.__proto__`,它的值也不是串时整张不交、一句说全两件事。
85
+ **交出的是副本**(原型 `Object.prototype`),改调用方的对象不影响读后的形。`isStringRecord` 与读器是同一个定义(包内没有第二把更宽的尺)。
86
+ 机读位:`McpEntryKeyIssue` 多两个可选格 —— `member`(成员名的回显形,可直接进日志与句子)与 `fault`(闭集词 `not_plain_record` / `member_not_string` / `member_name_reserved`),
87
+ 只有 `env` / `headers` 记录上的坏形带,调用方分流不必解析句子;句子 `reason` 仍是消费方拒句的来源(逐字同服务端请求腿,期望句取自服务端 7.132.0 读器的实测输出)。
88
+ - **行为修复(两只回显描述器用服务端的回显口径)**。`describeBadWireName` / `describeBadContentOrigin` 此前用 `JSON.stringify`(控制字符转义成 `\n` 字样、**双向控制字符原样输出**),名只截 32 字、内容出处值连引号截 64 字;
89
+ 现在:剥掉 C0 控制符、DEL 与双向控制字符(Unicode `Bidi_Control=Yes` 全集 12 个码点:U+061C · U+200E/200F · U+202A–202E · U+2066–2069;一个 RLO 能把一行字读反,一个换行能伪造一行日志)—— 是**删**,不转义;
90
+ 剥完再数,超 64 字截到 64 加 `…`(省略号在引号内);引号形 `"…"`,内部的引号与反斜杠逐字。清洗面不含 C1 与 U+2028 / U+2029(加宽是行为变更,要各自的判据)。`N chars > 128` 的 N 仍是送来的原长度。
91
+ 双向这一半的属主是引擎(`BIDI_CONTROL_CHARS`),本包运行期不依赖引擎,以镜像承载并由上游漂移门 ⑨ 逐码点全扫(U+0000–U+10FFFF)对拍。
92
+ 三问(这两条一起答)—— **谁需要**:服务端请求腿(与壳的发送前预检)。它们此前各留一份自家的传输读器与两只描述器,因为本包的读法比请求腿松;读法同严之后切到 import,请求面没有第二份判官。
93
+ **谁受伤**:① 直接调读器、把 `Map` / `Set` / 类实例当 `env` 或 `headers` 递进来的调用方 —— 此前本包放行(服务端请求腿已拒),现在整张不交并记 `lost`,受理新提交的门据它 400,两层同拒;
94
+ ② 带自有 `__proto__` 成员的 JSON 体 —— 现在 `lost` 点名它、副本里没有它;③ 读坏名 / 坏内容出处回显的运维 —— 换行不再显示成 `\n`、双向字符不再原样出现、名的回显从 32 字变 64 字、引号内不再转义。
95
+ **补偿**:把 `Map` / 类实例展开成纯对象再递(`Object.fromEntries(map)` / `{ ...instance }`);删掉 JSON 体里的 `__proto__` 键;分流用 `lost` 的 `path` / `member` / `fault`,不解析句子。
96
+ 钉:`test/mcp-request-entry-14.0.0.test.ts` ⑧(`Map` / `Set` / 类实例 / 原型上的记录 / `Date` · 非对象值 · 非串成员 · 自有 `__proto__` 成员与值也坏的并列 · 副本 · 隐藏成员与访问器 · 成员名回显清洗 · `lost` 读序 · 正控 · `isStringRecord` 与读器同答)·
97
+ ⑨(C0 / DEL / 12 个双向码点逐个剥,两只描述器各验 · C1 与 U+2028 / U+2029 不剥 · 先剥后数 · 64 字界 · 引号形 · 空白种类词与清洗同在)· `test/upstream-drift.test.ts` ⑨(回显清洗类对引擎 `BIDI_CONTROL_CHARS` 逐码点全扫,两只描述器各扫一遍 + 门有牙)。
98
+ - **下游(表态制:认领 + 排期 / 显式拒绝 + 理由 / 申请裁定)**:
99
+ **server**:申报名判官已读本包,抬到本版随之收严,零代码改动;请求腿的传输读器(`env` / `headers` 半场)与两只回显描述器的读法与服务端 7.132.0 同严,可整个换成 import、删自家三份
100
+ (描述器切过来时空白名多一个种类词);A2A 对端 `headers` 的记录读法(自家的 `plain-string-record.ts`)仍在服务端,本包不导出记录读器。
101
+ **cli**:可直接 import(14.0.0 起);读它即与服务端受理同答,本版无导出变化。**client-core**:对 14.0.0 档里「经包转出判官要加运行期依赖,请选」的答 = 选**构建期抄本**、不加运行期依赖;抄本的对拍钉请对本版
102
+ `isUsableWireName` 的真值表(首尾空白 = `trim` 那一组 ⇒ 坏名,内部空白合法,界 128);读器与描述器若也抄,请跟本版的读法(纯数据记录、回显口径)。**sdk**:零引用。
103
+
9
104
  ## 14.0.0 — 2026-10-17 — major:prompts 域单轴(中心 artifact v3 的结构镜像;声明轴与 v1 catalog 轴退役,一次性迁移 SS-M10)· 请求面 MCP 条目判官入包
10
105
 
11
106
  > 档位:**major**。两件事,一条律各一句:
package/README.md CHANGED
@@ -56,7 +56,8 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
56
56
  - **存量**:一次性迁移 SS-M10 删掉空的 `bindings: []`;非空 `bindings` 与 `catalog` 被拒、原样留着 —— 从中心编辑器重新发布(本地 `config.d` 的 `bindings`
57
57
  引擎从未读过,删键即可)。
58
58
  - **请求面 MCP 条目**:`isUsableWireName` / `readRequestMcpTransport` / `readMcpIdentityHeaders` / `isContentOriginWord` 住本包(判据与原因句与服务端请求腿逐字同),
59
- 壳的发送前预检与服务端受理同答。
59
+ 壳的发送前预检与服务端受理同答。申报名判官 `isUsableWireName` 另判首尾空白(坏名;不 trim、不规范化,内部空白照旧合法);`readRequestMcpTransport` 的 `env` / `headers`
60
+ 只收纯数据的「名 → 串」记录(`Map` / `Set` / 类实例 / 自有 `__proto__` 成员不收,交出的是副本,`lost` 条目带 `member` / `fault` 机读位);两只回显描述器剥控制与双向字符、截 64 字,与服务端回显同口径。
60
61
  - **部署序**:中心(发布方)与服务端同一维护窗升级,中心先;窗内新旧两端互拒整份候选、停在上一份好配置。细节与逐条三问见 CHANGELOG 14.0.0。
61
62
 
62
63
  ## 升级指引:10.x → 11.0.0 · 持久面:一次性迁移(`./one-time-migrations`)
@@ -291,6 +292,11 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
291
292
  (`src/sema/settings/keyContracts.ts` self-documents the retirement) and its resolver now really imports
292
293
  `isAtLeast` from this package (`src/sema/settings/resolver.ts`). Exporting the primitive DID make it the
293
294
  single source, for this consumer.
295
+ The permission-mode **kill-switches** (`disableBypassPermissionsMode` / `disableAutoMode`) have their own appended, memberized row,
296
+ `killSwitches`: tighten-only from every layer — switching one ON is admitted at `local` (any settings file), OFF / a non-boolean / a valueless
297
+ query sits at the row floor `global`, and the merge is boolStricter (one layer's ON beats every layer's OFF). The table has no managed-only floor
298
+ for them (`permissionMode`'s seam no longer says so): a consumer that stamped one locally retires it onto this row, so its judge and its
299
+ resolver read the same row.
294
300
  `MinTrust` gains the `task` word (the runtime task overlay, the loosest floor; appended, declaration order
295
301
  otherwise unchanged — the declaration order is not the ladder, `trustRank` is). The published table does not
296
302
  use `task`; the widened vocabulary is visible to consumers that hold a `Record<MinTrust, …>` / exhaustive
@@ -306,6 +312,15 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
306
312
  is the one lookup of the table (unknown source ⇒ RangeError): every function in this package that needs a layer's tier reads it
307
313
  there and none takes a tier from its caller — a layer is named by its source only. The hooks execution gate
308
314
  (`declined` ⇒ no hook from any layer runs) is contract text in `./hooks`, not layer admission.
315
+ The repository's settings files written for **another tool** (a cloned repository's `.claude/settings.json` / `.claude/settings.local.json`) are
316
+ the appended source `foreignProjectSettings` (project tier, repository-controllable, `tightenOnly`): read in the tighten direction only whatever
317
+ the workspace reading — its deny / ask rules, kill-switches switched ON and sandbox restrictions are admitted, everything else answers
318
+ `tighten_only` (appended verdict; accepting a workspace never admits it) or `below_trust_floor`. `layerReadsTightenOnly(source, workspace)` is the
319
+ one answer to "is this layer read for restrictions only" (that source always; this product's repository files while the workspace is not
320
+ `accepted`), and such a layer names what it did not read in one line: `buildLayerPartsUnreadNotice({ source, workspace, file, parts })` → code
321
+ `settings.layer_parts_unread` (`LAYER_PARTS_UNREAD_CODE`) + `<file>: read for restrictions only (<cause>) — not read: <parts>`. Upgrading a
322
+ consumer that does not read these files yet: until its reading lands it may emit that line naming every key the file holds (disclose, read
323
+ nothing) — that interim is the consumer's own release note, not a switch in this package.
309
324
  - **Auth wire contract** (`api/auth.ts`, subpath `./api/auth`): the `/api/v1/auth/*` face — RFC 8628
310
325
  device flow (code / token poll / approve consent) + rotating refresh + logout, the OAuth error envelope,
311
326
  and the pure client helpers `normalizeUserCode` / `nextPollInterval`. Shipped M1 shapes frozen.
@@ -460,11 +475,16 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
460
475
  a layer is admitted ⇔ `layerAdmission(source, workspace, "httpHookAllowlists") === "admitted"` (user / flag / managed; project / local are below the floor).
461
476
  - **MCP server list verdict** (`mcp-server-policy.ts`, 13.0.0): `mcpServerPolicyVerdict` → `McpPolicyBlock{name, list, rule, target,
462
477
  written, sources}` — judged on the expanded target, echoed only as written; the managed layer (whose `allowManagedMcpServersOnly` counts)
463
- is the source whose tier in `SETTINGS_SOURCE_CONTRACT` is `managed`.
478
+ is the source whose tier in `SETTINGS_SOURCE_CONTRACT` is `managed`; a tighten-only source contributes its deny list only (allow lists
479
+ concatenate across layers, so reading its allow list would widen the others').
464
480
  - **Request-plane MCP entry judges** (`mcp-request-entry.ts`, 14.0.0): `isUsableWireName` / `describeBadWireName` (the engine's wire-name bound
465
- `TOOL_WIRE_NAME_MAX_CHARS`, mirrored), `isContentOriginWord` / `describeBadContentOrigin`, `readMcpIdentityHeaders`, `readRequestMcpTransport`
466
- (hard faults `ok:false` naming the key path; soft faults dropped by the reader and listed in `lost`) and the value-shape describers — one reading for
481
+ `TOOL_WIRE_NAME_MAX_CHARS`, mirrored; a name with leading or trailing whitespace is a bad name too — never trimmed, never normalised; inner whitespace stays legal), `isContentOriginWord` / `describeBadContentOrigin`, `readMcpIdentityHeaders`, `readRequestMcpTransport`
482
+ (hard faults `ok:false` naming the key path; soft faults dropped by the reader and listed in `lost`; `env` / `headers` are read as plain-data string records — a `Map`, a class instance or an own `__proto__` member is not one, the delivered record is a copy, and a `lost` entry for a record carries machine-readable `member` / `fault`; the two describers strip control and bidi characters and cut at 64 characters, the service's echo form) and the value-shape describers — one reading for
467
483
  the service's request legs and the shell's pre-send check. A `toolFaces` declaration stays parse-transparent here: its shape is the engine ingest side's to judge, and none of the tool-face vocabularies is mirrored in this package.
484
+ - **One-line echo strip set** (`control-and-bidi.ts`, main barrel): `CONTROL_AND_BIDI_CHARS` (C0 + DEL + the 12 `Bidi_Control=Yes` code points,
485
+ as regex character-class source text), `stripControlAndBidi(text)` and `hasControlOrBidi(text)` — the one set a caller- or repository-written string loses
486
+ before it lands in a refusal, a warning, a log line or one screen line (C1 and U+2028 / U+2029 deliberately outside it). It is an echo policy owned here,
487
+ not a mirror of the engine's bidi table: the request-plane describers read it, and so do consumers instead of keeping a class of their own.
468
488
  - **UI language** (`ui-language.ts`, 13.0.0): `semaUiLanguage` (`en` / `zh`, category `ui`, user settings) + `readUiLanguage`.
469
489
  - **Cross-session settings** (`cross-session.ts`): the two `settings.json` keys `crossSessionInbound`
470
490
  (wake/next-turn/hold/refuse — the core 7.30.0 vocabulary, `accept` retired in 6.0.0; `default` = unset) and `dialogExpiry` (60s/5m/10m/never), each with a reader that
@@ -1 +1 @@
1
- 14.0.0
1
+ 14.2.0
@@ -0,0 +1,4 @@
1
+ /** 回显的字符上限(剥完控制与双向字符之后数)。 */
2
+ export declare const CALLER_TEXT_ECHO_MAX_CHARS = 64;
3
+ /** 外来串的回显形(不带引号):剥控制与双向字符 → 超 {@link CALLER_TEXT_ECHO_MAX_CHARS} 字截断加 `…`。 */
4
+ export declare function echoCallerText(text: string): string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 包内私有叶子模块(不进任何导出子路径;运行期零依赖,浏览器安全):**调用方 / 仓库写的串的有界回显形** —— 全包一只。
3
+ * 剥一行回显的剥除集(`./control-and-bidi`)→ 剥完超 {@link CALLER_TEXT_ECHO_MAX_CHARS} 字截断加 `…`。拒句、告警元数据、披露行里点名一个外来串都走这里
4
+ * (串可能任意长、可能带换行与双向控制符);引号形与句子是各铸点自己的。
5
+ */
6
+ import { stripControlAndBidi } from "./control-and-bidi.js";
7
+ /** 回显的字符上限(剥完控制与双向字符之后数)。 */
8
+ export const CALLER_TEXT_ECHO_MAX_CHARS = 64;
9
+ /** 外来串的回显形(不带引号):剥控制与双向字符 → 超 {@link CALLER_TEXT_ECHO_MAX_CHARS} 字截断加 `…`。 */
10
+ export function echoCallerText(text) {
11
+ const clean = stripControlAndBidi(text);
12
+ return clean.length > CALLER_TEXT_ECHO_MAX_CHARS ? `${clean.slice(0, CALLER_TEXT_ECHO_MAX_CHARS)}…` : clean;
13
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * **一行回显的剥除集** —— 调用方 / 仓库写的串进拒句、告警、日志、屏上一行之前剥掉的那一族码点,唯一一份(住本包、主 barrel 导出)。
3
+ * 纯数据 + 纯函数,零 import,浏览器安全。
4
+ *
5
+ * ## 集合(一条)
6
+ * C0 控制符(U+0000–U+001F)+ DEL(U+007F)+ Unicode `Bidi_Control=Yes` 全集 12 个码点(U+061C、U+200E、U+200F、U+202A–U+202E、U+2066–U+2069)。
7
+ * 一个换行能在日志里伪造一行,一个 RLO 能把一行字读反 —— 回显只留可打印的一行。刻意**不含** C1(U+0080–U+009F)与 U+2028 / U+2029:
8
+ * 加宽是行为变更,要各自的判据。这张集合是**回显策略**,属主是本包:增删一个码点是本包的行为变更(走发版纪律),不跟随任何别处的词表
9
+ * (引擎的双向码点闭集、审批文本的拒集各为各的判官服务,集合与用途都不同)。
10
+ *
11
+ * ## 两只读法,同一个集合
12
+ * · {@link stripControlAndBidi} —— 剥:删掉集内每一个码点,别的字符一个不动(不截断、不 trim、不转义)。截断与引号形是各回显口自己的事。
13
+ * · {@link hasControlOrBidi} —— 检:这串里有没有集内码点。⇔ `stripControlAndBidi(s) !== s`。
14
+ * {@link CONTROL_AND_BIDI_CHARS} 是同一集合的正则字符类体(源码文本),给要拼自己更宽字符类的消费方;拼的是**更宽**的类时自己负责多出来的那部分。
15
+ */
16
+ /** 剥除集的正则字符类体(源码文本,`\uXXXX` 转义;放进 `[` `]` 即一只字符类):C0 + DEL + 12 个双向控制码点。 */
17
+ export declare const CONTROL_AND_BIDI_CHARS = "\\u0000-\\u001f\\u007f\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069";
18
+ /** 删掉 `text` 里剥除集({@link CONTROL_AND_BIDI_CHARS})的每一个码点;别的字符原样。 */
19
+ export declare function stripControlAndBidi(text: string): string;
20
+ /** `text` 里有没有剥除集({@link CONTROL_AND_BIDI_CHARS})的码点 —— 与 {@link stripControlAndBidi} 同一个集合:有 ⇔ 剥了会变。 */
21
+ export declare function hasControlOrBidi(text: string): boolean;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * **一行回显的剥除集** —— 调用方 / 仓库写的串进拒句、告警、日志、屏上一行之前剥掉的那一族码点,唯一一份(住本包、主 barrel 导出)。
3
+ * 纯数据 + 纯函数,零 import,浏览器安全。
4
+ *
5
+ * ## 集合(一条)
6
+ * C0 控制符(U+0000–U+001F)+ DEL(U+007F)+ Unicode `Bidi_Control=Yes` 全集 12 个码点(U+061C、U+200E、U+200F、U+202A–U+202E、U+2066–U+2069)。
7
+ * 一个换行能在日志里伪造一行,一个 RLO 能把一行字读反 —— 回显只留可打印的一行。刻意**不含** C1(U+0080–U+009F)与 U+2028 / U+2029:
8
+ * 加宽是行为变更,要各自的判据。这张集合是**回显策略**,属主是本包:增删一个码点是本包的行为变更(走发版纪律),不跟随任何别处的词表
9
+ * (引擎的双向码点闭集、审批文本的拒集各为各的判官服务,集合与用途都不同)。
10
+ *
11
+ * ## 两只读法,同一个集合
12
+ * · {@link stripControlAndBidi} —— 剥:删掉集内每一个码点,别的字符一个不动(不截断、不 trim、不转义)。截断与引号形是各回显口自己的事。
13
+ * · {@link hasControlOrBidi} —— 检:这串里有没有集内码点。⇔ `stripControlAndBidi(s) !== s`。
14
+ * {@link CONTROL_AND_BIDI_CHARS} 是同一集合的正则字符类体(源码文本),给要拼自己更宽字符类的消费方;拼的是**更宽**的类时自己负责多出来的那部分。
15
+ */
16
+ /** 剥除集的正则字符类体(源码文本,`\uXXXX` 转义;放进 `[` `]` 即一只字符类):C0 + DEL + 12 个双向控制码点。 */
17
+ export const CONTROL_AND_BIDI_CHARS = "\\u0000-\\u001f\\u007f\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069";
18
+ /** 剥用(全局)。只在本模块里用 `String.prototype.replace` 读:`replace` 返回前把 `lastIndex` 归零,模块级常量可安全复用;不导出(全局正则的游标是共享状态)。 */
19
+ const STRIP_RE = new RegExp(`[${CONTROL_AND_BIDI_CHARS}]`, "g");
20
+ /** 检用(非全局,`test` 无游标)。 */
21
+ const DETECT_RE = new RegExp(`[${CONTROL_AND_BIDI_CHARS}]`);
22
+ /** 删掉 `text` 里剥除集({@link CONTROL_AND_BIDI_CHARS})的每一个码点;别的字符原样。 */
23
+ export function stripControlAndBidi(text) {
24
+ return text.replace(STRIP_RE, "");
25
+ }
26
+ /** `text` 里有没有剥除集({@link CONTROL_AND_BIDI_CHARS})的码点 —— 与 {@link stripControlAndBidi} 同一个集合:有 ⇔ 剥了会变。 */
27
+ export function hasControlOrBidi(text) {
28
+ return DETECT_RE.test(text);
29
+ }
@@ -24,8 +24,12 @@
24
24
  * bad — a contract field whose value is wrong, or, for a piece rejected because it holds a guard sign, the member that holds the sign (an unknown
25
25
  * field the contract would strip; a contract field carrying more than its kind, such as `headers` or `input`; `hooks` for an entry that is itself
26
26
  * matcher-shaped). Absent only when the piece is not an object — and for the pieces above matchers, which have no fields to name. One piece, one
27
- * `field`: the first wrong contract field, or the first sign in document order; a sign on the piece itself decides before a wrong contract field
28
- * of the same piece does (it is why the piece is rejected rather than dropped).
27
+ * `field`: the first wrong contract field, or — when the piece holds signs — the piece's own sign first (its own guard-event key; for an entry,
28
+ * the entry itself being matcher-shaped, which names `hooks`), then the first nested sign in document order (the first member whose subtree holds
29
+ * one). So an own guard key written AFTER a member that nests a sign is still the one reported. A matcher's well-formed entry list (`hooks` holding
30
+ * an array) is not searched at the matcher level: the signs in it belong to the entries, each its own piece — so `{ matcher: 5, hooks: [<entry
31
+ * with a sign>] }` reports the matcher's wrong `matcher` field. A sign of the piece decides before a wrong contract field of the same piece does
32
+ * (it is why the piece is rejected rather than dropped).
29
33
  *
30
34
  * CC 2.1.296 DIVERGENCES (each in the stricter direction — a document CC would load may be rejected here, never the reverse): listed once,
31
35
  * in `docs/CC-DIVERGENCE.md` (section "hooks classifier", H1–H7) — the whole-subtree guard-sign scan is the first of them.
@@ -24,8 +24,12 @@
24
24
  * bad — a contract field whose value is wrong, or, for a piece rejected because it holds a guard sign, the member that holds the sign (an unknown
25
25
  * field the contract would strip; a contract field carrying more than its kind, such as `headers` or `input`; `hooks` for an entry that is itself
26
26
  * matcher-shaped). Absent only when the piece is not an object — and for the pieces above matchers, which have no fields to name. One piece, one
27
- * `field`: the first wrong contract field, or the first sign in document order; a sign on the piece itself decides before a wrong contract field
28
- * of the same piece does (it is why the piece is rejected rather than dropped).
27
+ * `field`: the first wrong contract field, or — when the piece holds signs — the piece's own sign first (its own guard-event key; for an entry,
28
+ * the entry itself being matcher-shaped, which names `hooks`), then the first nested sign in document order (the first member whose subtree holds
29
+ * one). So an own guard key written AFTER a member that nests a sign is still the one reported. A matcher's well-formed entry list (`hooks` holding
30
+ * an array) is not searched at the matcher level: the signs in it belong to the entries, each its own piece — so `{ matcher: 5, hooks: [<entry
31
+ * with a sign>] }` reports the matcher's wrong `matcher` field. A sign of the piece decides before a wrong contract field of the same piece does
32
+ * (it is why the piece is rejected rather than dropped).
29
33
  *
30
34
  * CC 2.1.296 DIVERGENCES (each in the stricter direction — a document CC would load may be rejected here, never the reverse): listed once,
31
35
  * in `docs/CC-DIVERGENCE.md` (section "hooks classifier", H1–H7) — the whole-subtree guard-sign scan is the first of them.
@@ -180,7 +184,9 @@ function hookLooksLikeMatcher(value) {
180
184
  return hookIsRecord(inner) && (Object.hasOwn(value, "matcher") || typeof inner.type === "string");
181
185
  }
182
186
  /**
183
- * The coordinate of the first guard sign (document order) in `root`'s subtree, or undefined. `place` is where `root` sits: `matcherList` =
187
+ * The coordinate of the first guard sign in `root`'s subtree, or undefined: each object answers for itself before its members are searched (its own
188
+ * guard-event key, then — outside a matcher's legal place — itself being matcher-shaped), and its members are searched in document order, depth first.
189
+ * `place` is where `root` sits: `matcherList` =
184
190
  * the array under an event name (its elements are in a matcher's legal place), `matcher` = one such element, `other` = anywhere else.
185
191
  * A guard-event member's coordinate ends with its key; a matcher-shaped object's is the object's own. Iterative, whole subtree, each object
186
192
  * visited once per place (cycles terminate).
@@ -292,7 +298,8 @@ function hookMatcherRead(matcher, at, guardEvent, reject, drop) {
292
298
  }
293
299
  const entries = Object.hasOwn(matcher, "hooks") ? matcher.hooks : undefined;
294
300
  // a sign at the matcher level: a guard-event key on the matcher itself, or anywhere in its fields other than a well-formed entry list;
295
- // the member that holds the first one (document order — the order hookGuardSignIn reports) is the rejection's `field`
301
+ // the rejection's `field` is the matcher's own guard-event key if it has one, else the first member (document order) whose subtree holds a sign —
302
+ // the order hookGuardSignIn reports
296
303
  const signField = hookGuardKeyOf(matcher) ??
297
304
  Object.keys(matcher).find((key) => !(key === "hooks" && Array.isArray(entries)) && hookGuardSignIn(matcher[key], [...at, key], "other") !== undefined);
298
305
  if (signField !== undefined) {
package/dist/index.d.ts CHANGED
@@ -36,4 +36,5 @@ export * from "./url-pattern.js";
36
36
  export * from "./http-hook-allowlists.js";
37
37
  export * from "./mcp-server-policy.js";
38
38
  export * from "./ui-language.js";
39
+ export * from "./control-and-bidi.js";
39
40
  export * from "./mcp-request-entry.js";
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ export * from "./url-pattern.js"; // 13.0.0 (S-1597): THE URL-list pattern judge
36
36
  export * from "./http-hook-allowlists.js"; // 13.0.0 (U3): allowedHttpHookUrls / httpHookAllowedEnvVars — one layer reader + httpHookUrlVerdict / httpHookEnvVarsAllowed (admitted layers INTERSECT; SAFETY_MERGE_SPEC.httpHookAllowlists), pure/browser-safe
37
37
  export * from "./mcp-server-policy.js"; // 13.0.0 (U6): mcpServerPolicyVerdict → McpPolicyBlock{name, list, rule, target, written, sources} (structure, no sentence; target judged expanded, echoed only as written), pure/browser-safe
38
38
  export * from "./ui-language.js"; // 13.0.0 (S-1592): the UI-language settings fragment (semaUiLanguage, en | zh, category ui, user settings) + readUiLanguage, pure/browser-safe
39
+ export * from "./control-and-bidi.js"; // the one-line echo strip set (C0 + DEL + the 12 Bidi_Control code points) — CONTROL_AND_BIDI_CHARS (the class, as regex source text) + stripControlAndBidi / hasControlOrBidi, pure/browser-safe
39
40
  export * from "./mcp-request-entry.js"; // 14.0.0: the request-plane MCP entry judges (isUsableWireName / isContentOriginWord / readMcpIdentityHeaders / readRequestMcpTransport + the value-shape describers) — one reading for the service legs and the shell pre-check, pure/browser-safe
40
41
  // NOTE: the Node-fs-backed helpers (FileConfigStore + the file editor) are NOT in this barrel — they pull
41
42
  // `node:fs`/`node:path` and would break a browser/client bundle that only wants the contract (e.g.
@@ -1,26 +1,3 @@
1
- /**
2
- * 请求面 MCP 条目(一条任务提交体 `mcpServers[i]`)的形判官 —— 14.0.0 起住本包:一只读法,消费方的请求腿 / 中心腿 / 壳的发送前预检都 import 这一份。
3
- * 纯函数、浏览器安全(唯一的值依赖是本包自己的内容出处词表)。
4
- *
5
- * ## 律(一条)
6
- * **一条请求面 MCP 条目的每一格「能不能用」只有一只判官。** 此前这些判官是消费方私有的:壳拿不到,只能在发送前披露「判不准、交给服务端」,
7
- * 或者自己抄一份 —— 两份判据就是将来漂开的那一份。现在判官在契约包,服务端与壳读同一份,同一条条目在两处同受理 / 同拒、同一个键路径、同一句原因。
8
- *
9
- * ## 语义是搬来的,不是重写的
10
- * 判据与原因句逐字等于消费方 7.131.x 的那一份(钉:`test/mcp-request-entry-14.0.0.test.ts` 逐例搬了消费方既有钉的夹具与期望)。
11
- * 本模块只答「这一格的形」;拒绝的整句(条目下标、条目名的回显清洗、收尾句)与「拒还是降级」的后果仍是消费方按自己的腿定的 —— 判据一份,后果按腿分。
12
- *
13
- * ## 依赖的引擎事实(镜像 + 漂移门)
14
- * · 申报名的线长界 {@link TOOL_WIRE_NAME_MAX_CHARS} = 引擎同名常量(值级对拍:上游漂移门 ⑧);
15
- * · 请求面传输形 {@link RequestMcpTransport} = 引擎 MCP 条目的传输形,http 形多一个本包承载的 `assertionHeader`(编译期双向对拍:门 ⑧);
16
- * · 内容出处词表 = {@link MCP_CONTENT_ORIGINS}(已有的镜像,门 ⑥);
17
- * · url 前缀守卫 = `./url-shape` 的 `startsWithHttpScheme`(本包判 URL 的正则只在那一个模块)。
18
- *
19
- * ## 与配置面的关系(如实)
20
- * 配置面的 MCP 条目(`McpServerSpec`,部署声明:凭据只许 env-NAME 引用)与请求面(提交体:可带字面凭据 `env` / `headers`)是两份契约,本模块只管后者。
21
- * 两份契约对「名」与「url」今天给的判据不同(配置面:名按字符集、url 按 `NON_SECRET_URL`;请求面:名按长度界、url 按 http(s) 前缀),本版刻意不动
22
- * (语义逐字搬),是一项已知待收的分歧。
23
- */
24
1
  import { type McpContentOrigin } from "./types.js";
25
2
  /** 值的**种类**(不回显值本身)。 */
26
3
  export declare function describeValueShape(v: unknown): string;
@@ -30,23 +7,31 @@ export declare function describeNotStringArray(v: unknown): string;
30
7
  export declare function describeNotStringRecord(v: unknown): string;
31
8
  /** 字符串数组(空数组合法)。 */
32
9
  export declare function isStringArray(v: unknown): v is string[];
33
- /** 字符串值的对象(空对象合法;数组不是)。 */
10
+ /**
11
+ * 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、自有 `__proto__` 成员、有非串成员的对象都不是)。与传输读器读 `env` / `headers` 是**同一个**定义
12
+ * ({@link readPlainStringRecord}):包内没有第二把更宽的尺。
13
+ */
34
14
  export declare function isStringRecord(v: unknown): v is Record<string, string>;
35
15
  /** 引擎 `TOOL_WIRE_NAME_MAX_CHARS` 的镜像:一台 MCP server(或 A2A 对端)的申报名会成为线上工具名的命名空间段,引擎对超界名是**拒**(不截断)。 */
36
16
  export declare const TOOL_WIRE_NAME_MAX_CHARS = 128;
37
17
  /**
38
- * 这个申报名能用吗。严判:非串、空串、超 {@link TOOL_WIRE_NAME_MAX_CHARS} 一律不可用。不截断、不规范化 —— 截短一个名字就是**换一个身份**,
39
- * 而被换掉身份的那台 server 上挂的 allowTools / toolFaces 收窄声明会一起对不上号。
18
+ * 这个申报名能用吗。严判:非串、空串、超 {@link TOOL_WIRE_NAME_MAX_CHARS}、**首尾带空白**一律不可用。不截断、不 trim、不规范化 —— 截短或剥掉一个名字就是**换一个身份**,
19
+ * 而被换掉身份的那台 server 上挂的 allowTools / toolFaces 收窄声明会一起对不上号。首尾空白 = 坏名(它不是另一个名字:名字是 `mcp__<server>__<tool>` 的身份片,
20
+ * 带着空白的一端无从与不带的那个对上号);内部空白(`a b`)照旧是合法名,本判官不扩判。
40
21
  */
41
22
  export declare function isUsableWireName(v: unknown): v is string;
42
- /** 坏名的**可打印**摘要(进拒句 / 告警元数据):种类、`empty`,或截到 32 字加长度事实 —— 绝不把任意长度的申报串原样回显。 */
23
+ /**
24
+ * 坏名的**可打印**摘要(进拒句 / 告警元数据):种类、`empty`,或引号形的回显加原因 —— 绝不把任意长度的申报串原样回显。回显口径见 {@link quotedCallerText}
25
+ * (剥控制与双向字符、截 64 字)。串的原因是:超界 ⇒ `N chars > 128`(N 是送来的原长度,不是剥完的);首尾带空白 ⇒ `leading or trailing whitespace`
26
+ * (制表 / 换行这类控制空白在回显里被剥掉,所以看得出是空白靠这个种类词,不靠引号里的样子);两件事并存 ⇒ 一句说全(`N chars > 128, leading or trailing whitespace`)。
27
+ */
43
28
  export declare function describeBadWireName(v: unknown): string;
44
29
  /**
45
30
  * 在内容出处词表({@link MCP_CONTENT_ORIGINS})内吗。严判:非串与大小写不符(`"LOCAL"`)一律不在 —— 引擎的词是字面量联合、不做规范化;
46
31
  * 替它「宽容一下」等于自造第二份值域语义,而这条轴上宽容的方向恰好是**放宽信任**(把一个写坏的声明读成 `"local"`)。
47
32
  */
48
33
  export declare function isContentOriginWord(v: unknown): v is McpContentOrigin;
49
- /** 坏内容出处值的**可打印**摘要:串 JSON 化、64 字截断加 `…`;非串只说种类。 */
34
+ /** 坏内容出处值的**可打印**摘要:串 ⇒ 引号形的回显({@link quotedCallerText}:剥控制与双向字符、截 64 字);非串只说种类。 */
50
35
  export declare function describeBadContentOrigin(v: unknown): string;
51
36
  /**
52
37
  * 一份 http 传输上两个身份头(`principalHeader` / `assertionHeader`)的读法:字符串才收;**`principalHeader` 胜** —— 它在场(不论值形)时
@@ -69,10 +54,21 @@ export type RequestMcpTransport = {
69
54
  principalHeader?: string;
70
55
  assertionHeader?: string;
71
56
  };
72
- /** 条目内一处坏形:`path` = 条目内的键路径(`transport.url`),`reason` = 判官的原因句(只说形与词表,不回显值)。 */
57
+ /**
58
+ * 条目内一处坏形:`path` = 条目内的键路径(`transport.url`),`reason` = 判官的原因句(只说形与词表,不回显值)。
59
+ *
60
+ * `env` / `headers` 这两张「名 → 串」记录上的坏形另带两格机读位,调用方分流不必解析句子:`fault` = 闭集词 ——
61
+ * · `not_plain_record`:这个键的值不是纯数据记录(不是对象 / 数组 / `null`,或原型不是 `Object.prototype` 与 `null`:`Map`、`Set`、类实例);整张记录不交;
62
+ * · `member_not_string`:有一个成员的值不是串(或访问器抛了);整张记录不交,`member` 点名它;
63
+ * · `member_name_reserved`:有一个成员叫 `__proto__`(引擎的声明收据拒这个名)。成员的值是串 ⇒ 只剔这个成员、其余照交,`path` 是成员级(`transport.env.__proto__`);
64
+ * 成员的值也不是串 ⇒ 整张记录不交,`path` 是键级,句子一次说全两件事 —— 名坏优先、值坏并列,所以词仍是 `member_name_reserved`。
65
+ * `member` 是成员名的**回显形**(剥控制与双向字符、截 64 字加 `…`),可直接进日志与句子,不是回查原对象的键。其余的坏形(`args` / `url` / `principalHeader` / ……)两格都不带。
66
+ */
73
67
  export interface McpEntryKeyIssue {
74
68
  readonly path: string;
75
69
  readonly reason: string;
70
+ readonly member?: string;
71
+ readonly fault?: "not_plain_record" | "member_not_string" | "member_name_reserved";
76
72
  }
77
73
  /** {@link readRequestMcpTransport} 的结果:`ok:false` = 硬坏形(这条 server 无从挂载);`ok:true` 的 `lost` = 读器**丢掉**的可选子键(server 照挂、少一个声明)。 */
78
74
  export type RequestMcpTransportRead = {
@@ -86,8 +82,11 @@ export type RequestMcpTransportRead = {
86
82
  /**
87
83
  * 请求面 `transport` 的**唯一读器**:读器投影 transport 用它,发送前 / 受理时的判形读它报的 `lost` —— 同一只函数两个消费点,「读器丢了什么」与「门拒什么」不可能分岔。
88
84
  * · 硬坏形(`transport` 不是对象 / `kind` 出闭集 / stdio 缺 `command` / http 的 `url` 不是 http(s))⇒ `ok:false`,`path` 是读器点名的键路径;
89
- * · 软坏形(`args` 不是串数组 / `env` `headers` 含非串值 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
85
+ * · 软坏形(`args` 不是串数组 / `env` `headers` 不是纯数据的串记录 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
90
86
  * (受理新提交的门据它响亮拒)。`assertionHeader` 不在 `lost` 里:两个身份头的键组合与 `assertionHeader` 的值形由调用方在读之前对原始传输判。
87
+ * · `env` / `headers` 只收**纯数据**的记录({@link readPlainStringRecord}):原型必须是 `Object.prototype` 或 `null`(`Map`、`Set`、类实例整张不收),每个自有成员的值都是串;
88
+ * 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)。名叫 `__proto__` 的成员(JSON 文本铸得出这个自有键,对象字面量写不出)不进副本、单独记进 `lost`。
89
+ * 坏形的 `lost` 条目带机读位(见 {@link McpEntryKeyIssue})。
91
90
  * url 的拒句不回显值:它常规地带凭据(userinfo)。
92
91
  */
93
92
  export declare function readRequestMcpTransport(t: unknown): RequestMcpTransportRead;