@sema-agent/settings-schema 14.6.0 → 14.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +475 -0
  2. package/README.md +89 -5
  3. package/conformance/VERSION +1 -1
  4. package/conformance/cases/c84-sandbox-git-block-carried-verbatim/expected.json +153 -0
  5. package/conformance/cases/c84-sandbox-git-block-carried-verbatim/input.json +23 -0
  6. package/conformance/cases/c84-sandbox-git-block-carried-verbatim/meta.json +5 -0
  7. package/conformance/cases/c85-sandbox-git-refused-block-still-carried/expected.json +153 -0
  8. package/conformance/cases/c85-sandbox-git-refused-block-still-carried/input.json +23 -0
  9. package/conformance/cases/c85-sandbox-git-refused-block-still-carried/meta.json +5 -0
  10. package/dist/api/auth-bridge.d.ts +16 -2
  11. package/dist/api/auth-bridge.js +59 -24
  12. package/dist/api/credentials.d.ts +281 -0
  13. package/dist/api/credentials.js +149 -0
  14. package/dist/api/scopes.js +5 -1
  15. package/dist/atomic-write.d.ts +15 -0
  16. package/dist/atomic-write.js +109 -5
  17. package/dist/catalog-manages.d.ts +15 -0
  18. package/dist/catalog-manages.js +60 -0
  19. package/dist/center-refs.d.ts +68 -0
  20. package/dist/center-refs.js +70 -0
  21. package/dist/config-d-file.js +33 -15
  22. package/dist/config-dir-migration.js +1 -0
  23. package/dist/config-fns.js +147 -81
  24. package/dist/config-root-documents.d.ts +106 -0
  25. package/dist/config-root-documents.js +93 -0
  26. package/dist/config-root-node.d.ts +117 -0
  27. package/dist/config-root-node.js +260 -0
  28. package/dist/conformance/canonical.js +4 -4
  29. package/dist/conformance/contract-adapter.js +26 -1
  30. package/dist/conformance/corpus.js +4 -4
  31. package/dist/conformance/derive.js +2 -2
  32. package/dist/conformance/run.d.ts +3 -0
  33. package/dist/conformance/run.js +53 -12
  34. package/dist/conformance/temp-roots.d.ts +31 -0
  35. package/dist/conformance/temp-roots.js +184 -0
  36. package/dist/cross-session.d.ts +4 -4
  37. package/dist/declared-capture.d.ts +41 -0
  38. package/dist/declared-capture.js +344 -0
  39. package/dist/default-model.d.ts +1 -1
  40. package/dist/default-model.js +5 -4
  41. package/dist/env-model-table.d.ts +163 -0
  42. package/dist/env-model-table.js +257 -0
  43. package/dist/file-edit.js +11 -15
  44. package/dist/fleet.d.ts +36 -7
  45. package/dist/fleet.js +83 -32
  46. package/dist/hash.d.ts +0 -9
  47. package/dist/hash.js +64 -7
  48. package/dist/hooks-config.js +18 -3
  49. package/dist/http-hook-allowlists.d.ts +22 -9
  50. package/dist/http-hook-allowlists.js +169 -91
  51. package/dist/index.d.ts +7 -0
  52. package/dist/index.js +7 -0
  53. package/dist/mcp-request-entry.d.ts +67 -13
  54. package/dist/mcp-request-entry.js +132 -93
  55. package/dist/mcp-server-name.d.ts +17 -0
  56. package/dist/mcp-server-name.js +19 -0
  57. package/dist/migration-receipt.d.ts +10 -6
  58. package/dist/migration-receipt.js +5 -3
  59. package/dist/node.d.ts +1 -0
  60. package/dist/node.js +2 -0
  61. package/dist/one-time-migrations.d.ts +4 -2
  62. package/dist/one-time-migrations.js +236 -96
  63. package/dist/own-key-read.d.ts +247 -0
  64. package/dist/own-key-read.js +449 -0
  65. package/dist/plain-json-object.d.ts +11 -1
  66. package/dist/plain-json-object.js +37 -1
  67. package/dist/remote-exec.js +9 -1
  68. package/dist/retired-form.d.ts +19 -0
  69. package/dist/retired-form.js +24 -1
  70. package/dist/safety-merge-spec.d.ts +6 -6
  71. package/dist/safety-merge-spec.js +50 -37
  72. package/dist/sandbox-git.d.ts +64 -0
  73. package/dist/sandbox-git.js +89 -0
  74. package/dist/scheduler-store.js +19 -11
  75. package/dist/settings-units.js +14 -12
  76. package/dist/tier-words.d.ts +33 -0
  77. package/dist/tier-words.js +43 -0
  78. package/dist/types.d.ts +12 -2
  79. package/dist/types.js +35 -6
  80. package/dist/url-shape.d.ts +16 -0
  81. package/dist/url-shape.js +18 -0
  82. package/dist/web-admin-credential.js +5 -2
  83. package/dist/web-admin-session-url.js +2 -2
  84. package/dist/worker-row-verdict.d.ts +11 -0
  85. package/dist/worker-row-verdict.js +58 -2
  86. package/dist/workers-rows.js +38 -10
  87. package/dist/zod-unwrap.d.ts +6 -0
  88. package/dist/zod-unwrap.js +14 -7
  89. package/docs/API-SURFACE.md +100 -5
  90. package/docs/CONSUMER-INTEGRATION.md +250 -2
  91. package/llms-full.txt +26 -4
  92. package/llms.txt +11 -4
  93. package/package.json +2 -2
  94. package/dist/definition-copy.d.ts +0 -23
  95. package/dist/definition-copy.js +0 -52
