@sema-agent/settings-schema 14.6.0 → 14.7.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 +266 -0
- package/README.md +31 -3
- 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/config-d-file.js +33 -15
- package/dist/config-fns.js +142 -78
- 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.js +43 -6
- 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/file-edit.js +11 -15
- package/dist/fleet.d.ts +11 -0
- package/dist/fleet.js +59 -26
- 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 +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp-request-entry.d.ts +9 -7
- package/dist/mcp-request-entry.js +89 -82
- package/dist/one-time-migrations.js +207 -95
- 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/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/types.d.ts +6 -2
- package/dist/types.js +11 -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 +51 -1
- 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 +35 -4
- package/docs/CONSUMER-INTEGRATION.md +178 -0
- package/llms-full.txt +23 -4
- package/llms.txt +3 -2
- package/package.json +1 -1
- package/dist/definition-copy.d.ts +0 -23
- package/dist/definition-copy.js +0 -52
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,272 @@
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 14.7.0 — 2026-10-25 — minor:沙箱 git 凭据平面的契约件 —— origin 规范形判官与有效配置块 `sandboxGit`(`SandboxGitConfig`)· 凭据取值口 `sandbox_git` 的 wire 型与三张词表(`SANDBOX_GIT_ORIGIN_VERDICTS` · `SANDBOX_GIT_REFUSALS` · `SANDBOX_GIT_PERMANENCE`)· `VerifiedIdentity.principalInc` 与令牌声明 `sub_inc` · 「后面要拼路径的基址」座位的禁用成分集与判官(`PATH_BASE_URL_FORBIDDEN_PARTS` · `pathBaseUrlFlaws`)与公告地址 env 名常量 `FLEET_ADVERTISE_ADDRESS_ENV`
|
|
12
|
+
|
|
13
|
+
> 档位(按发版纪律):**minor** —— 加性:新导出(包根 +28 名,其中 `FLEET_ADVERTISE_ADDRESS_ENV` 同时在 `./fleet` 上;`./api/auth-bridge` 子路径 +2 名:`PRINCIPAL_INC_CLAIM` · `PrincipalIncarnation`);`VerifiedIdentity` 多可选键 `principalInc`;`EffectiveWire` 多可选键 `sandboxGit?: unknown`;`HttpHookUrlBlock.rule` 闭集多一词 `malformed`(判官读不出的层的判决词,见 http hook 名单条)。
|
|
14
|
+
> **没有**新子路径(`src/api/credentials.ts` 与 `src/sandbox-git.ts` 只经包根出口)、没有删改的导出;`buildEffective` 的源码没动(但非门控域 `domain-defaulted` 告警里 `error` 的类由 `Error` 变 `TypeError`,见例外 ④;受影响导出表已列它),`readEffectiveWire` 对声明类型内的 JSON 载荷的答案逐字不变(读法改成自有键快照,见「读口对固定键的读只认自有键」条;例外只有节首五类 —— 其中非门控域读不进时的 `domain-defaulted` 告警里 `error` 的类由 `Error` 变 `TypeError`,见 ④),一致性门没有新腿;语料新增两条(`c84` / `c85`:`sandboxGit` 块合格与不合格各一,wire 腿都逐字承运、零告警),既有 case 的读数逐字不变。
|
|
15
|
+
> **读口行为变化**:① `toVerifiedIdentity`(`sub_inc` 那一条):令牌声明 `sub_inc` 在场而不是非空串 ⇒ 该令牌 `malformed`;此前这个声明被忽略。② **JS 对象面上的收严**(嵌入方交进来的 Proxy / `Map` / 类实例 / 被污染的 `Object.prototype` —— JSON 解析不出这些):
|
|
16
|
+
> 读口对固定键只认自有键、读不出各答各的、形状只问座位、`Map` / `Set` 作 hooks 块整份拒、继承来的身份头进 `lost`、`stableStringify` 读不出时抛自己的句子 —— 逐条见本节「读口 × 输入形 ⇒ 答法」及其后几条,各带三问与钉。**声明类型内的 JSON 输入,答案逐位同 v14.6.0**;其余全是新面。差异只有下面五类例外,逐例核过、没有第六类:随机差分对 v14.6.0 构建(2500 轮,每轮把本批改过的每只读口各喂一份随机 JSON;另含四类生成器 —— 「深嵌套字段」「自有 `__proto__` 数据键」「声明类型外的载荷」「怪形值」〔带 `length` / `toString` / `constructor` / `__proto__` 的对象与数组,撒在任意嵌套位置〕;两个种子各约 18.8 万例,差异 118 与 108 例,逐例归入这五类,未归类 0 例、新增的抛 0 例;令牌判官另跑 4 万次含怪形声明值的调用,从不抛)。第 ⑤ 类由另一个 realm 的 `JSON.parse` 产物单独探出,不在随机生成器里。
|
|
17
|
+
> ① **声明类型外的载荷**:`null` / `undefined` 作令牌载荷交给 `toVerifiedIdentity`(带不带受众都一样)与 `readTokenScope`,v14.6.0 抛引擎的 `TypeError`(`Cannot read properties of null`),现在答 `malformed` / `scope_claim_missing`;http hook 两只判官的声明类型外入参 —— `httpHookUrlVerdict` / `httpHookEnvVarsAllowed` 的 `layers`:层缺 `source`、`source` 不是串、元素不是对象(例 `[{}]` / `[1]` / `[null]`)、`layers` 本身不是数组(例 `null`),v14.6.0 抛(`RangeError`「unknown settings source」或引擎的 `TypeError`),现在那一层(或整个 `layers`)判坏形层(`rule: "malformed"`,全拦、不抛);`httpHookEnvVarsAllowed` 的 `hookVars`:不是数组 v14.6.0 抛引擎的 `TypeError`,含非串条目(例 `[1]`)v14.6.0 原样放回(`[1]`),现在都当空(`[]`);`httpHookUrlVerdict` 的层上名单不是串数组(例 `{}` / `"x"` / `[1]` / `null`):v14.6.0 答 `rule: "empty"`(点名那一层),现在答 `rule: "malformed"`(同样点名;两版都全拦)—— 方向:抛 / 原样放 / 借词 ⇒ 响亮的拒绝结果,fail-closed;本批改过的其余读口对 `null` / `undefined` / 数字 / 字符串 / 布尔 / `[]` / `[1]` 这几种入参的答案不变。
|
|
18
|
+
> ② **自有 `__proto__` 数据键**(`JSON.parse` 铸得出的合法 JSON):`migrateEffectiveSnapshot` 在**凡是真正改写了快照**的路径上(移除退役域、改写某个域的文档),顶层这个键 v14.6.0 都被赋值语义吞掉,现在作为自有数据键原样保留(与不改写快照的路径一致;改进,不回退;结果对象的原型不受影响)。
|
|
19
|
+
> ③ **要改写的 worker 行里嵌了几千层的字段**:v14.6.0 在 3000 层上就抛 `RangeError: Maximum call stack size exceeded`,现在照迁(1000 层以内两边同答,见 SS-M4 一条);没有 6.x 键的行两边都原样放行。
|
|
20
|
+
> ④ **合法 JSON 里让校验器自己抛的值**(例:`image: { length: { toString: [1] } }` —— 非字符串却带 `length`,长度检查要把它转成原始值,寻常对象上同样转不了):v14.6.0 里 `readWorkersRows` 与迁移两入口把引擎的 `TypeError` 抛出整次调用,现在该行被扣下 / 判 refused,拒因是定句 `… a value could not be read during validation`(方向:抛 ⇒ 响亮的拒);`parseDomain` 家族与 `readEffectiveWire` 的**门控域**对这类值 v14.6.0 本来就抛 `… (a value could not be read during validation)`,现在同一句、类由 `Error` 变 `TypeError`;`readEffectiveWire` 的**非门控域**此前对这类值答 `domain-defaulted` 告警(不抛),告警本身不变、告警里 `error` 的类同样由 `Error` 变 `TypeError`;逐条丢弃的条目(`entry-unreadable`:`salvageEntries` 返回的 `dropped[].error`、`entry-dropped` 告警的 `error`,经 `parseDomainSalvaged` / `readEffectiveWire` / `loadLocalConfig` 出来的同一条)的 `error` 类同样由 `Error` 变 `TypeError`(message 不变);`readEffectiveWire` 的 **skills 清单腿**此前对这类值(例:`skills: { skills: [{ name: { length: { toString: [1] } } }] }`)把引擎的 `TypeError` 抛出整次读取,现在与非门控域同一臂 —— 逐条抢救:让校验器抛的那一条丢它自己(`entry-dropped`,`reasonCode` 是 `entry-unreadable`,`error` 是 `TypeError` 那句中性话),同一清单里其余合法条目保留;一条都救不回(像例里那样只有这一条)⇒ `domain-defaulted` 告警(`error` 是 `TypeError` 那句中性话)+ 空清单(方向:抛 ⇒ 逐条丢 / 响亮的降级);`parseRemoteExecDocument`(以及读写 `config.d/remote-exec.json` 的 `loadRemoteExec` / `writeRemoteExec`)对这类值 v14.6.0 抛 `Error`(`invalid remoteExec (a value could not be read during validation)`),现在同一句、类由 `Error` 变 `TypeError`;同步口 `migrateConfigDocuments` 内部走 `migrateStoredDocuments`,继承迁移入口那一臂(此前抛引擎的 `TypeError`,现在 `kind: "refused"`)。
|
|
21
|
+
> ⑤ **另一个 realm 的 `JSON.parse` 产物**:http hook 安全轴的三只数组(名单、`hookVars`、`layers`)若是另一个 realm 的数组,本版按坏形全拦(名单 `[]` + `malformed`、`hookVars` 当空、`layers` 坏形层)—— v14.6.0 读得到;这一处是**收紧**,三问见 http hook 名单条 ③。另外几处(都是改进):workers **行**的类型句(`readWorkersRows` 扣行、迁移拒行)里 `received Object` 变成 `received object`;若该行里还有带自有 `constructor` 键的值,v14.6.0 会把用户文本放进拒因(`received hello world` —— 取的是 `constructor.name`),新版恒是 `received object`,与本 realm 的同形输入逐字同(改进);`constructor.name` 不能转成串(例:`{ toString: 1 }`)时,v14.6.0 从 `readWorkersRows` 抛 `TypeError: Cannot convert object to primitive value`,新版把这一行扣下(抛 ⇒ 扣下,同属改进)。同一机制的另几处(都是改进,方向:抛引擎的错 ⇒ 包自己的答案):`parseDomain` 家族与 `parseRemoteExecDocument` 在 zod 4.6 上 v14.6.0 抛 `TypeError: Cannot convert object to primitive value`,新版抛各自那一句 `… a value could not be read during validation` 的 `TypeError`;编辑器的 `validateDomain` / `validateRemoteExec`(`./node`)同形,v14.6.0 抛引擎的错,新版答 `{ ok: false }`、issue 是同一句;`readEffectiveWire` 的 skills 清单腿遇另一 realm 的怪形条目,在 zod 4.4 上 v14.6.0 抛引擎的 `TypeError`,新版那一条 `entry-dropped`(`entry-unreadable`)、其余保留;`readEffectiveWire` 的非门控域同理,`domain-defaulted` 告警的 `error` 由引擎的错变成包的那一句;令牌判官 `toVerifiedIdentity` 在 zod 4.4 上 v14.6.0 抛,新版答 `malformed`(zod 4.6 上两版都答 `malformed`)。原因是同一个:zod 4.6 起错误对象是首读才构造的 —— `error` 首读才拼 issue 句子、`message` 首读才 `JSON.stringify(issues)`(4.4 在 `safeParse` 里两步都做完),本包把 `safeParse`、错误对象的整个构造与读口对它的脱敏放进同一只守护(错误对象读不完 ⇒ 判决读不出),两版 zod 同答。同一机制另两处:`applyProjectPatch` 收到另一 realm 的补丁时 v14.6.0 抛引擎的 `TypeError`,现在答拒(`success: false`,坐标是该项目,句子 `the patch could not be read during validation`);被污染的运行时上(`Object.prototype.value` 一类:zod 拼句子时拿字面描述符调 `defineProperty` 而抛)`readWorkersRows` 不再抛 zod 的描述符错(该行扣下),`parseWebAdminCredentialFile` 答 `unreadable`(`envelope could not be read during validation`),都不再抛。同一机制的又一触发:**`z.record` 位上带符号键的对象**(例:`models` 域的 `roles` 里多一个符号键)—— zod 4.6 拼 issue 句子时把符号路径转串而抛,v14.6.0 从 `parseDomain` 家族与 `validateDomain` 抛引擎的 `TypeError: Cannot convert a Symbol value to a string`,新版 `parseDomain` 家族抛那一句 `… a value could not be read during validation`、`validateDomain` 答 `{ ok: false }`(zod 4.4 上两版都答 `invalid_key` 的拒)。**仍是缺口**(v14.6.0 同、两版 zod 同,如实):`parseDomain` 家族与 `readEffectiveWire` 的拒因里,zod 自己的 `invalid_type` 句子对另一 realm 的对象仍读 `constructor.name` —— 用户写的自有 `constructor` 键会作为文本进拒因(只有 workers 行的终判不读它)。随机差分里不含本版新增的 `sub_inc` 声明(那是新面)。
|
|
22
|
+
> 另有一处消费方要知道的契约变动(抄闭包的生成器):请求面 MCP 条目判官的闭包组(`mcp-request-entry` 的抄本)的注入名变了 —— 相对 14.6.0 多 7 个:`inspect` / `readKey` / `ownValueOf` / `ownFieldsOf` / `UNINSPECTABLE`(此前是模块内私有声明,随闭包一起抄)、`isJsonArray`(此前用内建 `Array.isArray`)与 `ownEntriesOf`(类型守卫 `isStringRecord` 改按严归类读成员,见「读内核覆盖声明键读与整容器读」条);`captureStringArray` / `dataDescriptor` 的**模块名**也变了(改由 `./own-key-read` 引入;此前的 `./definition-copy` 已并入内核、删除)。提货本版时生成器的「自由名恰等」门会红(提货即通知;已抄进消费方仓库的旧抄本照旧工作)。这几只内核函数自己的函数体,传递地还引用包内私有的 `sentenceOfMinted` 与它读的那张模块级身份表 `MINTED`(用内建 `WeakMap`)、读内核的三只件 `readOwnDescriptor` / `valueOfDescriptor` / `isAccessorDescriptor`(用内建 `Reflect`),以及整容器读 `containerView` 与它用到的 `isArrayIndexKey` / `NO_PRIOR_READS`(用内建 `Map`)/ `NOT_PLAIN_DATA` / `UninspectableError`(继承内建 `TypeError`)/ `appendOwn`(`captureStringArray` 与 `ownEntriesOf` 都经它),和 `captureStringArray` 自己经过的稠密判据 `denseArrayView`、拷贝循环 `captureDenseArray` 与条目判据 `isStringEntry`:消费方若把它们的函数体整段抄进闭包(而不是自备同义实现),这一组要多处理这十四个名字与新带进来的三个内建名 `WeakMap` / `Map` / `TypeError`(`Reflect` 在 14.6.0 的闭包里就有;注入名本身不变:传输 `env` / `headers` 的成员改走 `readKey`,不再自带一份描述符读法)。hooks 分类器闭包的自由名**不变**(`HOOK_EVENTS` + JS 内建,没有新内建:迭代协议的符号取自 `Array.prototype`,不引 `Symbol`)。
|
|
23
|
+
|
|
24
|
+
- **凭据取值口 `sandbox_git` 的 wire 型 + 三张词表(`src/api/credentials.ts`,包根)**。沙箱里的 git 用成员本人的凭据:server 向 web-admin 的取值口按 run 的身份申领一枚短期、一次性的句柄,再凭句柄兑换;此前两方各自手写这几个形,「同一份 zod,两方同 import」没有落点。
|
|
25
|
+
现在:请求 `SandboxGitCredentialRequest`(`kind` 字面量 `"sandbox_git"`、`principal` ≤ `PRINCIPAL_MAX_LENGTH`、`principalInc`、`scope`、`origins` 1–16 个规范形且两两不同、`sid`;**http origin 也收** —— 中心对它答判定序第 1 行,请求形不抢先拒)·
|
|
26
|
+
句柄应答 `CredentialHandleAnswer` 与兑换请求 `CredentialResolveRequest`(与凭据种类无关)· 逐 origin 应答行 `SandboxGitOriginAnswer`(按 `state` 判别)· 应答信封 `SandboxGitCredentialAnswer`(`answeredAt` / `cacheUntil` / `active` 行的 `expiresAt` 是同一只钟的毫秒,收方只用差)·
|
|
27
|
+
整次拒体 `SandboxGitCredentialRefusal`(`{ error, permanence, message }`)。三张词表**各一张、每个词只在 `src/api/credentials.ts` 里拼一次**:
|
|
28
|
+
判定序表 `SANDBOX_GIT_ORIGIN_VERDICTS`(11 行,序即判定序;状态六词 `SANDBOX_GIT_ORIGIN_STATES`、原因十词 `SANDBOX_GIT_ORIGIN_REASONS` 与类型由它派生,应答臂里 `reason` 的值域取自表 —— 状态与原因的配对由表定)·
|
|
29
|
+
拒答表 `SANDBOX_GIT_REFUSALS`(十个错误词 → HTTP 状态集与 `permanence`;`SANDBOX_GIT_REFUSAL_ERRORS` 与类型由它派生;拒体里的 `permanence` 必须等于表里这一行的,否则是畸形应答)· `SANDBOX_GIT_PERMANENCE`(`terminal` / `transient`)。
|
|
30
|
+
解析一律剥未知键(前向兼容),且 `secret` 只在 `active` 臂的形里 —— 别的臂即使收到也剥掉;`needs_reauth` 行必带 `credentialId`。
|
|
31
|
+
**本包不做**(如实,`docs/CONSUMER-INTEGRATION.md` §12.9):应答与请求的逐条对应 / 回显相等核对、租约算术与续期状态机、哪些 terminal 词可恢复、两个端点的路径常量、取值口另两种凭据(`model` / `mcp_static`)的形。
|
|
32
|
+
三问 —— **谁需要**:web-admin(取值口的实现:用同一份 zod 收请求、造应答与拒体)与 server(取值口客户端:发请求、收应答);**谁受伤**:无 —— 全是新导出,没有既有读者;取值口另两种凭据落地时与本版的 `sandbox_git` 形并成按 `kind` 判别的联合,本版的形不动;
|
|
33
|
+
**补偿**:无需。
|
|
34
|
+
钉:`test/sandbox-git-credential-wire-14.7.0.test.ts`(三张词表逐字对设计金样 · 判定序表 11 行各一格 + 状态 × 原因叉积恰 10 对 · `needs_reauth` 必带 `credentialId` · `secret` 只在 `active` 臂、别的臂剥掉 · 请求 / 句柄应答 / 兑换请求 / 应答信封 / 拒体正反 · 拒体 `permanence` 与表一致 · `principalInc` 与令牌声明是同一只 schema 对象 · 单一属主反向门:词只在 `src/api/credentials.ts` 里作代码字面量)。
|
|
35
|
+
- **origin 规范形判官 `isSandboxGitOrigin` 与有效配置块 `sandboxGit`(`SandboxGitConfig`,`src/sandbox-git.ts`,包根)**。规范形只有一条律:**一个 origin 就是它自己的 WHATWG origin**(串等于 `new URL(串).origin`,scheme ∈ {http, https});小写 host、去缺省端口、无尾斜杠、无 userinfo / 路径 / 查询 / 片段都是它的推论,不是一张手写规则表;
|
|
36
|
+
「是不是 http(s) URL」归包内唯一的 URL 形判官 `httpUrlFlaws`。origin 不表达子路径部署(`https://host/gitea` 不是 origin);没有「削成 origin」的规范化器 —— 把运维写的路径静默削掉会把凭据送到他没写的地方。
|
|
37
|
+
块 `sandboxGit?: { origins: [{ origin, connectUrl }] }` 是 `/effective` 载荷上的**非域顶层键**(web-admin 按部署登记册投影,不是存储域):可选、无默认(缺席 = 这个部署没有投影这一块),`origins` ≤ 16(`SANDBOX_GIT_MAX_ORIGINS`)、按 `origin` 两两不同、空表合法,`connectUrl` 是控制台「连接 Gitea(git)」页的绝对 https URL(非秘密)。
|
|
38
|
+
`readEffectiveWire` 对它走既有的 open-world 逐字承运(不校验、不丢;`EffectiveWire.sandboxGit` 是可选的 `unknown`),**判它的唯一座位是 `SandboxGitConfig`**,由消费方在用之前跑。
|
|
39
|
+
🔴 **块里 http origin 可读**:它为什么送不出凭据是逐 origin 应答里的一个词(`insecure_transport`)与能力读数里的一个词,不是块的校验拒 —— 块若因一个 http origin 整块不可读,同块里的 https origin 一并失效,「逐 origin 说拒因」就无从谈起。
|
|
40
|
+
三问 —— **谁需要**:web-admin(投影块前先过 `SandboxGitConfig`)与 server(读块、构造请求的 `origins`);**谁受伤**:无 —— 新导出与可选键;键缺席的老 web-admin 与不读它的老消费方行为不变;
|
|
41
|
+
**补偿**:无需。
|
|
42
|
+
一致性语料:`conformance/cases/c84-sandbox-git-block-carried-verbatim`(合格块,含一个 http origin)· `c85-sandbox-git-refused-block-still-carried`(不合格块)—— 两条的 wire 腿期望都是逐字承运、零告警,消费方的 wire 读腿跑语料时由此知道这个键必须原样过。
|
|
43
|
+
钉:`test/sandbox-git-origin-14.7.0.test.ts`(判官正 8 反 25,每个反例写明违反哪一条 · `SandboxGitOrigin` / `SandboxGitOriginList`(上界 16、重复被点名、空表合法)/ `SandboxGitConnectUrl` / `SandboxGitConfig` 正反各一 · 块里 http origin 可读 · 缺席不铸键、在场同一引用承运、坏块照样承运而 `SandboxGitConfig` 拒 · 类型面)。
|
|
44
|
+
- **`VerifiedIdentity.principalInc?` 与令牌声明 `sub_inc`(`api/auth-bridge`,包根与 `./api/auth-bridge`)**。同名主体删号重建之后是另一个人,`sub` 只命名一个槽位;现在令牌可以带化身号声明 `PRINCIPAL_INC_CLAIM`(`"sub_inc"`),`toVerifiedIdentity` 读它:**在场且是非空串 ⇒ `identity.principalInc` 原样**;
|
|
45
|
+
**缺席 ⇒ 读出形里不铸键**(从不读成 `0` / 空串 / 别的占位 —— 缺化身号不是本契约的拒,读方自己答「化身号未记录」);**在场而不是非空串(数字含 0 / 空串 / `null` / 布尔 / 对象 / 数组)⇒ 该令牌 `malformed`**(与 `sub`、`iat` 同律:声明形是垃圾,不降级成缺席 —— 降级会把坏掉的签发方读成「没有化身号」)。
|
|
46
|
+
规则就是输出字段自己的 schema `PrincipalIncarnation`(非空串),令牌声明、`VerifiedIdentity.principalInc` 与取值口请求 / 应答的同名字段读的是**同一只 schema 对象**。
|
|
47
|
+
三问 —— **谁需要**:读令牌的一方(把 run 的身份钉到化身上:取值口请求带它)与铸令牌的一方(web-admin:用 `PRINCIPAL_INC_CLAIM` 铸);**谁受伤**:令牌里**已经**带了形不对的 `sub_inc` 声明的签发方 —— 此前它被忽略,从本版起整张令牌 `malformed`(已知没有这样的签发方;老令牌不带该声明,读数与升级前逐字相同);直接拿 `VerifiedIdentity` 判身份袋的调用方同理 —— 袋里带形不对的 `principalInc`(例 `{ principal: "u", principalInc: 5 }`)此前被当未知键剥掉照过,现在拒(声明类型外的输入);
|
|
48
|
+
**补偿**:把签发方的 `sub_inc` 改成非空串或不铸;读方要化身号而令牌缺它时自己答「未记录」,不靠本契约拒令牌。
|
|
49
|
+
钉:`test/principal-inc-14.7.0.test.ts`(在场 / 缺席 / 非串(含 `0`、空串、`null`、布尔、对象、数组)三格 · 逐字(空白 / 大小写 / 非 ASCII 不动)· 老令牌读数与升级前逐字相同 · 声明名 · `PrincipalIncarnation` 与 `VerifiedIdentity` 字段对象恒等 · 类型面);`test/auth-bridge-iat-11.0.0.test.ts`、`test/api-scopes.test.ts`、`test/api-auth.test.ts` 照绿。
|
|
50
|
+
- **「后面要拼路径的基址」座位的禁用成分集与判官 `PATH_BASE_URL_FORBIDDEN_PARTS` / `pathBaseUrlFlaws`(`src/url-shape.ts`,包根)+ 公告地址的 env 名 `FLEET_ADVERTISE_ADDRESS_ENV`(`src/fleet.ts`,包根与 `./fleet`)**。有一种座位的值是别人要往它后面拼路径的基址 —— 中心基址三席、fleet 成员对中心公告的可达地址、会话网页链接的基址:凭据不进一个会被记日志、转述、拼路径的值;
|
|
51
|
+
`query` / `fragment` 挂在基址上,一拼路径就落到错的位置(`http://x?a=1` 拼上 `/v1/…` 是 `http://x?a=1/v1/…`);路径前缀放行。此前这条规则的三个词在多处各抄一份(server 一处、本包 `webAdminSessionUrl` 里内联一处),web-admin 编排器(渲染 `FLEET_ADVERTISE_ADDRESS=http://$(POD_IP):8090` 的一方)要「同一只判官、不手写 env 名」没有落点。
|
|
52
|
+
现在:`PATH_BASE_URL_FORBIDDEN_PARTS`(闭集、冻结,`userinfo` / `query` / `fragment`,判序;词与 server 今天那只逐字同;**策略集,独立于判官词表 `HTTP_URL_PARTS`** —— 词表说 URL 有哪些成分,本集说「拼路径的基址」禁哪些,今天碰巧同三词,但不由词表派生(派生会让词表加一个词静默变成策略多禁一个词),词表加词时本集要显式裁;三词字面量在包里恰两处:词表与本集)·
|
|
53
|
+
`pathBaseUrlFlaws(url)`(= `httpUrlFlaws(url, 该集)`,不另写判法;合格 ⇒ `[]`,否则按判序交回规则词,答案不含值的任何片段)·
|
|
54
|
+
`FLEET_ADVERTISE_ADDRESS_ENV = "FLEET_ADVERTISE_ADDRESS"`(与 `FLEET_GENERATION_ENV` / `FLEET_SCOPE_ENV` 同文件同形,字面量类型)。集与判官按座位的本质命名,不按某个消费键名;本包自己的 `webAdminSessionUrl` 改读这一只判官(对同一输入的答案与改前逐字相同)。
|
|
55
|
+
读数照 server 判官今日行为:`http://$(POD_IP):8090` 过、`http://[$(POD_IP)]:8090` 拒(括号里展开前不是 IPv6 字面量 ⇒ **判占位展开之后的值**)、路径前缀过(路径不在集里)、query / fragment / userinfo(含空 `?` / 空 `#`、只有用户名)各拒、非 http(s) / 不透明形 / 空串拒。
|
|
56
|
+
**没动的**(如实):`FLEET_ADVERTISE_ADDRESS` 不进 `RESERVED_WORKER_ENV`(运维可经 `worker.env` 声明它,与 generation / scope 两只「只有渲染器能写」不同);占位展开、可达性与缺席 / 空串的处置不在本包(`docs/CONSUMER-INTEGRATION.md` §13.4);登记表只跟名字不跟词,词与词序由钉守。
|
|
57
|
+
三问 —— **谁需要**:server(7.137.0 起中心基址三席与公告地址都改读 `pathBaseUrlFlaws`,删本地的禁用集副本与 env 名字面量)与 web-admin 编排器(渲染该 env 前对展开后的值调 `pathBaseUrlFlaws`,键名取常量);**谁受伤**:无 —— 新导出,没有既有读者;`webAdminSessionUrl` 的答案逐字不变;server 改读后判官读数逐字不变(词与判官是同一只);
|
|
58
|
+
**补偿**:无需。
|
|
59
|
+
钉:`test/path-base-url-forbids-14.7.0.test.ts`(env 名逐字 + 字面量类型 + 字面量全包一处 · 集逐字 / 闭集 / 冻结 / 字面量元组 / 独立于词表的一份 · 判官读数表 23 行 · 判官 ≡ `httpUrlFlaws` 配这一集且函数体无第二份 URL 判法 · 三词字面量全 `src/` 恰两处(词表 + 策略集,点名两坐标)且 `webAdminSessionUrl` 与判官同答 · 登记表登记与试行名零残留 · §13 与本条文字);`test/web-admin-session-url-11.2.0.test.ts`、`test/url-shape-11.0.0.test.ts`、`test/api-surface.test.ts` 照绿。
|
|
60
|
+
- **随包文字与导出面登记表**。`docs/CONSUMER-INTEGRATION.md` 新增 §12「沙箱 git 凭据平面」—— web-admin 与 server 两方各一小节的读法,判定序表(含各行的条件)与整次拒答表;README / `llms-full.txt` 点到新面;`docs/API-SURFACE.md` 重生成(包根 +28 名,`./fleet` +1 名,`./api/auth-bridge` +2 名,无删除、无改形)。
|
|
61
|
+
三问 —— **谁需要**:接线的两方(web-admin 与 server)与读 `dist/*.d.ts` 的人;**谁受伤**:无;**补偿**:无需。
|
|
62
|
+
钉:`test/sandbox-git-docs-14.7.0.test.ts`(§12 里的两张表与代码里的词表逐行同序同词 · 三张词表的每个词都在节里 · 本节三问形且钉指的测试文件真在 · README / `llms-full.txt` 点名新面);`test/api-surface.test.ts`(登记表与实际导出面逐名比对)。
|
|
63
|
+
- **发布说明里的「受影响导出表」每版相对上一个 tag 机器重算**。14.6.0 节里的两张表是那一版的快照(相对 v14.5.0),撑着它们的种子名单(改动过的声明 / 新导出 / 读座位的声明)是随版冻结的 —— 下一版一动 `src/` 它们就红,且红的原因是基线过期、不是回归。
|
|
64
|
+
现在:**基线是入参,不是常量**。基线 tag 由 `package.json` 的版本与仓库的 tag 表推出;「运行期代码有改动的声明」「新导出」「受影响的公开导出」全由两版源码现算,没有一处手写名单;
|
|
65
|
+
发版准备(版本已抬、tag 未打)时,该版本那一节必须带这张表、逐字同重算,多一行少一行、基线写错都红;**已发布版本的节(含 14.6.0 的两张表)永不再被核,一个字节没动**。
|
|
66
|
+
表的生成口:`node test/helpers/affected-exports.ts` 打印整块(标签行 + 围栏),粘进版本节。扫描器自己的盲区(非「具名函数 / 类 / 标识符变量」的顶层语句)由它自报、要求为空。没有 tag 的检出(浅克隆 / 没拉 tag)里依赖基线的几格会跳 —— 判不变(仍是跳),但原因响着打出来,并说怎么补(`git fetch --tags`)。
|
|
67
|
+
14.6.0 的「判定列」是那一版座位谓词变更的证据(v14.5.0 构建 × HEAD 构建对跨 realm 对象的答案由 false 变 true),随之退役;跨 realm 对象的行为面留作常驻钉(HEAD 一侧:每个探针 `foreign` 观察值 = `plain` 观察值,负控证明 `foreign` 真是另一个 realm 的)。
|
|
68
|
+
已知的边(如实):依赖包升级造成的答案变化不在 `src/` 里,这把扫描看不到;表回答的是「本包源码里哪些导出的运行期代码动过」。
|
|
69
|
+
三问 —— **谁需要**:读发布说明、要核「哪些既有导出的运行期代码动过」的消费方,与发版的人(不再手改一整套冻结名单);**谁受伤**:无 —— 没有导出的行为变化,变的只是发布说明里这张表的生成方式与基线;
|
|
70
|
+
**补偿**:无需。
|
|
71
|
+
钉:`test/affected-exports-since-baseline-14.7.0.test.ts`(合成小仓上的算法读数:改动声明 / 受影响导出含别名・命名空间成员・简写属性・类方法体・两跳、类型位置的引用不入 / 新导出不入表 / 空表 `(无)` / 盲区自报 / 种子缺席点名 / 公开值导出名 · 基线 tag 由版本与 tag 表推出(开发窗不核、发版准备窗核、数字序、无基线)· 表的定位与核对只看名下那一节 · 生成口整块粘进版本节即过核对 · 真树上盲区为空 / 门不空转 / 发版准备窗里当前版本节的表逐字同);`test/cross-realm-probes-14.6.0.test.ts`(每个探针跑得通且 `foreign` = `plain` · 负控)。
|
|
72
|
+
- **形状只问座位:全包裸 `Array.isArray` / `typeof x === "object"` 收口**。包里读「可能是嵌入方 JS 对象」的口各写各的形状判:裸 `Array.isArray(x)`(已撤销的 Proxy 上抛 `TypeError`,陷阱抛也一样)、裸 `typeof x === "object"`(只回答「是对象」,没问「是不是朴素 / 读得出」,随后的成员读就在原型链上乱走)。
|
|
73
|
+
现在:`Array.isArray(…)` 的调用点只在座位模块(两处:`jsonShapeOf` 的数组臂与 `isJsonArray`;安全轴的朴素数组判据 `isPlainJsonArray` 经 `isJsonArray` 问),外加 **hooks 闭包副本两处审过的例外**(`hooks-config.ts` 里的 `hookShapeOf` / `hookIsArray`:hooks 分类器的函数体要被消费方的生成器整段抄走,抄出去的闭包只许带固定的几个自由名、不能引座位模块,所以带座位的字节同文副本,同文门守着;这两处随闭包契约变更一起消失),别处一律问 `isJsonArray` / `jsonShapeOf`;直接的 `typeof x === "object"` 比较同样只许在座位模块、闭包副本的 `hookShapeOf` 与一处写明理由的审计例外(`quoted-issue.ts` 的模板片段,是本模块自己铸的)。反向门数 `src/` 的 AST。
|
|
74
|
+
读不出的对象(撤销的 Proxy、`getPrototypeOf` 陷阱抛)在各读口的答案由座位的形状词定,**答法**见下表;`stableStringify` / `stableHash` 对读不出的值**抛**(原先是引擎的 `TypeError`),句子是 `stableStringify: <全包唯一那一句> — refusing to hash`,JSON 与寻常非 JSON 值的哈希逐字不变。读得出的值:每个容器经那一只整容器读列一次成员(对象:可枚举的自有串键,同 `Object.keys`;数组:自有下标与 `length`),每个成员恰一次描述符,值只取自这份描述符 —— 不经 `[[Get]]`(Proxy 的 `get` / `has` 陷阱不问)、不顺原型链(数组的洞仍是洞)、不调数组实例上的方法;对象按键排序、数组按下标,每个成员在它的哈希位上取值(访问器此时调列成员时那只 getter 一次),所以 getter 的调用顺序与 14.6.0 同;数组长度是 `ToLength(ToNumber(长度值))`(BigInt / Symbol 长度 ⇒ 拒;超过数组上限 ⇒ 拒,下标的值一个都不取、getter 不调 —— 但列成员时自有下标的描述符已各问过一次,14.6.0 的 `map` 在问任何下标之前就抛)。
|
|
75
|
+
与 14.6.0 不同答的一类(全在 JS 面;一句话说全,下面的构造是举例、不是全集):**哈希只看自有描述符视图** —— 凡结构陷阱(`has` / `ownKeys` / `getOwnPropertyDescriptor`)或 `get` 陷阱与描述符视图不一致的 Proxy、哈希途中改写自身的 getter、原型上的数字键、数组实例上的方法,哈希随描述符视图(或拒);14.6.0 跟 `Object.keys` / `map` 的 `in` / `[[Get]]` / 实例方法走。JSON 面没有这一类。举例:
|
|
76
|
+
前面的 getter 改写 / 删掉 / 换成访问器的后面成员(或把数组的 `length` 改短)⇒ 按列成员那一刻的描述符取值(`{ b: 2, get a() { delete this.b; return 1; } }` 由 `{"a":1,"b":null}` 变 `{"a":1,"b":2}`;被删掉的访问器成员仍按列成员时那只 getter 调一次)·
|
|
77
|
+
`get` 陷阱抛 / 改写的 Proxy 按 target 哈希(14.6.0 抛 / 读陷阱的值;`get` 陷阱报 `1n` 长度的数组 14.6.0 抛、现在按 target 的真长度;描述符陷阱报 `1n` 的数组现在拒、14.6.0 按 target)·
|
|
78
|
+
`has` 陷阱答「没有」的数组 Proxy 照哈希它的自有下标(14.6.0 的 `map` 把它们当洞:`[,]`)· `ownKeys` 陷阱只报 `length` 的数组 Proxy 哈希成洞(14.6.0 照读下标)· `ownKeys` 陷阱抛的数组 Proxy 拒(14.6.0 照读)·
|
|
79
|
+
前面的 getter 填上后面的洞 ⇒ 仍是洞(列成员时那一格不是自有下标)· `Array.prototype` / `Object.prototype` 上被污染的数字键不再填进数组的洞 · 数组实例自有的 `map` 不再被调用(14.6.0 调它:`[1]` 带自有 `map = () => []` 哈希成 `[]`,现在 `[1]`)· 数组自有的 `constructor` / `Symbol.species` 不再被问(14.6.0 的 `map` 经它造结果数组,可以答任意值或抛)。
|
|
80
|
+
三问(哈希这一处)—— **谁需要**:用 `stableHash` 做漂移检测 / ETag 的调用方:哈希只该反映值本身,每个成员读一次,不被实例方法、陷阱、原型污染改写;**谁受伤**:哈希值靠 `get` / `has` / `ownKeys` 陷阱、实例 `map`、自有 `constructor` / `Symbol.species`、或哈希途中改写自身的 getter 供出的嵌入方对象 —— 哈希值变或改成拒(JSON 面无);**补偿**:交 JSON 往返后的值(`structuredClone` 对 Proxy 抛 `DataCloneError`)。
|
|
81
|
+
**读口 × 输入形 ⇒ 答法**(本批改过的读口;「—」= 与左格同答;JSON 入参的答案逐字不变;读口按**自有键**读,「继承」列说键只靠原型链可见时它怎么办):
|
|
82
|
+
|
|
83
|
+
| 读口 | 撤销的 Proxy / `getPrototypeOf` 陷阱抛 | 成员读不出(会抛的 getter / 访问器 / 陷阱抛;**哪些陷阱会被问**因读口而异 —— 见表后「读法两类」一段) | 键只靠继承可见(含 `Object.prototype` 被污染) |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| `classifyHooksConfig` | 整份拒(`guard-outside-hooks`,坐标 `[]`)| — | 当缺席 |
|
|
86
|
+
| `readHttpHookAllowlistLayer` | 两键读成 `[]` 并进 `malformed` | 该键读成 `[]` 并进 `malformed` | 该键读成 `[]` 并进 `malformed`(响亮) |
|
|
87
|
+
| `readRequestMcpTransport` | `ok:false`,拒句说全包唯一那一句 | — | 当缺席;身份头(`principalHeader` / `assertionHeader`)进 `lost`:`inherited key ignored` |
|
|
88
|
+
| `readMcpIdentityHeaders` | `{}` | `{}` | 当缺席(它没有 `lost` 通道) |
|
|
89
|
+
| `isStringArray` / `isStringRecord`(同组的 `isUsableWireName` / `isContentOriginWord` 只问 `typeof`,运行期代码没动,答法相同) | `false` | `false` | 当缺席 |
|
|
90
|
+
| 五只描述器(`describeValueShape` 等) | 全包唯一那一句 | 不读成员:答种类(`object` / `an object` / `an object with a non-string value` …),不抛 | 同左(不读成员) |
|
|
91
|
+
| `readEffectiveWire` | 抛 `TypeError`:`effective wire payload could not be read — <那一句>` | 会抛的 getter / 访问器 ⇒ 同为 `TypeError`,句子是「不是纯数据」那一句(getter 一次都不调);载荷里自有但不可枚举的键(`version` / `updatedAt` / 域键 / `prompts` / 未知键)同属读不出 ⇒ 整份 `TypeError`(「不是纯数据」那一句),不当缺席、不静默丢 | 当缺席 |
|
|
92
|
+
| `readWorkersRows` | 原样放行(同一引用)、零扣下 | `workers` 键与每行的 `name` 按描述符律读(不调 getter、不问 `get` 陷阱;键缺席时问一次 `has`):`workers` 读不成纯数据(访问器 —— 抛或不抛,getter 零次 —— / 不可枚举)或读不出 ⇒ 当「不是这个形」,原样放行(同一引用)、零扣下,交整域读侧判;`workers` 数组的**元素**按校验器解析数组的读法读(内核的整容器读:**只看下标键**,数组上挂的非下标键连描述符都不问 —— 数据下标取值、不可枚举的下标照读、访问器下标调它自己的 getter 一次,不问 `get` / `has` 陷阱;挂在数组上的非下标键,可枚举或隐藏,不看):元素读不出(`ownKeys` / `getOwnPropertyDescriptor` 陷阱抛、下标的 getter 抛)⇒ 同样原样放行、零扣下(此前逐下标 `[[Get]]`:下标上会抛的 getter、`get` / `has` 陷阱会抛的 Proxy 把嵌入方自己抛的值带出调用;不按条丢 —— 丢一行 = 对账方当那只 worker 已撤销);行的 `name` 读不出 ⇒ 「认不出名字」,该行原样留在 doc;名字读得出、但校验过程读不出的行 ⇒ 被扣下,拒因是定句 `… a value could not be read during validation`(此前这类抛直接抛出 `readWorkersRows`);扣下坏行时拼出的文档**不对调用方的整份文档做展开**(此前 `{ ...raw }` 会让无关键上会抛的 getter / `ownKeys` 陷阱把嵌入方自己抛的值带出调用):其余键经 `ownEntriesOf` 一次读全(整容器律,全或无)—— 其余键里只要有一个读不成纯数据(访问器 / 隐藏键)或整份读不出,其余键**一个都不带出**、只剩 `workers`(此前展开带出可读的键、调访问器、跳过隐藏键),符号键不带出(此前展开带出);workers 域只有 `workers` 一个键,其余键读侧本来就剥掉,影响只在未知键告警点名哪些键;交出寻常对象 | 当缺席 |
|
|
93
|
+
| `isSchedulerRecord` | `false` | `false` | 当缺席(缺字段 ⇒ `false`) |
|
|
94
|
+
| `readEngineSignal` / `readModelRefusals` / fleet 其余各读口(请求 / 回执 / 排空那几只;要理由的那一只见下一行) | `undefined` | `undefined`(**声明键与载体键**自有但读不成纯数据 —— 不可枚举的、会抛的 getter —— 同此:读不出整份,不是少了这个键照读;没声明的键里的隐藏键不读;声明位上的类实例 / `Map` / `Date` ⇒ 读不出,v14.6.0 照收) | 载体键当缺席 ⇒ `undefined` |
|
|
95
|
+
| `readCapabilityReading` | `{ kind: "unreadable", why: "not-an-object" }` | `{ kind: "unreadable", why: "invalid-capabilities" }`(自有但读不成纯数据的 `capabilities` / 其成员 —— 不可枚举的、会抛的 getter —— 同此:坏声明是读不出,不是没声明) | 当缺席 ⇒ `{ kind: "undeclared" }` |
|
|
96
|
+
| `toVerifiedIdentity` / `readTokenScope` | `malformed` / `scope_claim_missing` | 同(**声明键本身**走描述符律:自有访问器〔抛或不抛〕/ 不可枚举的自有声明 ⇒ `malformed` / `scope_claim_missing`,getter 零次 —— v14.6.0 getter 调一次照收、隐藏的声明照读;声明**值**是对象 / 数组 —— `aud`、`iat`、`sub_inc` —— 再先拍自有键快照再用:读不出 / 环 / 非纯数据 ⇒ `malformed`;校验器对快照做类型转换抛 ⇒ `malformed`) | 当缺席(缺 `sub` ⇒ `malformed`;缺 `aud` 而配置了受众 ⇒ `malformed`) |
|
|
97
|
+
| fleet 各读口与令牌声明的**捕获范围**(只捕获所读的键) | — | 没声明的键里放什么(`Date` / `Map` / 函数 / 环 / 会抛的 getter / 隐藏键)与联合里没选中的那一臂的键都不读不拷、不影响读数(校验器本来就剥掉、不读它们);载体键与声明键同一只描述符核(隐藏 / 抛 ⇒ 读不出);`z.unknown()` 这类自由值原样交给后面只做类型判的 transform(不捕获,内部的 getter 不会被调用);令牌的 `sub` / `role` / `jti` / `scope` 声明键按描述符律读一次、取值后只问「是不是非空串」,不深拷;**声明键**本身自有但读不成纯数据 ⇒ 读不出 | 同上(键只靠继承可见 ⇒ 缺席) |
|
|
98
|
+
| `migrateStoredDocuments` / `migrateEffectiveSnapshot` | 文档集 / 快照读不出(自有但不可枚举的键同属读不出)⇒ 抛 `TypeError`(带那一句;`migrateEffectiveSnapshot` 对 `null` / 数组 / 原始值 / 类实例照旧原样返回 —— 判形是读者的事,「读不出」不是「不是这个形」)| 抛 `TypeError`(带那一句:迁移步对文档**固定键**的读与存在判走描述符律 —— 自有访问器〔抛或不抛,getter 零次〕/ 不可枚举的自有键是「不是纯数据」那一句,读不出是「检视不了」那一句,v14.6.0 访问器的 getter 照调、隐藏键照读;**限于固定键的读** —— 步骤里其余的读法〔键枚举、交给校验器读成员〕读不出时,**嵌入方自己抛的值原样出来**,与 v14.6.0 同,是写明的缺口、不是承诺;键枚举(SS-M4 找车道键的 `Object.keys`)也不看不可枚举的键 —— 隐藏的车道键当缺席,与存在判对隐藏的店键抛「不是纯数据」不同答,同属这一缺口;要改写的行里读不出 / 环 / 非纯数据同样抛,各带各的句子)| 当缺席 |
|
|
99
|
+
| `readSettingsUnit` | `unreadable` | `unreadable` | 当缺席 |
|
|
100
|
+
| `tightenVerdict`(`none` 规则) | `unreadable` | 按描述符读,不触发 getter / 陷阱 ⇒ 访问器是 `unreadable` | 不读继承 |
|
|
101
|
+
| `stableStringify` / `stableHash` | 抛 `stableStringify: … refusing to hash` | 同(会抛的 getter / `ownKeys` 或描述符陷阱抛;`get` 陷阱不被问) | 不读继承(对象只走自有可枚举键;数组的洞不顺原型链读) |
|
|
102
|
+
| `judgeConfigDValue`(`config.d` 文档判) | 抛 `TypeError`:`must be a JSON object (got <那一句>)`(不是对象的 —— `null` / 数组 / 原始值 / 类实例 —— 仍是既有的 `Error`) | 照朴素对象收下(读成员是后面步骤的事) | — |
|
|
103
|
+
| `sanitizeZodError` | issue 里读不出的值被扣下(`[redacted-secret]`),不抛 | 同(issue 字段里的值 —— 数组 / 对象 —— 有会抛的 getter / 陷阱 ⇒ 这个值整个扣下;v14.6.0 抛出嵌入方的值);issue 本身的成员读不出(例:`params` 的模板成员是会抛的 getter)直接调它仍会抛 —— 包内的读口都在守护里调它,答各自的「读不出」 | — |
|
|
104
|
+
|
|
105
|
+
表里「撤销的 Proxy / `getPrototypeOf` 陷阱抛」一列,对**问座位形状**的读口(上表大多数行)是同一个答案;三处要分开读:**数组判不读原型**(`isJsonArray` 只问「是不是数组」:`getPrototypeOf` 陷阱抛、但元素读得出的数组仍读成数组,`isStringArray` / `readSettingsUnit` 的列表单元照收;只有撤销的 Proxy —— 连 `Array.isArray` 都抛 —— 读不出);**不问形状、只读自有键的读口**(`readMcpIdentityHeaders`、令牌声明读口 `toVerifiedIdentity` / `readTokenScope`):撤销的 Proxy 读不出,`getPrototypeOf` 陷阱抛但自有键读得出的对象照读自有键。`stableStringify` 与 `tightenVerdict` 的 `none` 规则**不在这一类**:它们先问座位形状,`getPrototypeOf` 陷阱抛的对象读不出(见各自那一行;v14.6.0 的 `stableStringify` 读得出那份对象的自有键,这是已写明的 JS 面变化)。该表逐格都有真跑的钉。
|
|
106
|
+
|
|
107
|
+
**错类只有一种**:输入**读不出**(撤销的 Proxy / 陷阱抛 / 会抛的 getter)时,本包抛的一律是 `TypeError`(调用方给的对象读不出是调用方的错;迁移入口原本就是,`instanceof Error` 不变,变的只有 `.name` / 构造器)。相对 v14.6.0,各口的变化:`stableStringify` / `stableHash` 与 `judgeConfigDValue`(撤销的 Proxy 此前是引擎自己的 `TypeError`,会抛的 getter 此前是嵌入方抛的值原样出来)现在是包自己那一句的 `TypeError`(类不变);`parseDomain` 家族与 `parseRemoteExecDocument` 的「a value could not be read during validation」两句此前是 `Error`,现在是 `TypeError`;`readEffectiveWire` 的两种告警同样 —— `domain-defaulted` 与 `entry-dropped`(逐条丢弃)的 `error` 都是 `TypeError`(同一原因一种错类;`entry-dropped` 此前是 `Error`);`readEffectiveWire` 对撤销的 Proxy 原先答「is not a JSON object」(`Error`),现在与读键读不出同一句:`effective wire payload could not be read — <那一句>`(「不是对象」—— `null` / 数组 / 原始值 / 类实例 —— 与「没有数字 version」「嵌太深」这类垃圾载荷的拒绝不动,仍是各自既有的 `Error`)。反向门数 `src/` 的 AST:`new Error(…)`(不论是不是被 throw —— 告警的 `error` 值也是这么铸出去的)的参数不许带「读不出」措辞。
|
|
108
|
+
|
|
109
|
+
**读法两类**(决定「哪些陷阱会被问、getter 调不调」):
|
|
110
|
+
① **整容器捕获**(令牌声明值的快照、迁移的行快照与文档集 / 快照袋、`readEffectiveWire` 的载荷袋、workers 行读口扣行时拼文档的那一步 —— 内核的 `captureJson` / `ownEntriesOf`)**先定域、再问描述符,值只从描述符里取,之后不再对被读的对象做任何属性读**:一次 `ownKeys` 列出自有键,域 = 串键(数组再只留下标键与 `length`),只对域内的键各问一次 `getOwnPropertyDescriptor` —— 符号键与数组上挂的非下标键的描述符从不被问(它们的描述符陷阱抛也不构成读不出 —— 带这样一个符号键的环境变量表照收、`workers` 数组上这样一个命名键不妨碍逐行隔离,两形都与 14.6.0 同答);被问的只有这两只结构陷阱(以及座位问形状的 `getPrototypeOf`)。数据读者(问「声明的成员是什么」)都走这一只域内键的整容器读;安全判官 `extraBodyUnsafe`(`ModelEntry.extraBody` 一类自由值的安全判)问的是另一件事 ——「任何自有键上有没有可疑的东西」,含符号键、含描述符会抛的键,抛即判不安全(fail-closed)—— 所以它另问**全部**自有键的描述符:两问各一读法,不是同一读法的例外;没有「前一个 getter `delete` 后面的自有键、后面那个键读成 `Object.prototype` 上被污染的继承值」这回事;**不调任何 getter**(自有访问器 ⇒ 读不成纯数据、整份拒,此前调一次);**不问 `get` 陷阱** —— 只有 `get` 陷阱的 Proxy 在这几处读到的是它 target 上的数据值,陷阱改写或抛出的内容不被问。**数组只看下标键**:挂在数组上的非下标键(可枚举或隐藏)不拷也不判(JSON 往返、校验器解析数组都不读它们)。
|
|
111
|
+
② **按声明键逐个读**(fleet 各读口、令牌声明键本身、迁移与 workers 行读口的固定键读;内核的 `readDeclaredKey` / `ownDeclaredValueOf` / `ownOrThrow`,与 `parseDeclared` 的声明键投影)**对每个声明键取一次自有描述符、只从描述符取值**(缺席时再问一次 `in`):被问的是 `getOwnPropertyDescriptor` 与 `has`,外加座位问形状的 `getPrototypeOf`;**从不调 `ownKeys`、不取全部描述符**(没声明的键一个都不碰 —— 所以 `ownKeys` 抛的 Proxy 在 fleet 读口上照常读声明键);同样不调 getter、不问 `get` 陷阱;自有访问器 / 不可枚举的自有声明键 ⇒ 读不成纯数据(读口答它既有的「读不出」,不当缺席)。
|
|
112
|
+
交回原引用 / 作类型守卫的读口(`isStringArray` / `isStringRecord` / `isSchedulerRecord`、`readSettingsUnit` 的列表单元与环境变量表、`tightenVerdict` 集合规则的串集合)属 ② 的严归类:访问器 / 不可枚举的元素、成员、字段 ⇒ `false` / `unreadable`,getter 零次。
|
|
113
|
+
走 `readKey` 的其余读口(都交副本或值:描述器、http hook 名单的读器与判官、传输的 `kind` / 字段袋 / 身份头 / `args`、`salvageEntries` 读条目集合字段、workers 行读口读 `workers` 数组的元素 …;闭包注入组里交副本的那一只读法)与 ② 是**同一只读内核**(一个键一次 `getOwnPropertyDescriptor`,在场与值出自这一份描述符,**不问 `get` 陷阱**),只是归类更宽:调自有访问器(这份描述符里的 getter)一次、**把不可枚举的自有数据键读成在场**;传输 `env` / `headers` 的**成员**按描述符读(就是这一读:不问 `get` 陷阱,访问器调一次,成员名含不可枚举的 —— 读器头注写明的契约,v14.6.0 同)。
|
|
114
|
+
**同一个输入按读口的三种答案**(今天一只读内核、两种归类,外加按名登记的几处自带读法与几处直接 `[[Get]]` —— hooks 分类器、`collectDropped` / `collectionSlots`、http hook 名单读器与判官的普通读对拍;`stableStringify` 本版起经内核;http hook 判官读每一层时走读器同一只读法 —— 读调用方对象的自有描述符与普通读两份观测(`source` 与名单);这是本版如实披露的一致性债,JSON 面不受影响;内核头注的读者普查与本表逐行同):
|
|
115
|
+
|
|
116
|
+
| 读口 | 不可枚举的自有数据键 | 自有访问器(不抛) | Proxy 的 `get` 陷阱 |
|
|
117
|
+
| --- | --- | --- | --- |
|
|
118
|
+
| 描述符族 —— 严归类(令牌声明、迁移的固定键与存在判、fleet 声明键与载体键、workers 行读口的 `workers` / `name` / 扣行时拼文档的其余键、`readEffectiveWire` 与迁移的载荷 / 文档集袋、整容器捕获;交回原引用 / 作类型守卫的读口:`isStringArray` / `isStringRecord` / `isSchedulerRecord`、`readSettingsUnit` 的列表单元与环境变量表、`tightenVerdict` 集合规则的串集合) | **拒**(读不出 / `false` / `unreadable`) | **拒**,getter 零次 | 不问(读 target 的数据值) |
|
|
119
|
+
| `readKey` 族 —— 宽归类,都交副本或值(与描述符族同一只读内核:名单层的读器与判官、传输 `kind` / 字段袋 / 身份头 / `env`·`headers` 的成员〔成员名含不可枚举〕、`captureStringArray` 的宽归类 —— `args`,下标上的 getter 同、http hook 名单 / `hookVars` / `layers` —— 经读器的双读一致律〔描述符那份是朴素数组判据 + 整容器读的宽归类,普通读那份逐下标对拍〕、remote-exec 判别词、未知键扫描、`salvageEntries` 读条目集合字段、workers 行读口读 `workers` 数组的元素〔只看下标键〕) | **在场**(照读) | 调一次、取它的值 | 不问(读 target 的数据值) |
|
|
120
|
+
| `stableStringify` / `stableHash`(那一只整容器读:对象只列可枚举的自有串键,数组列自有下标) | 对象:**缺席**(不列);数组:**在场**(下标照读) | 调一次(在它的哈希位上调列成员时那只 getter) | 不问(值取自描述符) |
|
|
121
|
+
| hooks 分类器(`Object.keys` + 成员读,闭包可抄的内建写法) | **缺席**(只走自有可枚举键) | 调一次、取它的值 | 问 |
|
|
122
|
+
| `tightenVerdict` 的 `none` 规则 | 对象:**缺席**(不可枚举的键跳过);数组:**在场**(按 `length` 逐位读描述符,不可枚举的下标照读 —— 与序列化同) | 整份 `unreadable` | 成员不问;数组的 `length` 走一次 `[[Get]]`(`get` 陷阱只被问这一次) |
|
|
123
|
+
| 被丢条目的身份(`entry-dropped` 的 `identity`:逐键读描述符) | **在场**(照读) | 当缺席(getter 不调) | 不问 |
|
|
124
|
+
| `ModelEntry.extraBody` 的安全判(逐层取全描述符) | **在场**(照判) | 判不安全(getter 不调) | 不问 |
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
不在表里的读口:`parseDomain` 家族与各 zod 校验导出读成员的是**校验器自己**(撤销的 Proxy / 读不出 ⇒ `parseDomain` 抛自己那一句 `a value could not be read during validation`(`TypeError`);`safeParse` 直读的导出由校验器库给答案),见下一条的「不在本批」。
|
|
128
|
+
三问 —— **谁需要**:把嵌入方 JS 对象(Proxy 包的配置、类实例、被污染的运行时)交给读口的消费方 —— 它们要的是「读不出」有一个确定的、响亮的、读口自己的答案,不是引擎的 `TypeError`;**谁受伤**:JSON 面无 —— JSON 解析出来的值只有自有数据属性,这条路上的答案逐字不变(声明类型内的输入;五类例外见本节开头那段);JS 对象面上,本来就抛引擎错的那几处仍是抛(`stableStringify` 与 `readEffectiveWire` 读不出的载荷由引擎的 `TypeError` / 「is not a JSON object」的 `Error` 改成包自己那一句的 `TypeError`);本来读得出、现在答「读不出」或换了答法的 JS 形(自有访问器 / 不可枚举的自有键 / 靠 `get` 陷阱供值的 Proxy)各在它所属的那一条写受伤者与补偿(「读口对固定键的读只认自有键」·「读内核覆盖声明键读与整容器读」);
|
|
129
|
+
**补偿**:嵌入方要捕获的话,按上表的答法写,不再依赖引擎的错误文案。
|
|
130
|
+
钉:`test/guarded-reads-census-14.7.0.test.ts`(反向门:`Array.isArray` 只在座位 · 直接 `typeof … === "object"` 只在座位与审计例外 · 描述器与判官对读不出的对象的答案);`test/hostile-shape-answers-14.7.0.test.ts`(`stableStringify` / `stableHash` · `tightenVerdict` · `judgeConfigDValue` · `sanitizeZodError`:读不出的输入各一格,每格配 JSON 正控);`test/own-key-readers-14.7.0.test.ts`(其余读口的「读不出」一侧);`test/fleet-readers-own-keys-14.7.0.test.ts`(fleet 七只读器 × 各读的键:合法 body 去掉键、`Object.prototype[键]` 挂合法值 ⇒ 答案与没污染时逐字相同,带正控;围栏轴三例的具体答案);`test/migration-row-verdict-14.7.0.test.ts`(行级终判:自有 `constructor` 键三形 × 迁移两入口 ⇒ 拒因与寻常对象逐字同、不含用户文本、不抛;读侧同形;校验过程抛 ⇒ 该行定句;`invalid_type` 句子与 zod 原句逐值对照);`test/declared-capture-14.7.0.test.ts`(只捕获所读的键:没声明的键里放杂物不影响七读器与令牌;声明键的律不变;判别联合只投影选中的臂、判别键缺席 / 非法 / 隐藏;普通联合逐臂、第一臂通过即止;载体键的描述符核另在 fleet 那个文件);`test/token-claims-validator-14.7.0.test.ts`(令牌声明给怪形 ⇒ malformed 从不抛);`test/descriptor-law-claims-keys-14.7.0.test.ts`(描述符律:令牌七个声明键 / `readTokenScope` / 迁移固定键与存在判 ⇒ 访问器、隐藏键 ⇒ malformed / 「不是纯数据」且 getter 零次;`entry-dropped` 告警的错类;workers 行读口扣行不展开整文档;skills 清单腿的守护);`test/descriptor-law-r6-14.7.0.test.ts`(迁移文档内固定键三形;描述符字段只读自有 —— 污染 `Object.prototype.get` / `set` 照常读;传输 `env` / `headers` 成员读法;声明位上类实例 ⇒ 读不出;嵌入方 Error 不逃出;另一 realm 的 `constructor.name` 转串抛 ⇒ 扣下;`unionFallback` / `preprocess` 响亮拒;`isZodLike` 排除函数;「三种答案」表的逐行特征钉);`test/descriptor-law-r7-14.7.0.test.ts`(skills 清单腿校验器抛 ⇒ 逐条抢救、零幸存者仍整份退默认 · 抢救后重判才抛 ⇒ 不逃出、告警带第一次判的原错 · workers 数组元素的描述符律〔下标 getter 抛 / `get`·`has`·`ownKeys` 陷阱抛 / 访问器与隐藏下标,getter 零次〕· `workers` 键的描述符律 · 扣行拼文档的整容器答法与符号键 · SS-M4 存在判遇隐藏店键 · `parseDeclared` 白名单〔拒的形逐个 · 放行的形对 zod 逐例同答,数据比到原型〕· 首读 `error` 在守护里〔另一 realm 的 `parseDomain` / 非门控域 / `parseRemoteExecDocument` / 令牌判官 · 被污染运行时的 `readWorkersRows`〕· `isZodLike` 原型读不出也照问 `issues` · 车内对抗补钉〔自定义 issue 的 `params` 有会抛的 getter ⇒ 该条 `entry-unreadable`、其余保留 · `sanitizeZodError` 扣下读不出的成员 · 白名单补三形 · `parseWebAdminCredentialFile` / `applyProjectPatch` 不抛〕);`test/hidden-own-keys-entries-14.7.0.test.ts`(取键值对三处入口:隐藏的自有键 ⇒ 整份读不出;内核 `ownEntriesOf` 的形);`test/judge-config-d-error-class-14.7.0.test.ts`(读不出 ⇒ `TypeError`、不是对象仍 `Error`);`test/conformance-stance-shape-14.7.0.test.ts`(函数不是对象表态);`test/unreadable-input-error-class-14.7.0.test.ts`(读不出的输入 ⇒ `TypeError` 九格 + 反向门);`test/read-answers-table-14.7.0.test.ts`(上表**逐格**:每个读口 × 三列各一格真跑,表过头这里红 —— 含 `aud` / `iat` / `sub_inc` 的声明值读不出 ⇒ `malformed`、`migrateEffectiveSnapshot` / `migrateStoredDocuments` 读不出 ⇒ 抛带那一句的 `TypeError`);`guarded-reads-census` 另有守本条与节首文字的格(闭包副本两处例外写明、三类读口的脚注在、节首没有无条件的「差异为零」说法且五类例外逐类写明、迁移那一行限定为固定键读)与一道 AST 反向门(内核与迁移入口的 catch 臂里,被捕获的值只许交给身份表或原样重抛);答法表文件另有例外与表外缺口各一格;`test/thrown-value-not-reflected-14.7.0.test.ts`(读口读一个会抛的成员,被抛出去的是撤销的 Proxy / 每只陷阱都抛的 Proxy / 反射面全抛的对象 / 原始值,三个入口加迁移入口与内核两只包装的答案都与抛寻常 `Error` 时逐字同)。
|
|
131
|
+
- **两只兄弟描述器 `describeBadWireName` / `describeBadContentOrigin` 对读不出的对象不再抛**。另外三只描述器早已总(读不出 ⇒ 全包唯一那一句),这两只漏了:撤销的 Proxy 上裸 `Array.isArray` 抛 `TypeError`。
|
|
132
|
+
现在:两只共用一只 `describeKindWord`(由座位的形状分类给词:`null` / `array` / `typeof` 词,读不出 ⇒ 那一句),五只描述器全部由座位的形状给答案;JSON 入参的句子逐字不变。两只在包内没有调用方,但在包根导出(服务端的请求受理读它们),所以是修不是删。
|
|
133
|
+
三问 —— **谁需要**:用这两只描述器拼拒句的服务端 / 壳(入参可能来自嵌入方);**谁受伤**:无;**补偿**:无需。
|
|
134
|
+
钉:`test/guarded-reads-census-14.7.0.test.ts`(撤销的对象 / 数组 Proxy 与 `getPrototypeOf` 陷阱抛的 Proxy 作入参,五只描述器都答那一句且不抛;JSON 入参的句子逐字不变的正控)。
|
|
135
|
+
- **读口对固定键的读只认自有键:`Object.prototype` 被污染成数据值时不注入**。读口里的 `x.key` 是 `[[Get]]` —— 键不在自有属性上时顺原型链读,`Object.prototype.<键>` 一旦被污染成数据值,就被当成输入里写着的值:缺 `role` 的令牌被读成 `admin`、缺 `aud` 的令牌被读成含本服务、缺 `engineSignal` 的 health 体被读成 `{ ready: true }`、缺 `models` 的有效配置载荷被读成有一份 models 域、迁移把污染值写进存储文档(`hosts` / `models` / `rosters` 里的固定键,以及 workers 行里 `env.DB_BACKEND` / `coords.tidbHost` 一类的店键)。
|
|
136
|
+
现在:这些读口的固定键读全经一只内核(包内私有 `./own-key-read`):继承来的值从不被读、继承来的 getter 从不被调;本条列出的读口(令牌、fleet、`readEffectiveWire`、迁移、workers 行读口)属描述符族 —— 一个声明键五种在场(自有带值 / 只靠继承 / 缺席 / 自有但读不成纯数据 / 读不出),getter 一次都不调、`get` 陷阱从不被问(见「读法两类」);名单层、传输等读口属 `readKey` 族(与描述符族同一只读内核、归类更宽,见「三种答案」表)。读不出的答读口自己的失败臂(见上表)。**如实**:`tightenVerdict` 的 `none` 规则按描述符读,用的是它专用的一只 `strictJsonCopy`(`safety-merge-spec`)—— 内核之外的第二份「自有键拷贝」实现(按名登记在内核头注的读者普查里);「全包只有一只内核」的反向门按 import 来源判(调用内核几只读法的名字必须从内核 import、不许本地声明),并不覆盖换了名字的第二份实现。覆盖:`readEffectiveWire`(载荷只读一次,键值对经内核一只 `ownEntriesOf` 拍成隔离原型(`COPY_PROTOTYPE`)的袋:自有但不可枚举的键、访问器 ⇒ 整份读不出,不静默当缺席;值只从每个成员那一份描述符里取)· `salvageEntries` · fleet 各读口(载体键**与整份 body 的每个键,必填键同样**:校验器读成员走 `[[Get]]`,必填键缺席时会被 `Object.prototype` 填上 —— 空回执读成 granted + TTL、`{engineSignal:{}}` 读成 ready、没有 `capabilities` 读成 declared;现在 body 先拍自有键快照再交校验器(副本的原型是内核那一只 `COPY_PROTOTYPE`:没有继承来的成员,但仍答 `toString` / `valueOf`,所以校验器对副本做类型转换 —— 例如对「不是字符串却带 `length` 的值」做长度检查 —— 与对寻常对象同答,不抛;**所有副本与本包造的容器**〔`emptyDocument()`、迁移里的各种累加容器、`readEffectiveWire` 的载荷袋、`tightenVerdict` 的拷贝〕**同用这一只原型**,没有混用;仍是零原型的只有闭包注入组里的袋〔`ownFieldsOf` / 传输成员袋:加自由名会破契约〕、描述符对象〔不继承被污染的 `get` / `set`〕与不交给校验器的内部累加袋,`src/` 里有一道门数着),校验后输出里可选键的读也只看自有键;**只捕获所读的键** —— 按 schema 的声明键集投影(嵌套按子 schema),没声明的键一个都不读不拷;**联合只投影校验器会试的那一臂**:判别联合先按判别键(与声明键同一只描述符读)选臂、只投影选中的那一臂(判别键缺席 / 不是任何一臂的词 ⇒ 只交过去判别键),普通联合按序逐臂 —— 各臂一份只含该臂声明键的快照、**只拿该臂的 schema 校验**(该臂通过才接受,第一臂通过即止,不取并集;拿整个联合去校验的话,后臂会用默认值接受前一臂的残缺投影);`z.xor` 所有臂都走一遍、恰一臂通过才接受(两臂都通过 zod 拒,这里同样拒);**门是闭集白名单** —— 只放行与 zod 同答(判决与数据,对 JSON 输入)的节点种类:放行单里每一种都按 zod 4.4 / 4.6 的读法逐种推过、并有对 zod 逐例比判决与数据的钉;反例搜索不是穷举 —— 找到不同答的形就从放行单里拿掉,不在放行单里的一律拒。放行单:叶子(标量 / 字面量 / 枚举 …:不跑用户函数、没有子 schema)、`optional` / `nullable` / `default` / `prefault`、后处理 `.transform(f)`、对象(声明键不是符号、不叫寻常对象继承来的名字)、判别联合(不带 `unionFallback`)、普通 / 互斥联合、`record`、数组、投影走到的位置上的 `unknown` / `any`(对象 / 数组 / record 上挂的检查看到的是 zod 自己铸的新输出,两边同一份;zod 4 的递归 getter schema 照走,门按「节点 × 位置」只判一次;校验只问判决、不要句子 —— 逐次 `error` 参数让 zod 不在副本上拼拒句,所以副本上用户写的自有 `constructor` 键不会让联合把一臂判成读不出),其余一律**响亮拒**(抛 `parseDeclared: unsupported schema shape`,不静默当不透明,不逐形补黑名单;fleet 七只 schema 全在白名单里):`.catch`(兜底函数看到整个原值 —— zod ≥ 4.6 交 `ctx.input`)、输入侧变换(`z.preprocess`、字段位上单独的 `z.transform`、两侧都是 schema 的管道)、`z.custom`(谓词看到的是副本)、深拷节点之下的 `unknown` / `any`(副本会进输出;passthrough 对象同因)、声明键叫寻常对象继承来的名字(`constructor` / `hasOwnProperty` …:zod 在调用方的值上读到继承的成员,投影读成缺席)或是符号、带 `unionFallback` 的判别联合、顶层普通联合自己挂的检查(联合上的 `.refine`:逐臂拿臂的 schema 校验,跑不到它)、loose record(键 schema 没匹配的键按引用带出 —— 副本会进输出)、有限键集(枚举 / 字面量键)的 record 里叫继承名的键(同声明键那一条)、`tuple` / `intersection` / `map` / `set` / `lazy` / `readonly` …(没证明过);嵌在对象里的普通联合当不透明(整个值深拷、对整只 schema 校验;fleet 现有 schema 里没有这种形);载体键(`engineSignal` / `modelRefusals`)的读与声明键同一只描述符核,`z.unknown()` 这类自由值原样交给后面只做类型判的 transform;令牌的 `sub` / `role` / `jti` / `scope` 只读一次值、不深拷,深拷只给交给会反射它的代码的值(`aud` / `iat` / `sub_inc`);**自有但读不成纯数据的键不当缺席**:声明键本身(以及随声明键一并捕获的值里)的不可枚举自有串键(数据或访问器)让快照整份拒、读口答它既有的「读不出」(没声明的键里的隐藏键不读、不影响读数)—— `readCapabilityReading` 答 `unreadable`,不答 `undeclared` / `declared`;隐藏的**合法**值同样读不出(v14.6.0 会读到;手造对象上的非可枚举键不是 JSON,围栏处取更严的一侧);迁移的行快照与令牌声明值的快照同此规则 —— 迁移抛带句的 `TypeError`(v14.6.0 拷行用 `structuredClone`:隐藏键静默丢、访问器被调)、令牌答 `malformed`(v14.6.0 照读隐藏的声明值))· `toVerifiedIdentity` / `readTokenScope`(身份轴;声明键本身走描述符律:自有访问器 / 不可枚举的自有声明 ⇒ `malformed` / `scope_claim_missing`,getter 零次)· `readWorkersRows` · `isSchedulerRecord` · hooks 分类器的 `type` 判(仍是闭包可抄的内建写法)· `readSettingsUnit` 的列表 / 环境变量表判 · 一次性迁移(工作副本用同一只 `COPY_PROTOTYPE`、文档集经 `ownEntriesOf` 只读一次;SS-M4 只对**要改写的行**拍深快照再改写 —— 没有任何 6.x 键的行一个字段都不读不拷,所以一份别的字段嵌得再深的合法存量文档不会因这一步抛;快照没有字面深度上限,坏形只有读不出 / 环 / 非纯数据三种;收尾交寻常对象;行级终判 —— 读口判存储形、SS-M4 判改写后的行 —— 的拒因里,`invalid_type` 的句子按 typeof / array / null 给(与 zod 原句逐字同,**不读 `constructor`**:行里用户写的自有 `constructor` 键既不改拒因、也不让整次调用抛 —— 原型不是 `Object.prototype` 的快照上 zod 自己的句子会读它;这句用 `safeParse` 的逐次 `error` 参数给,压过消费方的 `z.config`(locale / `customError`),这一处的类型句固定英文,sema-server 没调 `z.config`),校验过程抛 ⇒ 该行判「读不出」(定句,不含用户文本),读侧也不再把这类抛直接抛出 `readWorkersRows`)·
|
|
137
|
+
包自有的查表(`domainWriteRole` —— 写权限角色 —— / `isReservedExtensionKey` / 未知键扫描的已知键表:行缺席时不借原型链的一行)与包自己造的「缺席 ⇒ 空文档」(`parseDomain` / `validateDomain` / 线上 skills 清单,改用 `COPY_PROTOTYPE` —— 与所有副本同一只原型 —— 校验器读它声明的键时读不到污染值)。
|
|
138
|
+
**不在本批**(如实):fleet 读器之外的 zod 入口(`parseDomain` 家族、各导出 schema 的 `safeParse` 直读)里,由校验器读的键 —— **可选键与必填键都一样**,`Object.prototype` 被污染时缺席的键会被继承来的值填上(`ModelEntry.extraBody` 一类;必填键同样经原型链)—— 是校验器自己的 `[[Get]]`,属「先捕获再校验」的另一份设计,fleet 面对**数据值形**的污染已堵(校验器读的是自有键快照)、其余入口仍是披露的缺口;**访问器形**的污染(`Object.prototype` 上挂一个只读的 getter)fleet 面也没堵:读口交出的是 zod 铸的输出对象,zod 往上面赋值撞上继承来的只读访问器就静默不写,消费方读这个键读到的是那个 getter 的值(例:getter `verdict` 返回 `"granted"` 时,schema 已经读过(暖)的进程里 `readServingLeaseResponse({ verdict: "denied" })` 交出的对象上 `.verdict` 读成 `"granted"`(不是自有键);zod 4.4 上 schema 第一次被读就撞上这只 getter(冷)时读口答 `undefined`,zod 4.6 冷暖同为 `"granted"`;`readEngineSignal` 的 `ready` 同理)—— v14.6.0 同、两版 zod 同,是写明的缺口;稀疏数组经 `Array.prototype` 数字键污染读到继承值是 14.5.0 起披露的边;`Object.prototype` 被污染本身是嵌入方进程被攻破,本包不替它兜底,只保证**自己的读口不替污染背书**。
|
|
139
|
+
**fleet 读器的三处 JS 面事实**(如实;server 的入参是 `res.json()` 的产物,JSON 面不受影响):① 声明位上的类实例 / `Map` / `Date`(例:`capabilities` 是个类实例)由 v14.6.0 的**照收**(原型上的访问器、自有数据属性都读得到)变成**读不出**(`undefined` / `invalid-capabilities` …);只有「原型的原型是 `null`」那种座位判成朴素的对象上、只靠继承可见的声明键读成**缺席**(不按「读不出」答);② 自由值(`z.unknown()`,例:`denied` 回执的 `reason`)**不被捕获**、原样交给后面只做类型判的 transform,所以它内部的 getter 不会被调用(若改成深拷,会被调一次,且 `Date` / `Map` 之类会让整份读不出);③ record 位(`z.record`,例:`readModelRefusals` 的 `entries`)上的**符号键**不进副本、不影响读数 —— v14.6.0 把原值交校验器、zod 拒符号键答 `undefined`,现在读出字符串键那几条(JS 面的放宽;JSON 铸不出符号键)。
|
|
140
|
+
三问 —— **谁需要**:服务端 / 壳 / 中心(令牌声明、health 体、有效配置载荷、存储文档迁移都从外面进来的 JSON)—— 身份轴与存储轴上「污染 ⇒ 静默注入」要变成「污染 ⇒ 读不到」;**谁受伤**:JSON 面无 —— JSON 解析出的值没有继承来的成员,声明类型内的输入答案逐字不变(本节开头那段的五类例外除外);JS 面受伤的是两类手造的对象(它们本来就不是 JSON,文档契约写的是「JSON 对象」):① 靠原型链放字段的(类 getter、`Object.create(defaults)`)不再被这些读口读到;② 带不抛的自有访问器 / 不可枚举自有键的(响应式框架包过的对象:把每个键改成 getter / setter 的、挂隐藏键的)被描述符族整份拒 —— `toVerifiedIdentity` 由 ok 变 `malformed`、`readTokenScope` 答 `scope_claim_missing`、`readEffectiveWire` 抛「不是纯数据」那一句、fleet 读口答 `undefined`(v14.6.0 都照读);
|
|
141
|
+
**补偿**:要让读口读到的字段放成自有属性(`{ ...defaults, ...overrides }`);响应式对象先取出普通数据(JSON 往返,或框架自己的取原值口)再交;抄 `mcp-request-entry` 闭包组的生成器把 `./own-key-read` 的内核名字当作注入名(见档位说明)。
|
|
142
|
+
钉:`test/own-key-readers-14.7.0.test.ts`(每格 = 一个读口 × 一个它读的键:输入没有该键、`Object.prototype[键]` 挂一个会让答案变样的值 ⇒ 答案与没污染时逐字相同,56 格;「读不出」一侧;门不空转格);`test/own-key-readers-14.7.0.test.ts` 的迁移行级六键格(`enabled` / `env` / `secretEnv` / `runtime` / `coords` / `secretRefs`:行里没写、原型被污染 ⇒ 答案不变);`test/own-key-read-14.7.0.test.ts`(内核:四种在场 · 派生读法 · `captureJson` 快照(没有深度上限 · 环 / 非纯数据 / 读不出各一句 · 菱形共享)· `ownOrThrow` · 反向门:内核之外没有第二只读法 / 第二处「检视不了」那一句);`test/own-key-readers-14.7.0.test.ts` 末尾的深嵌套格(没有 6.x 键的行 260 / 3000 层 `note` ⇒ receipts 空、文档同一引用;要改写的行带同样深的 `note` 照迁;环 / 非纯数据只在要改写时才拒)。
|
|
143
|
+
- **hooks 分类器:内容靠迭代协议供出的非朴素对象(`Map` / `Set` / 生成器)当「可能带守卫」拒**。分类器按**自有键**扫守卫迹象;`new Map([["PreToolUse", [<带守卫的匹配器>]]])` 作 hooks 块(或块里任一子树)时守卫在 Map 的条目里、不在自有键上,扫不到 ⇒ 整块按 `hooks-not-object` 降级丢掉(`loaded-partial`)—— 一份写着守卫的 hooks 配置静默消失(14.5.0 起既有)。
|
|
144
|
+
现在:非朴素对象若靠迭代协议供内容(`Symbol.iterator in 它`),它的内容无从按自有键证明「没有守卫迹象」⇒ 记为它自己坐标上的一处迹象,整份拒,与「读不出」同一臂(fail-closed)。没有迭代协议的非朴素对象(类实例 / `Date` / `RegExp` / 函数)照旧按自有键扫:守卫迹象在自有键里 ⇒ 拒,没有 ⇒ 按今天的降级丢,既有行为不变。分类器闭包的自由名不变(迭代协议的符号取自 `Array.prototype`,不引新的 JS 内建;`test/hooks-classify-13.0.0.test.ts` ⑤ 守着)。
|
|
145
|
+
三问 —— **谁需要**:读 settings 文档里 hooks 的宿主(用分类器决定哪些块能加载):守卫是安全轴,一份写着守卫的配置不许静默变成「没有守卫」;**谁受伤**:把 `Map` / `Set` 当 hooks 块(或块里的子树)交进来的嵌入方 —— 此前降级加载、现在整份拒(JSON 解析不出这些值,契约写的是 JSON 文档);**补偿**:把它们展开成朴素对象 / 数组再交。
|
|
146
|
+
钉:`test/hooks-classify-opaque-content-14.7.0.test.ts`(五种载体 × hooks 块 / 顶层文档 / 顶层键 / 事件值 / 匹配器位 / 条目位 / 未知事件下 · 条目未知字段里藏 `Map` · 迭代协议读不出 ⇒ 根上兜住整份拒 · 正控:无迭代协议的非朴素对象照按自有键扫);既有钉 `test/plain-json-object-single-seat-14.6.0.test.ts` 里「`Map` 带自有迹象作成员」那一格的坐标由迹象键变成 `Map` 自己(「内容不在自有键上」那一臂先判)。
|
|
147
|
+
- **请求面传输:继承来的身份头不再无声**。14.6.0 把传输快照改成只读自有键之后,`principalHeader` / `assertionHeader` 只靠原型链可见时被当缺席、不记 `lost` —— 身份轴上的静默(`Object.prototype.principalHeader` 被污染时,所有 http 传输都读成「没写身份头」,受理新提交的门看不到)。
|
|
148
|
+
现在:身份头只靠继承可见 ⇒ 不读它的值(注入的值进不来),但进 `lost`:`{ path: "transport.principalHeader" | "transport.assertionHeader", reason: "inherited key ignored (…)" }`(机读位不带,`fault` 的闭集不加词),传输本身照 `ok:true`;
|
|
149
|
+
`principalHeader` 自有在场时 `assertionHeader` 一次不问(不点名);stdio 传输不用身份头,不被牵连;公开的 `readMcpIdentityHeaders` 没有 `lost` 通道,对继承仍答 `{}`。与托管层名单读器「只靠继承可见 ⇒ `malformed`,响亮」同向;别的键(`args` / `env` / `headers` / `command` / `url`)继承来的仍按缺席处理(它们缺席本身有各自的答案)。
|
|
150
|
+
三问 —— **谁需要**:受理请求面 MCP 条目的服务端 / 壳(据 `lost` 响亮拒新提交):身份轴不该有无声的读法;**谁受伤**:`Object.prototype` 被污染成身份头、或把身份头放在原型上的手造传输对象的调用方 —— 此前静默读成缺席,现在多一条 `lost`(受理新提交的门会据此拒;重放存量提交体的腿不受影响:传输照 ok);**补偿**:身份头写成自有键。
|
|
151
|
+
钉:`test/mcp-request-identity-inherited-14.7.0.test.ts`(两个身份头各自 / 同时只在原型上 · `Object.prototype` 被污染 · 自有身份头与 stdio 不被牵连 · 公开读者仍答 `{}`);`test/mcp-request-entry-14.0.0.test.ts`(三格 14.6.0 钉的 `lost` 期望随之点名)。
|
|
152
|
+
- **读内核覆盖声明键读与整容器读:一个键的在场与值出自同一份描述符,不再 `[[Get]]`(律变:`readKey` 族的读口也不问 `get` 陷阱);数组只看下标键;仍自带读法的既有读者按名登记**。此前 `readKey` 族先 `hasOwn` 问「是不是自有」、再 `[[Get]]` 取值 —— 两步之间描述符陷阱能删掉自有键,第二步就读到 `Object.prototype` 上被污染的同名值(例:`isSchedulerRecord` 收下 `prompt` 只剩继承来的串的记录;v14.6.0 答 `false`)。
|
|
153
|
+
现在:全包一个键只读一次 `getOwnPropertyDescriptor`(缺席时再问一次 `in`),在场与值都从这一份描述符里来 —— 数据属性取值,访问器调**这份描述符里的** getter 一次;从不 `[[Get]]`、不问 Proxy 的 `get` 陷阱。描述符族与 `readKey` 族是这一读的两种归类(严:只收自有、可枚举的数据属性;宽:不可枚举的自有键照读、访问器调一次),不再是两种读法;仍自带读法、未经内核的既有读者按名登记(本版不动):`tightenVerdict` 的 `none` 规则(`strictJsonCopy` / `strictJsonStored` 自己列成员,数组的 `length` 走一次 `[[Get]]`)、被丢条目的身份(`readEntryIdentity` 自己取描述符)、传输记录读法(`readPlainStringRecord` 自己枚举成员名),未经内核、直接 `[[Get]]` 的既有读者:`collectDropped`(未知键扫描,`raw[k]` / `raw[i]`)与 `collectionSlots`(条目集合的位置表,`forEach` / `Object.entries` 自己枚举容器),同样按名登记(第二次读抛时未知键告警静默消失,14.6.0 同);外加两类不是数据读的:安全判官 `extraBodyUnsafe` 的审计读、http hook 名单读器与判官的普通读对拍(见下);登记外出现自带的描述符 / 成员名读法,普查钉即红;传输 `env` / `headers` 的成员、设置单元的环境变量表也走这一读(此前各写一份;环境变量表按整表列一次成员,每个成员只问一次描述符)。
|
|
154
|
+
数组臂同一条:**只看下标键**,挂在数组上的非下标键(可枚举或隐藏,例:响应式框架给数组挂的 `__ob__`)不是元素、也不构成「读不出」;workers 行读口读 `workers` 的元素按校验器解析数组的读法(宽归类:访问器下标调一次、不可枚举的下标照读)—— 下标是不抛的访问器 / 不可枚举、或数组挂着隐藏非下标键的清单,坏行照常单独扣下(v14.6.0 的逐行隔离;此前整张清单原样放行、交整域读侧整份拒)。
|
|
155
|
+
**选哪一种归类只看读口交出去的是什么**:交回**副本**的读口(调用方只读这份副本)可以宽;交回**原引用**、作 `x is T` **类型守卫**、或对原值下判决的读口必须严 —— 调用方随后对同一个对象的普通读要读到判过的那一份,而访问器下一次可以答别的、可以抛。于是 `isStringArray` / `isStringRecord` / `isSchedulerRecord`、`readSettingsUnit` 的列表单元、环境变量表与 hooks 单元(值按原引用交回;hooks 单元先拍整树严快照、分类器判快照)、`tightenVerdict` 集合规则(`setUnion` / `setNarrower`)的串集合按**严归类**读:访问器或不可枚举的元素 / 成员 / 字段 ⇒ `false` / `unreadable`,getter 一次都不调(此前按宽归类:访问器调一次判了类型,调用方的普通读再调一次,可以读到数、对象或一读就抛 —— 三只守卫自 14.6.0 起对访问器就不健全,本版一并收;`readSettingsUnit` 此前也是 `unreadable`,但调了 getter;列表的不可枚举下标、环境变量表的不可枚举成员此前照收)。交副本的读口仍宽:请求面 `args`、传输 `env` / `headers` 的记录、workers 行判、哈希;http hook 名单也交副本,但它是安全轴 —— 宽归类之外还要普通读那份观测一致(见下)。
|
|
156
|
+
如实(JS 面的语言层限制):守卫读的是描述符,看不到 Proxy 的 `get` 陷阱 —— `get` 陷阱与描述符不一致的 Proxy 守卫为真,调用方的普通读读到陷阱给的值(14.6.0 的 `isStringArray` / `isSchedulerRecord` 与 `readSettingsUnit` 走 `[[Get]]`,对这类陷阱答 `false` / `unreadable`;`isStringRecord` 14.6.0 起就按描述符读,同此限制;对**有状态**的 `get` 陷阱 —— 第一次答串、之后答对象 —— 两版守卫都放行:描述符与 `[[Get]]` 两条通道都关不住 Proxy)。**谁受伤**:把 `get` 陷阱会撒谎的 Proxy 当数组 / 记录递给这几只守卫与原引用读口的程序;**补偿**:要防就先做一次 JSON 往返(`JSON.parse(JSON.stringify(…))`,只读一次、得到的是普通数据;`structuredClone` 对 Proxy 抛 `DataCloneError`)再递。
|
|
157
|
+
三问(归类这一处)—— **谁需要**:用这几只守卫收窄类型的调用方:`x is T` 为真,随后的读就必须是 `T`;**谁受伤**:把带访问器 / 不可枚举键的对象(响应式框架包过的数组与记录)交给这几只守卫 / 读口的嵌入方 —— 此前 `true` / `ok`,现在 `false` / `unreadable`;**补偿**:交普通数据(JSON 往返后的值,或框架自己的取原值口)。
|
|
158
|
+
**整容器读与这几只读口的追加走定义语义**(`appendOwn`:`Object.defineProperty` 定义自有下标;`push` / 下标赋值是 `[[Set]]`,`Object.prototype` 被污染出数字键的 getter + setter 时会把这一项吞掉):整容器读的成员表、快照的遍历栈、`extraBodyUnsafe` 的遍历栈(两臂)、`readWorkersRows` 的留下 / 扣下两张表走它(共七处);包内其余数组追加(`src/` 里 125 处 `.push(`,23 个文件)仍是 `push`,本版未收 —— 抽查过的安全轴判官(hooks 分类器、`readSettingsUnit("hooks")`)在 `"0"` 这一污染下方向收紧(拒 / `unreadable`),其余未逐处核。在这一污染下(两版 zod 同答):`extraBodyUnsafe` 照判嵌套 / 平铺 / 数组里的秘密 —— **v14.6.0 起就静默判「安全」**(遍历栈被吞,一层都不下;安全轴,本版收);`readWorkersRows`:zod 的 schema 已在污染前用过(热)时,两版 zod 都照常扣坏行、doc 不丢行 —— v14.6.0 在 zod 4.6.5 下抛、在 4.4.3 下静默丢行(好行与扣下的点名一起被吞);污染下第一次用到 schema(冷)时,本版两版 zod 都把每一行扣进 `held`(拒因「读不出」,原件随 `held[].original` 带回,一行不丢),v14.6.0 两版都抛;令牌的数组声明(`aud`)照读(v14.6.0 同答)。
|
|
159
|
+
http hook 名单走**安全轴的读法**,且**判官经读器读每一层**。① 读器(`readHttpHookAllowlistLayer`)读设置对象里的名单:**描述符视图与普通读两份观测一致才算读到** —— 描述符那份走本包读内核(名单键一次描述符、名单按整容器读),普通读那份是 v14.6.0 普通读的合取(名单键 `[[Get]]` 一次、`length` 经 `[[Get]]`、逐下标问「是不是自有」—— v14.6.0 读器的读法 —— 与「在不在」〔`i in v`,HasProperty:v14.6.0 判官对 `layers` / `hookVars` 的 `filter` 按它跳过下标〕再 `[[Get]]`);所有名单键的描述符观测都先于任何普通读(一个键的普通读改不动另一个键已经拿到的观测);两份都「不设」⇒ 不设;都是串数组且等长、逐元素同串 ⇒ 名单;其余一切(只一份读到、内容不同、任一份读不出 / 坏形、名单不是普通数组(见 ③)、名单键只靠继承可见、层不朴素)⇒ `[]`(全拦)并点名 `malformed`。于是对无状态构造,拦集包含 v14.6.0 的拦集(它只看普通读),两份不一致也拦;普通对象上两份观测恒同,JSON 面零变化。② 判官(`httpHookUrlVerdict` / `httpHookEnvVarsAllowed`)**经读器读每一层**,与读器同一只读法,没有「是不是读器交出的」判:`layers` 数组本身与 `hookVars` 也按两份观测一致律读(描述符视图与普通读对不上 ⇒ `layers` 整个当一张坏形层、`hookVars` 当空 —— 只看描述符会被藏起一张读数、把 `length` 报成 0 的陷阱骗过);每一层先读 `source` —— 与名单同一条双读一致律;读不出(缺席 / 只靠继承可见 / 两份不一致 / 不是串 / 层不是对象 / 撤销的 Proxy)⇒ **坏形层**:两张名单都按 `[]` 判(URL 全拦,判决词 `rule: "malformed"`;变量一个都不放),不抛,不问准入,`sources` 不点名它(它没有可信的源);源读得出、名单读不出(读器对同一份设置报 `malformed` 的那些形 —— 另一个 realm 的数组、带框架挂件的数组、名单键只靠原型污染可见、层对象不朴素 ……)⇒ 判决词同样是 `malformed`,`sources` 点名那一层(`empty` 只给真空名单 `[]`;读器交出的层里坏键已读成 `[]`,再交判官答 `empty` —— 坏形的告警走读器的 `malformed`);读得出的串 ⇒ 问准入 —— 闭集外的词在这里抛 `RangeError`(与 v14.6.0 同句同类:宿主写错源名是编程错误,不是数据坏形),准入层的名单 = 读器读这一层的读数;`layers` 整个读不出时同样是一张坏形层(`malformed`)。于是读器交出的层、它的展开 / 复制副本、手造的寻常层、本 realm 的 `JSON.parse` 出来的层,同一份内容同一个答案 —— 声明类型内、本 realm 的手造寻常层与 `JSON.parse` 层,判决与 v14.6.0 逐位同(声明类型外的层见版本头例外 ①;另一个 realm 的数组按坏形,见例外 ⑤);读器对自己交出的 `layer` 再读,名单逐位同,`malformed` 只在第一次读出现(坏键第一次已读成 `[]`,再读是空名单);类实例 / 名单放在原型上的层按读器的律(不朴素 ⇒ 两张名单都按 `[]`、点名它的源;v14.6.0 按 `[[Get]]` 读原型上的名单、照旧生效 —— JS 对象面收严);`Object.prototype` 被污染出名单、层上没写时,判官同样全拦。③ 安全轴的数组(`layers` / `hookVars` / 两张名单)**只许是朴素数组** = 寻常(自有键只有下标与 `length`;任何别的自有键 —— 方法覆盖、`constructor`、任意串键、符号键,含 `Symbol.species` / `Symbol.iterator` —— 都不是)∧ 三观测:① 是数组;② 原型恒等于**本 realm 的** `Array.prototype`(子类、数组实例当原型、原型直接是 `Object.prototype` 的假原型、`null` / 换过的原型都不是;**另一个 realm 的数组也不是** —— 见下面三问);③ 普通读 `constructor`(species 协议从它取结果数组的构造器)、`filter`(v14.6.0 的判官调它)、`flatMap`、`Symbol.iterator` 各一次 `[[Get]]`,与本 realm 的内建恒等(本包加载时取的那一份;`get` 陷阱对其中一个说谎、谎报原型的 `getPrototypeOf` 陷阱在这里败)。不是 ⇒ 整个输入按坏形、不读元素(名单 `[]` + `malformed`;`hookVars` 当空;`layers` 整个当一张坏形层)。v14.6.0 的判官调用的是调用方数组自己的 `filter`:数组上的自有 `filter = () => []`、子类 / 换过原型 / 假原型的数组上的方法、吞掉输出写入的 `Symbol.species` 构造器、空 `layers` 上供出限制层的 `filter` 都让它拦下或少放;只按下标读会把这些构造放行 —— 本版对这些构造一律拦。
|
|
160
|
+
如实(挡不住的,语言层限制):有状态 Proxy —— 两次观测都答同一份假名单,或第一次观测改了第二次观测的答案(例:名单数组的 `ownKeys` 陷阱打开放宽的 `get` ⇒ 描述符与普通读两份都读到放宽的那一份、放行,而 14.6.0 只做普通读、读到限制的那一份、拦下),两条通道被同一个谎骗过。**谁受伤**:把有状态 Proxy 当名单递进来的程序(JSON 解析不出这种值);**补偿**:要防就先对名单做一次 JSON 往返(`JSON.parse(JSON.stringify(…))`,只读一次、得到的是普通数组)再递。
|
|
161
|
+
三问(名单这一处)—— **谁需要**:托管层名单是安全轴,读取与判决只许收紧;判官与读器对同一份设置结构同答 —— 同一份设置只该有一个读数、判决只按它下;**谁受伤**:① 把名单写在原型上 / 类实例里、或 `source` 读不出(缺席 / 不是串 / getter 与描述符不一致)的层交给判官的调用方 —— 这一层全拦(JS 对象面;手造的寻常层、`JSON.parse` 出来的层不受影响,与 v14.6.0 同答);② 名单值由 `get` 陷阱 / 每次答得不同的 getter 供出的嵌入方 —— 两份观测对不上即全拦(名单键的访问器、名单下标的访问器都会被读两次);③ 把不朴素的数组(响应式框架挂的 `__ob__`、附带元数据的数组、换了方法的数组、子类数组、换过原型的数组、包着数组却改写 `constructor` / 原型 / 方法的 Proxy)或**另一个 realm 的数组**(`vm` 上下文、别的 iframe 里 `JSON.parse` 出来的)当名单 / `hookVars` / `layers` 递进来的嵌入方 —— 整个按坏形、全拦(另一个 realm 的那一形 v14.6.0 读得到:安全轴只许收紧,而拦集 ⊇ v14.6.0 要能证明,判「朴素」只能对本 realm 的内建恒等,不能靠「看起来像数组原型」);④ 在本包加载之后给 `Array.prototype` 的 `filter` / `flatMap` / 迭代器(哪怕只是转调原函数的包装)或 `Array.prototype.constructor` 装替身的宿主(加载后换掉 `globalThis.Array` 不在此列:包加载时取的内建已定)—— 内建被动过即判「不朴素」,不管配没配名单:纯 JSON 名单、连一张名单都没配(`layers` 为 `[]`)的宿主也全拦(`malformed`,`sources` 为空),变量一个都不插值(设计如此:内建被动过就不读);**补偿**:不包装这几只内建,或在本包加载之前装好(包加载时取的就是装好之后的那一份);层写成普通对象 —— `source` 与两张名单都是自有数据键(读器交出的 `layer`、`JSON.parse` 出来的层即是);名单与 `hookVars` 交本 realm 的普通数组(数组字面量;JSON 往返得到的就是 —— 另一个 realm 的数组先 `JSON.parse(JSON.stringify(…))` 一次;`structuredClone` 会把数组上可枚举的别的键一并拷过去,不行),`layers` 用数组字面量装层。
|
|
162
|
+
三问(判决词 `malformed`,`HttpHookUrlBlock.rule` 闭集新增一词,可加性)—— **谁需要**:按 `rule` 给用户铸句的宿主 —— 判官读不出的层不该被说成「名单是空的」(`empty`):源读不出的(陷阱 / 污染 / 手造残缺的 `source`,读不出的 `layers`)点不出源,名单读不出的(另一个 realm 的数组、框架挂件、原型污染)点得出源 —— 两样都答 `malformed`,区别只在 `sources` 有没有那一层;相邻两只 API(读器的 `malformed` 与判官的 `rule`)从此一个词一件事;**谁受伤**:对 `rule` 写穷举 `switch`(带 `never` 默认臂)的 TypeScript 调用方 —— 多一臂,编译即红;只按 `"empty"` / `"url"` 分支、没有默认臂的调用方,这一词走不到任何一臂(本组织兄弟仓的 `src/` 里没有读 `rule` 的调用方);**补偿**:补 `malformed` 臂(例句:「有一层 http hook 设置读不出,所有 http hook 已拦下」);`malformed` 先于 `empty` 先于 `url`。
|
|
163
|
+
三问 —— **谁需要**:所有读嵌入方对象的读口:「是不是自有」与「值是什么」之间不能有缝,有缝污染就照样注入;一只坏 worker 不该拖黑整张清单;**谁受伤**:交给走读内核的读口(传输、调度记录判、设置单元、串数组判官、remote-exec 判别词、未知键扫描、`salvageEntries` 读集合字段)一个靠 `get` 陷阱供值 / 改写值 / 抛错的 Proxy 的嵌入方 —— 此前读到陷阱给的值(或因陷阱抛读成读不出),现在读到 target 自有描述符里的值(例:`get` 陷阱对 `length` 撒谎的 Proxy 数组按 target 的真长度与真元素读;`get` 陷阱抛的传输 / 调度记录照读 target 的数据);http hook 名单不在此列 —— 安全轴上两份观测对不上即全拦(见上);workers 元素里会抛的下标 getter 现在被调一次(仍是原样放行);**补偿**:要读口读到的值放在对象的自有属性上(或由 `getOwnPropertyDescriptor` 陷阱给出),或交 JSON 往返后的普通对象。
|
|
164
|
+
钉:`test/descriptor-law-r8-14.7.0.test.ts`(删键陷阱 + 原型污染 ⇒ `isSchedulerRecord` 为 `false`、`readKey` 读到那份描述符里的值 · `get` 陷阱零次 · workers 三形逐行隔离 + 访问器下标调一次 · 传输成员没有第二只描述符读法 · 整容器读先定域:描述符陷阱会抛的符号键 / `workers` 数组命名键不构成读不出、读内核里没有 `getOwnPropertyDescriptors(`、全包按名登记的审计读只有 `extraBodyUnsafe` 那两处 · 哈希:列键时定成员与描述符、每成员恰一次描述符、数组臂同一条、与 14.6.0 不同答的一类与举例(含 `has` / `ownKeys` 结构陷阱三形) · 类型守卫与交回原引用的读口严归类〔访问器 / 隐藏键 ⇒ `false` / `unreadable`,getter 零次;交副本的读口仍宽;`get` 陷阱改写的 Proxy 一格写明现答法〕· 环境变量表的不可枚举成员 ⇒ 整表 `unreadable` · `Object.prototype["0"]` 挂 getter + setter 时整容器读与快照不被 `[[Set]]` 吞(令牌的数组声明照读)· 翻律格标题源码钉);翻律的既有格(标题写明翻律原因):`test/own-key-read-14.7.0.test.ts`(`get` 陷阱 · 数组上的隐藏非下标键)、`test/own-key-readers-14.7.0.test.ts`、`test/read-answers-table-14.7.0.test.ts`、`test/mcp-request-entry-14.0.0.test.ts`、`test/http-hook-allowlists-13.0.0.test.ts`(撒谎的 `length` 改由描述符陷阱造;`get` 陷阱撒谎一格;名单两份观测一致律 —— `get` 陷阱给窄 / `[]` / 抛、target 窄 `get` 宽、名单数组自己的 `get` 陷阱撒谎 ⇒ 坏形全拦,一次性读 / 翻转 / 两次两答的访问器 ⇒ 坏形;判官经读器读每一层 —— 手造的寻常层四格、`JSON.parse` 层与 v14.6.0 同答,读器产物再读幂等,副本与原层同答,`source` 双读一致(读不出 ⇒ 坏形层 `malformed` 全拦不抛;读得出的串不在闭集 ⇒ `RangeError`,与 v14.6.0 同句),类实例 / 原型上的名单按读器的律,读器返回形同 v14.6.0(普通对象、不冻结);源码钉:没有品牌表、名单与源各恰一处描述符读 + 一处普通读对拍;安全轴的数组只许是普通数组(方法覆盖 / `constructor` / 任意串键 / 符号键八形 × 三处);普通读逐下标问 `i in v`(`has` 陷阱藏下标 ⇒ 坏形,三处);翻律多格:污染下的缺席层与原型上放名单的手造层改全拦、`get` 陷阱撒谎的名单数组回到 14.6.0 的拦)、`test/descriptor-law-r7-14.7.0.test.ts`(workers 元素)、`test/guarded-reads-census-14.7.0.test.ts`(内核头注)。
|
|
165
|
+
- **fleet 读口从不抛:`parseDeclared` 的守护包住 schema 内省与快照递归;判别键只读一次**。① 冷路径:schema 第一次被读之前 `Object.prototype` 已被污染(`get` / `set` / `value` / 判别键名)⇒ zod 的惰性属性(声明键表、判别联合的 `propValues`)物化时抛 —— 此前抛在守护之外,七只 fleet 读口把引擎的错 / 嵌入方的抛出值抛给调用方(v14.6.0 一只不抛)。现在守护在 `parseDeclared` 入口一处,门的 schema 内省、投影快照的递归与校验都在里面:抛 ⇒ `{ ok: false }`,读口答「读不出」(污染窗口里逐只同 v14.6.0 在同一构造上的答案);门读的是 zod 校验时读的那一份声明键表,不读 zod 4.4 上另一只物化失败就永远答 `undefined` 的惰性属性 —— 污染撤掉之后照常读。**撤掉污染之后不是逐格同 v14.6.0**:在 zod 4.4 上 v14.6.0 的租约读口在 `get` / `set` 一类的污染(数据值、函数、会抛的访问器)撤掉之后仍永远答 `undefined`(它读的正是那一只惰性属性),本版不读那一只、恢复(改进,不是逐格同);`verdict` 污染撤掉之后,zod 4.4 上两版的租约读口都读不出(zod 4.4 判别联合的 `propValues` 物化失败就永远答 `undefined`,zod 自己的缺陷);zod 4.6 上两版都恢复。「不支持的 schema 形」是门交回的判语,在守护外响亮抛(与输入无关)。② 判别联合按判别键选臂后,选中那一臂的投影再读一次判别键、校验器再按快照里的词派发 —— 描述符陷阱两次答不同的词时,租约回执 `{ verdict: "denied", reason: "retired" }` 读成 `{ verdict: "unknown" }`(停机指令丢了;v14.6.0 答 denied)。现在判别键的那一次读数交给选中那一臂复用,快照里的词就是选臂的词,判别键在所有臂上都恰读一次(投影的臂直接复用;整份深拷的臂 —— `strict` / 带 catchall 的对象 —— 列成员时判别键的在场与值取那一次的读数、不再问它的描述符:第二次读不存在,也就影响不了接受与否 —— 此前这两种臂再问一次,第二次读到的值进了遍历,答一个 `Date` 就让整份读不出)。③ 递归 schema × 很深的数据(两万层):投影快照的递归此前抛 `RangeError`,现在 `{ ok: false }`(fleet 七只 schema 都不递归,今天的读口不触发)。
|
|
166
|
+
三问 —— **谁需要**:读租约 / 健康体 / 排空回执的服务端与 web-admin:读口「从不抛」、停机指令不丢是契约;**谁受伤**:无 —— 三处都回到 v14.6.0 的答法;**补偿**:无需。
|
|
167
|
+
钉:`test/descriptor-law-r8-14.7.0.test.ts`(冷路径:模块已加载、schema 未读过 ⇒ 污染 `get`〔数据值 / 函数 / 会抛哨兵的访问器〕/ `set` / `value` / `verdict` ⇒ 七只都不抛、逐只同 v14.6.0、撤掉污染照常读,两版 zod 各一张期望表 · 翻臂 Proxy 读计数 · 嵌套判别联合 · strip / strict / catchall 三种臂 × 判别键第二次读答 `Date` / 抛 ⇒ 同收、恰读一次 · 第一次读成「只靠继承」⇒ strict 臂与投影臂同答 · 两万层 ⇒ `{ ok: false }`);`test/declared-capture-14.7.0.test.ts`(臂投影的「描述符变化」格);`test/engine-signal-8.1.0.test.ts`(声明投影里一处 `safeParse`、一处守护、零 `catch`)。
|
|
168
|
+
|
|
169
|
+
**自 v14.6.0 起运行期代码有改动的公开导出(机器重算;上界 —— 可达 ≠ 一定变,不可达 ⇒ 一定不变;本版新增的导出不入表)**
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
AgentHookEntry [., ./hooks]
|
|
173
|
+
CommandHookEntry [., ./hooks]
|
|
174
|
+
DOMAINS [., ./types]
|
|
175
|
+
DOMAIN_SCHEMAS [., ./types]
|
|
176
|
+
FileConfigStore [./node]
|
|
177
|
+
HookEntry [., ./hooks]
|
|
178
|
+
HookMatcherEntry [., ./hooks]
|
|
179
|
+
HooksConfig [., ./hooks]
|
|
180
|
+
HttpHookEntry [., ./hooks]
|
|
181
|
+
McpToolHookEntry [., ./hooks]
|
|
182
|
+
ModelEntry [., ./types]
|
|
183
|
+
ModelsConfig [., ./types]
|
|
184
|
+
NON_PORTABLE_DOMAINS [./conformance]
|
|
185
|
+
PromptHookEntry [., ./hooks]
|
|
186
|
+
RemoteExecSpec [., ./remote-exec]
|
|
187
|
+
RosterEntry [., ./types]
|
|
188
|
+
RostersConfig [., ./types]
|
|
189
|
+
VerifiedIdentity [., ./api/auth-bridge]
|
|
190
|
+
applyProjectPatch [., ./types]
|
|
191
|
+
buildEffective [., ./config-fns]
|
|
192
|
+
canonicalize [./conformance]
|
|
193
|
+
classifyHooksConfig [., ./hooks]
|
|
194
|
+
createContractAdapter [./conformance]
|
|
195
|
+
createFileSchedulerStore [./node]
|
|
196
|
+
deepEqual [./conformance]
|
|
197
|
+
deriveLocalExpectation [./conformance]
|
|
198
|
+
deriveWireExpectation [./conformance]
|
|
199
|
+
deriveWireInput [./conformance]
|
|
200
|
+
describeBadContentOrigin [.]
|
|
201
|
+
describeBadWireName [.]
|
|
202
|
+
describeNotStringArray [.]
|
|
203
|
+
describeNotStringRecord [.]
|
|
204
|
+
describeValueShape [.]
|
|
205
|
+
diffJson [./conformance]
|
|
206
|
+
diffWarningMultiset [./conformance]
|
|
207
|
+
domainFilePath [./node]
|
|
208
|
+
domainWriteRole [., ./config-fns]
|
|
209
|
+
emptyEffective [., ./config-fns]
|
|
210
|
+
extraBodyUnsafe [., ./types]
|
|
211
|
+
findSecretLeaks [./conformance]
|
|
212
|
+
foreignKeysOf [./conformance]
|
|
213
|
+
httpHookEnvVarsAllowed [.]
|
|
214
|
+
httpHookUrlVerdict [.]
|
|
215
|
+
isDomainName [., ./types]
|
|
216
|
+
isReservedExtensionKey [., ./config-fns]
|
|
217
|
+
isSchedulerRecord [., ./scheduler-store]
|
|
218
|
+
isStringArray [.]
|
|
219
|
+
isStringRecord [.]
|
|
220
|
+
loadCase [./conformance]
|
|
221
|
+
loadCorpus [./conformance]
|
|
222
|
+
loadLocalConfig [./node]
|
|
223
|
+
loadRemoteExec [./node]
|
|
224
|
+
loadSchedulerStore [./node]
|
|
225
|
+
migrateConfigDirectory [./node]
|
|
226
|
+
migrateConfigDocuments [./node]
|
|
227
|
+
migrateEffectiveSnapshot [./one-time-migrations]
|
|
228
|
+
migrateStoredDocuments [./one-time-migrations]
|
|
229
|
+
mutateSchedulerStore [./node]
|
|
230
|
+
normalizeProjection [./conformance]
|
|
231
|
+
parseDomain [., ./config-fns]
|
|
232
|
+
parseDomainLoud [., ./config-fns]
|
|
233
|
+
parseDomainSalvaged [., ./config-fns]
|
|
234
|
+
parseSchedulerStore [., ./scheduler-store]
|
|
235
|
+
parseWebAdminCredentialFile [.]
|
|
236
|
+
projectionKey [./conformance]
|
|
237
|
+
projectionValue [./conformance]
|
|
238
|
+
readBundle [./node]
|
|
239
|
+
readCapabilityReading [., ./fleet]
|
|
240
|
+
readDrainReceipt [., ./fleet]
|
|
241
|
+
readDrainRequest [., ./fleet]
|
|
242
|
+
readEffectiveWire [., ./config-fns]
|
|
243
|
+
readEngineSignal [., ./fleet]
|
|
244
|
+
readHttpHookAllowlistLayer [.]
|
|
245
|
+
readMcpIdentityHeaders [.]
|
|
246
|
+
readModelRefusals [., ./fleet]
|
|
247
|
+
readRequestMcpTransport [.]
|
|
248
|
+
readServingLeaseRequest [., ./fleet]
|
|
249
|
+
readServingLeaseResponse [., ./fleet]
|
|
250
|
+
readSettingsUnit [.]
|
|
251
|
+
readTokenScope [., ./api/scopes]
|
|
252
|
+
readWorkersRows [.]
|
|
253
|
+
resolveEffectiveForWorker [., ./resolve-roster]
|
|
254
|
+
resolveLegExpectation [./conformance]
|
|
255
|
+
runConformance [./conformance]
|
|
256
|
+
salvageEntries [., ./config-fns]
|
|
257
|
+
sanitizeZodError [., ./config-fns]
|
|
258
|
+
stableHash [., ./hash]
|
|
259
|
+
stableKey [./conformance]
|
|
260
|
+
stableStringify [., ./hash]
|
|
261
|
+
tightenVerdict [., ./safety-merge-spec]
|
|
262
|
+
toManifestDoc [./conformance]
|
|
263
|
+
toVerifiedIdentity [., ./api/auth-bridge]
|
|
264
|
+
urlSeatPatterns [./one-time-migrations]
|
|
265
|
+
validateDomain [./node]
|
|
266
|
+
validateRemoteExec [./node]
|
|
267
|
+
webAdminSessionUrl [.]
|
|
268
|
+
writeBundle [./node]
|
|
269
|
+
writeDomainFile [./node]
|
|
270
|
+
writeRemoteExec [./node]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- **对 14.6.0 节的更正**(已发的 14.6.0 节一个字节不动,更正记在这里)。14.6.0 节「跨 realm 的普通 JSON 对象」那张表的说明里写「座位的读者 31 个顶层声明,去掉座位本身」—— 括号里的数法写错了。那 31 个是 v14.5.0 里**按原型恒等读形状**的全部顶层声明,**含**两个座位声明本身(`plain-json-object.ts::isPlainJsonObject` · `hooks-config.ts::hookIsRecord`)与 `types.ts::ModelEntry`:即 29 个读者 + 2 个座位 = 31;座位在表里、只是不当种子(现行的调用者到达它,只说明它们调了它,不说明它们在 v14.5.0 读的是恒等判)。表的内容与每一行的判定没有错,错的只是这一句括号的数法。
|
|
274
|
+
三问 —— **谁需要**:核 14.6.0 那张表怎么数出来的人(复核者、重算它的消费方):照字面读那句括号会数出「31 个读者 + 2 个座位 = 33」的错账(实数是 29 + 2 = 31);**谁受伤**:无 —— 表里每一行的判定与名单都没动,没有导出的行为变化;**补偿**:无需。
|
|
275
|
+
钉:无新钉(文字更正,没有行为面)。那 31 个 id 的出处是 tag `v14.6.0` 上 14.6.0 的跨 realm 受影响导出钉里的 `BASELINE`(31 项,含 `isPlainJsonObject` · `hookIsRecord` · `ModelEntry`),该钉在 14.6.0 发版时是绿的,本更正不改它。
|
|
276
|
+
|
|
11
277
|
## 14.6.0 — 2026-10-24 — minor:唯一比较器 `tightenVerdict`(`TightenRule` 尾插 `setUnion` / `numericLower`,无参)· 成员比较器由成员行的值类座推出(`MemberAdmission.list` · `memberTighten`)· settings 单元表与单元读法(`SETTINGS_UNITS` · `unitTighten` · `readSettingsUnit`)· 便携域表搬纯叶模块 · 失败句出路并成一张表与 `chmod` 落点 · 「纯 JSON 对象」谓词只有一个座位 · 请求面 url 两个答案的读数
|
|
12
278
|
|
|
13
279
|
> 档位(按发版纪律):**minor** —— 加性:`TightenRule` 尾插两词(无参;发版纪律的裁定例外:持穷举 `Record<TightenRule["rule"], …>` 或无 `default` 穷举 `switch` 的消费方**提货时编译红** = 提货成本,
|
package/README.md
CHANGED
|
@@ -356,8 +356,12 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
356
356
|
`VerifierConfig` / `VerifiedIdentity` / `AuthBridgeErrorCode` + pure decision fns
|
|
357
357
|
(`shouldForceJwksRefresh`, `jwksWithinOfflineGrace`, `classifyTimeClaims`, `toVerifiedIdentity`).
|
|
358
358
|
The normative rules R1–R10 in its header are the service verifier's implementation spec.
|
|
359
|
-
`VerifiedIdentity` carries an optional `scope` (the token's active tenant scope — see `api/scopes`)
|
|
360
|
-
(issued-at, integer seconds, verbatim — absent claim ⇒ no key; not a non-negative integer ⇒ the token is refused)
|
|
359
|
+
`VerifiedIdentity` carries an optional `scope` (the token's active tenant scope — see `api/scopes`), an optional `iat`
|
|
360
|
+
(issued-at, integer seconds, verbatim — absent claim ⇒ no key; not a non-negative integer ⇒ the token is refused) and an optional
|
|
361
|
+
`principalInc` (14.7.0: the principal's incarnation id, read from the token claim `PRINCIPAL_INC_CLAIM` = `sub_inc` — a non-empty string
|
|
362
|
+
verbatim; absent claim ⇒ no key, never a placeholder; present but not a non-empty string ⇒ the token is refused. A deleted-and-re-created
|
|
363
|
+
user keeps the name and gets a new incarnation, so `sub` alone names a slot, not a person; `PrincipalIncarnation` is the one schema every
|
|
364
|
+
incarnation field reads).
|
|
361
365
|
- **web-admin credential file + connection env names** (`web-admin-credential.ts`, main barrel; S-698): the file behind
|
|
362
366
|
`SEMA_WEB_ADMIN_TOKEN_FILE` is one of two KINDS — a bare line (one static token — the same kind of file as today:
|
|
363
367
|
its first line, printable ASCII with no control / non-ASCII characters; user JWT vs worker pull token stays the reader's `looksLikeJwt` call) or a
|
|
@@ -378,6 +382,30 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
378
382
|
no default) → `env` / `file` / `absent`, the one pick-exactly-one rule for every credential: an empty string is unset, BOTH set (non-empty) throws (the refusal
|
|
379
383
|
names the two keys, never a value), the file path comes back verbatim (resolving a relative one against a cwd
|
|
380
384
|
is the reader's job).
|
|
385
|
+
- **Sandbox git credential plane** (14.7.0; `sandbox-git.ts` + `api/credentials.ts`, main barrel only — no subpath): a sandbox's git uses the
|
|
386
|
+
MEMBER's own credential, fetched by the data plane from web-admin's credential port and used only through a proxy in that member's own pod. This
|
|
387
|
+
package carries the shapes and word tables, no IO. (1) `isSandboxGitOrigin` — a string equal to its own WHATWG origin with scheme http / https (the
|
|
388
|
+
canonical form is that one equation's consequence: lowercase host, no default port, no trailing slash, no userinfo / path / query / fragment;
|
|
389
|
+
an origin cannot express a sub-path deployment) — and the `sandboxGit` effective-config block `SandboxGitConfig`
|
|
390
|
+
(`{ origins: [{ origin, connectUrl }] }`, ≤ `SANDBOX_GIT_MAX_ORIGINS` = 16, distinct, empty list legal; an http origin is READABLE in the block — its
|
|
391
|
+
refusal is a per-origin answer word, not a block refusal). `sandboxGit` is a non-domain top-level key of the `/effective` payload: optional, no
|
|
392
|
+
default, carried verbatim by `readEffectiveWire` (`EffectiveWire.sandboxGit` is `unknown`) and judged by the consumer with `SandboxGitConfig`.
|
|
393
|
+
(2) The credential port's wire for `kind: "sandbox_git"`: `SandboxGitCredentialRequest` (claim a handle), `CredentialHandleAnswer` /
|
|
394
|
+
`CredentialResolveRequest` (kind-independent), `SandboxGitCredentialAnswer` with one `SandboxGitOriginAnswer` per origin (discriminated by
|
|
395
|
+
`state`; `secret` only on the `active` arm — other arms strip it), `SandboxGitCredentialRefusal` (`{ error, permanence, message }`). Three word tables,
|
|
396
|
+
each spelled once: `SANDBOX_GIT_ORIGIN_VERDICTS` (the 11-row per-origin judgment order; `SANDBOX_GIT_ORIGIN_STATES` / `SANDBOX_GIT_ORIGIN_REASONS`
|
|
397
|
+
and the answer arms' reason domains derive from it), `SANDBOX_GIT_REFUSALS` (10 refusal words → HTTP statuses + `permanence`;
|
|
398
|
+
`SANDBOX_GIT_REFUSAL_ERRORS` derives from it), `SANDBOX_GIT_PERMANENCE` (`terminal` / `transient`). Both sides' reading is in
|
|
399
|
+
`docs/CONSUMER-INTEGRATION.md` §12.
|
|
400
|
+
- **Path-base judge and the fleet advertise address** (14.7.0; `url-shape.ts` main barrel, and `fleet.ts` main barrel + `./fleet`): a seat whose value is a
|
|
401
|
+
"base that other code appends request paths to" — the center base URLs, the address a fleet member advertises to the control plane, the web link base of
|
|
402
|
+
`webAdminSessionUrl` — has one rule: no credential, no query, no fragment; a path prefix is fine. `PATH_BASE_URL_FORBIDDEN_PARTS` is the ONE closed, frozen,
|
|
403
|
+
ordered set of the URL parts such a seat forbids (`userinfo` · `query` · `fragment`; a POLICY set, independent of the judge's vocabulary `HTTP_URL_PARTS` — which says what parts a URL has — so a new vocabulary word
|
|
404
|
+
has to be ruled on here explicitly); `pathBaseUrlFlaws(url)` is `httpUrlFlaws(url, that set)` — `[]` = acceptable, otherwise the judge's rule words, never a piece of the
|
|
405
|
+
value. `webAdminSessionUrl` reads it too. `FLEET_ADVERTISE_ADDRESS_ENV` (`"FLEET_ADVERTISE_ADDRESS"`, beside `FLEET_GENERATION_ENV` / `FLEET_SCOPE_ENV`) is the env name both the
|
|
406
|
+
writer (the orchestrator that renders the value) and the reader (the engine at boot) import. Judge the value AFTER placeholders are expanded: `http://$(POD_IP):8090` passes,
|
|
407
|
+
`http://[$(POD_IP)]:8090` does not (a bracketed host must parse as IPv6). Placeholder expansion, reachability and the absent / empty-string reading are not this package's;
|
|
408
|
+
the env name is not a reserved worker env name. Both sides' reading is in `docs/CONSUMER-INTEGRATION.md` §13.
|
|
381
409
|
- **Session web link** (`web-admin-session-url.ts`, main barrel): `webAdminSessionUrl(webBaseUrl, { worker, sessionId })` — the ONE
|
|
382
410
|
template for a web-admin session link, `<webBaseUrl>/chat?agent=<worker>&session=<sessionId>`: both parts required (a link with only
|
|
383
411
|
the session id lands on whichever worker the page picks and may not find it), each part `encodeURIComponent`-escaped, the base a
|
|
@@ -496,7 +524,7 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
496
524
|
`urlAllowPatternMatches` (⊆ CC `g9`, four upstream holes closed) / `urlDenyPatternMatches` (⊇ `g9` ∪ whole-string glob);
|
|
497
525
|
divergences in `docs/CC-DIVERGENCE.md`.
|
|
498
526
|
- **HTTP hook allowlists** (`http-hook-allowlists.ts`, 13.0.0): category `SAFETY_MERGE_SPEC.httpHookAllowlists`; `readHttpHookAllowlistLayer(source, settings)`,
|
|
499
|
-
`httpHookUrlVerdict(url, layers, workspace)` (every admitted layer's list must pass — layers intersect) and `httpHookEnvVarsAllowed(hookVars, layers, workspace)`;
|
|
527
|
+
`httpHookUrlVerdict(url, layers, workspace)` (every admitted layer's list must pass — layers intersect) and `httpHookEnvVarsAllowed(hookVars, layers, workspace)`, where every layer in `layers` is read by the reader's own reading (a reader-produced `read.layer`, a hand-built plain layer and a `JSON.parse`d layer from this realm with the same content get the same answer; a layer whose `source` cannot be read — missing, inherited, inconsistent or not a string — is a malformed layer that blocks everything (`rule: "malformed"`, not named in `sources`; a readable source with an unreadable list is `malformed` too and is named; `empty` means a list that really is `[]`), and a readable source word outside the closed set throws `RangeError`; lists, `hookVars` and `layers` must be plain arrays — the prototype is this realm's `Array.prototype` (an array from another realm — a `vm` context, another iframe — is refused; round-trip it through JSON first), `constructor` / `filter` / `flatMap` / `Symbol.iterator` read as this realm's built-ins, and the own keys are only indices and `length`; anything else, subclasses included, is read as malformed);
|
|
500
528
|
a layer is admitted ⇔ `layerAdmission(source, workspace, "httpHookAllowlists") === "admitted"` (user / flag / managed; project / local are below the floor).
|
|
501
529
|
- **MCP server list verdict** (`mcp-server-policy.ts`, 13.0.0): `mcpServerPolicyVerdict` → `McpPolicyBlock{name, list, rule, target,
|
|
502
530
|
written, sources}` — judged on the expanded target, echoed only as written; the managed layer (whose `allowManagedMcpServersOnly` counts)
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
14.
|
|
1
|
+
14.7.0
|