@sema-agent/settings-schema 14.7.0 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +306 -0
- package/README.md +80 -3
- package/conformance/VERSION +1 -1
- package/conformance/cases/c59-s784-g3-k8s-missing-image/input.json +4 -1
- package/conformance/cases/c61-s784-g3-other-lane-key/input.json +1 -0
- package/conformance/cases/c64-s784-multitenant-local-store/input.json +2 -1
- package/dist/atomic-write.d.ts +15 -0
- package/dist/atomic-write.js +109 -5
- package/dist/catalog-manages.d.ts +15 -0
- package/dist/catalog-manages.js +60 -0
- package/dist/center-refs.d.ts +68 -0
- package/dist/center-refs.js +70 -0
- package/dist/config-dir-migration.js +1 -0
- package/dist/config-fns.js +5 -3
- package/dist/config-root-documents.d.ts +106 -0
- package/dist/config-root-documents.js +93 -0
- package/dist/config-root-node.d.ts +117 -0
- package/dist/config-root-node.js +260 -0
- package/dist/conformance/run.d.ts +3 -0
- package/dist/conformance/run.js +10 -6
- package/dist/conformance/temp-roots.d.ts +31 -0
- package/dist/conformance/temp-roots.js +184 -0
- package/dist/default-model.d.ts +1 -1
- package/dist/default-model.js +5 -4
- package/dist/env-model-table.d.ts +163 -0
- package/dist/env-model-table.js +257 -0
- package/dist/execution-lane-index.d.ts +6 -0
- package/dist/execution-lane-index.js +8 -0
- package/dist/execution-lanes.d.ts +2 -1
- package/dist/execution-lanes.js +6 -4
- package/dist/fleet.d.ts +25 -7
- package/dist/fleet.js +24 -6
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/mcp-request-entry.d.ts +58 -6
- package/dist/mcp-request-entry.js +45 -13
- package/dist/mcp-server-name.d.ts +17 -0
- package/dist/mcp-server-name.js +19 -0
- package/dist/migration-receipt.d.ts +10 -6
- package/dist/migration-receipt.js +5 -3
- package/dist/node.d.ts +1 -0
- package/dist/node.js +2 -0
- package/dist/one-time-migrations.d.ts +4 -2
- package/dist/one-time-migrations.js +29 -1
- package/dist/own-key-read.d.ts +1 -1
- package/dist/remote-exec.d.ts +1 -1
- package/dist/remote-exec.js +4 -2
- package/dist/retired-form.d.ts +19 -0
- package/dist/retired-form.js +24 -1
- package/dist/safety-merge-spec.d.ts +6 -6
- package/dist/tier-words.d.ts +33 -0
- package/dist/tier-words.js +43 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +29 -5
- package/dist/worker-row-verdict.js +7 -1
- package/docs/API-SURFACE.md +68 -4
- package/docs/CONSUMER-INTEGRATION.md +72 -2
- package/llms-full.txt +8 -3
- package/llms.txt +9 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,312 @@
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 15.0.0 — 2026-10-28 — major:k8s 车道的沙箱命名空间 `K8S_NAMESPACE` 由可选挪进必需(车道表的 k8s 行 · 本地文件契约 `RemoteExecSpec` 的 k8s 臂)· 写闸对必需坐标的值:空串或只含空白一律按缺(校验收紧三处:此前受理的声明从受理变成拒绝)
|
|
12
|
+
|
|
13
|
+
> 档位(按发版纪律):**major** —— 校验收紧三处,每一处都让此前受理的输入被拒:**B1** 车道表把 `K8S_NAMESPACE` 挪进 k8s 行的必需栏(缺它的 k8s worker 被写闸拒;存下来的 workers 文档里有这样一行,
|
|
14
|
+
> 整个 workers 域读不出);**B2** 本地文件契约 `RemoteExecSpec` 的 k8s 臂 `namespace` 由可选改必填(缺它的 `config.d/remote-exec.json` 被拒);**B3** 写闸对必需坐标的值:空串或只含空白一律按缺(所有车道、所有必需坐标同一只判官)。
|
|
15
|
+
> 发版纪律按消费方在**运行期**的可见性判档,不看 diff 大小。
|
|
16
|
+
> 一件事,一条律:**k8s 车道的沙箱命名空间是部署自己声明的坐标 —— 车道表不能再把它放在「可选」栏**。消费方按本表行拒启:服务端的必需 env 门按本表行 `requiredEnv ∪ requiredSecretEnv` 拒启,部署面的车道清单读同一行;
|
|
17
|
+
> 表此前把它写在 `optionalEnv`,写闸与这道门因此放行不带它的 k8s 声明:同一件事,表、写闸、文件契约各说各话。现在只有一种:`EXECUTION_LANES` 的 k8s 行 `requiredEnv` = `["K8S_SANDBOX_IMAGE", "K8S_NAMESPACE"]`,`optionalEnv` 去掉它;其余六行逐字不动。
|
|
18
|
+
> 导出面:包根 / `./types` / `./node` / `./execution-lanes` 的名字零增删;答案变的是常量 `EXECUTION_LANES` 的 k8s 行、读它的写闸与读函数,以及 `RemoteExecSpec` 的 k8s 臂(见节末机器重算表);判官 `isBlankCoordinate` 在包内私有、不进任何导出面。
|
|
19
|
+
> 一致性语料:条数不变;c59 / c61 / c64 三条拒因语料的输入各补一格 `coords.K8S_NAMESPACE`(它们要打的格不是命名空间,不补则各多一条拒因、不再「只因这一格被拒」),三条的拒绝态答案与金样一字不动;其余语料逐字同。
|
|
20
|
+
|
|
21
|
+
### BREAKING 清单(逐条三问 + 下游动作)
|
|
22
|
+
|
|
23
|
+
**B1 · k8s 车道的 `K8S_NAMESPACE` 是必需坐标**(零别名、零过渡读法、不自动补写)。读车道表这一格的地方都从同一张车道表派生,没有第二份手抄的车道必需键清单(本地文件契约上的同一件事是 B2):
|
|
24
|
+
写闸 `WorkerSpec` —— 缺 ⇒ issue `lane "k8s" requires coords.K8S_NAMESPACE (the engine refuses to start without it)`,`path` 指 `runtime.execution.coords.K8S_NAMESPACE`;给空串或纯空白 ⇒ `required coordinate K8S_NAMESPACE of lane "k8s" is empty or whitespace-only — it counts as absent, and the engine refuses to start without it`(空串此前作为可选坐标照收;纯空白见 B3);
|
|
25
|
+
存下来的 workers 文档的两只读函数(`parseDomainLoud` / `buildEffective`);一次性迁移 SS-M4 对没写命名空间的 6.x k8s 行的终判。缺键拒句(`lane "k8s" requires coords.<键> (the engine refuses to start without it)`)的措辞本版不改(它本来就说「缺了就起不了服」,现在表与它一致);空值拒句本版改了措辞(B3:`is empty — the engine reads an empty value as unset and refuses to start` → `is empty or whitespace-only — it counts as absent, and the engine refuses to start without it`),点名同一格、`path` 不变。
|
|
26
|
+
**谁需要**:沙箱 pod 住哪只有部署知道,没有哪一方替它猜;表、写闸、中心表单、服务端的必需 env 门与部署面的车道清单读同一张表 —— 表说必需,保存时就拦下,而不是等服务起来才发现。
|
|
27
|
+
**谁受伤**:
|
|
28
|
+
① 存下来的 workers 文档里 `lane` 是 `k8s` 而 `coords` 没有 `K8S_NAMESPACE`(或为空串)的行:整域读的两只读函数 `parseDomainLoud` 与 `buildEffective` 遇到它就抛 —— workers 域不逐条抢救
|
|
29
|
+
(丢一行 = 对账器把这台 worker 当成不再声明、去退役它),一行不合 = 整个 workers 文档读不出,拒句点名 `runtime.execution.coords.K8S_NAMESPACE`(响亮,不静默丢行);
|
|
30
|
+
按行读的 `readWorkersRows`(web-admin 的 workers 读口)则把这一行扣下(`held`,带原文与拒因,`field` 指到同一格),其余行照常进 `doc`;
|
|
31
|
+
② 保存 k8s worker 的写入席(`WorkerSpec.safeParse`、中心表单):不带命名空间的写入被拒;
|
|
32
|
+
③ 一次性迁移 SS-M4:没写命名空间的 6.x k8s 行(此前靠内置缺省命名空间)⇒ `refused` 点名同一格、行原样留着 —— 迁移不替运维猜命名空间;
|
|
33
|
+
④ 按本包车道表算「缺了就起不了服的键」的消费方(服务端的必需 env 门按本表行 `requiredEnv ∪ requiredSecretEnv` 拒启、部署面车道清单读同一行、壳的执行车道预检):k8s 的必需键多一名,提货后随表重算。
|
|
34
|
+
不受伤:本机 `config.d`(workers 是非便携域,本机腿不读它);其余六条车道;带齐两键的 k8s 行;语料里的 `c52`(合法 k8s 行本来就带 `K8S_NAMESPACE`)。
|
|
35
|
+
**补偿**:给每个 k8s 行补一格 `K8S_NAMESPACE`,值 = 部署里沙箱 pod 所在的命名空间;**此前靠引擎内置缺省命名空间 `sandboxes` 的部署写 `sandboxes` 即与升级前同**。
|
|
36
|
+
落点看行的形:现行形的行写 `runtime.execution.coords.K8S_NAMESPACE`;还没过 SS-M4 的 6.x 行(仍带 `env.REMOTE_EXEC`)写它自己的 `env.K8S_NAMESPACE` 再跑迁移(迁移把它搬进 `coords`;往 6.x 行里加 `runtime.execution` 是新旧两形并存,迁移拒、不替运维挑赢家)。
|
|
37
|
+
**部署序:先补键、后升级** —— 14.8.x 上 `K8S_NAMESPACE` 本来就是合法的可选坐标,现行形的存量文档可以在旧版本下先补齐,升级时没有新旧两版同时在读的窗。
|
|
38
|
+
**下游**:中心(web-admin)的 k8s 写入路径要带命名空间,存量 workers 文档先补键;服务端提货后必需键门与车道清单随表重算(清单由同一张表生成);壳提货后 k8s 车道的半组判定多一名;三方各自按能力表态制回帖。
|
|
39
|
+
|
|
40
|
+
**B2 · 本地文件契约 `RemoteExecSpec` 的 k8s 臂:`namespace` 由可选字段改必填**(`config.d/remote-exec.json` 里 `provider` 为 `k8s` 的那一臂;零别名、零过渡读法)。B1 说的那件事在文件契约面的另一份说法 ——
|
|
41
|
+
v14.8.0 时两处都说可选;B1 把车道表收成必需之后,文件契约若仍说可选,同一个部署声明沙箱 pod 住哪,读两个地方就得两个答案 —— 所以同版把文件契约收成同一个答案:也要求写。判据 = 非空串(与 `image` 同一只 `execStr`,含不许 NUL)。
|
|
42
|
+
入口同答:`RemoteExecSpec.safeParse` / `validateRemoteExec` / `parseRemoteExecDocument` 拒(issue 指到 `namespace`);`loadRemoteExec` 读到这样的文件抛(文件存在即赢、存在而坏即抛,不静默退回 host 默认);`writeRemoteExec` 不落盘。
|
|
43
|
+
**谁需要**:一件事一个答案 —— 文件契约与车道表对「命名空间要不要写」不再各说各话。
|
|
44
|
+
**谁受伤**:`config.d/remote-exec.json` 里 k8s 臂没写 `namespace` 的部署:读侧 `loadRemoteExec` 抛,写侧(配置编辑器经 `writeRemoteExec`)不带命名空间的 k8s 写入被拒。不受伤:其余五个 provider;带 `namespace` 的 k8s 文件。
|
|
45
|
+
**补偿**:补 `namespace`,值 = 部署里沙箱 pod 所在的命名空间;**此前靠引擎内置缺省命名空间 `sandboxes` 的部署写 `sandboxes` 即与升级前同**。14.8.x 下 `namespace` 本来就是合法的可选字段,先补后升级,没有新旧两版同时在读的窗。
|
|
46
|
+
**下游**:读写 `config.d/remote-exec.json` 的消费方(读 `loadRemoteExec`、写 `writeRemoteExec`),提货后按同一句话补。
|
|
47
|
+
|
|
48
|
+
**B3 · 写闸对必需坐标的值:空串或只含空白一律按缺**(所有车道、所有必需坐标同一只判官 `isBlankCoordinate`:值 `trim()` 之后为空;零别名、零过渡读法)。此前写闸只判 `=== ""`,`" "` 一类纯空白被当成「写了」放行 —— 保存成功、写了却等于没写。
|
|
49
|
+
拒句同 B1 的空串句(`required coordinate <键> of lane "<车道>" is empty or whitespace-only — …`),`path` 指到该坐标位。判官只判「是不是空白」,不 trim 值、不管值的格式(`" sandboxes "` 照过 —— 格式不在这一条的范围);可选坐标不走它(个别可选坐标上空串有语义,如 `K8S_RUNTIME_CLASS` 的空串 = 不用 Kata)。
|
|
50
|
+
**谁需要**:一个必需坐标写成纯空白,效果与没写一样;写闸放行它,就是保存时不拦、到用的时候才失败。
|
|
51
|
+
**谁受伤**:必需坐标(任一车道:`K8S_SANDBOX_IMAGE` / `K8S_NAMESPACE` / `SSH_HOST` / `SSH_USER` / `ADB_SERIAL` / `DOCKER_IMAGE`)写成纯空白的声明(病态输入,正常写入不会有);存下来的 workers 文档里有这样的行,处置与 B1 的缺键行相同(整域读函数抛、`readWorkersRows` 扣下并指到该格)。
|
|
52
|
+
**补偿**:拒因点名那一格 —— 填真值(必需坐标缺席同样被拒,删掉那一键不是出路)。
|
|
53
|
+
**下游**:同 B1。
|
|
54
|
+
|
|
55
|
+
### 钉
|
|
56
|
+
|
|
57
|
+
`test/k8s-namespace-required-15.0.0.test.ts`(四面:表行〔必需栏恰为两名、次序镜像在前、可选栏不再有它、归属索引的 required 位、别的车道必需栏一格没动〕· 写闸〔只给镜像 ⇒ 恰一条 issue 指到该格、给空串 ⇒ 「必需坐标为空」一条、两键都给 ⇒ 过、一键不给 ⇒ 两条〕·
|
|
58
|
+
存下来的 workers 文档〔`parseDomainLoud` / `buildEffective` 抛并点名该键、`readWorkersRows` 扣下这一行并把 `field` 指到该格、补键后同一份文档读进、没有 k8s 行的文档不受影响〕· 随包文字〔升级指引与本节写明迁移动作,README / `llms` 里没有一句把这一键说成可选〕);
|
|
59
|
+
B3 的钉在同一文件:必需坐标〔`K8S_NAMESPACE` 取空格 / 制表 / 换行 / U+00A0 / U+FEFF / 混合 ⇒ 各恰一条 issue 指到该坐标位 · 正控:`"sandboxes"` / `" sandboxes "` / `"Sandboxes"` / `"a/b"` 照过 · 每条车道的每个必需坐标〔从表现算〕写成纯空白 ⇒ 都有 issue 指到自己的坐标位 · 可选坐标纯空白照收 · 结构:写闸读判官、源里没有 `value === ""` 一臂 · 存下来的文档同缺键行〕;
|
|
60
|
+
B2 的钉在同一文件:本地文件契约面〔k8s 臂必填字段恰为 `provider` / `image` / `namespace`(逐字段问 `undefined` 过不过,不手抄)· 只给镜像 ⇒ 恰一条 issue 指到 `namespace` · `validateRemoteExec` / `parseRemoteExecDocument` / `writeRemoteExec` / `loadRemoteExec` 同答 · 补键后照读 · 别的 provider 不受影响〕;
|
|
61
|
+
`test/remote-exec.test.ts` / `test/remote-exec-migration-11.3.0.test.ts` / `test/url-shape-11.0.0.test.ts` / `test/custom-issue-params-11.3.0.test.ts` / `test/file-edit.test.ts`(k8s 臂夹具补 `namespace`,各格仍只因自己要打的那一处被拒);
|
|
62
|
+
`test/execution-lanes-7.0.0.test.ts`(两处镜像钉改成现状:k8s 必需键并集含它、可选栏逐行快照去掉它;钉的注释不再说「引擎 `REQUIRED_ENVS_BY_LANE` 逐字」—— 服务端已直读本表);
|
|
63
|
+
`test/conformance-workers-7.0.0.test.ts`(三条拒因语料「issue 恰一条」原样绿);`test/one-time-migration-ss-m4-workers.test.ts`(没写命名空间的 6.x k8s 行 ⇒ refused 点名该格、文档同一引用;多租户格补命名空间);`test/file-edit.test.ts`(基准 worker 补命名空间)。
|
|
64
|
+
|
|
65
|
+
**自 v14.8.0 起运行期代码有改动的公开导出(机器重算;上界 —— 可达 ≠ 一定变,不可达 ⇒ 一定不变;本版新增的导出不入表)**
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
DOMAINS [., ./types]
|
|
69
|
+
DOMAIN_SCHEMAS [., ./types]
|
|
70
|
+
EXECUTION_LANES [., ./execution-lanes]
|
|
71
|
+
FileConfigStore [./node]
|
|
72
|
+
NON_PORTABLE_DOMAINS [./conformance]
|
|
73
|
+
RESERVED_WORKER_ENV [., ./types]
|
|
74
|
+
RemoteExecSpec [., ./remote-exec]
|
|
75
|
+
WorkerSpec [., ./types]
|
|
76
|
+
WorkersConfig [., ./types]
|
|
77
|
+
buildEffective [., ./config-fns]
|
|
78
|
+
deriveLocalExpectation [./conformance]
|
|
79
|
+
deriveWireExpectation [./conformance]
|
|
80
|
+
domainFilePath [./node]
|
|
81
|
+
emptyEffective [., ./config-fns]
|
|
82
|
+
foreignKeysOf [./conformance]
|
|
83
|
+
isDomainName [., ./types]
|
|
84
|
+
loadLocalConfig [./node]
|
|
85
|
+
loadRemoteExec [./node]
|
|
86
|
+
migrateConfigDirectory [./node]
|
|
87
|
+
migrateConfigDocuments [./node]
|
|
88
|
+
migrateEffectiveSnapshot [./one-time-migrations]
|
|
89
|
+
migrateEnvModelsDecision [./node]
|
|
90
|
+
migrateStoredDocuments [./one-time-migrations]
|
|
91
|
+
parseDomain [., ./config-fns]
|
|
92
|
+
parseDomainLoud [., ./config-fns]
|
|
93
|
+
parseDomainSalvaged [., ./config-fns]
|
|
94
|
+
planEnvModelTableWrite [.]
|
|
95
|
+
readBundle [./node]
|
|
96
|
+
readEffectiveWire [., ./config-fns]
|
|
97
|
+
readWorkersRows [.]
|
|
98
|
+
resolveLegExpectation [./conformance]
|
|
99
|
+
runConformance [./conformance]
|
|
100
|
+
urlSeatPatterns [./one-time-migrations]
|
|
101
|
+
validateDomain [./node]
|
|
102
|
+
validateRemoteExec [./node]
|
|
103
|
+
writeBundle [./node]
|
|
104
|
+
writeDomainFile [./node]
|
|
105
|
+
writeRemoteExec [./node]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 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 与上游漂移门 ⑧ / ⑪
|
|
109
|
+
|
|
110
|
+
> 档位(按发版纪律):**minor** —— 加性:新导出(包根 +53 名,其中两只组拒码常量同时在 `./fleet` 上;`./node` +9 名。包根:根级文档的名字、闭集答案表与读数身份拼法、原文判官两只、env 模型表读法与计划助手族、两只组拒码常量、中心引用解析口 `resolveCenterRefs` 与它的两张词表和读数型、记录读器 `readPlainStringRecord` 与它的原因词表 / 保留成员名 / 读数型、MCP 服务器名的保留前缀 `REMOTE_MCP_SERVER_NAME_PREFIX` 与请求面 MCP 名判官一对 `isUsableMcpServerName` / `describeBadMcpServerName`;`./node`:决定文档与运行记录的两只读者、一只写函数、一次性迁移 SS-M11 的迁移体与它的 I/O 座;导出面登记表合计〔同名在多条子路径上各计一次〕1396 → 1460 名);
|
|
111
|
+
> 公开闭集拓宽五处:`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)。
|
|
112
|
+
> `McpServerSpec` 多两只可选键:`sessionMetaKey` 与 `startupTimeoutMs`(都解析透明,`PARSE_TRANSPARENT_KEYS` 各多一行,接在表尾);不带这两只键的条目解析结果逐字不变;
|
|
113
|
+
> 带着它们的条目,v14.7.0 剥掉这两把键并报 `unknown-keys-dropped`,本版原样带过、不告警。归 minor 的依据是「发版纪律」的「新键 / 新导出 / 词表加词 = minor:可选键(无默认)」;
|
|
114
|
+
> 两把键解析透明 ⇒ 本包一字不拒、不是校验收紧,坏值由引擎拒(存量里写着有效值的条目,交给引擎后按它生效;写着坏值或 `null` 的,此前键被剥掉并报 `unknown-keys-dropped`,本版原样交给引擎 —— 见下文两条各自的「谁受伤」)。
|
|
115
|
+
> **读口行为变化有两处**:① `readModelRefusals` 对 `plane` 是这两只新码的体,以前把码当未知收窄成缺席,现在读出来;② `readWorkersRows` 扣下带旧字段的行时,`held[].reason` 改了措辞(`field` 不变;见下文「读口 `readWorkersRows` 扣下带旧字段的行时」那一条)。别的既有导出答案逐位不变(`McpServerSpec` 族另有一处:上一行的两只新键;`readEntryIdentity` 改调共享的「显式停用」读法,同一读法搬家,钉逐形核过;搬出来的那一次描述符读在自有描述符读法的普查登记表里按名多登一处,访问器判定用内核唯一拼法 `isAccessorDescriptor`;`materializeLocalRoot` 不是读口,另有一处变化:它铸的临时根里多一个心跳文件 `.alive`,本进程第一次铸根时顺带清扫心跳停了 60 分钟以上的旧件,见下文临时根那一条)。
|
|
116
|
+
> 没有新子路径、没有删改的导出。
|
|
117
|
+
> **SS-M11 的窗**:本包出组拒码常量 `CATALOG_REPLACES_ENV_MODELS_CODE` 与决定文档供引擎读,强制在引擎一侧,今天引擎(`@sema-agent/core` 7.58.0)不拒这个组;所以窗的起算点 = 引擎开始强制这道闸的那一天,到期日 = 起算日 + 30 天;本包登记表里 SS-M11 那一行按本包的发布日计,管的是本包何时能删迁移码。
|
|
118
|
+
|
|
119
|
+
- **配置根上的两份根级文档 + 两只读者 / 决定文件的唯一写函数(`src/config-root-documents.ts` 包根,`src/config-root-node.ts` 经 `./node`)**。「一份管模型面的 `config.d/models.json` 能不能顶替 env 模型表」是**配置根级的部署决定**,
|
|
120
|
+
落在 `<根>/config.env-models.json`(`ENV_MODELS_DOCUMENT`,`{"envModels":"replaced"|"kept"}`,strict);一次性迁移的运行记录**按 id 一份**,落在 `<根>/config.migration.<id>.json`(今天只有 SS-M11:`{"migration":"SS-M11"}`,strict;没有聚合文件、没有追加写)。
|
|
121
|
+
两份都在 `config.d` 之外 —— 已核的各版文件仓库只扫 `<根>/config.d`(抽样,不证全称),被检视的配置读者不把它们当域文档:写者不探引擎版本。`CONFIG_ROOT_DOCUMENTS` 是两份的名单(引擎的目录信任判官、客户端的持久文件登记取它,不手抄)。
|
|
122
|
+
读只有两只:`readEnvModelsDecision(root)` ⇒ `absent | replaced{digest} | kept{digest} | unreadable{bad_json|bad_shape, digest} | unreadable{io, errno}`(闭集;**只有文件不存在才是 `absent`** —— 截断的文件、目录、悬空的符号链接都读不懂;
|
|
123
|
+
有字节的四臂带同一次读的字节 SHA-256 前 16 位,没字节的带 errno;可信属主的 0000 文件读恒 `EACCES`,`io` 可以是持久的)· `envModelsDecisionTag(decision)` 是读数身份的一种拼法(`absent` / `d:<digest>` / `io:<errno>`),
|
|
124
|
+
引擎读这份决定时(今天引擎还不读),「每份内容只告警一次」与本地 etag 都取它,不再开文件;`readMigrationMarker(root, id)` ⇒ `absent | present | unreadable`(「跑过」只问这一份文件,读不懂的按跑过处置)。两只的读数从不抛(`readMigrationMarker` 只对不写运行记录的 id 抛 `TypeError`,不拿它拼路径);都接一只注入的 I/O 座(`ConfigRootIo`,真绑定 `nodeConfigRootIo`,读字节)。
|
|
125
|
+
决定文件的写只有一只(迁移铸确认也经它的 `createIfAbsent`;运行记录不经它,由迁移独占创建):`writeEnvModelsDecision(root, "replaced"|"kept", { write })` —— `overwrite`(写者:用户确认之后整份覆盖,读不懂的旧文件也换掉)/ `createIfAbsent`(不存在才建,临时文件 fsync 后 `link` 成目标名;只有 `EEXIST` 算已在 ⇒ `present`、一字不动,
|
|
126
|
+
悬空的符号链接也算已在)。模式与 `config.d` 域文档**同一条规则**(原子写器的 `config-document` 类,模式表不变):新建 0o644 经进程 umask(更严的 umask 只会更严:umask 077 下是 0o600),已有的文件沿用它原来的模式
|
|
127
|
+
(运维 chmod 过的决定文件不被一次覆盖写改回;一份被改成 0000 的决定文件读恒 `EACCES`,要修由运维自己 chmod)。各端自写在 umask 002 下落 0664,会被引擎的目录信任判官拒 —— 一律调写函数。本包不判属主 / 写位:那是引擎在读之前的判官的事。
|
|
128
|
+
三问 —— **谁需要**:引擎(本地配置腿强制这道闸时读决定、起服时调迁移读写两份;今天引擎都还没接)与三个写者(写目录之前读决定、确认后写 `replaced`);**谁受伤**:无 —— 新导出、新文件名,没有既有读者;
|
|
129
|
+
**补偿**:无需。钉:`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 ·
|
|
130
|
+
digest 按内容 · 读数身份三拼法 · 聚合形旧草案文件一律不读 · overwrite / createIfAbsent 两写法 · 决定文件一个写点(结构:迁移经写函数)· 竞态不吞 · 与域文档同一条模式规则(umask 077 ⇒ 0o600、022 / 002 ⇒ 0o644;逐 umask 与 `config-document` 对拍,退化的 umask 也一样)· 已有 0600 / 0640 / 0664 / 0000 的决定文件被覆盖写后模式不动 · 写失败点名路径 ·
|
|
131
|
+
根级文档对文件仓库惰性: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 与精确模式一臂)。
|
|
132
|
+
- **一次性迁移 SS-M11:配置根上铸一次「env 模型表被目录顶替」的决定(`migrateEnvModelsDecision`,`./node`;登记见 `ONE_TIME_MIGRATIONS` 那一行)**。决定文档出现以前,管模型面的目录**静默**整表顶替 env 模型表;
|
|
133
|
+
引擎开始按决定执法之后,存量根要保住这个读法。规则一条:**运行记录缺席才跑;先读决定 D0 与 models.json 管不管 M0,再以「不存在才建」写运行记录 —— 建成的那个进程是本根唯一的铸造者,别的进程见 `EEXIST` 即停、永不铸;
|
|
134
|
+
铸造者只在「D0 缺席 ∧ M0 管」时以「不存在才建」铸 `replaced`(env 表声没声明都一样:保的是升级前「管模型面的目录整表顶替」的读法),此后不再读决定**(D0 是预置的 `replaced` 而它在选举之后被删 ⇒ 不重铸;D0 缺席而选举之后有人写了 `kept` ⇒ `exists`、一字不动)。
|
|
135
|
+
结局闭集 `EnvModelsMigration`:`already_ran` / `marker_unreadable`(按已跑处理)/ `not_minter` / `marker_write_failed{errno, error, wayOut}` / `recorded{createdRoot, decision, mint}`,`mint` = `minted` / `exists` / `failed{errno, error, wayOut}` /
|
|
136
|
+
`skipped{decision_present|not_managing}` / `models_unreadable{errno, error, wayOut}`(判序如列;只有 `ENOENT` 算没有 models.json,坏文件算不管;别的读错不折成「不管」、单列一词 —— 要运维动手:不替人铸(fail-closed),带 errno(底层错误带码时;悬空链接 / 不是常规文件没有)与手工恢复句,运行记录已在 ⇒ 此后不再尝试);env 只在铸成之后读一次、只读两槽;铸了时带被顶替的 env 模型(名 + 槽 + 来源键;env 表没声明 ⇒ 空),调用方据此发**一行**告警(本包不出声)。不建 `config.d`;根缺席 ⇒ 逐级以恰是 0o700 的目录建根(umask 0777 下也进得去)、只放运行记录。
|
|
137
|
+
运行记录在场之后,决定缺席 ≡ `kept`,读这份决定的一律这么读(引擎强制这道闸之后、迁移、写者)。撤销的终局按运行记录的创建者算:只有创建者能写确认;以运行记录在场为前提,创建者写完 / 判定不写 / 失败(结局 `minted` / `exists` / `skipped` / `models_unreadable` / `failed`,调用方各记一行)之后,删掉决定文件就是撤销且是终局(删掉运行记录再起服 ⇒ 迁移重跑,决定缺席即重铸);其它副本读到记录在场 = 已跑、跳过(或得 `EEXIST` 答 `not_minter`),它们的返回不构成终局;创建者当选到结局落定之间的窗口在挂起 / I/O 卡顿下无上界,窗口内唯一安全的撤销是写 `kept`(独占创建护住它;删掉的会被创建者照它先读的 D0 铸回);创建者在写确认之前崩溃 ⇒ 永久 `kept`(fail-closed,披露)。收据词表尾插 `minted`(只属 SS-M11:在缺席处写下一份新事实,不改任何已有的值);
|
|
138
|
+
登记表旧形待遇尾插 `read-as-default`(旧形是文档的缺席,读者按缺省读、没有拒句可点名)。窗与删法只在登记表那一行;删码之后缺席读作 `kept`;引擎强制这道闸之后,出路由它的组拒句点名(今天引擎不拒这个组)。
|
|
139
|
+
三问 —— **谁需要**:升级前已经用目录顶替 env 模型的本地部署(升级后照旧由目录服务,只多一行告警点名被顶替的 env 模型);**谁受伤**:① 迁移窗内首启、预置了管模型面目录的**新装**根 —— 迁移分不出「升级前」与「新装」,一样铸 `replaced`(告警点名);
|
|
140
|
+
② 整根只读(ConfigMap)的部署 —— 写不了运行记录就不铸,引擎开始强制这道闸之后升级当拍目录被拒、换 env 模型服务(今天引擎不拒这个组);③ 进程恰在写完运行记录、铸确认之前中断,或铸失败、或那一刻 models.json 读不出的根 —— 此后无人再铸,换 env 模型服务(披露);
|
|
141
|
+
④ 创建者当选到结局落定之间(窗口在挂起 / I/O 卡顿下无上界)有人删了确认 —— 被创建者照先读的 D0 铸回;其它副本的返回不构成终局(披露);⑤ 配置根缺席的部署 —— 迁移以引擎进程的用户、0o700 建根,与引擎不同用户运行的客户端之后进不去(披露);**补偿**:①:删掉决定文件或写 `kept`(运行记录在,迁移不再跑;下一次起服生效 —— 运行中的引擎保留它在服的那一代目录);②:升级前把 `config.env-models.json` 放进同一份 ConfigMap 的配置根;
|
|
142
|
+
③:按结局里的手工恢复句手写决定文件;④:窗口内撤销写 `kept`(不存在才建守住它;删除只在运行记录在场、创建者结局落定之后是终局);⑤:客户端在引擎首次起服之前自己建好配置根。
|
|
143
|
+
钉:`test/ss-m11-env-models-migration-14.8.0.test.ts`(登记 · 记录在场跳过 / 坏记录(语料每种 + 0000)按已跑 / 聚合旧草案文件不影响判定 / 一次性 · 铸的两合取逐格 · env 表未声明(含只有 cheap 槽)照铸、被顶替的为空,纯空白照点名 · env 表空且目录不管 ⇒ not_managing ·
|
|
144
|
+
不管 / 无 models.json / 坏文件不铸 · models.json 读错(真 0000 / 注入 EIO / 悬空链接 / 目录占位)⇒ models_unreadable、不铸、带 errno 与恢复句,决定已在时判序先答 decision_present · 带 BOM 的 models.json 照样算管 · 不建 config.d · 语料逐格处置(只有 D0 缺席铸)· 读写顺序 · 记录建不成不铸(注入 + 真只读根,带 errno 与恢复句)·
|
|
145
|
+
独占创建与唯一铸造者:建记录后写 kept ⇒ exists · 两副本交错 B 永不铸、删后不铸回 · 预置 replaced 建记录后删 ⇒ 不重铸 · 跨副本 ②″(B 返回之后删 ⇒ 创建者铸回 / 窗口内写 kept 护住 / 创建者写前崩溃 ⇒ 永久 kept)· 三处文字一致 · 铸失败 ENOSPC / EIO / EPERM / 硬链接不被支持 ⇒ failed 且不重试 · 中断在记录之后不补铸 ·
|
|
146
|
+
根缺席 umask 002 / 0777 下逐级 0o700 · 铸出的确认与运行记录与域文档同模式规则(umask 077 ⇒ 两份 0o600、022 ⇒ 0o644));`test/one-time-migrations-11.0.0.test.ts`(闭集尾插)。
|
|
147
|
+
- **「目录管不管模型面」的原文判官 `catalogManagesModelPlane(doc)` 与计数 `enabledEntryCount(doc)`(`src/catalog-manages.ts`,包根)**。各端各写一份「有没有启用条目」的判法,`enabled` 怎么读各说各的。
|
|
148
|
+
现在一条律:**`models` 里至少一条不是「显式停用」的条目 ⇔ 管**;「显式停用」= 纯 JSON 对象的自有数据属性 `enabled` 恰为 `false` —— 与被丢条目身份(`entry-dropped` 告警的 `identity.enabled`)读的是**同一只**谓词,本包只剩一份读法;
|
|
149
|
+
判官 = 计数 > 0。文档不是对象、`models` 缺席或不是数组 ⇒ 0;坏条目(不是对象)⇒ 计入(引擎读腿的载体半场同答:形不对 ⇒ 算管);读不出的值(撤销的 Proxy、访问器 / 隐藏键、类实例)⇒ `TypeError`(两个调用方失败方向相反,不替它们猜)。
|
|
150
|
+
读后表示(引擎自己的被丢条目载体那一半)不在本包。
|
|
151
|
+
三问 —— **谁需要**:引擎(强制这道闸时,「管不管」与日志计数调它;今天引擎还没接)、桌面与命令行(启用判改调它)、迁移与计划助手(本包内);**谁受伤**:无 —— 新导出;被丢条目身份的读数逐形不变;**补偿**:无需。
|
|
152
|
+
钉:`test/catalog-manages-14.8.0.test.ts`(逐形 14 格 · 只认自有数据属性 / 访问器零调用 / 原型污染不读 · 读不出抛 · 洞不是条目(只认自有下标,被污染的 `Array.prototype[i]` 不算条目)· 同一条坏条目:身份 `enabled:false` ⇔ 判官视作停用 · 结构:`readEntryIdentity` 调共享谓词的「形已判过」内核 `ownEnabledFlag`、不自读 `enabled`;同一条目经 `salvageEntries` 只被问 2 次原型,与 v14.7.0 同数)。
|
|
153
|
+
- **env 模型表的一份读法与写者的计划助手(`src/env-model-table.ts`,包根)**。`MODEL_ID` / `MODEL_CHEAP_ID` 两槽与它们铸出的角色底此前只在引擎里读;写者要在写目录之前知道「这道闸强制起来会不会拒、会顶掉什么」,没有同一份读法可调。
|
|
154
|
+
现在:`readEnvModelSlots(env)`(`MODEL_ID` 缺席 / 空串 = 没有主槽,纯空白算在场;cheap 槽四态 `EnvCheapSlot` + `mintedCheapId`)· `envModelTableDeclared(env)`(引擎一侧的顶替闸〔今天引擎还不强制〕与计划助手共用的那一只谓词)·
|
|
155
|
+
`buildEnvModelBaseline(env)`(条目 + 角色底 + `MODEL_VERIFIER` 读数;键名表 `ENV_MODEL_TABLE_KEYS`、别名键 `DEFAULT_MODEL_KEY`)。env 快照按「读到即算」(下标读)。
|
|
156
|
+
计划 `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`),判序:
|
|
157
|
+
目标腿不读 `config.d`(remote / none)⇒ 照写 → 候选不管 ⇒ 照写 → summarize 会悬空 ⇒ `fix_first`(不可确认)→ env 表未声明 ⇒ 照写、**不写确认** → 决定 `replaced` ⇒ 照写 → 确认键对得上 ⇒ 先写决定再写目录 → 否则问。
|
|
158
|
+
回执按**上游 id**:退场的 env 条目带 `servedAs`(候选里同 id 的启用条目名),每个 env 底角色带写前绑谁(env 表键 + 它解到的 id)与采纳后落点 `{name, id}`(缺省落点经 `resolveCatalogDefaultModel`;档位词 / CC 档位别名 / `best` 经候选的活跃档位组展开 —— 与引擎装配同一序:先摘掉绑到停用条目的档,再按 core 的档序向下落(绑到目录外的名过不了 schema:候选读不进 ⇒ `TypeError`);绑到停用条目或目录外、展开也落不到 = `dangles`);
|
|
159
|
+
回执带决定读数(读不懂的 —— 含持久 io —— 确认后整份覆盖);确认键 = 回执的定序 JSON,后果一变旧键就过期。候选读不出或整份读不进 models 域 ⇒ `TypeError`(先修文档,不替它算回执)。拒句投影 `envModelTableWriteRefusal(plan)`(`confirm` / `fix_first` 各一句;只带 env 键名、目录名与模型 id —— 可披露的原样、秘密形的打成记号;凭据与别的 env 值从不进句子)。
|
|
160
|
+
三问 —— **谁需要**:三个写者(命令行、桌面、客户端核心)在改写 models.json 的每一条路上调它;引擎改读 `readEnvModelSlots` / `buildEnvModelBaseline`(不再自写一份);**谁受伤**:无 —— 新导出;**补偿**:无需。
|
|
161
|
+
可用性(引擎的 brain 腿、端点、读腿逐条丢)计划判不了;决定性的 summarize 那一格由引擎的不变量兜。
|
|
162
|
+
钉:`test/env-model-table-plan-14.8.0.test.ts`(两槽读法 / 纯空白在场 / 原型分层快照 · 角色底(verifier 指 cheap、code 角色装饰、rejected、表空)· 判序七格各一 · 回执逐格(同 id 异名 servedAs、同名异 id 落点 id)· 档位缺省落点 · 继承的档位词按活跃组展开(flash / pro / opus / best,绑到停用条目的档摘掉后沿档序落)· {select} 缺省 · 停用绑定 dangles ·
|
|
163
|
+
确认键定序且随后果变 · 语料逐格处置与持久 io 读数 · 入参闭集 / 读不出 / 读不进抛 · 拒句不回显凭据);`test/tier-words-parity-14.8.0.test.ts`(档序与 CC 别名表 = devDependency core 的两张表;展开逐夹具逐词对拍 core `expandTiers` 的摘后表,含不可服务绑定四形;core 对未摘的原表整张抛)。
|
|
164
|
+
- **两只模型面组拒码尾插:`CATALOG_REPLACES_ENV_MODELS_CODE` / `SUMMARIZE_MODEL_MISSING_CODE`(`src/fleet.ts`,包根与 `./fleet`)**。本地配置腿上「管模型面的目录会顶替已声明的 env 表、决定不是 `replaced`」的那道闸,与 summarize 不变量(每个任务都要解的 summarize 落不到可服务条目)
|
|
165
|
+
各有一只组拒码;码名是契约词,由本包定,本包出常量供引擎读;强制在引擎一侧,今天引擎(`@sema-agent/core` 7.58.0)不拒这两个组。`MODEL_PLANE_REFUSAL_CODES` 尾插 `config.catalog_replaces_env_models`、`config.summarize_model_missing`(序即声明序)。
|
|
166
|
+
三问 —— **谁需要**:引擎(铸码)与读 `/health.modelRefusals` 的管理面 / 三端(渲染「为什么目录没落地」);**谁受伤**:对组拒码穷举的读者 —— 提货时编译红;**补偿**:为两码各补一臂。读者没提货之前,`readModelRefusals` 照旧把两码收窄成缺席(读不出原因,但不误判)。
|
|
167
|
+
钉:`test/env-model-table-plan-14.8.0.test.ts`(码逐字 + 尾插)、`test/model-refusals-6.2.0.test.ts`(闭集六码)。
|
|
168
|
+
- **随包文字与导出面登记表**。README 新增升级指引一节(写者动作:读决定 → 计划 → 确认后经 `writeEnvModelsDecision` 写、不自写;撤销 = 删掉或写 `kept`;整根只读部署的升级前置步;中断与铸造窗口的披露;SS-M11 的窗的到期日公式)、
|
|
169
|
+
本地文件布局补两份根级文档与「根归客户端、引擎只写这两份」及写序(离线预置(引擎在本根起服之前铺好)时两份文档与域文档可按任意顺序写;运行时写序是定的:写者先写决定、再写 `config.d/models.json`(同一把锁),迁移先建运行记录、再铸决定)、功能清单一条;`docs/API-SURFACE.md` 重生成。
|
|
170
|
+
三问 —— **谁需要**:三端与引擎的接线者;**谁受伤**:无;**补偿**:无需。
|
|
171
|
+
- **符合性 kit 的临时根不再在进程死掉后一直残留(`materializeLocalRoot` 的临时目录收口,包内 `src/conformance/temp-roots.ts`,不进导出面)**。每个 case 的本地腿建一个 `<tmp>/settings-conformance-<随机>/`,清理只在适配器腿的 `finally` 里 ——
|
|
172
|
+
跑语料的进程被 `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()` 停定时器、再删根(不开不关任何文件);
|
|
173
|
+
进程以任何方式死掉(`process.exit`、未捕获异常、信号〔`SIGINT` / `SIGTERM`〕的默认动作、SIGKILL),心跳随它停,根留着,`.alive` 就变老。本进程第一次铸根时清扫:名字恰是 `<前缀><六位随机>`(`mkdtemp` 直铸的形)、是目录、属主是当前用户、根内有 `.alive` 常规文件、
|
|
174
|
+
且 `.alive` 的 mtime 早于 60 分钟(`STALE_LOCAL_ROOT_AGE_MS`)的旧件,整根删 —— 规则只有这一条:`.alive` 超过 60 分钟没更新之后,由下一轮第一次铸根时的清扫收。不问进程号、不问主机名:证据与文件系统同域,共享同一 tmp 的几个进程、几个容器(跨 pid 命名空间也一样)读到的是同一个答案,`.alive` 在更新的根不被清(限制见下);
|
|
175
|
+
根里没有 `.alive` 的(更早的版本铸的、运维手造的同形目录)一概不动,拿不准就不删;符号链接与文件不动;
|
|
176
|
+
② 正常路径:适配器腿 `finally` 里的 `cleanup()` 停这个根的心跳定时器、再删根(只删自己的根;不开不关任何文件;还在飞的一拍随后在已摘出目录的文件上做完、或打不开而落空,被吞掉),语料落盘的文件名闸不变。本模块不在进程上装任何监听(不碰 `exit`,也不碰 `SIGINT` / `SIGTERM`;定时器 `unref`,不拖住进程退出)—— 宿主自己的 `exit` / 信号处理器(含排空、含铸根之后才装的)一概不受影响,宿主的 `exit` 处理器里照样读得到根。
|
|
177
|
+
清扫是尽力而为:删不掉的(别的用户的、没权限的)留着,之后的清扫再试,不改一次符合性结论。本模块不读进程的钟:清扫的「现在」是刚铸出的根目录的 mtime(建 `.alive` 那一刻由文件系统打),心跳的 mtime 同样由文件系统打,二者同一只钟,进程自己的钟拨偏多少都不改变清扫的答案。
|
|
178
|
+
三问 —— **谁需要**:跑符合性语料的 cli / core(语料大、进程常被 CI 超时杀);**谁受伤**:心跳停了 60 分钟的活进程 —— 它的根会被别的进程当作死进程的根清掉,这是已知限制,不是本版给出的保证。心跳停有两种:事件循环停转超过 60 分钟(被 `SIGSTOP` 暂停、容器被冻结、停在调试器断点上);心跳连续 60 分钟写不进去(文件系统写满、被重挂成只读 —— 这一拍作罢、不抛、不报);
|
|
179
|
+
别的部署只多一次对 tmp 目录的读、每个根里一个空文件、每 5 分钟一次对它的一拍(一次打开、一字节写入与截回、一次关,不占常驻的文件描述符),以及清扫里对符合条件旧件的删除(名字恰是前缀加六位随机、是目录、属主是当前用户、`.alive` 老过年龄线才删;符号链接与文件不动);
|
|
180
|
+
**补偿**:`ALIVE_TOUCH_INTERVAL_MS`(5 分钟)与 `STALE_LOCAL_ROOT_AGE_MS`(60 分钟)是源里的具名常量、读得到,**不是旋钮**(没有 env、没有参数改它们);一个 case 的一条腿只要几毫秒、整套语料也只要几秒,60 分钟远长于一次正常的跑。
|
|
181
|
+
披露:`process.exit` 未 cleanup、未捕获异常、被信号的默认动作或 SIGKILL 收走的进程,都留一个根,`.alive` 超过 60 分钟没更新之后,由下一轮第一次铸根时清掉;宿主要在退出时即时收根的,在收尾处自己调 `cleanup()`。
|
|
182
|
+
宿主进程活着时,它铸出而没 cleanup 的根一直带着新鲜的 `.alive`,清扫不碰,进程死后才开始变老;
|
|
183
|
+
宿主活着却没 `cleanup()` 的根,它的心跳定时器(`unref`,不拖住进程退出)每 5 分钟(`ALIVE_TOUCH_INTERVAL_MS`)开写一次、直到进程结束 —— 代价是一次一字节的小文件写(一拍一开一关,不占常驻的文件描述符),补偿是调 `cleanup()`(契约本就要求);
|
|
184
|
+
心跳一拍可能卡在文件系统上(挂住的网络盘):上一拍还没做完时,到期的这一拍直接跳过(每个根一面在飞旗)—— 不叠写、不排队;挂住的那一拍做完(成功或失败)后旗复位,心跳照常。
|
|
185
|
+
`mkdtemp` 与写 `.alive` 之间被杀的进程留下一个没有 `.alive` 的根,清扫不认它,要清的手工删。
|
|
186
|
+
定时器取自 `node:timers`:只接管全局 `setInterval` 的伪计时器(vitest 的 `vi.useFakeTimers()` 默认如此)接管不了它;连 `timers` 模块一起接管、且先于本包首次 import 装上的伪计时器库会把这只心跳接过去,跑尽定时器的调用(如 `runAll`)会撞上它 —— 补偿是跑尽定时器之前先调 `cleanup()`(契约本就要求;它清掉的正是这只心跳)。
|
|
187
|
+
`.alive` 静态为空,只给清扫看:`loadLocalConfig` 只按名读 `config.d/<域>.json`、`config.d/remote-exec.json` 与 `.env`,列文件只列 `config.d/`,所以它不被 `loadLocalConfig` 自动读到;谁在 `.env` 里用 `file:` 显式引用它,读到的是空文件、被拒(`file:` 引用不接受去掉首尾空白后为空的文件,与引用一个不存在的文件同拒),不会解析成一个值;心跳写进去的那一个字节是换行(空白),只存在于心跳那两步之间,截回失败或进程恰在两步之间死掉时它会留在文件里,但 `file:` 引用读到后会去掉首尾空白,留着一个换行的文件去掉首尾空白后仍为空,照样被拒。
|
|
188
|
+
钉:`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` / 自然退出 / 未捕获异常三种退出);
|
|
189
|
+
SIGKILL 与没有宿主处理器的 SIGTERM / SIGINT 留一个根、进程死于该信号,清扫收掉它而 `.alive` 新鲜的 / 没有 `.alive` 的 / 异名的 / 文件 / 符号链接不动;宿主已装优雅退出处理器(`on` / `once`)时不被杀;宿主装着排空处理器(`on` / `once`,含铸根之后才用 `prependOnceListener` 装的)时信号到来、200 ms 后根与语料文件仍在、case 跑完;
|
|
190
|
+
宿主在投递中把监听整个换成自己的一只 ⇒ 根不动、宿主跑完;首次铸根的清扫中途来信号、宿主吞掉它 ⇒ 铸根照常完成、根还在;名字是 `<前缀><六位随机>`、根内是空的 `.alive`;铸了根没 cleanup 而手上没别的事 ⇒ 进程自己退出(定时器 `unref`);连 `timers` 模块一起接管、先于本包首次 import 装上的伪计时器库取走心跳,`cleanup()` 清掉的就是它;
|
|
191
|
+
根目录自己的 mtime 再老、`.alive` 新鲜 ⇒ 不动(铸它的进程早已死也一样),`.alive` 老了才清;没有 `.alive` 的同形旧目录(含真进程里 `mkdtemp` 直铸的)再老也不动;`.alive` 是目录或符号链接不算;名字形的边界与年龄线的边界;
|
|
192
|
+
铸根之后进程上每一种事件的监听数不变、`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`(文件名闸原样绿)。
|
|
193
|
+
- **「公开导出有哪些」全仓只枚举一遍(`scripts/api-surface.mjs` 的 `readModuleExports`,仓内脚本,不进包)**。导出面登记表的生成器与发版准备时重算「受影响导出表」的助手(`test/helpers/affected-exports.ts`)此前各枚举一遍 exports 表里每条子路径的导出:
|
|
194
|
+
生成器沿别名链逐跳问「是不是 type-only 再导出」,助手只问导出符号自己、再一步解到真身 —— 中间一跳是 `export type { X } from …` 的再导出链上,生成器答「只是类型」(运行期拿不到,登记表的「判据自证」格对着运行期模块对象核过),助手答「值导出」。
|
|
195
|
+
现在生成器接收一棵树的根(缺省本仓)并暴露符号级读法(子路径 + 名字 + 形 + 走完别名链的真身符号;别名链不设跳数上限,环靠「见过」收住),`computeApiSurface` 是它的投影,助手取它结果里形不是 `type` 的当公开值导出、删掉自带枚举。真树上两份答案今天逐名一致(651 个名字),`docs/API-SURFACE.md` 逐字不变。
|
|
196
|
+
三问 —— **谁需要**:发版准备的人(受影响导出表不会再把一个运行期不存在的名字报进表);**谁受伤**:无 —— 包内容与导出面不变,只改仓内脚本与测试助手;**补偿**:无需。
|
|
197
|
+
钉:`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`。
|
|
198
|
+
- **读口 `readWorkersRows` 扣下带旧字段的行时,`reason` 不再点名退役键、不再只叫人去跑迁移(`held[].reason`;`field` 不变)**。一行 6.x 形的存量行若被一次性迁移 SS-M4 拒了(例:`runtime.sessionBackend: "tidb"` 而没有 SQL 坐标),迁移不写回半成品、原行留在盘上;
|
|
199
|
+
读口再读到它,`reason` 是退役键墓碑的原句 —— `runtime.sessionBackend: runtime.sessionBackend was retired … data stored before 7.0.0: run the one-time migration SS-M4 …`:点名的是盘上的退役键,出路只有「去跑那条已经拒过的迁移」。
|
|
200
|
+
现在读口对墓碑 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` ——
|
|
201
|
+
退役键名只留在 `field`(结构化坐标,管理面据它标红),文字里没有;读口是纯函数、不知道这一行跑没跑过迁移,所以一句话对两种情形都成立。寻常坏行的 `reason`(`<坐标>: <句子>`)不变,读写两面(整域读、写闸、迁移收据)的墓碑原句不变。
|
|
202
|
+
实现:退役键墓碑(`retiredKey`)在铸造时登记进包内的墓碑表(键名、形、整句),读口按整句认出墓碑 issue、测试从表读名单。
|
|
203
|
+
三问 —— **谁需要**:读 `held[].reason` 把它渲染给运维的管理面(web-admin 的 workers 列表);**谁受伤**:把 `reason` 当机读字符串匹配墓碑原句的消费方(`reason` 一直是给人读的文字,机读的是 `field`)—— 提货后匹配落空;**补偿**:改读 `held[].field`(退役键的坐标)。
|
|
204
|
+
钉:`test/worker-held-sentence-14.8.0.test.ts`(迁移拒过的行 · 读口 held 句不含退役键名 · 墓碑表 ↔ schema 对账 · 七个旧字段 × 各一行:拒句不含表里任何退役键名、含现行位与读面整句、`field` 仍指到那个键 · 读面整句逐字)、`test/workers-rows-11.0.0.test.ts`(翻钉)。
|
|
205
|
+
- **中心引用的解析口 `resolveCenterRefs(refs, lookup)`(`src/center-refs.ts`,包根)+ 记录读器 `readPlainStringRecord` 公开导出、原因词写成闭集表(`src/mcp-request-entry.ts`,包根)**。中心名册里 MCP server 的 `envRefs` / `headerRefs` 只存变量名;此前本包只发这两张表的形、不发读它们的法 ——
|
|
206
|
+
变量没设、设了但读到的不是串、读它的时候抛了,留给每个宿主自己写,各按各的口径处置、各留各的句子。现在一个读法:**每个成员一句判定,整台一个结论;本口只判,不决定挂不挂**。
|
|
207
|
+
① 解析口:`refs` 经公开读器读(见 ②),读不出整张不交 ⇒ 整台 `refs_unreadable`、带读器的词、**一个变量都不问**;读器会把叫 `__proto__` 的串值成员剔出副本,本口不许这样悄悄丢一条引用再挂一台少了鉴权的 server,同样答 `refs_unreadable` / `member_name_reserved`。
|
|
208
|
+
**查找函数 `lookup(name)` 由调用方给,本口不读 `process.env`**(源里没有 `process` / `globalThis`);没给 / 不是函数 ⇒ `TypeError`(调用方的编程错,不折成成员的 `read_threw`,不回落任何环境)。
|
|
209
|
+
每个成员恰问一次 `lookup`(实参只有变量名),判成**一个词**:`resolved`(答了串,空串也是串)· `missing_name`(答 `undefined`)· `not_a_string`(答了别的种类;对普通对象做 `obj[name]` 读到继承来的函数就是这一形)· `read_threw`(`lookup` 抛了,不传出);一个成员坏了不早停。
|
|
210
|
+
整台结论三个词:`resolved` / `unresolved`(有成员没解到)/ `refs_unreadable`;**值只在 `resolved` 臂的 `values` 里**,`unresolved` 臂的行只有词(失败读数会进日志)。`refs` 是 `undefined`(没声明引用)⇒ `resolved`、零成员、零次查找,与空表同答。
|
|
211
|
+
词表常量 `CENTER_REF_FAULTS`(三词)· `CENTER_REFS_VERDICTS`(三词)`as const`,型由它推出;读数型 `CenterRefsResolution` 按 `verdict` 分三臂,消费方穷尽 `switch`。
|
|
212
|
+
② 记录读器:`readPlainStringRecord(v)` 加 `export`(判据与每一句原因句一字不改;传输读器读 `env` / `headers` 用的就是这一只),读数型 `PlainStringRecordRead` 与保留成员名 `PLAIN_STRING_RECORD_RESERVED_MEMBER`(`"__proto__"`)一并导出。
|
|
213
|
+
原因词写成一张闭集表 `PLAIN_STRING_RECORD_FAULTS`(`as const`,三词、`PlainStringRecordFault` 由它推出;`McpEntryKeyIssue.fault` 与读数的 `fault` 都引这个型,不再有字面量并集,词与 v14.7.0 的机读位同):`not_plain_record`(整张不是纯数据)·
|
|
214
|
+
`member_not_string`(成员的值不是串,或读成员就抛 —— 自有访问器抛了、描述符陷阱抛了;两种情形句子分说)· `member_name_reserved`(叫 `__proto__` 的成员;名坏优先,值坏或读不出时句子并列说全、词仍是这一个)。
|
|
215
|
+
传输读器的 `lost[]` 与 v14.7.0 逐位同答(机读位、句子、`member`、整键不交的处置)。
|
|
216
|
+
三问 —— **谁需要**:读中心名册引用表的宿主(客户端核心的本地腿、服务端的中心腿);**谁受伤**:无 —— 新导出,既有读口的答案不变;**补偿**:无需。
|
|
217
|
+
钉:`test/center-refs-14.8.0.test.ts`(成员三词各一格〔含每种非串形、五种抛法〕· 整台三词 · 不早停且每名恰问一次 · 空表 / 缺席 · 引用表读不出的十三形词与读器逐格同且一个变量都不问 · 不读 `process.env`〔AST 零 `process` / `globalThis` / `import.meta` / `node:` 引入,运行期 `process.env` 换成全陷阱抛的 Proxy,缺 lookup 抛 `TypeError`〕·
|
|
218
|
+
`unresolved` 臂不含变量值 · `Object.prototype` 数字键 setter 吞不掉 · 一事一源〔只引公开读器与读内核检视口〕· 词表穷尽 `switch`)、`test/plain-string-record-public-14.8.0.test.ts`(读器与词表导出同一引用 · 词表三词 · 成员级一词、句子分说 · 名坏优先 · 传输读器 `lost` 是读器读数的投影 · 声明只有一处 · 读器从不抛)、
|
|
219
|
+
`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` 各一条、导出面登记表。
|
|
220
|
+
- **MCP 条目的 `sessionMetaKey`(`McpServerSpec.sessionMetaKey`,解析透明;`PARSE_TRANSPARENT_KEYS` 多一行 `mcp.servers[].sessionMetaKey`,`validatedBy: "engine-ingest"`)**。引擎在发给一台 MCP 服务器的每个 `tools/call` / `resources/read` 的 `params._meta[key]` 里放发送腿自己的会话 id,`key` 由这台服务器的声明给;
|
|
221
|
+
此前本包的条目没有这把键,本地腿与 wire 腿都把它剥掉(`unknown-keys-dropped`),引擎读到缺席。现在逐字承运、缺席不铸键。这把键只有一只判官 —— 引擎包根导出的 `judgeSessionMetaKey`(类型、非空、上界、MCP `_meta` 键名文法、MCP 保留前缀、`progressToken`、引擎的 `sema/` 命名空间一只函数一并判,坏键在引擎的准备门整条拒、点名这把键);
|
|
222
|
+
本包不判(与 `toolAxes` 同形),任何 JSON 值原样过 —— 本包一字不拒、不是校验收紧,坏值由引擎拒。
|
|
223
|
+
三问 —— **谁需要**:要按(主体, 会话)保存状态的 MCP 服务器(声明一把自家前缀下的键,如 `computer-use/sessionId`);**谁受伤**:本包一字不拒、不带它的条目解析结果逐字不变;存量里已经写着这把键的条目 —— 此前这把键被剥掉并报 `unknown-keys-dropped`(没有效果),现在原样交给引擎:值合引擎判官的,引擎从此按这把键在发给这台服务器的每个 `tools/call` / `resources/read` 的 `params._meta` 里放发送腿的会话 id(运维当初写下的意图被兑现,行为随升级而变);
|
|
224
|
+
值不合的(含 `null`),交给引擎后引擎在准备门整条拒这一台服务器、点名这把键;
|
|
225
|
+
**补偿**:删掉这把键,或改成引擎收的键名。写声明的一方要早判,调引擎的同一只判官。
|
|
226
|
+
钉:`test/mcp-session-meta-key-14.8.0.test.ts`(三道门同答 · 缺席不铸 · 文档读法 / wire / 本地三处承运且零告警 · 任何 JSON 值原样过 · 登记行)、`test/upstream-drift.test.ts` 门 ⑩(引擎条目型有这把键、判官入参 `unknown` · 登记行点名它 · 语料上本包恒原样承运、引擎的答案逐值钉)、`test/parse-transparent-keys.test.ts`。
|
|
227
|
+
- **MCP 条目的 `startupTimeoutMs`(`McpServerSpec.startupTimeoutMs`,解析透明;`PARSE_TRANSPARENT_KEYS` 多一行 `mcp.servers[].startupTimeoutMs`,`validatedBy: "engine-ingest"`)**。引擎的每台 MCP 服务器可以带自己的启动界(整数毫秒):握手(连接 + initialize)与首次工具列举各带它,缺席读进程级的缺省界;它只界启动阶段,不动单次调用的超时。
|
|
228
|
+
此前本包的条目没有这把键,本地腿与 wire 腿都把它剥掉(`unknown-keys-dropped`),配置声明的服务器只剩进程级的缺省界。现在逐字承运、缺席不铸键。**格式与界由引擎判**:是不是数、整数性、范围只有一只判官 —— 引擎包根导出的 `mcpStartupTimeoutMs`,在拨号时判,不合 ⇒ 拒这一台服务器的拨号(其余服务器照挂)、点名 `config.mcp_startup_timeout_invalid`,不夹取、不当缺省;
|
|
229
|
+
本包不判(与 `sessionMetaKey` 同形),任何 JSON 值原样过 —— 本包一字不拒、不是校验收紧,坏值由引擎拒;哪怕只判「是不是数」,也是第二只判官的一部分,并且会让存量里已经带着这把键的条目由本包改拒,那才是校验收紧。写声明的一方要早判,调引擎的同一只。
|
|
230
|
+
三问 —— **谁需要**:有启动慢的 MCP 服务器、要单独放宽(或收紧)它的启动界而不想动进程级 `MCP_TIMEOUT` 的部署;**谁受伤**:本包一字不拒、不带它的条目解析结果逐字不变;存量里已经写着这把键的条目 —— 此前这把键被剥掉并报 `unknown-keys-dropped`(没有效果),现在原样交给引擎:值合引擎判官的,引擎从此按这把键给这一台服务器的启动设界(运维当初写下的意图被兑现,行为随升级而变);
|
|
231
|
+
值不合的(含 `null`),交给引擎后引擎在拨号时拒这一台服务器(其余服务器照挂,点名 `config.mcp_startup_timeout_invalid`);**补偿**:删掉这把键,或改成引擎收的整数毫秒。
|
|
232
|
+
钉:`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` 各一条。
|
|
233
|
+
- **MCP 服务器名的 `remote-` 前缀保留给设备执行端提供的远程 MCP 服务器(常量 `REMOTE_MCP_SERVER_NAME_PREFIX`;请求面 `isUsableMcpServerName` / `describeBadMcpServerName`)**。设备执行端提供的远程 MCP 服务器挂成 `remote-<名>`,工具名 `mcp__remote-<名>__<工具>`;
|
|
234
|
+
请求带来的 MCP 条目若用同一前缀,就与设备上的服务器同名、落进同一个命名空间。本节只落**加性的两面**:前缀常量,与请求面新增的 `isUsableMcpServerName`(= `isUsableWireName` 再加这一臂)和它的摘要 `describeBadMcpServerName`
|
|
235
|
+
(线上名本身坏的与 `describeBadWireName` 逐字同,保留名说那一句原因:`the "remote-" prefix is reserved for the remote MCP servers a device executor offers`)。比较按字面、区分大小写(引擎铸工具名前缀时保留大小写,`Remote-x` 不在这个命名空间里)。
|
|
236
|
+
请求面 MCP 服务器名的判官是 `isUsableMcpServerName`;`isUsableWireName` 不变,管 A2A 对端名(A2A 工具名前缀是 `a2a__`)。
|
|
237
|
+
**两面的读法不同**:请求面判官拒 `remote-` 前缀;配置面 `McpServerSpec.name` 今天照旧受理这个前缀(本包一字不拒;此前合法的名由本包改拒才是校验收紧,按「发版纪律」归 major)。
|
|
238
|
+
三问 —— **谁需要**:请求面的受理方与壳的发送前预检(拒带保留前缀的 MCP 条目名)、设备执行端的远程 MCP 挂载(名字空间不撞);**谁受伤**:本包的既有判官与配置面答案不变(加性);请求面受理方改调 `isUsableMcpServerName` 之后,请求带来的 `remote-` 起头的 MCP 条目名被拒、点名那一句原因;配置面已有的 `remote-` 起头名字照旧解析、不被丢;
|
|
239
|
+
**残留**:配置面可声明 `remote-` 起头的名,它与设备上同名的挂载服务器落进同一个命名空间 —— **补偿**:部署方自己不用这个前缀(存量里有这样的名,建议现在就改;工具名随之变,按名写的规则 / `allowTools` 跟着改)。
|
|
240
|
+
钉:`test/mcp-remote-name-reserved-14.8.0.test.ts`(配置面仍受理、三道门同答 · 非字面前缀照旧过(含 `Remote-x`)· 请求面与线上名判官差分 · 线上名判官不变 · 保留名的拒句),两版 zod(区间两端)各跑一遍。
|
|
241
|
+
- **引擎钉与上游漂移门 ⑧ / ⑪**:devDependency `@sema-agent/core` 7.53.0 → 7.58.0(锁文件多一只传递依赖 `@sema-agent/primitives`)。引擎的 MCP 条目传输形删了字面值 `env` / `headers`、加了不透明引用 `credentials`;请求面的传输形(`RequestMcpTransport`)**不变**,仍收字面 `env` / `headers`,
|
|
242
|
+
它与引擎传输形的关系改由一张处置表对拍:两边每种传输的每个键恰好一行(`engine` 同键同型 · `literal` 请求带来的字面件,交给引擎之前由消费方拿走、按自己的对端座答 · `carried` 本包承载的 `assertionHeader` · `engine_only` 请求面不收的 `credentials`),表的行 ≡ 两边键的并集,谁多一个键都编译红;读器交出的键 ≡ 请求型的行。
|
|
243
|
+
同一张处置表的形铺到配置面的 `McpServerSpec`(门 ⑪:条目层与两种传输层各一张表,判官与 ⑧ 是同一只):引擎的每个条目键在配置面恰好一行 —— 同键同型 / 解析透明 / 本包承载 / 有意不读 / 欠账未裁,
|
|
244
|
+
引擎加键、本包加键而没写行、行的词与两边的型对不上、两边的传输种类不同都编译红;欠账行是公开的未决(传输层 `credentials`:配置面没有这把键,也没有一处写明是有意不读),门只保证欠账不被悄悄跳过;这一门本身不加键、不改任何校验。
|
|
245
|
+
其余镜像门(② ③ ④ ⑤ ⑥ ⑦)对新钉逐门复核同。新钉的引擎把模型座的「没写」收成只有缺席(`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(本包的答案与引擎对同一绑定的拒各钉一格)。
|
|
246
|
+
三问 —— **谁需要 / 谁受伤 / 补偿**:无运行期变化(只改 devDependency 与测试)。钉:`test/upstream-drift.test.ts` 门 ⑧(每种传输过一遍共用判官 · 处置表读数 · 读器的键 · 五颗牙)· 门 ⑪(三张表过判官 · 读数 · 欠账行清单 · 不读的键各有 why · 表的本包一侧 ≡ 真 zod 形的键 · 十一颗牙 · `startupTimeoutMs` 对拍引擎判官的一格)。
|
|
247
|
+
- **`SERVING_LEASE_DRIFT_MARGIN_MS` 的注改成现行用法(只改注,值与导出面不动)**。旧注写的是「中心把它加到自己记录的租约到期上再判过期」,这条用法已退役;现在的注写它的用法:读引擎报的 `EngineSignal.leaseUntil`(引擎时钟上的 epoch 毫秒)时,读的一方拿它对**自己的** now,两个钟不必一致 ——
|
|
248
|
+
「租约还在」只在 `leaseUntil >= now + SERVING_LEASE_DRIFT_MARGIN_MS` 时成立,取保守方向(偏斜不超过余量时,还在的租约可能读短,过了期的租约绝不读成还在);它不属于租约本身的计时(引擎按自己的单调钟执法,中心按库时间记自己的到期,两边不比钟)。钉:`test/engine-signal-8.1.0.test.ts`(注里有这条用法、没有旧用法;值仍是 `DEFAULT_HEARTBEAT_MS`)。
|
|
249
|
+
- **披露(补全 14.7.0「不在本批」那一条):zod 4.4 上,`Object.prototype` 上有可枚举键时才第一次被读的判别联合,永久读不出 —— 撤掉污染也不恢复**。14.7.0 写的是访问器形的 `verdict` 污染在 zod 4.4 上让 `readServingLeaseResponse` 冷路径答 `undefined`;完整的读数更宽:zod 4.4 把判别联合的判别值表惰性物化(物化时按键枚举各臂的形),
|
|
250
|
+
物化之前 `Object.prototype` 上就有一个可枚举键、且它的值不是带 `_zod` 成员的对象时,物化当场抛,zod 4.4 记下「物化中」就不再重算 —— 这个联合此后永久读不出(已核:值为字符串 / 数 / `null` / 普通对象 / 函数都触发,键名 `value` / `zzz`;带 `_zod` 成员的对象不触发;判别键 `verdict` 的访问器形与数据形不论可枚举与否都触发)。
|
|
251
|
+
租约应答读口 `readServingLeaseResponse` 是其中一只(fleet 另外六只读口没有判别联合,读数:zod 4.4 线,污染窗口里与撤掉之后都照常读出),包里另有 11 处 `z.discriminatedUnion`(`McpTransport`(经 `McpServerSpec` / `McpConfig`)· `ServingLeaseResponse` · `RemoteExecSpec` · `HookInput` · `PermissionUpdate` · `TightenRule` · `AtMention` · `ProjectContextItem` · `PluginSource` · `SettingSource` · `SandboxGitOriginAnswer`)在 zod 4.4 上同样
|
|
252
|
+
(已核:`McpServerSpec` 的合法条目在污染之后永久抛;其余各联合的判别值表读数同),直接 `safeParse` 它们的消费方一样读不出。zod 4.6 上这些在污染窗口里读不出,撤掉污染即恢复。v14.6.0 同,**非本包造成**(zod 4.4 自己的惰性物化缺陷)。
|
|
253
|
+
失败方向仍是读不出:租约读口读不出 = 引擎按中心不可达处置,不读成 `granted`、也不读成 `denied`。`Object.prototype` 被污染本身是嵌入方进程被攻破,本包不替它兜底。
|
|
254
|
+
**出路**:zod 升到 4.6 或更高(包的区间 `^4.4.0` 两端都在测);或在加载本包之后、嵌入方的代码跑之前,让每一个要用的判别联合都被一次干净的读走到 —— 租约读口:读一次干净的应答即可(已核);嵌套的联合要有一份合法输入走到它:`McpConfig` 读一条合法条目才暖到 `McpTransport`,空配置 `{ servers: [] }` 走不到、污染之后 zod 4.4 上仍永久读不出(已核)。
|
|
255
|
+
钉:`test/descriptor-law-r8-14.7.0.test.ts` 的「🚧 披露的缺口」一组(特征钉:冷 / 暖 / 嵌套 / 触发的值形,两版 zod 各一张读数;缺口补上则 4.4 臂翻成恢复,这一条随之改)。
|
|
256
|
+
|
|
257
|
+
**自 v14.7.0 起运行期代码有改动的公开导出(机器重算;上界 —— 可达 ≠ 一定变,不可达 ⇒ 一定不变;本版新增的导出不入表)**
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
DEFAULT_ROLE_TIER_LADDER [.]
|
|
261
|
+
DOMAINS [., ./types]
|
|
262
|
+
DOMAIN_SCHEMAS [., ./types]
|
|
263
|
+
FileConfigStore [./node]
|
|
264
|
+
MIGRATION_APPLIED_AS [./one-time-migrations]
|
|
265
|
+
MODEL_PLANE_REFUSAL_CODES [., ./fleet]
|
|
266
|
+
McpConfig [., ./types]
|
|
267
|
+
McpServerSpec [., ./types]
|
|
268
|
+
ModelPlaneRefusalCode [., ./fleet]
|
|
269
|
+
ModelsConfig [., ./types]
|
|
270
|
+
NON_PORTABLE_DOMAINS [./conformance]
|
|
271
|
+
ONE_TIME_MIGRATIONS [./one-time-migrations]
|
|
272
|
+
ONE_TIME_MIGRATION_IDS [./one-time-migrations]
|
|
273
|
+
PARSE_TRANSPARENT_KEYS [., ./types]
|
|
274
|
+
PromptsConfig [., ./types]
|
|
275
|
+
RosterEntry [., ./types]
|
|
276
|
+
RostersConfig [., ./types]
|
|
277
|
+
WorkerSpec [., ./types]
|
|
278
|
+
WorkersConfig [., ./types]
|
|
279
|
+
buildEffective [., ./config-fns]
|
|
280
|
+
createContractAdapter [./conformance]
|
|
281
|
+
createFileSchedulerStore [./node]
|
|
282
|
+
deriveLocalExpectation [./conformance]
|
|
283
|
+
deriveWireExpectation [./conformance]
|
|
284
|
+
domainFilePath [./node]
|
|
285
|
+
emptyEffective [., ./config-fns]
|
|
286
|
+
foreignKeysOf [./conformance]
|
|
287
|
+
isDomainName [., ./types]
|
|
288
|
+
isStringRecord [.]
|
|
289
|
+
loadLocalConfig [./node]
|
|
290
|
+
materializeLocalRoot [./conformance]
|
|
291
|
+
migrateConfigDirectory [./node]
|
|
292
|
+
migrateConfigDocuments [./node]
|
|
293
|
+
migrateEffectiveSnapshot [./one-time-migrations]
|
|
294
|
+
migrateStoredDocuments [./one-time-migrations]
|
|
295
|
+
mutateSchedulerStore [./node]
|
|
296
|
+
nodeConfigDirectoryIo [./node]
|
|
297
|
+
parseDomain [., ./config-fns]
|
|
298
|
+
parseDomainLoud [., ./config-fns]
|
|
299
|
+
parseDomainSalvaged [., ./config-fns]
|
|
300
|
+
readBundle [./node]
|
|
301
|
+
readEffectiveWire [., ./config-fns]
|
|
302
|
+
readModelRefusals [., ./fleet]
|
|
303
|
+
readRequestMcpTransport [.]
|
|
304
|
+
readWorkersRows [.]
|
|
305
|
+
resolveCatalogDefaultModel [.]
|
|
306
|
+
resolveLegExpectation [./conformance]
|
|
307
|
+
runConformance [./conformance]
|
|
308
|
+
salvageEntries [., ./config-fns]
|
|
309
|
+
saveSchedulerStore [./node]
|
|
310
|
+
urlSeatPatterns [./one-time-migrations]
|
|
311
|
+
validateDomain [./node]
|
|
312
|
+
writeBundle [./node]
|
|
313
|
+
writeDomainFile [./node]
|
|
314
|
+
writeRemoteExec [./node]
|
|
315
|
+
```
|
|
316
|
+
|
|
11
317
|
## 14.7.0 — 2026-10-25 — minor:沙箱 git 凭据平面的契约件 —— origin 规范形判官与有效配置块 `sandboxGit`(`SandboxGitConfig`)· 凭据取值口 `sandbox_git` 的 wire 型与三张词表(`SANDBOX_GIT_ORIGIN_VERDICTS` · `SANDBOX_GIT_REFUSALS` · `SANDBOX_GIT_PERMANENCE`)· `VerifiedIdentity.principalInc` 与令牌声明 `sub_inc` · 「后面要拼路径的基址」座位的禁用成分集与判官(`PATH_BASE_URL_FORBIDDEN_PARTS` · `pathBaseUrlFlaws`)与公告地址 env 名常量 `FLEET_ADVERTISE_ADDRESS_ENV`
|
|
12
318
|
|
|
13
319
|
> 档位(按发版纪律):**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 名单条)。
|
package/README.md
CHANGED
|
@@ -47,6 +47,58 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
47
47
|
> zod 3, nested inside every zod-4 host). The move to zod 4 did not change what any schema accepts or produces —
|
|
48
48
|
> the conformance corpus reads byte-identically on both — only the wording of validation errors (see CHANGELOG 11.0.0).
|
|
49
49
|
|
|
50
|
+
## 升级指引:14.8.x → 15.0.0(BREAKING:k8s 车道的 `K8S_NAMESPACE` 由可选变必需 —— 车道表与本地文件契约两处;必需坐标的纯空白值按缺)
|
|
51
|
+
|
|
52
|
+
- **改了什么**:车道表 `EXECUTION_LANES` 的 k8s 行把 `K8S_NAMESPACE`(沙箱 pod 所在的命名空间)从 `optionalEnv` 挪进 `requiredEnv`(现为 `["K8S_SANDBOX_IMAGE", "K8S_NAMESPACE"]`)。
|
|
53
|
+
其余六行逐字不动,导出面名字零增删。沙箱 pod 住哪是部署自己的声明,表不替它留缺省:服务端的必需 env 门按本表行 `requiredEnv ∪ requiredSecretEnv` 拒启,部署面的车道清单读同一行;
|
|
54
|
+
此前表把它写在可选栏、这道门放行不带它的 k8s 声明,现在车道表(写闸、归属索引与迁移终判也读它)是唯一来源。零别名、零过渡读法。
|
|
55
|
+
- **谁会红**:① 存下来的 workers 文档里,`runtime.execution.lane` 是 `k8s` 而 `coords` 没有 `K8S_NAMESPACE`(或为空串)的行 —— workers 域整域读、不逐条抢救,
|
|
56
|
+
一行不合 = 整个 workers 文档读不出(`buildEffective` / `parseDomainLoud` 抛,拒句点名 `runtime.execution.coords.K8S_NAMESPACE`),不是静默丢掉那一行;
|
|
57
|
+
按行读的 `readWorkersRows`(web-admin 的 workers 读口)把这一行扣下(`held`,带原文与拒因,`field` 指到同一格)、其余行照常进 `doc`;
|
|
58
|
+
② 保存新 k8s worker 的写入席(`WorkerSpec.safeParse` / 中心表单):不带命名空间的写入现在被拒,拒句 `lane "k8s" requires coords.K8S_NAMESPACE (the engine refuses to start without it)`;
|
|
59
|
+
③ 一次性迁移 SS-M4 遇到没写命名空间的 6.x k8s 行 ⇒ `refused` 点名同一格、行原样留着(迁移不替运维猜命名空间)。本机 `config.d` 不读 workers 文档,本机腿不受影响。
|
|
60
|
+
- **要做的**:给每个 k8s 行补一格 `K8S_NAMESPACE`,值 = 部署里沙箱 pod 所在的命名空间。**此前靠引擎内置缺省命名空间 `sandboxes` 的部署,写 `sandboxes` 即与升级前同。**
|
|
61
|
+
落点看行的形:现行形的行(有 `runtime.execution`)写在 `runtime.execution.coords.K8S_NAMESPACE`;还没过一次性迁移 SS-M4 的 6.x 行(仍带 `env.REMOTE_EXEC`)写在它自己的 `env.K8S_NAMESPACE`,再跑迁移,迁移把它搬进 `coords`
|
|
62
|
+
(往 6.x 行里加 `runtime.execution` 会造成新旧两形并存,迁移拒、不替运维挑赢家)。
|
|
63
|
+
- **文件契约面的同一件事**:本地文件契约 `RemoteExecSpec` 的 k8s 臂(`config.d/remote-exec.json`)的 `namespace` 由可选字段改必填 —— 没写的文件 `loadRemoteExec` 抛(文件存在即赢、存在而坏即抛)、`validateRemoteExec` / `parseRemoteExecDocument` 拒、`writeRemoteExec` 不落盘。
|
|
64
|
+
补 `namespace`(值 = 部署里沙箱 pod 所在的命名空间;此前靠内置缺省的写 `sandboxes`)。14.8.x 下该字段本就合法,先补后升级。
|
|
65
|
+
- **必需坐标的值(B3)**:写闸对所有车道、所有必需坐标同判一只判官 —— 空串或只含空白一律按缺(此前只判空串),拒因点名那一格(`required coordinate <键> of lane "<车道>" is empty or whitespace-only`)。
|
|
66
|
+
只影响把必需坐标写成纯空白的声明(病态输入);补偿 = 填真值(必需坐标缺席同样被拒,删掉那一键不是出路)。判官只判「是不是空白」,不 trim 值、不管值的格式。
|
|
67
|
+
- **部署序**:**先补键、后升级** —— 14.8.x 上 `K8S_NAMESPACE` 本来就是合法的可选坐标,现行形的存量文档可以在旧版本下先补齐,升级时不存在新旧两版同时在读的窗。
|
|
68
|
+
找存量:列出 `runtime.execution.lane` 为 `k8s` 且 `coords.K8S_NAMESPACE` 缺席或为空的 worker 行,以及带 `env.REMOTE_EXEC` 为 `k8s` 而 `env.K8S_NAMESPACE` 缺席或为空的 6.x 行。
|
|
69
|
+
- 逐条三问与钉见 CHANGELOG 15.0.0。
|
|
70
|
+
|
|
71
|
+
## 升级指引:14.7.x → 14.8.0(加性:配置根上的两份根级文档 · env 模型表的一份读法与写者的计划助手 · 一次性迁移 SS-M11)
|
|
72
|
+
|
|
73
|
+
- **新面**:配置根(持有 `config.d` 的那个目录)上多两份**根级文档** —— `config.env-models.json`(`{"envModels":"replaced"|"kept"}`:一份管模型面的
|
|
74
|
+
`config.d/models.json` 能不能顶替 env 模型表 `MODEL_ID` / `MODEL_CHEAP_ID`;运行记录在场之后缺席 ≡ `kept`)与 `config.migration.SS-M11.json`(一次性迁移 SS-M11 的运行记录,按 id 一份)。
|
|
75
|
+
读只经 `readEnvModelsDecision` / `readMigrationMarker`;决定文件的写只经 `writeEnvModelsDecision`(迁移铸确认也经它),运行记录由迁移独占创建(都在 `./node`);名字、闭集答案表与读数身份 `envModelsDecisionTag` 在包根。
|
|
76
|
+
这道闸的两样东西由本包出、供引擎读:组拒码常量 `CATALOG_REPLACES_ENV_MODELS_CODE`(`config.catalog_replaces_env_models`)与上面的决定文档;码的意思是 —— env 表已声明、决定不是 `replaced` 时,管模型面的目录被整组拒、env 模型照服。
|
|
77
|
+
强制在引擎一侧:今天引擎(`@sema-agent/core` 7.58.0)不拒这个组。
|
|
78
|
+
- **写者要做的**(改写 `config.d/models.json` 的每一条路):写之前读 `readEnvModelsDecision(root)`,调 `planEnvModelTableWrite({ env, target, decision, candidate, intent? })`;
|
|
79
|
+
`confirm` ⇒ 把回执给用户看(句子可用 `envModelTableWriteRefusal`),确认后带回 `confirmKey` 再调;`write_with_decision` ⇒ 先
|
|
80
|
+
`writeEnvModelsDecision(root, "replaced", { write: "overwrite" })`、再写 models.json(两次写在你对 models.json 的同一把锁里);`fix_first` ⇒ 先改文档。env 表未声明时计划照写、不写确认。
|
|
81
|
+
**不要自己写这份文件**:写函数给的模式与 `config.d` 域文档同一条规则(新建 0o644 经进程 umask,已有的文件沿用它原来的模式);自写在 umask 002 下落 0664,引擎的目录信任判官会拒。
|
|
82
|
+
撤销 = 删掉决定文件或写 `kept`(运行记录在场、创建者的迁移结局落定之后两者同义;落定之前只有写 `kept` 安全,见下一条)。撤销在下一次起服生效:运行中的引擎保留它在服的那一代目录,env 模型要到下一次起服才回来。
|
|
83
|
+
- **存量**:引擎首次以本地配置起服时跑一次 SS-M11(`migrateEnvModelsDecision`):运行记录缺席才跑;**先读**决定与 models.json,再以「不存在才建」写运行记录 ——
|
|
84
|
+
建成的那个进程是本根唯一的铸造者(别的副本见记录已在即停、永不铸),只在「先读到的决定缺席 ∧ 目录管模型面」时以「不存在才建」铸 `replaced`(env 表声没声明都一样 —— 保的是升级前目录整表顶替的读法)
|
|
85
|
+
(结局 `minted` / `exists` / `failed`,只有「已在」算 `exists`)。根缺席 ⇒ 逐级以恰是 0o700 的目录建根、只放运行记录;不建 `config.d`。记录建不成 / 确认建不成 / models.json 读不出(只有 `ENOENT` 算没有)⇒ 不铸(按 kept),
|
|
86
|
+
结局带 errno(若有)与手工恢复句;读不懂的运行记录按「已跑」处置。**披露**:进程恰在写完运行记录、铸确认之前中断,或铸失败、或那一刻 models.json 读不出 ⇒ 此后无人再铸,升级前由目录服务的根换成 env 模型服务 ——
|
|
87
|
+
出路是手写决定文件。撤销的终局按运行记录的创建者算:只有创建者能写确认;以运行记录在场为前提,创建者写完 / 判定不写 / 失败(结局 `minted` / `exists` / `skipped` / `models_unreadable` / `failed`,调用方各记一行)之后,删掉决定文件就是撤销且是终局(删掉运行记录再起服 ⇒ 迁移重跑,决定缺席即重铸);其它副本读到记录在场 = 已跑、跳过(或得 `EEXIST` 答 `not_minter`),它们的返回不构成终局;创建者当选到结局落定之间的窗口在挂起 / I/O 卡顿下无上界,窗口内唯一安全的撤销是写 `kept`(独占创建护住它;删掉的会被创建者照它先读的 D0 铸回);创建者在写确认之前崩溃 ⇒ 永久 `kept`(fail-closed,披露)。
|
|
88
|
+
- **MCP 条目**:`McpServerSpec` 多两只可选键,都解析透明、原样承运。`sessionMetaKey`(引擎在发给这台服务器的请求 `_meta` 里放发送腿会话 id 的那把键;唯一判官是引擎包根导出的 `judgeSessionMetaKey` —— 写声明的一方要早判就调它;存量里已写着这把键的条目(此前被剥掉并报 `unknown-keys-dropped`),本版原样交给引擎:值合引擎判官的,引擎从此按这把键放会话 id(运维当初写下的意图被兑现,行为随升级而变);值不合的(含 `null`),交给引擎后在准备门整条被拒、点名这把键:删掉这把键,或改成引擎收的键名)。
|
|
89
|
+
`startupTimeoutMs`(这一台服务器自己的启动界,整数毫秒,握手与首次工具列举各带它;缺席读进程级的缺省界):解析透明,**格式与界由引擎判** —— 唯一判官是引擎包根导出的 `mcpStartupTimeoutMs`,在拨号时判:不合 ⇒ 拒这一台服务器的拨号(其余服务器照挂),本包不判、任何 JSON 值原样过 —— 写声明的一方要早判就调它;存量里已写着这把键的条目(此前被剥掉并报 `unknown-keys-dropped`),本版原样交给引擎:值合引擎判官的,引擎从此按这把键给这一台服务器的启动设界(运维当初写下的意图被兑现,行为随升级而变);值不合的(含 `null`),交给引擎后会在拨号时被拒:删掉这把键,或改成引擎收的整数毫秒。
|
|
90
|
+
MCP 服务器名的 `remote-` 前缀保留给设备执行端提供的远程 MCP 服务器(`REMOTE_MCP_SERVER_NAME_PREFIX`):请求面 MCP 服务器名的判官是 `isUsableMcpServerName` / `describeBadMcpServerName`(= 线上名判官再加这一臂;线上名判官 `isUsableWireName` / `describeBadWireName` 管 A2A 对端名);配置面 `McpServerSpec.name` 受理这个前缀(拒是校验收紧,按发版纪律归 major)—— 部署声明 MCP 服务器时请自己不用这个前缀。
|
|
91
|
+
- **中心引用解析口**:`resolveCenterRefs(refs, lookup)` 读中心名册里 MCP server 的 `envRefs` / `headerRefs`(变量名,不是值):**每个成员一句判定**(`resolved` / `missing_name` / `not_a_string` / `read_threw`,
|
|
92
|
+
闭集常量 `CENTER_REF_FAULTS`)、**整台一个结论**(`resolved` / `unresolved` / `refs_unreadable`,`CENTER_REFS_VERDICTS`);只判不决定挂不挂。`lookup` 由调用方给 —— 本包不读 `process.env`,没给 ⇒ `TypeError`。
|
|
93
|
+
引用表经公开的记录读器 `readPlainStringRecord` 读(传输读器读 `env` / `headers` 用的就是这一只;原因词表 `PLAIN_STRING_RECORD_FAULTS` 三词:`not_plain_record` / `member_not_string` / `member_name_reserved`,与传输读器 `lost[].fault` 同一张表)。两方读法见 `docs/CONSUMER-INTEGRATION.md` §14。
|
|
94
|
+
- **已知边界(zod 4.4 臂)**:`Object.prototype` 上有可枚举键(值不是带 `_zod` 成员的对象)时才第一次被读的判别联合,在 zod 4.4 上永久读不出,撤掉污染也不恢复 —— 租约应答读口 `readServingLeaseResponse` 与包里另外 11 处 `z.discriminatedUnion`(如 `McpServerSpec` 的传输)同(读数:zod 4.4 线,键 `value` / `zzz`;zod 4.6 上撤掉污染即恢复;v14.6.0 同,非本包造成,zod 4.4 自己的惰性物化缺陷)。
|
|
95
|
+
`Object.prototype` 被污染本身是嵌入方进程被攻破,本包不替它兜底;失败方向是读不出(租约读口读不出 = 引擎按中心不可达处置,不读成 `granted` 也不读成 `denied`)。出路:zod 升到 4.6 或更高,或加载本包之后、嵌入方的代码跑之前让每一个要用的判别联合都被一次干净的读走到(租约读口读一次干净应答即可;`McpConfig` 要读一条合法条目才暖到传输联合,空配置走不到)。
|
|
96
|
+
- **整根只读的部署**(配置根是 ConfigMap / 只读挂载):迁移写不了运行记录、不会替你铸 —— **升级引擎之前**把 `config.env-models.json` 放进同一份 ConfigMap 的配置根
|
|
97
|
+
(要目录继续优先就写 `{"envModels":"replaced"}`)。
|
|
98
|
+
- **SS-M11 的窗**:起算点 = 引擎开始强制这道闸的那一天(上文:今天引擎不拒这个组,窗还没有起算),到期日 = 起算日 + 30 天;本包登记表里 SS-M11 那一行按本包的发布日计,
|
|
99
|
+
管的是本包何时能删迁移码。
|
|
100
|
+
- **回滚**:回滚到已核的各版旧引擎都安全 —— 两份根级文档在 `config.d` 之外,被检视的配置读者不把它们当域文档。
|
|
101
|
+
|
|
50
102
|
## 升级指引:13.x → 14.0.0(BREAKING:prompts 域单轴 = 中心 artifact;请求面 MCP 条目判官入包)
|
|
51
103
|
|
|
52
104
|
- **prompts**:域里只装引擎当期 schema 的中心 artifact。存储 `PromptsConfig = { artifact?, workers? }`(每份是 artifact 的身份 `{ payload, artifactDigest }`);
|
|
@@ -233,7 +285,7 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
233
285
|
`MEMORY_ENGINE_BACKENDS`. Web-admin's write gate (this package's `WorkerSpec.safeParse`), web-admin's env
|
|
234
286
|
renderer and the engine's boot checks all read that one table, so they cannot drift. A worker declares
|
|
235
287
|
`runtime.execution = { lane, coords, secretRefs }` (non-secret coordinates as literals, secrets as host env-NAMEs,
|
|
236
|
-
keys ⊆ that lane's columns, required ones present and
|
|
288
|
+
keys ⊆ that lane's columns, required ones present and not blank — an empty or whitespace-only value counts as absent) and `runtime.store = { backend, session, memory,
|
|
237
289
|
db? }` (+ `secretRefs.dbPasswordEnv`); an enabled worker must declare both. Cross-field refusals live in the
|
|
238
290
|
schema as zod issues whose `path` names the field to change and whose message carries the way out: a
|
|
239
291
|
multi-tenant worker (`requirePrincipal`) on a lane that does not separate tenants (outlets computed from the
|
|
@@ -406,6 +458,20 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
406
458
|
writer (the orchestrator that renders the value) and the reader (the engine at boot) import. Judge the value AFTER placeholders are expanded: `http://$(POD_IP):8090` passes,
|
|
407
459
|
`http://[$(POD_IP)]:8090` does not (a bracketed host must parse as IPv6). Placeholder expansion, reachability and the absent / empty-string reading are not this package's;
|
|
408
460
|
the env name is not a reserved worker env name. Both sides' reading is in `docs/CONSUMER-INTEGRATION.md` §13.
|
|
461
|
+
- **Config-root documents and the env model table** (14.8.0; names / types / judges / plan in the main barrel, readers / writer / migration in `./node`): the config
|
|
462
|
+
root (the directory holding `config.d`) carries two root-level documents outside `config.d` — `config.env-models.json` (`ENV_MODELS_DOCUMENT`: whether a catalog
|
|
463
|
+
that manages the model plane may replace the env model table, `replaced` | `kept`; absent ≡ `kept` once the run marker exists) and `config.migration.SS-M11.json`
|
|
464
|
+
(the per-id run marker of one-time migration SS-M11); `CONFIG_ROOT_DOCUMENTS` is the list an engine's directory trust judge walks. ONE reader each —
|
|
465
|
+
`readEnvModelsDecision(root)` → `absent | replaced{digest} | kept{digest} | unreadable{bad_json|bad_shape, digest} | unreadable{io, errno}` (the engine gate, the
|
|
466
|
+
one-time migration and the writer's plan answer from it; `envModelsDecisionTag` is the one tagged identity of a reading — `absent` / `d:<digest>` / `io:<errno>` —
|
|
467
|
+
for warning dedup and the etag) and `readMigrationMarker(root, id)` → `absent | present | unreadable` — and ONE writer of the decision,
|
|
468
|
+
`writeEnvModelsDecision(root, "replaced"|"kept", { write: "overwrite" | "createIfAbsent" })` (the package's atomic writer, the same mode rule as a `config.d` domain document: a new file is 0o644 through the process umask, an existing file keeps its own mode;
|
|
469
|
+
the migration mints through it too, while the run marker is created only by the migration). One-time migration SS-M11 = `migrateEnvModelsDecision({ root, env })` (read first, then elect by creating the marker exclusively: the creator is
|
|
470
|
+
the only minter; a `config.d/models.json` that cannot be read — anything but `ENOENT` — mints nothing and is reported with its errno when the error carries one). The judge `catalogManagesModelPlane(doc)` (raw half: at least one entry not explicitly disabled — the same `enabled` reading as a dropped entry's
|
|
471
|
+
identity) and `enabledEntryCount(doc)`. The env model table read once — `readEnvModelSlots` / `envModelTableDeclared` (`MODEL_ID` present and non-empty) /
|
|
472
|
+
`buildEnvModelBaseline` — and the writer's plan `planEnvModelTableWrite({ env, target, decision, candidate, intent? })` (receipt by upstream id with landings
|
|
473
|
+
`{name, id}`, `confirmKey` = the receipt's sorted JSON) with its one sentence `envModelTableWriteRefusal(plan)`. Group-refusal codes
|
|
474
|
+
`CATALOG_REPLACES_ENV_MODELS_CODE` / `SUMMARIZE_MODEL_MISSING_CODE` (tail of `MODEL_PLANE_REFUSAL_CODES`).
|
|
409
475
|
- **Session web link** (`web-admin-session-url.ts`, main barrel): `webAdminSessionUrl(webBaseUrl, { worker, sessionId })` — the ONE
|
|
410
476
|
template for a web-admin session link, `<webBaseUrl>/chat?agent=<worker>&session=<sessionId>`: both parts required (a link with only
|
|
411
477
|
the session id lands on whichever worker the page picks and may not find it), each part `encodeURIComponent`-escaped, the base a
|
|
@@ -531,7 +597,7 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
531
597
|
is the source whose tier in `SETTINGS_SOURCE_CONTRACT` is `managed`; a tighten-only source contributes its deny list only (allow lists
|
|
532
598
|
concatenate across layers, so reading its allow list would widen the others').
|
|
533
599
|
- **Request-plane MCP entry judges** (`mcp-request-entry.ts`, 14.0.0): `isUsableWireName` / `describeBadWireName` (the engine's wire-name bound
|
|
534
|
-
`TOOL_WIRE_NAME_MAX_CHARS`, mirrored; a name with leading or trailing whitespace is a bad name too — never trimmed, never normalised; inner whitespace stays legal), `isContentOriginWord` / `describeBadContentOrigin`, `readMcpIdentityHeaders`, `readRequestMcpTransport`
|
|
600
|
+
`TOOL_WIRE_NAME_MAX_CHARS`, mirrored; a name with leading or trailing whitespace is a bad name too — never trimmed, never normalised; inner whitespace stays legal), `isUsableMcpServerName` / `describeBadMcpServerName` (14.8.0; the request plane's judge of an MCP server's name: the wire-name judge plus one arm — the `remote-` prefix, `REMOTE_MCP_SERVER_NAME_PREFIX`, is reserved for the remote MCP servers a device executor offers; the config plane's `McpServerSpec.name` accepts it — refusing would be a validation tightening, which the release discipline files under a major; A2A peer names keep `isUsableWireName`), `isContentOriginWord` / `describeBadContentOrigin`, `readMcpIdentityHeaders`, `readRequestMcpTransport`
|
|
535
601
|
(hard faults `ok:false` naming the key path; soft faults dropped by the reader and listed in `lost`; `env` / `headers` are read as plain-data string records — a `Map`, a class instance or an own `__proto__` member is not one, the delivered record is a copy, and a `lost` entry for a record carries machine-readable `member` / `fault`; the two describers strip control and bidi characters and cut at 64 characters, the service's echo form) and the value-shape describers — one reading for
|
|
536
602
|
the service's request legs and the shell's pre-send check. A `toolFaces` declaration stays parse-transparent here: its shape is the engine ingest side's to judge, and none of the tool-face vocabularies is mirrored in this package.
|
|
537
603
|
- **One-line echo strip set** (`control-and-bidi.ts`, main barrel): `CONTROL_AND_BIDI_CHARS` (C0 + DEL + the 12 `Bidi_Control=Yes` code points,
|
|
@@ -608,7 +674,7 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
608
674
|
carries verbatim WITHOUT checking their shape, each row naming who does check it (the engine's ingest side or
|
|
609
675
|
a consumer's enforcement point). Today: `models[].compat` (1.10.0), `models[].headers` (6.4.0; the header-name
|
|
610
676
|
allowlist × lane is sema-server's, not restated here), `mcp.servers[].source` (1.7.2), `mcp.servers[].toolFaces`
|
|
611
|
-
(1.9.0), `mcp.servers[].toolAxes` / `authForm` / `entryKind` (12.2.0; connector-entry declarations — `toolAxes` has exactly one shape judge, the engine's root export `readMcpToolAxes`; the other two keys' semantics are the consumer's),
|
|
677
|
+
(1.9.0), `mcp.servers[].toolAxes` / `authForm` / `entryKind` (12.2.0; connector-entry declarations — `toolAxes` has exactly one shape judge, the engine's root export `readMcpToolAxes`; the other two keys' semantics are the consumer's), `mcp.servers[].sessionMetaKey` (14.8.0; the `_meta` key the engine sends the leg's session id under — one judge, the engine's root export `judgeSessionMetaKey`), `mcp.servers[].startupTimeoutMs` (one server's own startup bound — one judge, the engine's root export `mcpStartupTimeoutMs`),
|
|
612
678
|
and an entitlement override's `value`. `models[].extraBody` is NOT one of them — it has its own shape
|
|
613
679
|
gate (a plain JSON object); what it leaves open is the key vocabulary inside. `compat` / `headers` / `extraBody`
|
|
614
680
|
still clear the one free-form safety judgment (secret-shaped key/value, accessor, cycle, non-JSON, too deep ⇒ rejected).
|
|
@@ -661,9 +727,20 @@ local `.env`). This package never carries a credential.
|
|
|
661
727
|
models.json rosters.json skills.json mcp.json scenarios.json plugins.json
|
|
662
728
|
collab.json runtime.json governance.json execution.json prompts.json limits.json
|
|
663
729
|
remote-exec.json # NOT a domain (the exec backend; read by loadRemoteExec)
|
|
730
|
+
config.env-models.json # the env-model-table decision {"envModels":"replaced"|"kept"} (readEnvModelsDecision / writeEnvModelsDecision)
|
|
731
|
+
config.migration.SS-M11.json # the run marker of one-time migration SS-M11 {"migration":"SS-M11"} (readMigrationMarker; written only by the migration)
|
|
664
732
|
.env # secret VALUES only (KEY=value); gitignored; never read into any *.json
|
|
665
733
|
```
|
|
666
734
|
|
|
735
|
+
**The root belongs to the client (TOC).** An engine writes exactly two files in it — the two root-level documents above, both outside `config.d` (the sampled
|
|
736
|
+
file stores read only `config.d`, and no inspected config reader treats these files as domain documents, so they are inert to old and new engines alike); a client
|
|
737
|
+
keeps both in its own registry of persistent files (hygiene, export and reset must not drop or flag them). Bundles (`exportBundle` / `readBundle`) carry only the
|
|
738
|
+
portable `config.d` domains: these two are deployment decisions and do not travel. Laid down offline (before any engine starts on the root: an image, a
|
|
739
|
+
ConfigMap, a restore), the two documents and the domain documents may be written in any order; at runtime the order is fixed — a writer writes the
|
|
740
|
+
decision before `config.d/models.json` (both under its lock on `models.json`), and the one-time migration creates its run marker before it mints the
|
|
741
|
+
decision. When the root itself is absent, the engine's one-time migration creates it (and any missing parent) owner-only (0o700), owned by the engine's
|
|
742
|
+
user: a client that runs under a different user should create the root itself before the engine first starts.
|
|
743
|
+
|
|
667
744
|
Fleet-only domains (`workers`, `hosts`, `systems`) are omitted on a single machine — they resolve to schema
|
|
668
745
|
defaults. Each `config.d/<domain>.json` is exactly that domain's zod object.
|
|
669
746
|
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
15.0.0
|