package/CHANGELOG.md CHANGED
@@ -8,6 +8,481 @@
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 14.8.0 — 2026-10-27 — minor:配置根上的两份根级文档(env 模型表被目录顶替的决定文档 `config.env-models.json` 与按 id 一份的迁移运行记录)· 两只读者 `readEnvModelsDecision` / `readMigrationMarker` 与决定文件的唯一写函数 `writeEnvModelsDecision`(`./node`)· 一次性迁移 SS-M11(`migrateEnvModelsDecision`)· 目录管不管模型面的原文判官 `catalogManagesModelPlane` 与计数 `enabledEntryCount` · env 模型表的一份读法与写者的计划助手(`readEnvModelSlots` · `buildEnvModelBaseline` · `planEnvModelTableWrite`)· 两只模型面组拒码 `CATALOG_REPLACES_ENV_MODELS_CODE` / `SUMMARIZE_MODEL_MISSING_CODE` · 中心引用解析口 `resolveCenterRefs` 与记录读器 `readPlainStringRecord` · MCP 条目的 `sessionMetaKey` / `startupTimeoutMs`(解析透明)· MCP 服务器名保留前缀 `REMOTE_MCP_SERVER_NAME_PREFIX` 与请求面判官一对 `isUsableMcpServerName` / `describeBadMcpServerName` · 引擎钉 7.58.0 与上游漂移门 ⑧ / ⑪
12
+
13
+ > 档位(按发版纪律):**minor** —— 加性:新导出(包根 +53 名,其中两只组拒码常量同时在 `./fleet` 上;`./node` +9 名。包根:根级文档的名字、闭集答案表与读数身份拼法、原文判官两只、env 模型表读法与计划助手族、两只组拒码常量、中心引用解析口 `resolveCenterRefs` 与它的两张词表和读数型、记录读器 `readPlainStringRecord` 与它的原因词表 / 保留成员名 / 读数型、MCP 服务器名的保留前缀 `REMOTE_MCP_SERVER_NAME_PREFIX` 与请求面 MCP 名判官一对 `isUsableMcpServerName` / `describeBadMcpServerName`;`./node`:决定文档与运行记录的两只读者、一只写函数、一次性迁移 SS-M11 的迁移体与它的 I/O 座;导出面登记表合计〔同名在多条子路径上各计一次〕1396 → 1460 名);
14
+ > 公开闭集拓宽五处:`MODEL_PLANE_REFUSAL_CODES` 尾插两码、`ONE_TIME_MIGRATION_IDS` 尾插 `SS-M11`、`MIGRATION_APPLIED_AS` 尾插 `minted`(`MigrationReceipt` 的 `appliedAs` 随它)、`OneTimeMigrationWindow.oldForm` 加 `read-as-default`、`MigrationDocument` 加 `"config.env-models"`(对这五处持穷举 `switch` / `Record<…, …>` / `never` 兜底的消费方提货时编译红 —— 提货成本,不是 major)。
15
+ > `McpServerSpec` 多两只可选键:`sessionMetaKey` 与 `startupTimeoutMs`(都解析透明,`PARSE_TRANSPARENT_KEYS` 各多一行,接在表尾);不带这两只键的条目解析结果逐字不变;
16
+ > 带着它们的条目,v14.7.0 剥掉这两把键并报 `unknown-keys-dropped`,本版原样带过、不告警。归 minor 的依据是「发版纪律」的「新键 / 新导出 / 词表加词 = minor:可选键(无默认)」;
17
+ > 两把键解析透明 ⇒ 本包一字不拒、不是校验收紧,坏值由引擎拒(存量里写着有效值的条目,交给引擎后按它生效;写着坏值或 `null` 的,此前键被剥掉并报 `unknown-keys-dropped`,本版原样交给引擎 —— 见下文两条各自的「谁受伤」)。
18
+ > **读口行为变化有两处**:① `readModelRefusals` 对 `plane` 是这两只新码的体,以前把码当未知收窄成缺席,现在读出来;② `readWorkersRows` 扣下带旧字段的行时,`held[].reason` 改了措辞(`field` 不变;见下文「读口 `readWorkersRows` 扣下带旧字段的行时」那一条)。别的既有导出答案逐位不变(`McpServerSpec` 族另有一处:上一行的两只新键;`readEntryIdentity` 改调共享的「显式停用」读法,同一读法搬家,钉逐形核过;搬出来的那一次描述符读在自有描述符读法的普查登记表里按名多登一处,访问器判定用内核唯一拼法 `isAccessorDescriptor`;`materializeLocalRoot` 不是读口,另有一处变化:它铸的临时根里多一个心跳文件 `.alive`,本进程第一次铸根时顺带清扫心跳停了 60 分钟以上的旧件,见下文临时根那一条)。
19
+ > 没有新子路径、没有删改的导出。
20
+ > **SS-M11 的窗**:本包出组拒码常量 `CATALOG_REPLACES_ENV_MODELS_CODE` 与决定文档供引擎读,强制在引擎一侧,今天引擎(`@sema-agent/core` 7.58.0)不拒这个组;所以窗的起算点 = 引擎开始强制这道闸的那一天,到期日 = 起算日 + 30 天;本包登记表里 SS-M11 那一行按本包的发布日计,管的是本包何时能删迁移码。
21
+
22
+ - **配置根上的两份根级文档 + 两只读者 / 决定文件的唯一写函数(`src/config-root-documents.ts` 包根,`src/config-root-node.ts` 经 `./node`)**。「一份管模型面的 `config.d/models.json` 能不能顶替 env 模型表」是**配置根级的部署决定**,
23
+ 落在 `<根>/config.env-models.json`(`ENV_MODELS_DOCUMENT`,`{"envModels":"replaced"|"kept"}`,strict);一次性迁移的运行记录**按 id 一份**,落在 `<根>/config.migration.<id>.json`(今天只有 SS-M11:`{"migration":"SS-M11"}`,strict;没有聚合文件、没有追加写)。
24
+ 两份都在 `config.d` 之外 —— 已核的各版文件仓库只扫 `<根>/config.d`(抽样,不证全称),被检视的配置读者不把它们当域文档:写者不探引擎版本。`CONFIG_ROOT_DOCUMENTS` 是两份的名单(引擎的目录信任判官、客户端的持久文件登记取它,不手抄)。
25
+ 读只有两只:`readEnvModelsDecision(root)` ⇒ `absent | replaced{digest} | kept{digest} | unreadable{bad_json|bad_shape, digest} | unreadable{io, errno}`(闭集;**只有文件不存在才是 `absent`** —— 截断的文件、目录、悬空的符号链接都读不懂;
26
+ 有字节的四臂带同一次读的字节 SHA-256 前 16 位,没字节的带 errno;可信属主的 0000 文件读恒 `EACCES`,`io` 可以是持久的)· `envModelsDecisionTag(decision)` 是读数身份的一种拼法(`absent` / `d:<digest>` / `io:<errno>`),
27
+ 引擎读这份决定时(今天引擎还不读),「每份内容只告警一次」与本地 etag 都取它,不再开文件;`readMigrationMarker(root, id)` ⇒ `absent | present | unreadable`(「跑过」只问这一份文件,读不懂的按跑过处置)。两只的读数从不抛(`readMigrationMarker` 只对不写运行记录的 id 抛 `TypeError`,不拿它拼路径);都接一只注入的 I/O 座(`ConfigRootIo`,真绑定 `nodeConfigRootIo`,读字节)。
28
+ 决定文件的写只有一只(迁移铸确认也经它的 `createIfAbsent`;运行记录不经它,由迁移独占创建):`writeEnvModelsDecision(root, "replaced"|"kept", { write })` —— `overwrite`(写者:用户确认之后整份覆盖,读不懂的旧文件也换掉)/ `createIfAbsent`(不存在才建,临时文件 fsync 后 `link` 成目标名;只有 `EEXIST` 算已在 ⇒ `present`、一字不动,
29
+ 悬空的符号链接也算已在)。模式与 `config.d` 域文档**同一条规则**(原子写器的 `config-document` 类,模式表不变):新建 0o644 经进程 umask(更严的 umask 只会更严:umask 077 下是 0o600),已有的文件沿用它原来的模式
30
+ (运维 chmod 过的决定文件不被一次覆盖写改回;一份被改成 0000 的决定文件读恒 `EACCES`,要修由运维自己 chmod)。各端自写在 umask 002 下落 0664,会被引擎的目录信任判官拒 —— 一律调写函数。本包不判属主 / 写位:那是引擎在读之前的判官的事。
31
+ 三问 —— **谁需要**:引擎(本地配置腿强制这道闸时读决定、起服时调迁移读写两份;今天引擎都还没接)与三个写者(写目录之前读决定、确认后写 `replaced`);**谁受伤**:无 —— 新导出、新文件名,没有既有读者;
32
+ **补偿**:无需。钉:`test/config-root-documents-14.8.0.test.ts`(语料逐格:决定 16 格、运行记录 11 格,语料文件 `test/fixtures/config-root-documents-corpus.json` 两条包线共用、原样再跑 · io 格注入读座与真 0000 文件 · 注入读座抛出读不出的东西(会抛的 `code` getter / 已撤销的 Proxy)⇒ `io`、errno `uncoded`,只靠继承的 `code` 不算缺席(errno 只认自有的)· 目录 / 悬空链接 = io ·
33
+ digest 按内容 · 读数身份三拼法 · 聚合形旧草案文件一律不读 · overwrite / createIfAbsent 两写法 · 决定文件一个写点(结构:迁移经写函数)· 竞态不吞 · 与域文档同一条模式规则(umask 077 ⇒ 0o600、022 / 002 ⇒ 0o644;逐 umask 与 `config-document` 对拍,退化的 umask 也一样)· 已有 0600 / 0640 / 0664 / 0000 的决定文件被覆盖写后模式不动 · 写失败点名路径 ·
34
+ 根级文档对文件仓库惰性:effective / version / 告警逐字不变 · 结构:名字字面一处、文本读法只被读者调、迁移经两只读者读);`test/atomic-write.test.ts`(模式表两行,`existing` 只有 keep / reset 两个词)、`test/atomic-write-exclusive-14.8.0.test.ts`(不存在才建:短写不发布半截文件 · `link` 只有 `EEXIST` 算已在 · fsync 失败落点 · 任何写器都不 fchmod)、`test/file-modes.test.ts`(结构:`src/` 里没有 fchmod 与精确模式一臂)。
35
+ - **一次性迁移 SS-M11:配置根上铸一次「env 模型表被目录顶替」的决定(`migrateEnvModelsDecision`,`./node`;登记见 `ONE_TIME_MIGRATIONS` 那一行)**。决定文档出现以前,管模型面的目录**静默**整表顶替 env 模型表;
36
+ 引擎开始按决定执法之后,存量根要保住这个读法。规则一条:**运行记录缺席才跑;先读决定 D0 与 models.json 管不管 M0,再以「不存在才建」写运行记录 —— 建成的那个进程是本根唯一的铸造者,别的进程见 `EEXIST` 即停、永不铸;
37
+ 铸造者只在「D0 缺席 ∧ M0 管」时以「不存在才建」铸 `replaced`(env 表声没声明都一样:保的是升级前「管模型面的目录整表顶替」的读法),此后不再读决定**(D0 是预置的 `replaced` 而它在选举之后被删 ⇒ 不重铸;D0 缺席而选举之后有人写了 `kept` ⇒ `exists`、一字不动)。
38
+ 结局闭集 `EnvModelsMigration`:`already_ran` / `marker_unreadable`(按已跑处理)/ `not_minter` / `marker_write_failed{errno, error, wayOut}` / `recorded{createdRoot, decision, mint}`,`mint` = `minted` / `exists` / `failed{errno, error, wayOut}` /
39
+ `skipped{decision_present|not_managing}` / `models_unreadable{errno, error, wayOut}`(判序如列;只有 `ENOENT` 算没有 models.json,坏文件算不管;别的读错不折成「不管」、单列一词 —— 要运维动手:不替人铸(fail-closed),带 errno(底层错误带码时;悬空链接 / 不是常规文件没有)与手工恢复句,运行记录已在 ⇒ 此后不再尝试);env 只在铸成之后读一次、只读两槽;铸了时带被顶替的 env 模型(名 + 槽 + 来源键;env 表没声明 ⇒ 空),调用方据此发**一行**告警(本包不出声)。不建 `config.d`;根缺席 ⇒ 逐级以恰是 0o700 的目录建根(umask 0777 下也进得去)、只放运行记录。
40
+ 运行记录在场之后,决定缺席 ≡ `kept`,读这份决定的一律这么读(引擎强制这道闸之后、迁移、写者)。撤销的终局按运行记录的创建者算:只有创建者能写确认;以运行记录在场为前提,创建者写完 / 判定不写 / 失败(结局 `minted` / `exists` / `skipped` / `models_unreadable` / `failed`,调用方各记一行)之后,删掉决定文件就是撤销且是终局(删掉运行记录再起服 ⇒ 迁移重跑,决定缺席即重铸);其它副本读到记录在场 = 已跑、跳过(或得 `EEXIST` 答 `not_minter`),它们的返回不构成终局;创建者当选到结局落定之间的窗口在挂起 / I/O 卡顿下无上界,窗口内唯一安全的撤销是写 `kept`(独占创建护住它;删掉的会被创建者照它先读的 D0 铸回);创建者在写确认之前崩溃 ⇒ 永久 `kept`(fail-closed,披露)。收据词表尾插 `minted`(只属 SS-M11:在缺席处写下一份新事实,不改任何已有的值);
41
+ 登记表旧形待遇尾插 `read-as-default`(旧形是文档的缺席,读者按缺省读、没有拒句可点名)。窗与删法只在登记表那一行;删码之后缺席读作 `kept`;引擎强制这道闸之后,出路由它的组拒句点名(今天引擎不拒这个组)。
42
+ 三问 —— **谁需要**:升级前已经用目录顶替 env 模型的本地部署(升级后照旧由目录服务,只多一行告警点名被顶替的 env 模型);**谁受伤**:① 迁移窗内首启、预置了管模型面目录的**新装**根 —— 迁移分不出「升级前」与「新装」,一样铸 `replaced`(告警点名);
43
+ ② 整根只读(ConfigMap)的部署 —— 写不了运行记录就不铸,引擎开始强制这道闸之后升级当拍目录被拒、换 env 模型服务(今天引擎不拒这个组);③ 进程恰在写完运行记录、铸确认之前中断,或铸失败、或那一刻 models.json 读不出的根 —— 此后无人再铸,换 env 模型服务(披露);
44
+ ④ 创建者当选到结局落定之间(窗口在挂起 / I/O 卡顿下无上界)有人删了确认 —— 被创建者照先读的 D0 铸回;其它副本的返回不构成终局(披露);⑤ 配置根缺席的部署 —— 迁移以引擎进程的用户、0o700 建根,与引擎不同用户运行的客户端之后进不去(披露);**补偿**:①:删掉决定文件或写 `kept`(运行记录在,迁移不再跑;下一次起服生效 —— 运行中的引擎保留它在服的那一代目录);②:升级前把 `config.env-models.json` 放进同一份 ConfigMap 的配置根;
45
+ ③:按结局里的手工恢复句手写决定文件;④:窗口内撤销写 `kept`(不存在才建守住它;删除只在运行记录在场、创建者结局落定之后是终局);⑤:客户端在引擎首次起服之前自己建好配置根。
46
+ 钉:`test/ss-m11-env-models-migration-14.8.0.test.ts`(登记 · 记录在场跳过 / 坏记录(语料每种 + 0000)按已跑 / 聚合旧草案文件不影响判定 / 一次性 · 铸的两合取逐格 · env 表未声明(含只有 cheap 槽)照铸、被顶替的为空,纯空白照点名 · env 表空且目录不管 ⇒ not_managing ·
47
+ 不管 / 无 models.json / 坏文件不铸 · models.json 读错(真 0000 / 注入 EIO / 悬空链接 / 目录占位)⇒ models_unreadable、不铸、带 errno 与恢复句,决定已在时判序先答 decision_present · 带 BOM 的 models.json 照样算管 · 不建 config.d · 语料逐格处置(只有 D0 缺席铸)· 读写顺序 · 记录建不成不铸(注入 + 真只读根,带 errno 与恢复句)·
48
+ 独占创建与唯一铸造者:建记录后写 kept ⇒ exists · 两副本交错 B 永不铸、删后不铸回 · 预置 replaced 建记录后删 ⇒ 不重铸 · 跨副本 ②″(B 返回之后删 ⇒ 创建者铸回 / 窗口内写 kept 护住 / 创建者写前崩溃 ⇒ 永久 kept)· 三处文字一致 · 铸失败 ENOSPC / EIO / EPERM / 硬链接不被支持 ⇒ failed 且不重试 · 中断在记录之后不补铸 ·
49
+ 根缺席 umask 002 / 0777 下逐级 0o700 · 铸出的确认与运行记录与域文档同模式规则(umask 077 ⇒ 两份 0o600、022 ⇒ 0o644));`test/one-time-migrations-11.0.0.test.ts`(闭集尾插)。
50
+ - **「目录管不管模型面」的原文判官 `catalogManagesModelPlane(doc)` 与计数 `enabledEntryCount(doc)`(`src/catalog-manages.ts`,包根)**。各端各写一份「有没有启用条目」的判法,`enabled` 怎么读各说各的。
51
+ 现在一条律:**`models` 里至少一条不是「显式停用」的条目 ⇔ 管**;「显式停用」= 纯 JSON 对象的自有数据属性 `enabled` 恰为 `false` —— 与被丢条目身份(`entry-dropped` 告警的 `identity.enabled`)读的是**同一只**谓词,本包只剩一份读法;
52
+ 判官 = 计数 > 0。文档不是对象、`models` 缺席或不是数组 ⇒ 0;坏条目(不是对象)⇒ 计入(引擎读腿的载体半场同答:形不对 ⇒ 算管);读不出的值(撤销的 Proxy、访问器 / 隐藏键、类实例)⇒ `TypeError`(两个调用方失败方向相反,不替它们猜)。
53
+ 读后表示(引擎自己的被丢条目载体那一半)不在本包。
54
+ 三问 —— **谁需要**:引擎(强制这道闸时,「管不管」与日志计数调它;今天引擎还没接)、桌面与命令行(启用判改调它)、迁移与计划助手(本包内);**谁受伤**:无 —— 新导出;被丢条目身份的读数逐形不变;**补偿**:无需。
55
+ 钉:`test/catalog-manages-14.8.0.test.ts`(逐形 14 格 · 只认自有数据属性 / 访问器零调用 / 原型污染不读 · 读不出抛 · 洞不是条目(只认自有下标,被污染的 `Array.prototype[i]` 不算条目)· 同一条坏条目:身份 `enabled:false` ⇔ 判官视作停用 · 结构:`readEntryIdentity` 调共享谓词的「形已判过」内核 `ownEnabledFlag`、不自读 `enabled`;同一条目经 `salvageEntries` 只被问 2 次原型,与 v14.7.0 同数)。
56
+ - **env 模型表的一份读法与写者的计划助手(`src/env-model-table.ts`,包根)**。`MODEL_ID` / `MODEL_CHEAP_ID` 两槽与它们铸出的角色底此前只在引擎里读;写者要在写目录之前知道「这道闸强制起来会不会拒、会顶掉什么」,没有同一份读法可调。
57
+ 现在:`readEnvModelSlots(env)`(`MODEL_ID` 缺席 / 空串 = 没有主槽,纯空白算在场;cheap 槽四态 `EnvCheapSlot` + `mintedCheapId`)· `envModelTableDeclared(env)`(引擎一侧的顶替闸〔今天引擎还不强制〕与计划助手共用的那一只谓词)·
58
+ `buildEnvModelBaseline(env)`(条目 + 角色底 + `MODEL_VERIFIER` 读数;键名表 `ENV_MODEL_TABLE_KEYS`、别名键 `DEFAULT_MODEL_KEY`)。env 快照按「读到即算」(下标读)。
59
+ 计划 `planEnvModelTableWrite({ env, target, decision, candidate, intent? })`(结局闭集 `write{not_read|not_managing|env_table_empty|acknowledged}` / `write_with_decision` / `confirm{confirmKey, intent: absent|stale}` / `fix_first`),判序:
60
+ 目标腿不读 `config.d`(remote / none)⇒ 照写 → 候选不管 ⇒ 照写 → summarize 会悬空 ⇒ `fix_first`(不可确认)→ env 表未声明 ⇒ 照写、**不写确认** → 决定 `replaced` ⇒ 照写 → 确认键对得上 ⇒ 先写决定再写目录 → 否则问。
61
+ 回执按**上游 id**:退场的 env 条目带 `servedAs`(候选里同 id 的启用条目名),每个 env 底角色带写前绑谁(env 表键 + 它解到的 id)与采纳后落点 `{name, id}`(缺省落点经 `resolveCatalogDefaultModel`;档位词 / CC 档位别名 / `best` 经候选的活跃档位组展开 —— 与引擎装配同一序:先摘掉绑到停用条目的档,再按 core 的档序向下落(绑到目录外的名过不了 schema:候选读不进 ⇒ `TypeError`);绑到停用条目或目录外、展开也落不到 = `dangles`);
62
+ 回执带决定读数(读不懂的 —— 含持久 io —— 确认后整份覆盖);确认键 = 回执的定序 JSON,后果一变旧键就过期。候选读不出或整份读不进 models 域 ⇒ `TypeError`(先修文档,不替它算回执)。拒句投影 `envModelTableWriteRefusal(plan)`(`confirm` / `fix_first` 各一句;只带 env 键名、目录名与模型 id —— 可披露的原样、秘密形的打成记号;凭据与别的 env 值从不进句子)。
63
+ 三问 —— **谁需要**:三个写者(命令行、桌面、客户端核心)在改写 models.json 的每一条路上调它;引擎改读 `readEnvModelSlots` / `buildEnvModelBaseline`(不再自写一份);**谁受伤**:无 —— 新导出;**补偿**:无需。
64
+ 可用性(引擎的 brain 腿、端点、读腿逐条丢)计划判不了;决定性的 summarize 那一格由引擎的不变量兜。
65
+ 钉:`test/env-model-table-plan-14.8.0.test.ts`(两槽读法 / 纯空白在场 / 原型分层快照 · 角色底(verifier 指 cheap、code 角色装饰、rejected、表空)· 判序七格各一 · 回执逐格(同 id 异名 servedAs、同名异 id 落点 id)· 档位缺省落点 · 继承的档位词按活跃组展开(flash / pro / opus / best,绑到停用条目的档摘掉后沿档序落)· {select} 缺省 · 停用绑定 dangles ·
66
+ 确认键定序且随后果变 · 语料逐格处置与持久 io 读数 · 入参闭集 / 读不出 / 读不进抛 · 拒句不回显凭据);`test/tier-words-parity-14.8.0.test.ts`(档序与 CC 别名表 = devDependency core 的两张表;展开逐夹具逐词对拍 core `expandTiers` 的摘后表,含不可服务绑定四形;core 对未摘的原表整张抛)。
67
+ - **两只模型面组拒码尾插:`CATALOG_REPLACES_ENV_MODELS_CODE` / `SUMMARIZE_MODEL_MISSING_CODE`(`src/fleet.ts`,包根与 `./fleet`)**。本地配置腿上「管模型面的目录会顶替已声明的 env 表、决定不是 `replaced`」的那道闸,与 summarize 不变量(每个任务都要解的 summarize 落不到可服务条目)
68
+ 各有一只组拒码;码名是契约词,由本包定,本包出常量供引擎读;强制在引擎一侧,今天引擎(`@sema-agent/core` 7.58.0)不拒这两个组。`MODEL_PLANE_REFUSAL_CODES` 尾插 `config.catalog_replaces_env_models`、`config.summarize_model_missing`(序即声明序)。
69
+ 三问 —— **谁需要**:引擎(铸码)与读 `/health.modelRefusals` 的管理面 / 三端(渲染「为什么目录没落地」);**谁受伤**:对组拒码穷举的读者 —— 提货时编译红;**补偿**:为两码各补一臂。读者没提货之前,`readModelRefusals` 照旧把两码收窄成缺席(读不出原因,但不误判)。
70
+ 钉:`test/env-model-table-plan-14.8.0.test.ts`(码逐字 + 尾插)、`test/model-refusals-6.2.0.test.ts`(闭集六码)。
71
+ - **随包文字与导出面登记表**。README 新增升级指引一节(写者动作:读决定 → 计划 → 确认后经 `writeEnvModelsDecision` 写、不自写;撤销 = 删掉或写 `kept`;整根只读部署的升级前置步;中断与铸造窗口的披露;SS-M11 的窗的到期日公式)、
72
+ 本地文件布局补两份根级文档与「根归客户端、引擎只写这两份」及写序(离线预置(引擎在本根起服之前铺好)时两份文档与域文档可按任意顺序写;运行时写序是定的:写者先写决定、再写 `config.d/models.json`(同一把锁),迁移先建运行记录、再铸决定)、功能清单一条;`docs/API-SURFACE.md` 重生成。
73
+ 三问 —— **谁需要**:三端与引擎的接线者;**谁受伤**:无;**补偿**:无需。
74
+ - **符合性 kit 的临时根不再在进程死掉后一直残留(`materializeLocalRoot` 的临时目录收口,包内 `src/conformance/temp-roots.ts`,不进导出面)**。每个 case 的本地腿建一个 `<tmp>/settings-conformance-<随机>/`,清理只在适配器腿的 `finally` 里 ——
75
+ 跑语料的进程被 `process.exit` 截断或未捕获异常时 `finally` 不跑,根就留在宿主 tmp 下(跑符合性语料的机器上实测累积过万个)。收口两道,一个模块:① **活性证据是根里的心跳文件**:铸根时在根内以 `O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW` 建一个空文件 `.alive`(静态为空),`fstat` 记下它的 `(dev, ino)` 就**关掉**,再起一只 `unref()` 的定时器,每 5 分钟(`ALIVE_TOUCH_INTERVAL_MS`)一拍:按路径 `O_WRONLY | O_NOFOLLOW | O_NONBLOCK` 打开 `.alive`(不带 `O_CREAT`;`O_NONBLOCK` 让被换成没有读端的命名管道的 `.alive` 当场打不开〔`ENXIO`〕作罢,不在文件系统线程池里一直阻塞)、`fstat` 比 `(dev, ino)`,**相等才**写一个字节(换行,空白)再截回空、mtime 由文件系统自己打(两步都刷新它),写完关掉;不相等就关掉、这一拍不写;**心跳按 inode 身份写、不持句柄**:规则只有一条「同一 inode 才写」—— `.alive` 被换成符号链接(有 `O_NOFOLLOW` 的平台上打开就被拒;没有它的平台上打开会跟过去,身份比对仍拦住指向别的文件的链接)、硬链接到别的文件、换成另一个文件或目录、被删,或根被删后同名重建,心跳都不写别的文件,也不把任何路径建出来;模块里没有常驻的文件描述符(铸根时那一只用完即关,每一拍那一只一拍一开一关);`cleanup()` 停定时器、再删根(不开不关任何文件);
76
+ 进程以任何方式死掉(`process.exit`、未捕获异常、信号〔`SIGINT` / `SIGTERM`〕的默认动作、SIGKILL),心跳随它停,根留着,`.alive` 就变老。本进程第一次铸根时清扫:名字恰是 `<前缀><六位随机>`(`mkdtemp` 直铸的形)、是目录、属主是当前用户、根内有 `.alive` 常规文件、
77
+ 且 `.alive` 的 mtime 早于 60 分钟(`STALE_LOCAL_ROOT_AGE_MS`)的旧件,整根删 —— 规则只有这一条:`.alive` 超过 60 分钟没更新之后,由下一轮第一次铸根时的清扫收。不问进程号、不问主机名:证据与文件系统同域,共享同一 tmp 的几个进程、几个容器(跨 pid 命名空间也一样)读到的是同一个答案,`.alive` 在更新的根不被清(限制见下);
78
+ 根里没有 `.alive` 的(更早的版本铸的、运维手造的同形目录)一概不动,拿不准就不删;符号链接与文件不动;
79
+ ② 正常路径:适配器腿 `finally` 里的 `cleanup()` 停这个根的心跳定时器、再删根(只删自己的根;不开不关任何文件;还在飞的一拍随后在已摘出目录的文件上做完、或打不开而落空,被吞掉),语料落盘的文件名闸不变。本模块不在进程上装任何监听(不碰 `exit`,也不碰 `SIGINT` / `SIGTERM`;定时器 `unref`,不拖住进程退出)—— 宿主自己的 `exit` / 信号处理器(含排空、含铸根之后才装的)一概不受影响,宿主的 `exit` 处理器里照样读得到根。
80
+ 清扫是尽力而为:删不掉的(别的用户的、没权限的)留着,之后的清扫再试,不改一次符合性结论。本模块不读进程的钟:清扫的「现在」是刚铸出的根目录的 mtime(建 `.alive` 那一刻由文件系统打),心跳的 mtime 同样由文件系统打,二者同一只钟,进程自己的钟拨偏多少都不改变清扫的答案。
81
+ 三问 —— **谁需要**:跑符合性语料的 cli / core(语料大、进程常被 CI 超时杀);**谁受伤**:心跳停了 60 分钟的活进程 —— 它的根会被别的进程当作死进程的根清掉,这是已知限制,不是本版给出的保证。心跳停有两种:事件循环停转超过 60 分钟(被 `SIGSTOP` 暂停、容器被冻结、停在调试器断点上);心跳连续 60 分钟写不进去(文件系统写满、被重挂成只读 —— 这一拍作罢、不抛、不报);
82
+ 别的部署只多一次对 tmp 目录的读、每个根里一个空文件、每 5 分钟一次对它的一拍(一次打开、一字节写入与截回、一次关,不占常驻的文件描述符),以及清扫里对符合条件旧件的删除(名字恰是前缀加六位随机、是目录、属主是当前用户、`.alive` 老过年龄线才删;符号链接与文件不动);
83
+ **补偿**:`ALIVE_TOUCH_INTERVAL_MS`(5 分钟)与 `STALE_LOCAL_ROOT_AGE_MS`(60 分钟)是源里的具名常量、读得到,**不是旋钮**(没有 env、没有参数改它们);一个 case 的一条腿只要几毫秒、整套语料也只要几秒,60 分钟远长于一次正常的跑。
84
+ 披露:`process.exit` 未 cleanup、未捕获异常、被信号的默认动作或 SIGKILL 收走的进程,都留一个根,`.alive` 超过 60 分钟没更新之后,由下一轮第一次铸根时清掉;宿主要在退出时即时收根的,在收尾处自己调 `cleanup()`。
85
+ 宿主进程活着时,它铸出而没 cleanup 的根一直带着新鲜的 `.alive`,清扫不碰,进程死后才开始变老;
86
+ 宿主活着却没 `cleanup()` 的根,它的心跳定时器(`unref`,不拖住进程退出)每 5 分钟(`ALIVE_TOUCH_INTERVAL_MS`)开写一次、直到进程结束 —— 代价是一次一字节的小文件写(一拍一开一关,不占常驻的文件描述符),补偿是调 `cleanup()`(契约本就要求);
87
+ 心跳一拍可能卡在文件系统上(挂住的网络盘):上一拍还没做完时,到期的这一拍直接跳过(每个根一面在飞旗)—— 不叠写、不排队;挂住的那一拍做完(成功或失败)后旗复位,心跳照常。
88
+ `mkdtemp` 与写 `.alive` 之间被杀的进程留下一个没有 `.alive` 的根,清扫不认它,要清的手工删。
89
+ 定时器取自 `node:timers`:只接管全局 `setInterval` 的伪计时器(vitest 的 `vi.useFakeTimers()` 默认如此)接管不了它;连 `timers` 模块一起接管、且先于本包首次 import 装上的伪计时器库会把这只心跳接过去,跑尽定时器的调用(如 `runAll`)会撞上它 —— 补偿是跑尽定时器之前先调 `cleanup()`(契约本就要求;它清掉的正是这只心跳)。
90
+ `.alive` 静态为空,只给清扫看:`loadLocalConfig` 只按名读 `config.d/<域>.json`、`config.d/remote-exec.json` 与 `.env`,列文件只列 `config.d/`,所以它不被 `loadLocalConfig` 自动读到;谁在 `.env` 里用 `file:` 显式引用它,读到的是空文件、被拒(`file:` 引用不接受去掉首尾空白后为空的文件,与引用一个不存在的文件同拒),不会解析成一个值;心跳写进去的那一个字节是换行(空白),只存在于心跳那两步之间,截回失败或进程恰在两步之间死掉时它会留在文件里,但 `file:` 引用读到后会去掉首尾空白,留着一个换行的文件去掉首尾空白后仍为空,照样被拒。
91
+ 钉:`test/conformance-temp-roots-14.8.0.test.ts`(真进程:描述符数限(软、硬都是 256)的子进程里先铸 300 个根再统一 `cleanup()`,300 个都铸成、都清掉、tmp 里一个不剩,与不带心跳的 v14.7.0 同答;建根后 `process.exit` / 未捕获异常没 cleanup ⇒ 根留着、退出码照旧,把根里 `.alive` 的 mtime 拨到两小时前,再起一个进程铸根的清扫收走它;宿主在铸根之后装的 `exit` 处理器里读得到 `config.d/models.json`(`process.exit` / 自然退出 / 未捕获异常三种退出);
92
+ SIGKILL 与没有宿主处理器的 SIGTERM / SIGINT 留一个根、进程死于该信号,清扫收掉它而 `.alive` 新鲜的 / 没有 `.alive` 的 / 异名的 / 文件 / 符号链接不动;宿主已装优雅退出处理器(`on` / `once`)时不被杀;宿主装着排空处理器(`on` / `once`,含铸根之后才用 `prependOnceListener` 装的)时信号到来、200 ms 后根与语料文件仍在、case 跑完;
93
+ 宿主在投递中把监听整个换成自己的一只 ⇒ 根不动、宿主跑完;首次铸根的清扫中途来信号、宿主吞掉它 ⇒ 铸根照常完成、根还在;名字是 `<前缀><六位随机>`、根内是空的 `.alive`;铸了根没 cleanup 而手上没别的事 ⇒ 进程自己退出(定时器 `unref`);连 `timers` 模块一起接管、先于本包首次 import 装上的伪计时器库取走心跳,`cleanup()` 清掉的就是它;
94
+ 根目录自己的 mtime 再老、`.alive` 新鲜 ⇒ 不动(铸它的进程早已死也一样),`.alive` 老了才清;没有 `.alive` 的同形旧目录(含真进程里 `mkdtemp` 直铸的)再老也不动;`.alive` 是目录或符号链接不算;名字形的边界与年龄线的边界;
95
+ 铸根之后进程上每一种事件的监听数不变、`src/conformance/` 下没有给进程挂 / 摘监听的调用;`cleanup()` 只删自己的根;随包文字与实现同读)、`test/conformance-temp-roots-alive-14.8.0.test.ts`(进程内:铸根的名字与根内空的 `.alive` · 铸根不问主机名 · 定时器取自 `node:timers`、间隔与 `unref`,由注入的驱动逐拍触发 · 间隔到期才写、下一个间隔再写 · 每一拍做完 `.alive` 的大小回到 0、mtime 前进、仍是同一个文件 · 截回空失败时留着的那一个字节是空白、`file:` 引用仍被拒 · 上一拍还没做完(写挂住)时本拍跳过、不叠写,做完(成功或失败)后旗复位 · 心跳不持句柄、按 inode 身份写:`.alive` 被换成指向受害文件的符号链接 / 硬链接、根被删后同名重建并放符号链接,几拍之后受害文件的内容 / 大小 / inode / mtime 不变、根里不多任何文件、没有一次写或截;`.alive` 被另一个常规文件原子顶替或换成目录 ⇒ 每一拍都开始又收场、不写不建不抛;铸根与打开之间路径被放了符号链接 ⇒ 铸根抛、不留根、不起定时器;`.alive` 被换成没有读端的命名管道 ⇒ 这一拍当场打不开、整拍收场;身份比 `(dev, ino)` 两样、用 `bigint` 比;铸根返回时与每一拍之间手里没有开着的句柄,每拍的打开标志位恰是 `O_WRONLY | O_NOFOLLOW | O_NONBLOCK`、铸根的恰是 `O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW`,`cleanup()` 不开不关任何文件;`cleanup()` 撞上还在飞的一拍不抛、不冒未处理的拒绝、不重建路径;重叠的两次 `cleanup()` 都不抛;记下身份、关掉 `.alive` 之后铸根失败(如起定时器抛)回滚到什么也没留下:根删、无持有项;源里零 `FileHandle`、按路径打开恰两处(铸根一处、心跳一拍一处,后者不带 `O_CREAT`、`close()` 在 `finally` 里) · 进程自己的钟拨偏两小时后 `.alive` 的 mtime 仍是文件系统的现在、清扫不删 · 心跳不创建 `.alive` · 删根即停这个根的定时器 · 根里的 `.alive` 不被 `loadLocalConfig` 自动读到:没有谁用 `file:` 显式引用它时,整份答案与没有它的同内容根逐字相同;`.env` 里用 `file:` 引用它被拒,与引用一个不存在的文件同拒 · 源里没有取主机名 / 取进程号 / 问进程在不在的调用 · 导出面是闭集)、`test/conformance-temp-roots-fake-timers-14.8.0.test.ts`(消费方开着只接管全局的伪计时器〔`vi.useFakeTimers()`〕铸根,`vi.runAllTimers()` 不抛、伪计时器里没有心跳)、`test/conformance-local-root.test.ts`(文件名闸原样绿)。
96
+ - **「公开导出有哪些」全仓只枚举一遍(`scripts/api-surface.mjs` 的 `readModuleExports`,仓内脚本,不进包)**。导出面登记表的生成器与发版准备时重算「受影响导出表」的助手(`test/helpers/affected-exports.ts`)此前各枚举一遍 exports 表里每条子路径的导出:
97
+ 生成器沿别名链逐跳问「是不是 type-only 再导出」,助手只问导出符号自己、再一步解到真身 —— 中间一跳是 `export type { X } from …` 的再导出链上,生成器答「只是类型」(运行期拿不到,登记表的「判据自证」格对着运行期模块对象核过),助手答「值导出」。
98
+ 现在生成器接收一棵树的根(缺省本仓)并暴露符号级读法(子路径 + 名字 + 形 + 走完别名链的真身符号;别名链不设跳数上限,环靠「见过」收住),`computeApiSurface` 是它的投影,助手取它结果里形不是 `type` 的当公开值导出、删掉自带枚举。真树上两份答案今天逐名一致(651 个名字),`docs/API-SURFACE.md` 逐字不变。
99
+ 三问 —— **谁需要**:发版准备的人(受影响导出表不会再把一个运行期不存在的名字报进表);**谁受伤**:无 —— 包内容与导出面不变,只改仓内脚本与测试助手;**补偿**:无需。
100
+ 钉:`test/public-value-exports-one-reading-14.8.0.test.ts`(链形合成树与真树上两条读法答案逐字同 · `getExportsOfModule(` 在 `scripts/` 与 `test/` 里只有一处 · 助手从生成器引用读法 · 生成器按入参的根读)、`test/affected-exports-since-baseline-14.7.0.test.ts`、`test/api-surface.test.ts`。
101
+ - **读口 `readWorkersRows` 扣下带旧字段的行时,`reason` 不再点名退役键、不再只叫人去跑迁移(`held[].reason`;`field` 不变)**。一行 6.x 形的存量行若被一次性迁移 SS-M4 拒了(例:`runtime.sessionBackend: "tidb"` 而没有 SQL 坐标),迁移不写回半成品、原行留在盘上;
102
+ 读口再读到它,`reason` 是退役键墓碑的原句 —— `runtime.sessionBackend: runtime.sessionBackend was retired … data stored before 7.0.0: run the one-time migration SS-M4 …`:点名的是盘上的退役键,出路只有「去跑那条已经拒过的迁移」。
103
+ 现在读口对墓碑 issue 换成读面的说法:`a field retired in 7.0.0 is still in the row — now <现行位>; data stored before 7.0.0: run the one-time migration SS-M4 (…); if that migration already refused this row, write the current form by hand and delete the old field` ——
104
+ 退役键名只留在 `field`(结构化坐标,管理面据它标红),文字里没有;读口是纯函数、不知道这一行跑没跑过迁移,所以一句话对两种情形都成立。寻常坏行的 `reason`(`<坐标>: <句子>`)不变,读写两面(整域读、写闸、迁移收据)的墓碑原句不变。
105
+ 实现:退役键墓碑(`retiredKey`)在铸造时登记进包内的墓碑表(键名、形、整句),读口按整句认出墓碑 issue、测试从表读名单。
106
+ 三问 —— **谁需要**:读 `held[].reason` 把它渲染给运维的管理面(web-admin 的 workers 列表);**谁受伤**:把 `reason` 当机读字符串匹配墓碑原句的消费方(`reason` 一直是给人读的文字,机读的是 `field`)—— 提货后匹配落空;**补偿**:改读 `held[].field`(退役键的坐标)。
107
+ 钉:`test/worker-held-sentence-14.8.0.test.ts`(迁移拒过的行 · 读口 held 句不含退役键名 · 墓碑表 ↔ schema 对账 · 七个旧字段 × 各一行:拒句不含表里任何退役键名、含现行位与读面整句、`field` 仍指到那个键 · 读面整句逐字)、`test/workers-rows-11.0.0.test.ts`(翻钉)。
108
+ - **中心引用的解析口 `resolveCenterRefs(refs, lookup)`(`src/center-refs.ts`,包根)+ 记录读器 `readPlainStringRecord` 公开导出、原因词写成闭集表(`src/mcp-request-entry.ts`,包根)**。中心名册里 MCP server 的 `envRefs` / `headerRefs` 只存变量名;此前本包只发这两张表的形、不发读它们的法 ——
109
+ 变量没设、设了但读到的不是串、读它的时候抛了,留给每个宿主自己写,各按各的口径处置、各留各的句子。现在一个读法:**每个成员一句判定,整台一个结论;本口只判,不决定挂不挂**。
110
+ ① 解析口:`refs` 经公开读器读(见 ②),读不出整张不交 ⇒ 整台 `refs_unreadable`、带读器的词、**一个变量都不问**;读器会把叫 `__proto__` 的串值成员剔出副本,本口不许这样悄悄丢一条引用再挂一台少了鉴权的 server,同样答 `refs_unreadable` / `member_name_reserved`。
111
+ **查找函数 `lookup(name)` 由调用方给,本口不读 `process.env`**(源里没有 `process` / `globalThis`);没给 / 不是函数 ⇒ `TypeError`(调用方的编程错,不折成成员的 `read_threw`,不回落任何环境)。
112
+ 每个成员恰问一次 `lookup`(实参只有变量名),判成**一个词**:`resolved`(答了串,空串也是串)· `missing_name`(答 `undefined`)· `not_a_string`(答了别的种类;对普通对象做 `obj[name]` 读到继承来的函数就是这一形)· `read_threw`(`lookup` 抛了,不传出);一个成员坏了不早停。
113
+ 整台结论三个词:`resolved` / `unresolved`(有成员没解到)/ `refs_unreadable`;**值只在 `resolved` 臂的 `values` 里**,`unresolved` 臂的行只有词(失败读数会进日志)。`refs` 是 `undefined`(没声明引用)⇒ `resolved`、零成员、零次查找,与空表同答。
114
+ 词表常量 `CENTER_REF_FAULTS`(三词)· `CENTER_REFS_VERDICTS`(三词)`as const`,型由它推出;读数型 `CenterRefsResolution` 按 `verdict` 分三臂,消费方穷尽 `switch`。
115
+ ② 记录读器:`readPlainStringRecord(v)` 加 `export`(判据与每一句原因句一字不改;传输读器读 `env` / `headers` 用的就是这一只),读数型 `PlainStringRecordRead` 与保留成员名 `PLAIN_STRING_RECORD_RESERVED_MEMBER`(`"__proto__"`)一并导出。
116
+ 原因词写成一张闭集表 `PLAIN_STRING_RECORD_FAULTS`(`as const`,三词、`PlainStringRecordFault` 由它推出;`McpEntryKeyIssue.fault` 与读数的 `fault` 都引这个型,不再有字面量并集,词与 v14.7.0 的机读位同):`not_plain_record`(整张不是纯数据)·
117
+ `member_not_string`(成员的值不是串,或读成员就抛 —— 自有访问器抛了、描述符陷阱抛了;两种情形句子分说)· `member_name_reserved`(叫 `__proto__` 的成员;名坏优先,值坏或读不出时句子并列说全、词仍是这一个)。
118
+ 传输读器的 `lost[]` 与 v14.7.0 逐位同答(机读位、句子、`member`、整键不交的处置)。
119
+ 三问 —— **谁需要**:读中心名册引用表的宿主(客户端核心的本地腿、服务端的中心腿);**谁受伤**:无 —— 新导出,既有读口的答案不变;**补偿**:无需。
120
+ 钉:`test/center-refs-14.8.0.test.ts`(成员三词各一格〔含每种非串形、五种抛法〕· 整台三词 · 不早停且每名恰问一次 · 空表 / 缺席 · 引用表读不出的十三形词与读器逐格同且一个变量都不问 · 不读 `process.env`〔AST 零 `process` / `globalThis` / `import.meta` / `node:` 引入,运行期 `process.env` 换成全陷阱抛的 Proxy,缺 lookup 抛 `TypeError`〕·
121
+ `unresolved` 臂不含变量值 · `Object.prototype` 数字键 setter 吞不掉 · 一事一源〔只引公开读器与读内核检视口〕· 词表穷尽 `switch`)、`test/plain-string-record-public-14.8.0.test.ts`(读器与词表导出同一引用 · 词表三词 · 成员级一词、句子分说 · 名坏优先 · 传输读器 `lost` 是读器读数的投影 · 声明只有一处 · 读器从不抛)、
122
+ `test/mcp-request-entry-14.0.0.test.ts` 与 `test/descriptor-law-r6-14.7.0.test.ts`(读成员就抛的三格:机读位与 v14.7.0 同)。随包文字:`docs/CONSUMER-INTEGRATION.md` §14(两方的读法与处置)、README 与 `llms.txt` 各一条、导出面登记表。
123
+ - **MCP 条目的 `sessionMetaKey`(`McpServerSpec.sessionMetaKey`,解析透明;`PARSE_TRANSPARENT_KEYS` 多一行 `mcp.servers[].sessionMetaKey`,`validatedBy: "engine-ingest"`)**。引擎在发给一台 MCP 服务器的每个 `tools/call` / `resources/read` 的 `params._meta[key]` 里放发送腿自己的会话 id,`key` 由这台服务器的声明给;
124
+ 此前本包的条目没有这把键,本地腿与 wire 腿都把它剥掉(`unknown-keys-dropped`),引擎读到缺席。现在逐字承运、缺席不铸键。这把键只有一只判官 —— 引擎包根导出的 `judgeSessionMetaKey`(类型、非空、上界、MCP `_meta` 键名文法、MCP 保留前缀、`progressToken`、引擎的 `sema/` 命名空间一只函数一并判,坏键在引擎的准备门整条拒、点名这把键);
125
+ 本包不判(与 `toolAxes` 同形),任何 JSON 值原样过 —— 本包一字不拒、不是校验收紧,坏值由引擎拒。
126
+ 三问 —— **谁需要**:要按(主体, 会话)保存状态的 MCP 服务器(声明一把自家前缀下的键,如 `computer-use/sessionId`);**谁受伤**:本包一字不拒、不带它的条目解析结果逐字不变;存量里已经写着这把键的条目 —— 此前这把键被剥掉并报 `unknown-keys-dropped`(没有效果),现在原样交给引擎:值合引擎判官的,引擎从此按这把键在发给这台服务器的每个 `tools/call` / `resources/read` 的 `params._meta` 里放发送腿的会话 id(运维当初写下的意图被兑现,行为随升级而变);
127
+ 值不合的(含 `null`),交给引擎后引擎在准备门整条拒这一台服务器、点名这把键;
128
+ **补偿**:删掉这把键,或改成引擎收的键名。写声明的一方要早判,调引擎的同一只判官。
129
+ 钉:`test/mcp-session-meta-key-14.8.0.test.ts`(三道门同答 · 缺席不铸 · 文档读法 / wire / 本地三处承运且零告警 · 任何 JSON 值原样过 · 登记行)、`test/upstream-drift.test.ts` 门 ⑩(引擎条目型有这把键、判官入参 `unknown` · 登记行点名它 · 语料上本包恒原样承运、引擎的答案逐值钉)、`test/parse-transparent-keys.test.ts`。
130
+ - **MCP 条目的 `startupTimeoutMs`(`McpServerSpec.startupTimeoutMs`,解析透明;`PARSE_TRANSPARENT_KEYS` 多一行 `mcp.servers[].startupTimeoutMs`,`validatedBy: "engine-ingest"`)**。引擎的每台 MCP 服务器可以带自己的启动界(整数毫秒):握手(连接 + initialize)与首次工具列举各带它,缺席读进程级的缺省界;它只界启动阶段,不动单次调用的超时。
131
+ 此前本包的条目没有这把键,本地腿与 wire 腿都把它剥掉(`unknown-keys-dropped`),配置声明的服务器只剩进程级的缺省界。现在逐字承运、缺席不铸键。**格式与界由引擎判**:是不是数、整数性、范围只有一只判官 —— 引擎包根导出的 `mcpStartupTimeoutMs`,在拨号时判,不合 ⇒ 拒这一台服务器的拨号(其余服务器照挂)、点名 `config.mcp_startup_timeout_invalid`,不夹取、不当缺省;
132
+ 本包不判(与 `sessionMetaKey` 同形),任何 JSON 值原样过 —— 本包一字不拒、不是校验收紧,坏值由引擎拒;哪怕只判「是不是数」,也是第二只判官的一部分,并且会让存量里已经带着这把键的条目由本包改拒,那才是校验收紧。写声明的一方要早判,调引擎的同一只。
133
+ 三问 —— **谁需要**:有启动慢的 MCP 服务器、要单独放宽(或收紧)它的启动界而不想动进程级 `MCP_TIMEOUT` 的部署;**谁受伤**:本包一字不拒、不带它的条目解析结果逐字不变;存量里已经写着这把键的条目 —— 此前这把键被剥掉并报 `unknown-keys-dropped`(没有效果),现在原样交给引擎:值合引擎判官的,引擎从此按这把键给这一台服务器的启动设界(运维当初写下的意图被兑现,行为随升级而变);
134
+ 值不合的(含 `null`),交给引擎后引擎在拨号时拒这一台服务器(其余服务器照挂,点名 `config.mcp_startup_timeout_invalid`);**补偿**:删掉这把键,或改成引擎收的整数毫秒。
135
+ 钉:`test/mcp-startup-timeout-14.8.0.test.ts`(在场逐字承运,三道门 × 两种传输同答 · 缺席不铸 · 文档读法 / 远端读腿 / 本地读腿承运且零告警 · 任何值原样过、读腿不丢条目 · 登记行点名 `mcpStartupTimeoutMs` · 随包文字的说法与行为一致)、`test/upstream-drift.test.ts` 门 ⑪(这一行是解析透明 · 引擎的判官 `mcpStartupTimeoutMs` 在钉住的引擎里真在、语料上本包恒原样承运而引擎收一个拒其余)、`test/parse-transparent-keys.test.ts`。随包文字:README 与 `llms.txt` / `llms-full.txt` 各一条。
136
+ - **MCP 服务器名的 `remote-` 前缀保留给设备执行端提供的远程 MCP 服务器(常量 `REMOTE_MCP_SERVER_NAME_PREFIX`;请求面 `isUsableMcpServerName` / `describeBadMcpServerName`)**。设备执行端提供的远程 MCP 服务器挂成 `remote-<名>`,工具名 `mcp__remote-<名>__<工具>`;
137
+ 请求带来的 MCP 条目若用同一前缀,就与设备上的服务器同名、落进同一个命名空间。本节只落**加性的两面**:前缀常量,与请求面新增的 `isUsableMcpServerName`(= `isUsableWireName` 再加这一臂)和它的摘要 `describeBadMcpServerName`
138
+ (线上名本身坏的与 `describeBadWireName` 逐字同,保留名说那一句原因:`the "remote-" prefix is reserved for the remote MCP servers a device executor offers`)。比较按字面、区分大小写(引擎铸工具名前缀时保留大小写,`Remote-x` 不在这个命名空间里)。
139
+ 请求面 MCP 服务器名的判官是 `isUsableMcpServerName`;`isUsableWireName` 不变,管 A2A 对端名(A2A 工具名前缀是 `a2a__`)。
140
+ **两面的读法不同**:请求面判官拒 `remote-` 前缀;配置面 `McpServerSpec.name` 今天照旧受理这个前缀(本包一字不拒;此前合法的名由本包改拒才是校验收紧,按「发版纪律」归 major)。
141
+ 三问 —— **谁需要**:请求面的受理方与壳的发送前预检(拒带保留前缀的 MCP 条目名)、设备执行端的远程 MCP 挂载(名字空间不撞);**谁受伤**:本包的既有判官与配置面答案不变(加性);请求面受理方改调 `isUsableMcpServerName` 之后,请求带来的 `remote-` 起头的 MCP 条目名被拒、点名那一句原因;配置面已有的 `remote-` 起头名字照旧解析、不被丢;
142
+ **残留**:配置面可声明 `remote-` 起头的名,它与设备上同名的挂载服务器落进同一个命名空间 —— **补偿**:部署方自己不用这个前缀(存量里有这样的名,建议现在就改;工具名随之变,按名写的规则 / `allowTools` 跟着改)。
143
+ 钉:`test/mcp-remote-name-reserved-14.8.0.test.ts`(配置面仍受理、三道门同答 · 非字面前缀照旧过(含 `Remote-x`)· 请求面与线上名判官差分 · 线上名判官不变 · 保留名的拒句),两版 zod(区间两端)各跑一遍。
144
+ - **引擎钉与上游漂移门 ⑧ / ⑪**:devDependency `@sema-agent/core` 7.53.0 → 7.58.0(锁文件多一只传递依赖 `@sema-agent/primitives`)。引擎的 MCP 条目传输形删了字面值 `env` / `headers`、加了不透明引用 `credentials`;请求面的传输形(`RequestMcpTransport`)**不变**,仍收字面 `env` / `headers`,
145
+ 它与引擎传输形的关系改由一张处置表对拍:两边每种传输的每个键恰好一行(`engine` 同键同型 · `literal` 请求带来的字面件,交给引擎之前由消费方拿走、按自己的对端座答 · `carried` 本包承载的 `assertionHeader` · `engine_only` 请求面不收的 `credentials`),表的行 ≡ 两边键的并集,谁多一个键都编译红;读器交出的键 ≡ 请求型的行。
146
+ 同一张处置表的形铺到配置面的 `McpServerSpec`(门 ⑪:条目层与两种传输层各一张表,判官与 ⑧ 是同一只):引擎的每个条目键在配置面恰好一行 —— 同键同型 / 解析透明 / 本包承载 / 有意不读 / 欠账未裁,
147
+ 引擎加键、本包加键而没写行、行的词与两边的型对不上、两边的传输种类不同都编译红;欠账行是公开的未决(传输层 `credentials`:配置面没有这把键,也没有一处写明是有意不读),门只保证欠账不被悄悄跳过;这一门本身不加键、不改任何校验。
148
+ 其余镜像门(② ③ ④ ⑤ ⑥ ⑦)对新钉逐门复核同。新钉的引擎把模型座的「没写」收成只有缺席(`undefined`)一形:`roles.default` 写出来的空串 `model` 按名拒(`unknown_model_ref`)、`model` 在场就不看 `select`;本包的 `resolveCatalogDefaultModel` 照旧按真值判(答案与 v14.7.0 逐位同)—— 对绕过 schema 递入的空串 `model`(`{model:"", select}` 交回 `select`、`{model:""}` 跳过那一级)两边不同答;schema 读过的值没有这一形(`RoleTarget.model` 是非空串),钉:`test/default-model-11.0.0.test.ts` Y4(本包的答案与引擎对同一绑定的拒各钉一格)。
149
+ 三问 —— **谁需要 / 谁受伤 / 补偿**:无运行期变化(只改 devDependency 与测试)。钉:`test/upstream-drift.test.ts` 门 ⑧(每种传输过一遍共用判官 · 处置表读数 · 读器的键 · 五颗牙)· 门 ⑪(三张表过判官 · 读数 · 欠账行清单 · 不读的键各有 why · 表的本包一侧 ≡ 真 zod 形的键 · 十一颗牙 · `startupTimeoutMs` 对拍引擎判官的一格)。
150
+ - **`SERVING_LEASE_DRIFT_MARGIN_MS` 的注改成现行用法(只改注,值与导出面不动)**。旧注写的是「中心把它加到自己记录的租约到期上再判过期」,这条用法已退役;现在的注写它的用法:读引擎报的 `EngineSignal.leaseUntil`(引擎时钟上的 epoch 毫秒)时,读的一方拿它对**自己的** now,两个钟不必一致 ——
151
+ 「租约还在」只在 `leaseUntil >= now + SERVING_LEASE_DRIFT_MARGIN_MS` 时成立,取保守方向(偏斜不超过余量时,还在的租约可能读短,过了期的租约绝不读成还在);它不属于租约本身的计时(引擎按自己的单调钟执法,中心按库时间记自己的到期,两边不比钟)。钉:`test/engine-signal-8.1.0.test.ts`(注里有这条用法、没有旧用法;值仍是 `DEFAULT_HEARTBEAT_MS`)。
152
+ - **披露(补全 14.7.0「不在本批」那一条):zod 4.4 上,`Object.prototype` 上有可枚举键时才第一次被读的判别联合,永久读不出 —— 撤掉污染也不恢复**。14.7.0 写的是访问器形的 `verdict` 污染在 zod 4.4 上让 `readServingLeaseResponse` 冷路径答 `undefined`;完整的读数更宽:zod 4.4 把判别联合的判别值表惰性物化(物化时按键枚举各臂的形),
153
+ 物化之前 `Object.prototype` 上就有一个可枚举键、且它的值不是带 `_zod` 成员的对象时,物化当场抛,zod 4.4 记下「物化中」就不再重算 —— 这个联合此后永久读不出(已核:值为字符串 / 数 / `null` / 普通对象 / 函数都触发,键名 `value` / `zzz`;带 `_zod` 成员的对象不触发;判别键 `verdict` 的访问器形与数据形不论可枚举与否都触发)。
154
+ 租约应答读口 `readServingLeaseResponse` 是其中一只(fleet 另外六只读口没有判别联合,读数:zod 4.4 线,污染窗口里与撤掉之后都照常读出),包里另有 11 处 `z.discriminatedUnion`(`McpTransport`(经 `McpServerSpec` / `McpConfig`)· `ServingLeaseResponse` · `RemoteExecSpec` · `HookInput` · `PermissionUpdate` · `TightenRule` · `AtMention` · `ProjectContextItem` · `PluginSource` · `SettingSource` · `SandboxGitOriginAnswer`)在 zod 4.4 上同样
155
+ (已核:`McpServerSpec` 的合法条目在污染之后永久抛;其余各联合的判别值表读数同),直接 `safeParse` 它们的消费方一样读不出。zod 4.6 上这些在污染窗口里读不出,撤掉污染即恢复。v14.6.0 同,**非本包造成**(zod 4.4 自己的惰性物化缺陷)。
156
+ 失败方向仍是读不出:租约读口读不出 = 引擎按中心不可达处置,不读成 `granted`、也不读成 `denied`。`Object.prototype` 被污染本身是嵌入方进程被攻破,本包不替它兜底。
157
+ **出路**:zod 升到 4.6 或更高(包的区间 `^4.4.0` 两端都在测);或在加载本包之后、嵌入方的代码跑之前,让每一个要用的判别联合都被一次干净的读走到 —— 租约读口:读一次干净的应答即可(已核);嵌套的联合要有一份合法输入走到它:`McpConfig` 读一条合法条目才暖到 `McpTransport`,空配置 `{ servers: [] }` 走不到、污染之后 zod 4.4 上仍永久读不出(已核)。
158
+ 钉:`test/descriptor-law-r8-14.7.0.test.ts` 的「🚧 披露的缺口」一组(特征钉:冷 / 暖 / 嵌套 / 触发的值形,两版 zod 各一张读数;缺口补上则 4.4 臂翻成恢复,这一条随之改)。
159
+
160
+ **自 v14.7.0 起运行期代码有改动的公开导出(机器重算;上界 —— 可达 ≠ 一定变,不可达 ⇒ 一定不变;本版新增的导出不入表)**
161
+
162
+ ```text
163
+ DEFAULT_ROLE_TIER_LADDER [.]
164
+ DOMAINS [., ./types]
165
+ DOMAIN_SCHEMAS [., ./types]
166
+ FileConfigStore [./node]
167
+ MIGRATION_APPLIED_AS [./one-time-migrations]
168
+ MODEL_PLANE_REFUSAL_CODES [., ./fleet]
169
+ McpConfig [., ./types]
170
+ McpServerSpec [., ./types]
171
+ ModelPlaneRefusalCode [., ./fleet]
172
+ ModelsConfig [., ./types]
173
+ NON_PORTABLE_DOMAINS [./conformance]
174
+ ONE_TIME_MIGRATIONS [./one-time-migrations]
175
+ ONE_TIME_MIGRATION_IDS [./one-time-migrations]
176
+ PARSE_TRANSPARENT_KEYS [., ./types]
177
+ PromptsConfig [., ./types]
178
+ RosterEntry [., ./types]
179
+ RostersConfig [., ./types]
180
+ WorkerSpec [., ./types]
181
+ WorkersConfig [., ./types]
182
+ buildEffective [., ./config-fns]
183
+ createContractAdapter [./conformance]
184
+ createFileSchedulerStore [./node]
185
+ deriveLocalExpectation [./conformance]
186
+ deriveWireExpectation [./conformance]
187
+ domainFilePath [./node]
188
+ emptyEffective [., ./config-fns]
189
+ foreignKeysOf [./conformance]
190
+ isDomainName [., ./types]
191
+ isStringRecord [.]
192
+ loadLocalConfig [./node]
193
+ materializeLocalRoot [./conformance]
194
+ migrateConfigDirectory [./node]
195
+ migrateConfigDocuments [./node]
196
+ migrateEffectiveSnapshot [./one-time-migrations]
197
+ migrateStoredDocuments [./one-time-migrations]
198
+ mutateSchedulerStore [./node]
199
+ nodeConfigDirectoryIo [./node]
200
+ parseDomain [., ./config-fns]
201
+ parseDomainLoud [., ./config-fns]
202
+ parseDomainSalvaged [., ./config-fns]
203
+ readBundle [./node]
204
+ readEffectiveWire [., ./config-fns]
205
+ readModelRefusals [., ./fleet]
206
+ readRequestMcpTransport [.]
207
+ readWorkersRows [.]
208
+ resolveCatalogDefaultModel [.]
209
+ resolveLegExpectation [./conformance]
210
+ runConformance [./conformance]
211
+ salvageEntries [., ./config-fns]
212
+ saveSchedulerStore [./node]
213
+ urlSeatPatterns [./one-time-migrations]
214
+ validateDomain [./node]
215
+ writeBundle [./node]
216
+ writeDomainFile [./node]
217
+ writeRemoteExec [./node]
218
+ ```
219
+
220
+ ## 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`
221
+
222
+ > 档位(按发版纪律):**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 名单条)。
223
+ > **没有**新子路径(`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 的读数逐字不变。
224
+ > **读口行为变化**:① `toVerifiedIdentity`(`sub_inc` 那一条):令牌声明 `sub_inc` 在场而不是非空串 ⇒ 该令牌 `malformed`;此前这个声明被忽略。② **JS 对象面上的收严**(嵌入方交进来的 Proxy / `Map` / 类实例 / 被污染的 `Object.prototype` —— JSON 解析不出这些):
225
+ > 读口对固定键只认自有键、读不出各答各的、形状只问座位、`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` 产物单独探出,不在随机生成器里。
226
+ > ① **声明类型外的载荷**:`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]` 这几种入参的答案不变。
227
+ > ② **自有 `__proto__` 数据键**(`JSON.parse` 铸得出的合法 JSON):`migrateEffectiveSnapshot` 在**凡是真正改写了快照**的路径上(移除退役域、改写某个域的文档),顶层这个键 v14.6.0 都被赋值语义吞掉,现在作为自有数据键原样保留(与不改写快照的路径一致;改进,不回退;结果对象的原型不受影响)。
228
+ > ③ **要改写的 worker 行里嵌了几千层的字段**:v14.6.0 在 3000 层上就抛 `RangeError: Maximum call stack size exceeded`,现在照迁(1000 层以内两边同答,见 SS-M4 一条);没有 6.x 键的行两边都原样放行。
229
+ > ④ **合法 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"`)。
230
+ > ⑤ **另一个 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` 声明(那是新面)。
231
+ > 另有一处消费方要知道的契约变动(抄闭包的生成器):请求面 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`)。
232
+
233
+ - **凭据取值口 `sandbox_git` 的 wire 型 + 三张词表(`src/api/credentials.ts`,包根)**。沙箱里的 git 用成员本人的凭据:server 向 web-admin 的取值口按 run 的身份申领一枚短期、一次性的句柄,再凭句柄兑换;此前两方各自手写这几个形,「同一份 zod,两方同 import」没有落点。
234
+ 现在:请求 `SandboxGitCredentialRequest`(`kind` 字面量 `"sandbox_git"`、`principal` ≤ `PRINCIPAL_MAX_LENGTH`、`principalInc`、`scope`、`origins` 1–16 个规范形且两两不同、`sid`;**http origin 也收** —— 中心对它答判定序第 1 行,请求形不抢先拒)·
235
+ 句柄应答 `CredentialHandleAnswer` 与兑换请求 `CredentialResolveRequest`(与凭据种类无关)· 逐 origin 应答行 `SandboxGitOriginAnswer`(按 `state` 判别)· 应答信封 `SandboxGitCredentialAnswer`(`answeredAt` / `cacheUntil` / `active` 行的 `expiresAt` 是同一只钟的毫秒,收方只用差)·
236
+ 整次拒体 `SandboxGitCredentialRefusal`(`{ error, permanence, message }`)。三张词表**各一张、每个词只在 `src/api/credentials.ts` 里拼一次**:
237
+ 判定序表 `SANDBOX_GIT_ORIGIN_VERDICTS`(11 行,序即判定序;状态六词 `SANDBOX_GIT_ORIGIN_STATES`、原因十词 `SANDBOX_GIT_ORIGIN_REASONS` 与类型由它派生,应答臂里 `reason` 的值域取自表 —— 状态与原因的配对由表定)·
238
+ 拒答表 `SANDBOX_GIT_REFUSALS`(十个错误词 → HTTP 状态集与 `permanence`;`SANDBOX_GIT_REFUSAL_ERRORS` 与类型由它派生;拒体里的 `permanence` 必须等于表里这一行的,否则是畸形应答)· `SANDBOX_GIT_PERMANENCE`(`terminal` / `transient`)。
239
+ 解析一律剥未知键(前向兼容),且 `secret` 只在 `active` 臂的形里 —— 别的臂即使收到也剥掉;`needs_reauth` 行必带 `credentialId`。
240
+ **本包不做**(如实,`docs/CONSUMER-INTEGRATION.md` §12.9):应答与请求的逐条对应 / 回显相等核对、租约算术与续期状态机、哪些 terminal 词可恢复、两个端点的路径常量、取值口另两种凭据(`model` / `mcp_static`)的形。
241
+ 三问 —— **谁需要**:web-admin(取值口的实现:用同一份 zod 收请求、造应答与拒体)与 server(取值口客户端:发请求、收应答);**谁受伤**:无 —— 全是新导出,没有既有读者;取值口另两种凭据落地时与本版的 `sandbox_git` 形并成按 `kind` 判别的联合,本版的形不动;
242
+ **补偿**:无需。
243
+ 钉:`test/sandbox-git-credential-wire-14.7.0.test.ts`(三张词表逐字对设计金样 · 判定序表 11 行各一格 + 状态 × 原因叉积恰 10 对 · `needs_reauth` 必带 `credentialId` · `secret` 只在 `active` 臂、别的臂剥掉 · 请求 / 句柄应答 / 兑换请求 / 应答信封 / 拒体正反 · 拒体 `permanence` 与表一致 · `principalInc` 与令牌声明是同一只 schema 对象 · 单一属主反向门:词只在 `src/api/credentials.ts` 里作代码字面量)。
244
+ - **origin 规范形判官 `isSandboxGitOrigin` 与有效配置块 `sandboxGit`(`SandboxGitConfig`,`src/sandbox-git.ts`,包根)**。规范形只有一条律:**一个 origin 就是它自己的 WHATWG origin**(串等于 `new URL(串).origin`,scheme ∈ {http, https});小写 host、去缺省端口、无尾斜杠、无 userinfo / 路径 / 查询 / 片段都是它的推论,不是一张手写规则表;
245
+ 「是不是 http(s) URL」归包内唯一的 URL 形判官 `httpUrlFlaws`。origin 不表达子路径部署(`https://host/gitea` 不是 origin);没有「削成 origin」的规范化器 —— 把运维写的路径静默削掉会把凭据送到他没写的地方。
246
+ 块 `sandboxGit?: { origins: [{ origin, connectUrl }] }` 是 `/effective` 载荷上的**非域顶层键**(web-admin 按部署登记册投影,不是存储域):可选、无默认(缺席 = 这个部署没有投影这一块),`origins` ≤ 16(`SANDBOX_GIT_MAX_ORIGINS`)、按 `origin` 两两不同、空表合法,`connectUrl` 是控制台「连接 Gitea(git)」页的绝对 https URL(非秘密)。
247
+ `readEffectiveWire` 对它走既有的 open-world 逐字承运(不校验、不丢;`EffectiveWire.sandboxGit` 是可选的 `unknown`),**判它的唯一座位是 `SandboxGitConfig`**,由消费方在用之前跑。
248
+ 🔴 **块里 http origin 可读**:它为什么送不出凭据是逐 origin 应答里的一个词(`insecure_transport`)与能力读数里的一个词,不是块的校验拒 —— 块若因一个 http origin 整块不可读,同块里的 https origin 一并失效,「逐 origin 说拒因」就无从谈起。
249
+ 三问 —— **谁需要**:web-admin(投影块前先过 `SandboxGitConfig`)与 server(读块、构造请求的 `origins`);**谁受伤**:无 —— 新导出与可选键;键缺席的老 web-admin 与不读它的老消费方行为不变;
250
+ **补偿**:无需。
251
+ 一致性语料:`conformance/cases/c84-sandbox-git-block-carried-verbatim`(合格块,含一个 http origin)· `c85-sandbox-git-refused-block-still-carried`(不合格块)—— 两条的 wire 腿期望都是逐字承运、零告警,消费方的 wire 读腿跑语料时由此知道这个键必须原样过。
252
+ 钉:`test/sandbox-git-origin-14.7.0.test.ts`(判官正 8 反 25,每个反例写明违反哪一条 · `SandboxGitOrigin` / `SandboxGitOriginList`(上界 16、重复被点名、空表合法)/ `SandboxGitConnectUrl` / `SandboxGitConfig` 正反各一 · 块里 http origin 可读 · 缺席不铸键、在场同一引用承运、坏块照样承运而 `SandboxGitConfig` 拒 · 类型面)。
253
+ - **`VerifiedIdentity.principalInc?` 与令牌声明 `sub_inc`(`api/auth-bridge`,包根与 `./api/auth-bridge`)**。同名主体删号重建之后是另一个人,`sub` 只命名一个槽位;现在令牌可以带化身号声明 `PRINCIPAL_INC_CLAIM`(`"sub_inc"`),`toVerifiedIdentity` 读它:**在场且是非空串 ⇒ `identity.principalInc` 原样**;
254
+ **缺席 ⇒ 读出形里不铸键**(从不读成 `0` / 空串 / 别的占位 —— 缺化身号不是本契约的拒,读方自己答「化身号未记录」);**在场而不是非空串(数字含 0 / 空串 / `null` / 布尔 / 对象 / 数组)⇒ 该令牌 `malformed`**(与 `sub`、`iat` 同律:声明形是垃圾,不降级成缺席 —— 降级会把坏掉的签发方读成「没有化身号」)。
255
+ 规则就是输出字段自己的 schema `PrincipalIncarnation`(非空串),令牌声明、`VerifiedIdentity.principalInc` 与取值口请求 / 应答的同名字段读的是**同一只 schema 对象**。
256
+ 三问 —— **谁需要**:读令牌的一方(把 run 的身份钉到化身上:取值口请求带它)与铸令牌的一方(web-admin:用 `PRINCIPAL_INC_CLAIM` 铸);**谁受伤**:令牌里**已经**带了形不对的 `sub_inc` 声明的签发方 —— 此前它被忽略,从本版起整张令牌 `malformed`(已知没有这样的签发方;老令牌不带该声明,读数与升级前逐字相同);直接拿 `VerifiedIdentity` 判身份袋的调用方同理 —— 袋里带形不对的 `principalInc`(例 `{ principal: "u", principalInc: 5 }`)此前被当未知键剥掉照过,现在拒(声明类型外的输入);
257
+ **补偿**:把签发方的 `sub_inc` 改成非空串或不铸;读方要化身号而令牌缺它时自己答「未记录」,不靠本契约拒令牌。
258
+ 钉:`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` 照绿。
259
+ - **「后面要拼路径的基址」座位的禁用成分集与判官 `PATH_BASE_URL_FORBIDDEN_PARTS` / `pathBaseUrlFlaws`(`src/url-shape.ts`,包根)+ 公告地址的 env 名 `FLEET_ADVERTISE_ADDRESS_ENV`(`src/fleet.ts`,包根与 `./fleet`)**。有一种座位的值是别人要往它后面拼路径的基址 —— 中心基址三席、fleet 成员对中心公告的可达地址、会话网页链接的基址:凭据不进一个会被记日志、转述、拼路径的值;
260
+ `query` / `fragment` 挂在基址上,一拼路径就落到错的位置(`http://x?a=1` 拼上 `/v1/…` 是 `http://x?a=1/v1/…`);路径前缀放行。此前这条规则的三个词在多处各抄一份(server 一处、本包 `webAdminSessionUrl` 里内联一处),web-admin 编排器(渲染 `FLEET_ADVERTISE_ADDRESS=http://$(POD_IP):8090` 的一方)要「同一只判官、不手写 env 名」没有落点。
261
+ 现在:`PATH_BASE_URL_FORBIDDEN_PARTS`(闭集、冻结,`userinfo` / `query` / `fragment`,判序;词与 server 今天那只逐字同;**策略集,独立于判官词表 `HTTP_URL_PARTS`** —— 词表说 URL 有哪些成分,本集说「拼路径的基址」禁哪些,今天碰巧同三词,但不由词表派生(派生会让词表加一个词静默变成策略多禁一个词),词表加词时本集要显式裁;三词字面量在包里恰两处:词表与本集)·
262
+ `pathBaseUrlFlaws(url)`(= `httpUrlFlaws(url, 该集)`,不另写判法;合格 ⇒ `[]`,否则按判序交回规则词,答案不含值的任何片段)·
263
+ `FLEET_ADVERTISE_ADDRESS_ENV = "FLEET_ADVERTISE_ADDRESS"`(与 `FLEET_GENERATION_ENV` / `FLEET_SCOPE_ENV` 同文件同形,字面量类型)。集与判官按座位的本质命名,不按某个消费键名;本包自己的 `webAdminSessionUrl` 改读这一只判官(对同一输入的答案与改前逐字相同)。
264
+ 读数照 server 判官今日行为:`http://$(POD_IP):8090` 过、`http://[$(POD_IP)]:8090` 拒(括号里展开前不是 IPv6 字面量 ⇒ **判占位展开之后的值**)、路径前缀过(路径不在集里)、query / fragment / userinfo(含空 `?` / 空 `#`、只有用户名)各拒、非 http(s) / 不透明形 / 空串拒。
265
+ **没动的**(如实):`FLEET_ADVERTISE_ADDRESS` 不进 `RESERVED_WORKER_ENV`(运维可经 `worker.env` 声明它,与 generation / scope 两只「只有渲染器能写」不同);占位展开、可达性与缺席 / 空串的处置不在本包(`docs/CONSUMER-INTEGRATION.md` §13.4);登记表只跟名字不跟词,词与词序由钉守。
266
+ 三问 —— **谁需要**:server(7.137.0 起中心基址三席与公告地址都改读 `pathBaseUrlFlaws`,删本地的禁用集副本与 env 名字面量)与 web-admin 编排器(渲染该 env 前对展开后的值调 `pathBaseUrlFlaws`,键名取常量);**谁受伤**:无 —— 新导出,没有既有读者;`webAdminSessionUrl` 的答案逐字不变;server 改读后判官读数逐字不变(词与判官是同一只);
267
+ **补偿**:无需。
268
+ 钉:`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` 照绿。
269
+ - **随包文字与导出面登记表**。`docs/CONSUMER-INTEGRATION.md` 新增 §12「沙箱 git 凭据平面」—— web-admin 与 server 两方各一小节的读法,判定序表(含各行的条件)与整次拒答表;README / `llms-full.txt` 点到新面;`docs/API-SURFACE.md` 重生成(包根 +28 名,`./fleet` +1 名,`./api/auth-bridge` +2 名,无删除、无改形)。
270
+ 三问 —— **谁需要**:接线的两方(web-admin 与 server)与读 `dist/*.d.ts` 的人;**谁受伤**:无;**补偿**:无需。
271
+ 钉:`test/sandbox-git-docs-14.7.0.test.ts`(§12 里的两张表与代码里的词表逐行同序同词 · 三张词表的每个词都在节里 · 本节三问形且钉指的测试文件真在 · README / `llms-full.txt` 点名新面);`test/api-surface.test.ts`(登记表与实际导出面逐名比对)。
272
+ - **发布说明里的「受影响导出表」每版相对上一个 tag 机器重算**。14.6.0 节里的两张表是那一版的快照(相对 v14.5.0),撑着它们的种子名单(改动过的声明 / 新导出 / 读座位的声明)是随版冻结的 —— 下一版一动 `src/` 它们就红,且红的原因是基线过期、不是回归。
273
+ 现在:**基线是入参,不是常量**。基线 tag 由 `package.json` 的版本与仓库的 tag 表推出;「运行期代码有改动的声明」「新导出」「受影响的公开导出」全由两版源码现算,没有一处手写名单;
274
+ 发版准备(版本已抬、tag 未打)时,该版本那一节必须带这张表、逐字同重算,多一行少一行、基线写错都红;**已发布版本的节(含 14.6.0 的两张表)永不再被核,一个字节没动**。
275
+ 表的生成口:`node test/helpers/affected-exports.ts` 打印整块(标签行 + 围栏),粘进版本节。扫描器自己的盲区(非「具名函数 / 类 / 标识符变量」的顶层语句)由它自报、要求为空。没有 tag 的检出(浅克隆 / 没拉 tag)里依赖基线的几格会跳 —— 判不变(仍是跳),但原因响着打出来,并说怎么补(`git fetch --tags`)。
276
+ 14.6.0 的「判定列」是那一版座位谓词变更的证据(v14.5.0 构建 × HEAD 构建对跨 realm 对象的答案由 false 变 true),随之退役;跨 realm 对象的行为面留作常驻钉(HEAD 一侧:每个探针 `foreign` 观察值 = `plain` 观察值,负控证明 `foreign` 真是另一个 realm 的)。
277
+ 已知的边(如实):依赖包升级造成的答案变化不在 `src/` 里,这把扫描看不到;表回答的是「本包源码里哪些导出的运行期代码动过」。
278
+ 三问 —— **谁需要**:读发布说明、要核「哪些既有导出的运行期代码动过」的消费方,与发版的人(不再手改一整套冻结名单);**谁受伤**:无 —— 没有导出的行为变化,变的只是发布说明里这张表的生成方式与基线;
279
+ **补偿**:无需。
280
+ 钉:`test/affected-exports-since-baseline-14.7.0.test.ts`(合成小仓上的算法读数:改动声明 / 受影响导出含别名・命名空间成员・简写属性・类方法体・两跳、类型位置的引用不入 / 新导出不入表 / 空表 `(无)` / 盲区自报 / 种子缺席点名 / 公开值导出名 · 基线 tag 由版本与 tag 表推出(开发窗不核、发版准备窗核、数字序、无基线)· 表的定位与核对只看名下那一节 · 生成口整块粘进版本节即过核对 · 真树上盲区为空 / 门不空转 / 发版准备窗里当前版本节的表逐字同);`test/cross-realm-probes-14.6.0.test.ts`(每个探针跑得通且 `foreign` = `plain` · 负控)。
281
+ - **形状只问座位:全包裸 `Array.isArray` / `typeof x === "object"` 收口**。包里读「可能是嵌入方 JS 对象」的口各写各的形状判:裸 `Array.isArray(x)`(已撤销的 Proxy 上抛 `TypeError`,陷阱抛也一样)、裸 `typeof x === "object"`(只回答「是对象」,没问「是不是朴素 / 读得出」,随后的成员读就在原型链上乱走)。
282
+ 现在:`Array.isArray(…)` 的调用点只在座位模块(两处:`jsonShapeOf` 的数组臂与 `isJsonArray`;安全轴的朴素数组判据 `isPlainJsonArray` 经 `isJsonArray` 问),外加 **hooks 闭包副本两处审过的例外**(`hooks-config.ts` 里的 `hookShapeOf` / `hookIsArray`:hooks 分类器的函数体要被消费方的生成器整段抄走,抄出去的闭包只许带固定的几个自由名、不能引座位模块,所以带座位的字节同文副本,同文门守着;这两处随闭包契约变更一起消失),别处一律问 `isJsonArray` / `jsonShapeOf`;直接的 `typeof x === "object"` 比较同样只许在座位模块、闭包副本的 `hookShapeOf` 与一处写明理由的审计例外(`quoted-issue.ts` 的模板片段,是本模块自己铸的)。反向门数 `src/` 的 AST。
283
+ 读不出的对象(撤销的 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` 在问任何下标之前就抛)。
284
+ 与 14.6.0 不同答的一类(全在 JS 面;一句话说全,下面的构造是举例、不是全集):**哈希只看自有描述符视图** —— 凡结构陷阱(`has` / `ownKeys` / `getOwnPropertyDescriptor`)或 `get` 陷阱与描述符视图不一致的 Proxy、哈希途中改写自身的 getter、原型上的数字键、数组实例上的方法,哈希随描述符视图(或拒);14.6.0 跟 `Object.keys` / `map` 的 `in` / `[[Get]]` / 实例方法走。JSON 面没有这一类。举例:
285
+ 前面的 getter 改写 / 删掉 / 换成访问器的后面成员(或把数组的 `length` 改短)⇒ 按列成员那一刻的描述符取值(`{ b: 2, get a() { delete this.b; return 1; } }` 由 `{"a":1,"b":null}` 变 `{"a":1,"b":2}`;被删掉的访问器成员仍按列成员时那只 getter 调一次)·
286
+ `get` 陷阱抛 / 改写的 Proxy 按 target 哈希(14.6.0 抛 / 读陷阱的值;`get` 陷阱报 `1n` 长度的数组 14.6.0 抛、现在按 target 的真长度;描述符陷阱报 `1n` 的数组现在拒、14.6.0 按 target)·
287
+ `has` 陷阱答「没有」的数组 Proxy 照哈希它的自有下标(14.6.0 的 `map` 把它们当洞:`[,]`)· `ownKeys` 陷阱只报 `length` 的数组 Proxy 哈希成洞(14.6.0 照读下标)· `ownKeys` 陷阱抛的数组 Proxy 拒(14.6.0 照读)·
288
+ 前面的 getter 填上后面的洞 ⇒ 仍是洞(列成员时那一格不是自有下标)· `Array.prototype` / `Object.prototype` 上被污染的数字键不再填进数组的洞 · 数组实例自有的 `map` 不再被调用(14.6.0 调它:`[1]` 带自有 `map = () => []` 哈希成 `[]`,现在 `[1]`)· 数组自有的 `constructor` / `Symbol.species` 不再被问(14.6.0 的 `map` 经它造结果数组,可以答任意值或抛)。
289
+ 三问(哈希这一处)—— **谁需要**:用 `stableHash` 做漂移检测 / ETag 的调用方:哈希只该反映值本身,每个成员读一次,不被实例方法、陷阱、原型污染改写;**谁受伤**:哈希值靠 `get` / `has` / `ownKeys` 陷阱、实例 `map`、自有 `constructor` / `Symbol.species`、或哈希途中改写自身的 getter 供出的嵌入方对象 —— 哈希值变或改成拒(JSON 面无);**补偿**:交 JSON 往返后的值(`structuredClone` 对 Proxy 抛 `DataCloneError`)。
290
+ **读口 × 输入形 ⇒ 答法**(本批改过的读口;「—」= 与左格同答;JSON 入参的答案逐字不变;读口按**自有键**读,「继承」列说键只靠原型链可见时它怎么办):
291
+
292
+ | 读口 | 撤销的 Proxy / `getPrototypeOf` 陷阱抛 | 成员读不出(会抛的 getter / 访问器 / 陷阱抛;**哪些陷阱会被问**因读口而异 —— 见表后「读法两类」一段) | 键只靠继承可见(含 `Object.prototype` 被污染) |
293
+ | --- | --- | --- | --- |
294
+ | `classifyHooksConfig` | 整份拒(`guard-outside-hooks`,坐标 `[]`)| — | 当缺席 |
295
+ | `readHttpHookAllowlistLayer` | 两键读成 `[]` 并进 `malformed` | 该键读成 `[]` 并进 `malformed` | 该键读成 `[]` 并进 `malformed`(响亮) |
296
+ | `readRequestMcpTransport` | `ok:false`,拒句说全包唯一那一句 | — | 当缺席;身份头(`principalHeader` / `assertionHeader`)进 `lost`:`inherited key ignored` |
297
+ | `readMcpIdentityHeaders` | `{}` | `{}` | 当缺席(它没有 `lost` 通道) |
298
+ | `isStringArray` / `isStringRecord`(同组的 `isUsableWireName` / `isContentOriginWord` 只问 `typeof`,运行期代码没动,答法相同) | `false` | `false` | 当缺席 |
299
+ | 五只描述器(`describeValueShape` 等) | 全包唯一那一句 | 不读成员:答种类(`object` / `an object` / `an object with a non-string value` …),不抛 | 同左(不读成员) |
300
+ | `readEffectiveWire` | 抛 `TypeError`:`effective wire payload could not be read — <那一句>` | 会抛的 getter / 访问器 ⇒ 同为 `TypeError`,句子是「不是纯数据」那一句(getter 一次都不调);载荷里自有但不可枚举的键(`version` / `updatedAt` / 域键 / `prompts` / 未知键)同属读不出 ⇒ 整份 `TypeError`(「不是纯数据」那一句),不当缺席、不静默丢 | 当缺席 |
301
+ | `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` 一个键,其余键读侧本来就剥掉,影响只在未知键告警点名哪些键;交出寻常对象 | 当缺席 |
302
+ | `isSchedulerRecord` | `false` | `false` | 当缺席(缺字段 ⇒ `false`) |
303
+ | `readEngineSignal` / `readModelRefusals` / fleet 其余各读口(请求 / 回执 / 排空那几只;要理由的那一只见下一行) | `undefined` | `undefined`(**声明键与载体键**自有但读不成纯数据 —— 不可枚举的、会抛的 getter —— 同此:读不出整份,不是少了这个键照读;没声明的键里的隐藏键不读;声明位上的类实例 / `Map` / `Date` ⇒ 读不出,v14.6.0 照收) | 载体键当缺席 ⇒ `undefined` |
304
+ | `readCapabilityReading` | `{ kind: "unreadable", why: "not-an-object" }` | `{ kind: "unreadable", why: "invalid-capabilities" }`(自有但读不成纯数据的 `capabilities` / 其成员 —— 不可枚举的、会抛的 getter —— 同此:坏声明是读不出,不是没声明) | 当缺席 ⇒ `{ kind: "undeclared" }` |
305
+ | `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`) |
306
+ | fleet 各读口与令牌声明的**捕获范围**(只捕获所读的键) | — | 没声明的键里放什么(`Date` / `Map` / 函数 / 环 / 会抛的 getter / 隐藏键)与联合里没选中的那一臂的键都不读不拷、不影响读数(校验器本来就剥掉、不读它们);载体键与声明键同一只描述符核(隐藏 / 抛 ⇒ 读不出);`z.unknown()` 这类自由值原样交给后面只做类型判的 transform(不捕获,内部的 getter 不会被调用);令牌的 `sub` / `role` / `jti` / `scope` 声明键按描述符律读一次、取值后只问「是不是非空串」,不深拷;**声明键**本身自有但读不成纯数据 ⇒ 读不出 | 同上(键只靠继承可见 ⇒ 缺席) |
307
+ | `migrateStoredDocuments` / `migrateEffectiveSnapshot` | 文档集 / 快照读不出(自有但不可枚举的键同属读不出)⇒ 抛 `TypeError`(带那一句;`migrateEffectiveSnapshot` 对 `null` / 数组 / 原始值 / 类实例照旧原样返回 —— 判形是读者的事,「读不出」不是「不是这个形」)| 抛 `TypeError`(带那一句:迁移步对文档**固定键**的读与存在判走描述符律 —— 自有访问器〔抛或不抛,getter 零次〕/ 不可枚举的自有键是「不是纯数据」那一句,读不出是「检视不了」那一句,v14.6.0 访问器的 getter 照调、隐藏键照读;**限于固定键的读** —— 步骤里其余的读法〔键枚举、交给校验器读成员〕读不出时,**嵌入方自己抛的值原样出来**,与 v14.6.0 同,是写明的缺口、不是承诺;键枚举(SS-M4 找车道键的 `Object.keys`)也不看不可枚举的键 —— 隐藏的车道键当缺席,与存在判对隐藏的店键抛「不是纯数据」不同答,同属这一缺口;要改写的行里读不出 / 环 / 非纯数据同样抛,各带各的句子)| 当缺席 |
308
+ | `readSettingsUnit` | `unreadable` | `unreadable` | 当缺席 |
309
+ | `tightenVerdict`(`none` 规则) | `unreadable` | 按描述符读,不触发 getter / 陷阱 ⇒ 访问器是 `unreadable` | 不读继承 |
310
+ | `stableStringify` / `stableHash` | 抛 `stableStringify: … refusing to hash` | 同(会抛的 getter / `ownKeys` 或描述符陷阱抛;`get` 陷阱不被问) | 不读继承(对象只走自有可枚举键;数组的洞不顺原型链读) |
311
+ | `judgeConfigDValue`(`config.d` 文档判) | 抛 `TypeError`:`must be a JSON object (got <那一句>)`(不是对象的 —— `null` / 数组 / 原始值 / 类实例 —— 仍是既有的 `Error`) | 照朴素对象收下(读成员是后面步骤的事) | — |
312
+ | `sanitizeZodError` | issue 里读不出的值被扣下(`[redacted-secret]`),不抛 | 同(issue 字段里的值 —— 数组 / 对象 —— 有会抛的 getter / 陷阱 ⇒ 这个值整个扣下;v14.6.0 抛出嵌入方的值);issue 本身的成员读不出(例:`params` 的模板成员是会抛的 getter)直接调它仍会抛 —— 包内的读口都在守护里调它,答各自的「读不出」 | — |
313
+
314
+ 表里「撤销的 Proxy / `getPrototypeOf` 陷阱抛」一列,对**问座位形状**的读口(上表大多数行)是同一个答案;三处要分开读:**数组判不读原型**(`isJsonArray` 只问「是不是数组」:`getPrototypeOf` 陷阱抛、但元素读得出的数组仍读成数组,`isStringArray` / `readSettingsUnit` 的列表单元照收;只有撤销的 Proxy —— 连 `Array.isArray` 都抛 —— 读不出);**不问形状、只读自有键的读口**(`readMcpIdentityHeaders`、令牌声明读口 `toVerifiedIdentity` / `readTokenScope`):撤销的 Proxy 读不出,`getPrototypeOf` 陷阱抛但自有键读得出的对象照读自有键。`stableStringify` 与 `tightenVerdict` 的 `none` 规则**不在这一类**:它们先问座位形状,`getPrototypeOf` 陷阱抛的对象读不出(见各自那一行;v14.6.0 的 `stableStringify` 读得出那份对象的自有键,这是已写明的 JS 面变化)。该表逐格都有真跑的钉。
315
+
316
+ **错类只有一种**:输入**读不出**(撤销的 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` 值也是这么铸出去的)的参数不许带「读不出」措辞。
317
+
318
+ **读法两类**(决定「哪些陷阱会被问、getter 调不调」):
319
+ ① **整容器捕获**(令牌声明值的快照、迁移的行快照与文档集 / 快照袋、`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 往返、校验器解析数组都不读它们)。
320
+ ② **按声明键逐个读**(fleet 各读口、令牌声明键本身、迁移与 workers 行读口的固定键读;内核的 `readDeclaredKey` / `ownDeclaredValueOf` / `ownOrThrow`,与 `parseDeclared` 的声明键投影)**对每个声明键取一次自有描述符、只从描述符取值**(缺席时再问一次 `in`):被问的是 `getOwnPropertyDescriptor` 与 `has`,外加座位问形状的 `getPrototypeOf`;**从不调 `ownKeys`、不取全部描述符**(没声明的键一个都不碰 —— 所以 `ownKeys` 抛的 Proxy 在 fleet 读口上照常读声明键);同样不调 getter、不问 `get` 陷阱;自有访问器 / 不可枚举的自有声明键 ⇒ 读不成纯数据(读口答它既有的「读不出」,不当缺席)。
321
+ 交回原引用 / 作类型守卫的读口(`isStringArray` / `isStringRecord` / `isSchedulerRecord`、`readSettingsUnit` 的列表单元与环境变量表、`tightenVerdict` 集合规则的串集合)属 ② 的严归类:访问器 / 不可枚举的元素、成员、字段 ⇒ `false` / `unreadable`,getter 零次。
322
+ 走 `readKey` 的其余读口(都交副本或值:描述器、http hook 名单的读器与判官、传输的 `kind` / 字段袋 / 身份头 / `args`、`salvageEntries` 读条目集合字段、workers 行读口读 `workers` 数组的元素 …;闭包注入组里交副本的那一只读法)与 ② 是**同一只读内核**(一个键一次 `getOwnPropertyDescriptor`,在场与值出自这一份描述符,**不问 `get` 陷阱**),只是归类更宽:调自有访问器(这份描述符里的 getter)一次、**把不可枚举的自有数据键读成在场**;传输 `env` / `headers` 的**成员**按描述符读(就是这一读:不问 `get` 陷阱,访问器调一次,成员名含不可枚举的 —— 读器头注写明的契约,v14.6.0 同)。
323
+ **同一个输入按读口的三种答案**(今天一只读内核、两种归类,外加按名登记的几处自带读法与几处直接 `[[Get]]` —— hooks 分类器、`collectDropped` / `collectionSlots`、http hook 名单读器与判官的普通读对拍;`stableStringify` 本版起经内核;http hook 判官读每一层时走读器同一只读法 —— 读调用方对象的自有描述符与普通读两份观测(`source` 与名单);这是本版如实披露的一致性债,JSON 面不受影响;内核头注的读者普查与本表逐行同):
324
+
325
+ | 读口 | 不可枚举的自有数据键 | 自有访问器(不抛) | Proxy 的 `get` 陷阱 |
326
+ | --- | --- | --- | --- |
327
+ | 描述符族 —— 严归类(令牌声明、迁移的固定键与存在判、fleet 声明键与载体键、workers 行读口的 `workers` / `name` / 扣行时拼文档的其余键、`readEffectiveWire` 与迁移的载荷 / 文档集袋、整容器捕获;交回原引用 / 作类型守卫的读口:`isStringArray` / `isStringRecord` / `isSchedulerRecord`、`readSettingsUnit` 的列表单元与环境变量表、`tightenVerdict` 集合规则的串集合) | **拒**(读不出 / `false` / `unreadable`) | **拒**,getter 零次 | 不问(读 target 的数据值) |
328
+ | `readKey` 族 —— 宽归类,都交副本或值(与描述符族同一只读内核:名单层的读器与判官、传输 `kind` / 字段袋 / 身份头 / `env`·`headers` 的成员〔成员名含不可枚举〕、`captureStringArray` 的宽归类 —— `args`,下标上的 getter 同、http hook 名单 / `hookVars` / `layers` —— 经读器的双读一致律〔描述符那份是朴素数组判据 + 整容器读的宽归类,普通读那份逐下标对拍〕、remote-exec 判别词、未知键扫描、`salvageEntries` 读条目集合字段、workers 行读口读 `workers` 数组的元素〔只看下标键〕) | **在场**(照读) | 调一次、取它的值 | 不问(读 target 的数据值) |
329
+ | `stableStringify` / `stableHash`(那一只整容器读:对象只列可枚举的自有串键,数组列自有下标) | 对象:**缺席**(不列);数组:**在场**(下标照读) | 调一次(在它的哈希位上调列成员时那只 getter) | 不问(值取自描述符) |
330
+ | hooks 分类器(`Object.keys` + 成员读,闭包可抄的内建写法) | **缺席**(只走自有可枚举键) | 调一次、取它的值 | 问 |
331
+ | `tightenVerdict` 的 `none` 规则 | 对象:**缺席**(不可枚举的键跳过);数组:**在场**(按 `length` 逐位读描述符,不可枚举的下标照读 —— 与序列化同) | 整份 `unreadable` | 成员不问;数组的 `length` 走一次 `[[Get]]`(`get` 陷阱只被问这一次) |
332
+ | 被丢条目的身份(`entry-dropped` 的 `identity`:逐键读描述符) | **在场**(照读) | 当缺席(getter 不调) | 不问 |
333
+ | `ModelEntry.extraBody` 的安全判(逐层取全描述符) | **在场**(照判) | 判不安全(getter 不调) | 不问 |
334
+
335
+
336
+ 不在表里的读口:`parseDomain` 家族与各 zod 校验导出读成员的是**校验器自己**(撤销的 Proxy / 读不出 ⇒ `parseDomain` 抛自己那一句 `a value could not be read during validation`(`TypeError`);`safeParse` 直读的导出由校验器库给答案),见下一条的「不在本批」。
337
+ 三问 —— **谁需要**:把嵌入方 JS 对象(Proxy 包的配置、类实例、被污染的运行时)交给读口的消费方 —— 它们要的是「读不出」有一个确定的、响亮的、读口自己的答案,不是引擎的 `TypeError`;**谁受伤**:JSON 面无 —— JSON 解析出来的值只有自有数据属性,这条路上的答案逐字不变(声明类型内的输入;五类例外见本节开头那段);JS 对象面上,本来就抛引擎错的那几处仍是抛(`stableStringify` 与 `readEffectiveWire` 读不出的载荷由引擎的 `TypeError` / 「is not a JSON object」的 `Error` 改成包自己那一句的 `TypeError`);本来读得出、现在答「读不出」或换了答法的 JS 形(自有访问器 / 不可枚举的自有键 / 靠 `get` 陷阱供值的 Proxy)各在它所属的那一条写受伤者与补偿(「读口对固定键的读只认自有键」·「读内核覆盖声明键读与整容器读」);
338
+ **补偿**:嵌入方要捕获的话,按上表的答法写,不再依赖引擎的错误文案。
339
+ 钉:`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` 时逐字同)。
340
+ - **两只兄弟描述器 `describeBadWireName` / `describeBadContentOrigin` 对读不出的对象不再抛**。另外三只描述器早已总(读不出 ⇒ 全包唯一那一句),这两只漏了:撤销的 Proxy 上裸 `Array.isArray` 抛 `TypeError`。
341
+ 现在:两只共用一只 `describeKindWord`(由座位的形状分类给词:`null` / `array` / `typeof` 词,读不出 ⇒ 那一句),五只描述器全部由座位的形状给答案;JSON 入参的句子逐字不变。两只在包内没有调用方,但在包根导出(服务端的请求受理读它们),所以是修不是删。
342
+ 三问 —— **谁需要**:用这两只描述器拼拒句的服务端 / 壳(入参可能来自嵌入方);**谁受伤**:无;**补偿**:无需。
343
+ 钉:`test/guarded-reads-census-14.7.0.test.ts`(撤销的对象 / 数组 Proxy 与 `getPrototypeOf` 陷阱抛的 Proxy 作入参,五只描述器都答那一句且不抛;JSON 入参的句子逐字不变的正控)。
344
+ - **读口对固定键的读只认自有键:`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` 一类的店键)。
345
+ 现在:这些读口的固定键读全经一只内核(包内私有 `./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`)·
346
+ 包自有的查表(`domainWriteRole` —— 写权限角色 —— / `isReservedExtensionKey` / 未知键扫描的已知键表:行缺席时不借原型链的一行)与包自己造的「缺席 ⇒ 空文档」(`parseDomain` / `validateDomain` / 线上 skills 清单,改用 `COPY_PROTOTYPE` —— 与所有副本同一只原型 —— 校验器读它声明的键时读不到污染值)。
347
+ **不在本批**(如实):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` 被污染本身是嵌入方进程被攻破,本包不替它兜底,只保证**自己的读口不替污染背书**。
348
+ **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 铸不出符号键)。
349
+ 三问 —— **谁需要**:服务端 / 壳 / 中心(令牌声明、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 都照读);
350
+ **补偿**:要让读口读到的字段放成自有属性(`{ ...defaults, ...overrides }`);响应式对象先取出普通数据(JSON 往返,或框架自己的取原值口)再交;抄 `mcp-request-entry` 闭包组的生成器把 `./own-key-read` 的内核名字当作注入名(见档位说明)。
351
+ 钉:`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` 照迁;环 / 非纯数据只在要改写时才拒)。
352
+ - **hooks 分类器:内容靠迭代协议供出的非朴素对象(`Map` / `Set` / 生成器)当「可能带守卫」拒**。分类器按**自有键**扫守卫迹象;`new Map([["PreToolUse", [<带守卫的匹配器>]]])` 作 hooks 块(或块里任一子树)时守卫在 Map 的条目里、不在自有键上,扫不到 ⇒ 整块按 `hooks-not-object` 降级丢掉(`loaded-partial`)—— 一份写着守卫的 hooks 配置静默消失(14.5.0 起既有)。
353
+ 现在:非朴素对象若靠迭代协议供内容(`Symbol.iterator in 它`),它的内容无从按自有键证明「没有守卫迹象」⇒ 记为它自己坐标上的一处迹象,整份拒,与「读不出」同一臂(fail-closed)。没有迭代协议的非朴素对象(类实例 / `Date` / `RegExp` / 函数)照旧按自有键扫:守卫迹象在自有键里 ⇒ 拒,没有 ⇒ 按今天的降级丢,既有行为不变。分类器闭包的自由名不变(迭代协议的符号取自 `Array.prototype`,不引新的 JS 内建;`test/hooks-classify-13.0.0.test.ts` ⑤ 守着)。
354
+ 三问 —— **谁需要**:读 settings 文档里 hooks 的宿主(用分类器决定哪些块能加载):守卫是安全轴,一份写着守卫的配置不许静默变成「没有守卫」;**谁受伤**:把 `Map` / `Set` 当 hooks 块(或块里的子树)交进来的嵌入方 —— 此前降级加载、现在整份拒(JSON 解析不出这些值,契约写的是 JSON 文档);**补偿**:把它们展开成朴素对象 / 数组再交。
355
+ 钉:`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` 自己(「内容不在自有键上」那一臂先判)。
356
+ - **请求面传输:继承来的身份头不再无声**。14.6.0 把传输快照改成只读自有键之后,`principalHeader` / `assertionHeader` 只靠原型链可见时被当缺席、不记 `lost` —— 身份轴上的静默(`Object.prototype.principalHeader` 被污染时,所有 http 传输都读成「没写身份头」,受理新提交的门看不到)。
357
+ 现在:身份头只靠继承可见 ⇒ 不读它的值(注入的值进不来),但进 `lost`:`{ path: "transport.principalHeader" | "transport.assertionHeader", reason: "inherited key ignored (…)" }`(机读位不带,`fault` 的闭集不加词),传输本身照 `ok:true`;
358
+ `principalHeader` 自有在场时 `assertionHeader` 一次不问(不点名);stdio 传输不用身份头,不被牵连;公开的 `readMcpIdentityHeaders` 没有 `lost` 通道,对继承仍答 `{}`。与托管层名单读器「只靠继承可见 ⇒ `malformed`,响亮」同向;别的键(`args` / `env` / `headers` / `command` / `url`)继承来的仍按缺席处理(它们缺席本身有各自的答案)。
359
+ 三问 —— **谁需要**:受理请求面 MCP 条目的服务端 / 壳(据 `lost` 响亮拒新提交):身份轴不该有无声的读法;**谁受伤**:`Object.prototype` 被污染成身份头、或把身份头放在原型上的手造传输对象的调用方 —— 此前静默读成缺席,现在多一条 `lost`(受理新提交的门会据此拒;重放存量提交体的腿不受影响:传输照 ok);**补偿**:身份头写成自有键。
360
+ 钉:`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` 期望随之点名)。
361
+ - **读内核覆盖声明键读与整容器读:一个键的在场与值出自同一份描述符,不再 `[[Get]]`(律变:`readKey` 族的读口也不问 `get` 陷阱);数组只看下标键;仍自带读法的既有读者按名登记**。此前 `readKey` 族先 `hasOwn` 问「是不是自有」、再 `[[Get]]` 取值 —— 两步之间描述符陷阱能删掉自有键,第二步就读到 `Object.prototype` 上被污染的同名值(例:`isSchedulerRecord` 收下 `prompt` 只剩继承来的串的记录;v14.6.0 答 `false`)。
362
+ 现在:全包一个键只读一次 `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` 的成员、设置单元的环境变量表也走这一读(此前各写一份;环境变量表按整表列一次成员,每个成员只问一次描述符)。
363
+ 数组臂同一条:**只看下标键**,挂在数组上的非下标键(可枚举或隐藏,例:响应式框架给数组挂的 `__ob__`)不是元素、也不构成「读不出」;workers 行读口读 `workers` 的元素按校验器解析数组的读法(宽归类:访问器下标调一次、不可枚举的下标照读)—— 下标是不抛的访问器 / 不可枚举、或数组挂着隐藏非下标键的清单,坏行照常单独扣下(v14.6.0 的逐行隔离;此前整张清单原样放行、交整域读侧整份拒)。
364
+ **选哪一种归类只看读口交出去的是什么**:交回**副本**的读口(调用方只读这份副本)可以宽;交回**原引用**、作 `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 名单也交副本,但它是安全轴 —— 宽归类之外还要普通读那份观测一致(见下)。
365
+ 如实(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`)再递。
366
+ 三问(归类这一处)—— **谁需要**:用这几只守卫收窄类型的调用方:`x is T` 为真,随后的读就必须是 `T`;**谁受伤**:把带访问器 / 不可枚举键的对象(响应式框架包过的数组与记录)交给这几只守卫 / 读口的嵌入方 —— 此前 `true` / `ok`,现在 `false` / `unreadable`;**补偿**:交普通数据(JSON 往返后的值,或框架自己的取原值口)。
367
+ **整容器读与这几只读口的追加走定义语义**(`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 同答)。
368
+ 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` 都让它拦下或少放;只按下标读会把这些构造放行 —— 本版对这些构造一律拦。
369
+ 如实(挡不住的,语言层限制):有状态 Proxy —— 两次观测都答同一份假名单,或第一次观测改了第二次观测的答案(例:名单数组的 `ownKeys` 陷阱打开放宽的 `get` ⇒ 描述符与普通读两份都读到放宽的那一份、放行,而 14.6.0 只做普通读、读到限制的那一份、拦下),两条通道被同一个谎骗过。**谁受伤**:把有状态 Proxy 当名单递进来的程序(JSON 解析不出这种值);**补偿**:要防就先对名单做一次 JSON 往返(`JSON.parse(JSON.stringify(…))`,只读一次、得到的是普通数组)再递。
370
+ 三问(名单这一处)—— **谁需要**:托管层名单是安全轴,读取与判决只许收紧;判官与读器对同一份设置结构同答 —— 同一份设置只该有一个读数、判决只按它下;**谁受伤**:① 把名单写在原型上 / 类实例里、或 `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` 用数组字面量装层。
371
+ 三问(判决词 `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`。
372
+ 三问 —— **谁需要**:所有读嵌入方对象的读口:「是不是自有」与「值是什么」之间不能有缝,有缝污染就照样注入;一只坏 worker 不该拖黑整张清单;**谁受伤**:交给走读内核的读口(传输、调度记录判、设置单元、串数组判官、remote-exec 判别词、未知键扫描、`salvageEntries` 读集合字段)一个靠 `get` 陷阱供值 / 改写值 / 抛错的 Proxy 的嵌入方 —— 此前读到陷阱给的值(或因陷阱抛读成读不出),现在读到 target 自有描述符里的值(例:`get` 陷阱对 `length` 撒谎的 Proxy 数组按 target 的真长度与真元素读;`get` 陷阱抛的传输 / 调度记录照读 target 的数据);http hook 名单不在此列 —— 安全轴上两份观测对不上即全拦(见上);workers 元素里会抛的下标 getter 现在被调一次(仍是原样放行);**补偿**:要读口读到的值放在对象的自有属性上(或由 `getOwnPropertyDescriptor` 陷阱给出),或交 JSON 往返后的普通对象。
373
+ 钉:`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`(内核头注)。
374
+ - **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 都不递归,今天的读口不触发)。
375
+ 三问 —— **谁需要**:读租约 / 健康体 / 排空回执的服务端与 web-admin:读口「从不抛」、停机指令不丢是契约;**谁受伤**:无 —— 三处都回到 v14.6.0 的答法;**补偿**:无需。
376
+ 钉:`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`)。
377
+
378
+ **自 v14.6.0 起运行期代码有改动的公开导出(机器重算;上界 —— 可达 ≠ 一定变,不可达 ⇒ 一定不变;本版新增的导出不入表)**
379
+
380
+ ```text
381
+ AgentHookEntry [., ./hooks]
382
+ CommandHookEntry [., ./hooks]
383
+ DOMAINS [., ./types]
384
+ DOMAIN_SCHEMAS [., ./types]
385
+ FileConfigStore [./node]
386
+ HookEntry [., ./hooks]
387
+ HookMatcherEntry [., ./hooks]
388
+ HooksConfig [., ./hooks]
389
+ HttpHookEntry [., ./hooks]
390
+ McpToolHookEntry [., ./hooks]
391
+ ModelEntry [., ./types]
392
+ ModelsConfig [., ./types]
393
+ NON_PORTABLE_DOMAINS [./conformance]
394
+ PromptHookEntry [., ./hooks]
395
+ RemoteExecSpec [., ./remote-exec]
396
+ RosterEntry [., ./types]
397
+ RostersConfig [., ./types]
398
+ VerifiedIdentity [., ./api/auth-bridge]
399
+ applyProjectPatch [., ./types]
400
+ buildEffective [., ./config-fns]
401
+ canonicalize [./conformance]
402
+ classifyHooksConfig [., ./hooks]
403
+ createContractAdapter [./conformance]
404
+ createFileSchedulerStore [./node]
405
+ deepEqual [./conformance]
406
+ deriveLocalExpectation [./conformance]
407
+ deriveWireExpectation [./conformance]
408
+ deriveWireInput [./conformance]
409
+ describeBadContentOrigin [.]
410
+ describeBadWireName [.]
411
+ describeNotStringArray [.]
412
+ describeNotStringRecord [.]
413
+ describeValueShape [.]
414
+ diffJson [./conformance]
415
+ diffWarningMultiset [./conformance]
416
+ domainFilePath [./node]
417
+ domainWriteRole [., ./config-fns]
418
+ emptyEffective [., ./config-fns]
419
+ extraBodyUnsafe [., ./types]
420
+ findSecretLeaks [./conformance]
421
+ foreignKeysOf [./conformance]
422
+ httpHookEnvVarsAllowed [.]
423
+ httpHookUrlVerdict [.]
424
+ isDomainName [., ./types]
425
+ isReservedExtensionKey [., ./config-fns]
426
+ isSchedulerRecord [., ./scheduler-store]
427
+ isStringArray [.]
428
+ isStringRecord [.]
429
+ loadCase [./conformance]
430
+ loadCorpus [./conformance]
431
+ loadLocalConfig [./node]
432
+ loadRemoteExec [./node]
433
+ loadSchedulerStore [./node]
434
+ migrateConfigDirectory [./node]
435
+ migrateConfigDocuments [./node]
436
+ migrateEffectiveSnapshot [./one-time-migrations]
437
+ migrateStoredDocuments [./one-time-migrations]
438
+ mutateSchedulerStore [./node]
439
+ normalizeProjection [./conformance]
440
+ parseDomain [., ./config-fns]
441
+ parseDomainLoud [., ./config-fns]
442
+ parseDomainSalvaged [., ./config-fns]
443
+ parseSchedulerStore [., ./scheduler-store]
444
+ parseWebAdminCredentialFile [.]
445
+ projectionKey [./conformance]
446
+ projectionValue [./conformance]
447
+ readBundle [./node]
448
+ readCapabilityReading [., ./fleet]
449
+ readDrainReceipt [., ./fleet]
450
+ readDrainRequest [., ./fleet]
451
+ readEffectiveWire [., ./config-fns]
452
+ readEngineSignal [., ./fleet]
453
+ readHttpHookAllowlistLayer [.]
454
+ readMcpIdentityHeaders [.]
455
+ readModelRefusals [., ./fleet]
456
+ readRequestMcpTransport [.]
457
+ readServingLeaseRequest [., ./fleet]
458
+ readServingLeaseResponse [., ./fleet]
459
+ readSettingsUnit [.]
460
+ readTokenScope [., ./api/scopes]
461
+ readWorkersRows [.]
462
+ resolveEffectiveForWorker [., ./resolve-roster]
463
+ resolveLegExpectation [./conformance]
464
+ runConformance [./conformance]
465
+ salvageEntries [., ./config-fns]
466
+ sanitizeZodError [., ./config-fns]
467
+ stableHash [., ./hash]
468
+ stableKey [./conformance]
469
+ stableStringify [., ./hash]
470
+ tightenVerdict [., ./safety-merge-spec]
471
+ toManifestDoc [./conformance]
472
+ toVerifiedIdentity [., ./api/auth-bridge]
473
+ urlSeatPatterns [./one-time-migrations]
474
+ validateDomain [./node]
475
+ validateRemoteExec [./node]
476
+ webAdminSessionUrl [.]
477
+ writeBundle [./node]
478
+ writeDomainFile [./node]
479
+ writeRemoteExec [./node]
480
+ ```
481
+
482
+ - **对 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 读的是恒等判)。表的内容与每一行的判定没有错,错的只是这一句括号的数法。
483
+ 三问 —— **谁需要**:核 14.6.0 那张表怎么数出来的人(复核者、重算它的消费方):照字面读那句括号会数出「31 个读者 + 2 个座位 = 33」的错账(实数是 29 + 2 = 31);**谁受伤**:无 —— 表里每一行的判定与名单都没动,没有导出的行为变化;**补偿**:无需。
484
+ 钉:无新钉(文字更正,没有行为面)。那 31 个 id 的出处是 tag `v14.6.0` 上 14.6.0 的跨 realm 受影响导出钉里的 `BASELINE`(31 项,含 `isPlainJsonObject` · `hookIsRecord` · `ModelEntry`),该钉在 14.6.0 发版时是绿的,本更正不改它。
485
+
11
486
  ## 14.6.0 — 2026-10-24 — minor:唯一比较器 `tightenVerdict`(`TightenRule` 尾插 `setUnion` / `numericLower`,无参)· 成员比较器由成员行的值类座推出(`MemberAdmission.list` · `memberTighten`)· settings 单元表与单元读法(`SETTINGS_UNITS` · `unitTighten` · `readSettingsUnit`)· 便携域表搬纯叶模块 · 失败句出路并成一张表与 `chmod` 落点 · 「纯 JSON 对象」谓词只有一个座位 · 请求面 url 两个答案的读数
12
487
 
13
488
  > 档位(按发版纪律):**minor** —— 加性:`TightenRule` 尾插两词(无参;发版纪律的裁定例外:持穷举 `Record<TightenRule["rule"], …>` 或无 `default` 穷举 `switch` 的消费方**提货时编译红** = 提货成本,