@sema-agent/settings-schema 7.0.1 → 8.1.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 +207 -1
- package/README.md +60 -6
- package/conformance/VERSION +1 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +5 -0
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +5 -0
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +41 -0
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +5 -0
- package/conformance/cases/c05-gate-domain-bad-entry-throws/expected.json +5 -0
- package/conformance/cases/c06-legacy-governance-lift/expected.json +12 -0
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +5 -0
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +77 -0
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +78 -0
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +5 -0
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +14 -0
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +5 -0
- package/conformance/cases/c13-models-field-null/expected.json +5 -0
- package/conformance/cases/c14-models-field-empty-array/expected.json +16 -0
- package/conformance/cases/c15-models-field-number/expected.json +5 -0
- package/conformance/cases/c16-deep-nesting-205/expected.json +14 -0
- package/conformance/cases/c17-empty-domain-file/expected.json +10 -0
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +10 -0
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +10 -0
- package/conformance/cases/c20-governance-absent-lifts/expected.json +12 -0
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +13 -0
- package/conformance/cases/c22-governance-null-throws/expected.json +5 -0
- package/conformance/cases/c23-mixed-validity-models/expected.json +5 -0
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +29 -0
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +33 -0
- package/conformance/cases/c26-hosts-array-1024/expected.json +7183 -0
- package/conformance/cases/c27-hosts-array-1025/expected.json +5 -0
- package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -0
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +5 -0
- package/conformance/cases/c30-version-triple-a-first/expected.json +29 -0
- package/conformance/cases/c31-version-triple-b-changed/expected.json +29 -0
- package/conformance/cases/c32-version-triple-a-restored/expected.json +29 -0
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +17 -0
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +23 -0
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +5 -0
- package/conformance/cases/c36-skills-entry-without-content/expected.json +23 -0
- package/conformance/cases/c37-skills-field-null/expected.json +5 -0
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +5 -0
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +5 -0
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +5 -0
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +5 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +33 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +5 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +5 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +5 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/expected.json +5 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/expected.json +5 -0
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +59 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +5 -0
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +44 -0
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +46 -0
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +48 -0
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +48 -0
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +44 -0
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +45 -0
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +51 -0
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +5 -0
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +5 -0
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +5 -0
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +5 -0
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +5 -0
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +5 -0
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +5 -0
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +5 -0
- package/conformance/cases/c65-s784-pg-without-db/expected.json +5 -0
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +5 -0
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +5 -0
- package/conformance/cases/c68-s784-device-local-store/expected.json +5 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +54 -0
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +27 -0
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +33 -0
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +5 -0
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +5 -0
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +22 -0
- package/conformance/cases/cli04-env-value-with-newline/expected.json +46 -0
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +53 -0
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +5 -0
- package/conformance/cases/t02-entry-salvage-vs-whole-domain-refusal/expected.json +5 -0
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -0
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +5 -0
- package/conformance/cases/z01-every-domain-empty-object/expected.json +64 -0
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +66 -0
- package/dist/api/auth-bridge.d.ts +2 -2
- package/dist/cli.js +58 -26
- package/dist/config-fns.js +7 -1
- package/dist/conformance/contract-adapter.d.ts +4 -2
- package/dist/conformance/contract-adapter.js +21 -7
- package/dist/conformance/corpus.js +12 -7
- package/dist/conformance/derive.d.ts +26 -0
- package/dist/conformance/derive.js +25 -2
- package/dist/conformance/index.d.ts +4 -3
- package/dist/conformance/index.js +4 -3
- package/dist/conformance/run.d.ts +3 -3
- package/dist/conformance/run.js +87 -17
- package/dist/conformance/types.d.ts +57 -17
- package/dist/conformance/types.js +4 -2
- package/dist/fleet.d.ts +417 -3
- package/dist/fleet.js +287 -12
- package/dist/hooks.d.ts +4 -4
- package/dist/types.js +3 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,213 @@
|
|
|
6
6
|
> `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
|
|
7
7
|
> 包名是**历史记录**,如实保留不追改。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 8.1.0 — 2026-10-08 — 引擎自铸信号的共享型:`RestartSignal.withheld` · `EngineSignal` · 服务租约 API 型与中心能力位 · 排空请求 / 回执型 · fleet env 名常量(全部 additive)
|
|
10
|
+
|
|
11
|
+
> **判档 = minor**(README「发版纪律」:新键 / 新导出 = minor)。本版只加**型与契约**,零行为:既有字段的语义、`InstanceHealth` / `InstanceAnnouncement`
|
|
12
|
+
> 的形、`isReadyForTraffic` / `isRoutable` 的行为一字未动(**唯一一处校验方向的收紧**:`FLEET_GENERATION` 进保留 env 名表,见第五节——这个名字是本版才出现的,此前没有任何合法写手可被收紧,判档仍按 minor);`RestartSignal` 只多一个**可选**位(缺席 = 旧形,读出形不多铸键)。新增的是 `./fleet`
|
|
13
|
+
> (与主入口)上的 27 个导出名,导出面登记表里只多名字行(合计数字随之变),无删、无改形。
|
|
14
|
+
|
|
15
|
+
### 一、`RestartSignal.withheld?`(可选布尔,缺席 = false)
|
|
16
|
+
|
|
17
|
+
引擎自己判定「这一拍不该被换代」(需要重启,但此刻换掉这只实例会丢它手里的状态)。它是 `restart` **里面**的限定词,不是独立信号:只在 `restart` 在场时才有。
|
|
18
|
+
缺席与显式 `false` 同义;读器不替生产方铸这个键。`readRestartSignal` 原样读出,心跳 `InstanceAnnouncement.restart` 与 `/health` 两个出口仍是同一只 schema。
|
|
19
|
+
金样加一格带 `withheld` 的 `/health` 体(`test/restart-signal-6.1.0.test.ts`)。
|
|
20
|
+
|
|
21
|
+
### 二、`EngineSignal` + `readEngineSignal(health)`(+ `EngineDraining`)
|
|
22
|
+
|
|
23
|
+
- `EngineSignal` = 引擎自铸的那一只对象:`{ ready, draining?: { actionId, since, deadlineAt }, restart?, generation?, instanceId?, leaseUntil?, configTargetVersion?, configAppliedVersion? }`。
|
|
24
|
+
心跳与 `/health` 携带**同名键、同值**,键**平铺**在体上(没有包装键)。`restart` 引的就是 `RestartSignal` 那一只 schema,`draining` 的三键同时是排空回执的三键(`EngineDraining`)。
|
|
25
|
+
`generation` 是配置渲染器所设 env 的回声(缺席 ⇔ 非 fleet 成员),**规范十进制串**(见下条);**`instanceId` 是引擎每进程自铸的身份**,不读 env(同一个 Deployment 的副本共用一份 env、容器重启复用 env,env 给不了进程身份),
|
|
26
|
+
单机引擎上也在场——只有 `generation` 标记 fleet 成员。
|
|
27
|
+
- **与 `InstanceHealth` 的关系**:`InstanceHealth` 是 `/health` 的自描述(开放记录,`.passthrough()`,也装宿主自己加的 build / pid / port / 存储姿态);`EngineSignal` 是其中**引擎自铸**那一子集的**闭形**。
|
|
28
|
+
读时走默认 strip——整份 `/health` 体进去,只出这八个键。任一键形坏 ⇒ 整只信号 `undefined`(不拿剩下的充数)。
|
|
29
|
+
- **读口只有一只**:`readEngineSignal` 与 `readRestartSignal` / `readModelRefusals` 同走一个私有的受保护读口(原先只服务 `/health` 单键的那只,本版把「取哪个键」参数化、改名 `readSignalBody`;
|
|
30
|
+
行为不变)。同一份敌意输入,三个读器同一个结局。
|
|
31
|
+
- **方言**:`draining` 在 `InstanceHealth` / `InstanceAnnouncement` 上是**布尔**(8.0 方言,本版不动),在 `EngineSignal` 上是**对象**。布尔 `draining` 的体读不出 `EngineSignal`(`undefined`);
|
|
32
|
+
`undefined` 是「没有这只信号」,**永远不是「就绪」**——路由轴上的调用方应把它当不可路由,除非有意回落到布尔方言。
|
|
33
|
+
`isReadyForTraffic` / `isRoutable` 的输入留在布尔方言(`EngineSignal` 过不了它们的类型,这是故意的:`!== true` 会把对象形 `draining` 读成「就绪」);下一个 major 把布尔对(`draining` / `drainingSince`)退役、两个谓词改吃 `EngineSignal`。
|
|
34
|
+
|
|
35
|
+
### 三、服务租约 API 型
|
|
36
|
+
|
|
37
|
+
- `ServingLeaseRequest = { worker, scope?, generation, instanceId }`;
|
|
38
|
+
`ServingLeaseResponse` = 判别联合,**三词闭集** `granted { ttlMs }` / `denied { reason }` / `unknown`(`SERVING_LEASE_VERDICTS`);`denied` 的原因闭集恰两词 `superseded` | `retired`(`SERVING_LEASE_DENIED_REASONS`)。
|
|
39
|
+
- **`unknown` 不是 `denied` 的一种**:它不带 reason(带了也被剥掉,读不成 denied),表示中心此刻没有这只实例的生命周期对象(库恢复中、scope 迁移中、尚未认领)——对「该不该继续服务」什么都没说。
|
|
40
|
+
只有 `denied` 臂约束引擎停服,且**只凭正面证据**:中心对 (scope, worker) 持久化一只代际下限(永不回收),低于下限 ⇒ `superseded`;`retired` 同样要有中心持有的记录;**对象缺席 ⇒ `unknown`,永不拒**。
|
|
41
|
+
**读器按 `verdict` 键判**(两张脸,失败方向随含义):严格线形 `ServingLeaseResponse` 说的是中心**能发什么**(原因闭集恰两词);`readServingLeaseResponse` 返回 `ServingLeaseReading`——
|
|
42
|
+
`verdict: "denied"` 时 `reason` 不在闭集 / 缺 / 非串 ⇒ **仍是 `denied`**,`reason` 读成占位词 `SERVING_LEASE_UNKNOWN_REASON`(`"unknown_reason"`),引擎照样自排空,
|
|
43
|
+
不会因中心学会第三个原因词就把 `denied` 读成 `undefined` 而继续服务。占位词只在读出形里,生产方发不出它。判别词本身不在三词闭集、或 `granted` 的 `ttlMs` 不合 ⇒ `undefined`(读方按中心不可达处理,既不当 `denied` 也不当 `granted`)。
|
|
44
|
+
- **`generation` = 非负整数的规范十进制串**(`"0"`、`"7"`、`"10"`;不许 `"07"`、符号、小数、空白、非 ASCII 数字,也不许数字型),三处 wire 形(`EngineSignal.generation` / `ServingLeaseRequest.generation` / `DrainRequest.expectedGeneration`)同一条判据。
|
|
45
|
+
新导出 `isCanonicalGeneration(value)`(判别器,接 `unknown`)与 `compareGeneration(a, b)`(数值比,`"9"` 早于 `"10"`,位数不限;非规范入参响亮抛 `TypeError`)——中心持久化的代际下限靠它比较。
|
|
46
|
+
- **`granted` 带的是时长 `ttlMs`(正整数 ms),不是绝对时刻**:引擎按「发出请求时的本地单调钟 + `ttlMs`」自己算到期,中心按库时间记自己的到期,两边不比钟(`EngineSignal.leaseUntil` 是引擎据此算出的到期)。
|
|
47
|
+
- 常量:`SERVING_LEASE_TTL_DEFAULT_MS = 600_000`、`SERVING_LEASE_TTL_FLOOR_MS = 60_000`、`SERVING_LEASE_DRIFT_MARGIN_MS`(**派生自** `DEFAULT_HEARTBEAT_MS`,不是第二个数)。
|
|
48
|
+
配置的 TTL 过**一只**夹取函数 `clampServingLeaseTtlMs(n)`:读不出(缺 / `NaN` / 无穷 / 非数字)⇒ 缺省;低于地板(含 0 与负数)⇒ 地板;其余取整数 ms,无上限。它给**签发侧**的配置用;
|
|
49
|
+
应答里的 `ttlMs` 按授予值原样使用、**不**向上夹——引擎把短授予拉长到地板,等于持有租约超过中心记下的期限,那正是围栏要防的事。
|
|
50
|
+
- 读器:`readServingLeaseRequest`(中心侧)/ `readServingLeaseResponse`(引擎侧),同一只受保护读口。
|
|
51
|
+
- **中心能力位 `CenterCapabilities = { servingLease?: true }`** + `readCenterCapabilities(body)`:租约客户端**只在位在场时启用**;缺席(老中心 / 读不出)= 中心不支持租约 ⇒ 引擎不续租、也不被围栏,
|
|
52
|
+
所以执行器先于中心上线时,不会把还没有租约端点的中心逼成 `lease_expired`。位只有「`true` 或缺席」一种拼法(没有 `false`);未知位被剥,已知位的值不是 `true` ⇒ 整次读 `undefined`(读方等同缺席)。
|
|
53
|
+
这是兼容门、不是裁决——形里没有「中心拒绝租约」这句话。本仓原先没有中心能力位的表达,故自立这一只;承载它的中心端点不在本包定。
|
|
54
|
+
|
|
55
|
+
### 四、排空请求 / 回执型
|
|
56
|
+
|
|
57
|
+
- `DrainRequest = { actionId, scope?, worker, expectedGeneration?, instanceId?, deadlineAt, term? }`:身份(`scope?` / `worker` / `expectedGeneration?` / `instanceId?`)由排空口逐字段「缺席对缺席」比对——
|
|
58
|
+
非 fleet 成员没有 generation,故缺 `expectedGeneration` 是单机形而不是通配;`instanceId` 在场时是引擎自铸的进程身份。本形只说什么是合式请求,身份不符是排空口的拒因。
|
|
59
|
+
**两条车道,一个形**:Docker 车道与中心直调用这条 HTTP 排空请求;Kubernetes 车道不逐 pod 发 HTTP(寻址与 RBAC 不合),靠**租约拒 ⇒ 引擎自排空**。
|
|
60
|
+
- `DrainReceipt = EngineDraining.extend({ state: "draining" | "already_draining" })`(`DRAIN_RECEIPT_STATES`),与 `EngineSignal.draining` 同一只形,不抄一份。
|
|
61
|
+
- 读器:`readDrainRequest` / `readDrainReceipt`。
|
|
62
|
+
|
|
63
|
+
### 五、fleet env 名常量
|
|
64
|
+
|
|
65
|
+
`FLEET_GENERATION_ENV = "FLEET_GENERATION"`:配置渲染器设、引擎读回并回声成 `EngineSignal.generation`;**它进了保留 env 名表**(`RESERVED_WORKER_ENV`,`WorkerSpec.env` / `secretEnv` 设它即被拒,拒因指路「set by the config renderer … not configurable」),渲染器是唯一写手;两侧都 import 常量,不各自拼字符串
|
|
66
|
+
(整个 `src/` 里这个字面量只出现一次,有源码文本钉)。env 缺席 ⇔ 非 fleet 成员。**刻意没有实例身份的 env 名**——实例身份由引擎每进程自铸(见第二节),`src/` 里 `FLEET_INSTANCE_ID` 一次都不许出现(负钉)。
|
|
67
|
+
|
|
68
|
+
### 用户可见变化(谁受影响 / 怎么改 / 不改会怎样)
|
|
69
|
+
|
|
70
|
+
1. **只用 schema / 读写 helper、不碰 fleet 的消费方**:零变化。
|
|
71
|
+
2. **构造或解析 `RestartSignal` 的消费方**:可以多带一个可选布尔 `withheld`;不带照旧。**对 `RestartSignal` 的键做穷举**(`Record<keyof RestartSignal, …>`)的会编译红(多一个键)——按「提货成本」处理,补一行即可。
|
|
72
|
+
3. **要读引擎信号的消费方**(中心 / 壳 / 编排器):用 `readEngineSignal(health)`,不要再各自对 `health.ready` / `health.draining` 手写判断;`undefined` 当「没有信号」,不当「就绪」。
|
|
73
|
+
4. **仍读 `InstanceHealth.draining`(布尔)的**:不动。但注意:一旦某个引擎版本把 `/health` 的 `draining` 铸成对象(`EngineSignal` 方言),`InstanceHealth.safeParse` 对那份体会失败——那是该引擎版本与下一个 major 之间的接线问题,本版没有替它做兼容臂。
|
|
74
|
+
5. **实现租约客户端 / 排空口的引擎(server)与中心(管理面)**:用 `ServingLeaseRequest` / `ServingLeaseResponse` / `DrainRequest` / `DrainReceipt` / `CenterCapabilities` 与 `FLEET_GENERATION_ENV` 对齐形;
|
|
75
|
+
租约客户端只在 `readCenterCapabilities(...)?.servingLease === true` 时启用;`instanceId` 自己每进程铸一个,不读 env;对 `verdict` 做 `switch` 时加 `default: never` 臂,以后加词编译会红;
|
|
76
|
+
`granted` 用 `ttlMs`(时长)按发请求时的本地单调钟算到期;应答一律走 `readServingLeaseResponse`(按 `verdict` 键判,`denied` 的原因词读不出也照样排空);`generation` 一律写规范十进制串(`"7"`),用 `compareGeneration` 比大小。
|
|
77
|
+
6. **在 `WorkerSpec.env` / `secretEnv` 里设了 `FLEET_GENERATION` 的**:现在被拒(保留名,拒因指路「set by the config renderer … not configurable」)。这个名字是本版才出现的,正常部署没人设过;
|
|
78
|
+
设过的删掉那一行,由渲染器按该 worker 的代际写。**持有 `RESERVED_WORKER_ENV` 总数断言的测试**(本仓的历史钉就是一例)要 +1。
|
|
79
|
+
|
|
80
|
+
### 红先 / 门
|
|
81
|
+
|
|
82
|
+
- 先写 `test/engine-signal-8.1.0.test.ts`(含 `restart-signal-6.1.0` 的金样格)对着 8.0.0 的 `src/fleet.ts` 跑:新增 31 格里 25 格红(新导出缺席、`withheld` 被剥掉),
|
|
83
|
+
另 6 格是 additive 反向格(「不带 withheld 的旧形读出形不铸键」「`InstanceHealth` 四键 / passthrough 照旧」「就绪谓词行为照旧」等),在 8.0.0 上本来就绿;
|
|
84
|
+
逐步实现后同文件全绿(含 3 格源码文本钉:读口只有一只、漂移余量的定义、env 字面量单点与实例身份无 env 名)。
|
|
85
|
+
之后两轮增补(第一轮:删实例身份 env 名常量、加中心能力位、两处注;第二轮:`ttlMs`、规范 `generation`、读器按 `verdict` 键判、保留 env 名与夹取函数)里,
|
|
86
|
+
除中心能力位(源码先写、事后用变异自证补咬合)外,新格都先红再绿;`test/migration-signpost-7.0.1.test.ts` 里「保留名总数 = 90」的历史钉改成「90 + 登记的增词」(增词表里目前只有 `FLEET_GENERATION`)。
|
|
87
|
+
- 变异自证(29 个变异,每个都至少使一格红,红的是预期的那一格):`withheld` 改 `.default(false)`、`EngineSignal` 改 `.passthrough()`、`unknown` 臂加 reason、`denied` 的 reason 改可选、漂移余量改字面量、`DrainReceipt` 改成抄一份、`EngineSignal.restart` 改成第二份形、`readEngineSignal` 自带一份 try/catch、`ready` 改可选、`draining` 兼收布尔、原因 / 判别词表多一词、env 名改字或在别处再拼一份、实例身份 env 名常量回来、能力位可写 `false`、能力位自带读口 / 严判未知位、`ttlMs` 变可选、`generation` 放宽成任意非空串 / 规范串正则放过前导零、`compareGeneration` 改字典序 / 不再抛、`denied` 读脸对 reason 严判、占位词混进闭集、`FLEET_GENERATION` 从保留表摘掉、`clampServingLeaseTtlMs` 去地板 / 把读不出夹到地板等。
|
|
88
|
+
- 导出面登记表(`docs/API-SURFACE.md`)重生成:`.` 与 `./fleet` 相对 8.0.0 各 +27 名,无删无改形。
|
|
89
|
+
|
|
90
|
+
### 未闭环(如实)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## 8.0.0 — 2026-10-04 — 一致性门第四条腿 `strictWrite`(严写受理)+ 每条腿都要表态(BREAKING,kit 类型面与 release 律)· `migrate-workers --check` 与真跑过同一只判定(BREAKING,CLI 退出码 / `refused[]`)· 导出面登记表机器门
|
|
94
|
+
|
|
95
|
+
> **判档 = major**(README「发版纪律」):kit 的闭集 `LegName` 加一员、`ConformanceAdapter.legs` 从 `Partial<Record<…>>` 改成全量
|
|
96
|
+
> `Record<LegName, LegStance>`(实现了 kit 适配器的消费方 tsc 红,即通知);release 律收紧(没表态的腿从「跳过、不算违例」变成违例);
|
|
97
|
+
> CLI 机读契约字段 `outcome` / `refused` 在一种文档上改了值(`--check` 对整域会被拒的文档从 `pending` / 退出 3 变成 `refused` / 退出 1,
|
|
98
|
+
> `refused[]` 从空变成逐条)。契约 schema、域默认值、三条读腿的读数**一字未动**:conformance 全语料重加冕,`domains` / `wire` / `localRoot`
|
|
99
|
+
> 三腿金样、`baseline.json`、迁移金样 `m01–m04` 零 diff(`expected.json` 只新增 `strictWrite` 一格,82 个文件 0 行删改)。
|
|
100
|
+
|
|
101
|
+
### 一、一致性门第四条腿 `strictWrite`(严写受理)
|
|
102
|
+
|
|
103
|
+
**病**:写入席(管理面)判一份文档收不收,读侧(worker / CLI)逐条容错 —— 两侧此前没有任何一格放在同一份文档上对过。kit 的三条腿都是读语义:
|
|
104
|
+
读腿抢救的地方(丢坏条目、整域降级、主机名夹紧)写入席**应该拒**,而这一点没有金样、没有门。
|
|
105
|
+
|
|
106
|
+
**修**:
|
|
107
|
+
|
|
108
|
+
- `LegName` 闭集 +1 `"strictWrite"`(`LEG_NAMES` 同步)。入参与 `domains` 腿**同一份字段表**(新导出类型 `DomainDocsInput`,两臂共用,
|
|
109
|
+
不抄第二份)。结局只用既有两类(`LegOutcome` 不动):全部受理 ⇒ `effective` = 提交的每个键 → 受理后的域文档(**只含提交的键、不补缺席域**;
|
|
110
|
+
空集受理成 `{}`),告警面为空,元数据回报 harness 注入的那一份;任何一份不受理 ⇒ `rejected{ stage: "validate" }`(写是一个动作,不逐份抢救)。
|
|
111
|
+
- 契约仓自己的适配器用 `parseDomain` 接这条腿(`ContractModule` 加 `parseDomain`,`createContractAdapter` 的 `entries` 要第四个入口)。
|
|
112
|
+
- **期望从哪来是一张表**:`derive.ts` 新导出 `LEG_EXPECTATION`(`Record<LegName, { source: "explicit" | "derivable"; why }>`,新开一条腿不表态
|
|
113
|
+
即编译红)。`strictWrite` 入参恒等、**期望不派生**(严写结局不是读结局的表示变换;从 `domains` 期望派生会让一份金样的错同时改写两条腿)。
|
|
114
|
+
语料加载器只按这张表判「能不能写 `{derive:true}`」—— 原来对 `domains` 的特判并进同一条规则。
|
|
115
|
+
- **语料**:82 条 case 逐条加 `strictWrite` 显式金样,每条带 `why`(为什么受理 / 为什么拒):40 受理、42 拒(全在 `validate` 层)。
|
|
116
|
+
拒的 42 条 = 读腿抢救过的 22 条(全在逐条容错档的域上:models / skills / mcp / hosts)+ 读腿整条拒的 15 条 + 带非域名键的 5 条。
|
|
117
|
+
- **金样逐条核**(`test/conformance-strict-write-8.0.0.test.ts`):一条**不调 `parseDomain`** 的律 ——「严写拒 ⇔ 文档集里有非域名键 ∨ 读腿整条拒 ∨
|
|
118
|
+
读腿在提交的某个域上抢救过(`entry-dropped` / `domain-defaulted` / `hosts-grandfathered`);否则受理,受理形 = 读腿 effective 里那几格」——
|
|
119
|
+
对着已冻结的 `domains` 金样逐条推导,与入库的严写金样 82/82 一致。两份金样从此互相压着。
|
|
120
|
+
- **负控**(`test/conformance-mutation.test.ts` 第十一靶):把 `parseDomain` 换成读腿的宽松解析 ⇒ 严写腿**恰好**红在那 22 条读腿抢救 case 上
|
|
121
|
+
(别的腿、别的 case 一格不动);把非域名键静默跳过 ⇒ 恰好红在那 5 条上。
|
|
122
|
+
- **`parseDomain` 对非域名响亮拒**:`"x" is not a config domain`(名字出门前过与告警坐标同一道脱敏)。此前 `DOMAIN_SCHEMAS[x]` 是 undefined,
|
|
123
|
+
TypeError 被 getter 守卫吞掉、说成 `invalid "x" config (a value could not be read during validation)` —— 抛还是抛,原因说错了。
|
|
124
|
+
严写腿把文档集的每个键都交给它,所以这句话现在是用户看得见的拒因。真域名的行为一字不变。
|
|
125
|
+
|
|
126
|
+
### 二、每条腿都要表态:实现,或显式声明不承担(一条规则,四腿同律)
|
|
127
|
+
|
|
128
|
+
**病**:3.0.0 起「未登记的腿 ⇒ `skipped: no adapter`,不算违例」。闭集加一条腿时,这条律让每个没接新腿的消费方**静默跳过、默认当通过** ——
|
|
129
|
+
「这个消费方结构上没有这条写法」与「这个消费方忘了接」在报告里长一个样。
|
|
130
|
+
|
|
131
|
+
**修**:
|
|
132
|
+
|
|
133
|
+
- `ConformanceAdapter.legs` 改为全量 `Record<LegName, LegStance>`;`LegStance = ConformanceLeg | LegDeclined`,
|
|
134
|
+
`LegDeclined = { declined: LegDeclineReason; why: string }`。两臂**互斥**(`ConformanceLeg.declined?: never`、`LegDeclined.entry?` / `run?: never`),
|
|
135
|
+
运行期 release 门同一条律(带 `declined` 又带 `entry` 或 `run` = 形不对)—— 编译过了就证明表态形合法。收窄用 `stance.declined !== undefined`
|
|
136
|
+
(`"run" in stance` 收不窄)。理由词是闭集(`LEG_DECLINE_REASONS`),今天一个词 `"no-production-path"`
|
|
137
|
+
= 你的生产代码里结构上就没有这种读法 / 写法。刻意没有「有路径、还没接」的词:有路径就接,结局上的已知差走既有账本(带台账号、翻转条件、stale 律)。
|
|
138
|
+
- release 律:**每条腿都有表态**(缺表态 / 形不对 / 理由词不在闭集 / `why` 为空 / 既实现又声明 = 违例)· 至少**实现**一条腿 · **实现的腿必须跑**。
|
|
139
|
+
声明的腿逐格记 `skipped`(detail = `consumer declines this leg (<理由词>): <why>`),报告新增 `adapter.declined` 逐条列出。historical 模式不跑这些律。
|
|
140
|
+
- 新导出(`./conformance`):`LEG_DECLINE_REASONS`、`LEG_EXPECTATION`、类型 `LegStance` / `LegDeclined` / `LegDeclineReason` / `DomainDocsInput`。
|
|
141
|
+
|
|
142
|
+
### 三、`migrate-workers --check` 与真跑过同一只判定
|
|
143
|
+
|
|
144
|
+
**病**(7.0.0 既有,7.0.1 披露):`--check` 在整域终判**之前**就返回 —— 整域会被拒的文档(例:两只同名 worker)`--check` 报
|
|
145
|
+
`outcome=pending` / 退出 3,真跑报 `outcome=refused` / 退出 1,按 `--check` 做闸的脚本会放行一次必被拒的真跑;整域终判拒时 stdout 的
|
|
146
|
+
`refused[]` 为空,原因只在 stderr。
|
|
147
|
+
|
|
148
|
+
**修**:写之前只有**一只**判定 —— 逐只 refused → 无改动 ⇒ noop → 整域终判 → 有改动;`--check` 不参与判定,只决定「有改动」那一支写不写
|
|
149
|
+
(`pending` / `applied`)。整域终判拒时 `refused[]` 逐条带出被拒项,形与逐只 refused 同一套(`name` / `field` / `reason`;`field` 相对那只 worker;
|
|
150
|
+
条目本身没有字符串名字时 `name` 为 `null`,不编名字)。stderr 两句文案、退出码的含义表、`outcome` 闭集都不变。`migrateWorkersDomain` 的输出不变。
|
|
151
|
+
|
|
152
|
+
### 四、导出面登记表(API 面卫生的机器门,零新依赖)
|
|
153
|
+
|
|
154
|
+
- `scripts/api-surface.mjs`(`npm run api:surface [-- --check]`)用 TypeScript 检查器(本来就在 devDependencies)列出 exports 表里 22 条模块子路径的
|
|
155
|
+
全部导出名(`export *` 展开,`value` / `type` / `value+type` 三形),写成 `docs/API-SURFACE.md`(不随包发)。
|
|
156
|
+
- `test/api-surface.test.ts`:登记表逐名逐形 ≡ 真实导出面;子路径集合与次序 = exports 表;登记表 = 生成器原样输出;判据自证(每条子路径「值」形的名字
|
|
157
|
+
≡ 运行期模块对象的键)。加 / 删 / 改形一个导出而不重生成登记表 ⇒ 红 —— 导出面的每次变动从此以登记表 diff 出现在评审里。
|
|
158
|
+
- 编译器层的死代码门早已在位:`tsconfig.json` 的 `noUnusedLocals` / `noUnusedParameters`(`npm run typecheck` 覆盖 src + test),本版不重复造。
|
|
159
|
+
- **`./migrate` 本版不删**:五个消费仓 `origin/main` 普查,管理面仍有三处生产 import(hosts grandfather 路由、hosts status 路由、
|
|
160
|
+
workers migrate-model 路由:`grandfatherHosts` / `planWorkerModelMigration` / `applyModelMigrationPlan` / `migrationDiffViolations` /
|
|
161
|
+
`ApplyMigrationDocs` / `LegacyWorkerModel`);server / cli / client-core / sdk 零 import。零外部消费的前提不成立 ⇒ 不删(ROADMAP 条目已按普查改写)。
|
|
162
|
+
`./api/wire` 早在 `3.0.0` 删除;`createSiblingResolver` 的工厂命名在 `2.0.0` 已改(本版核过,无动作)。
|
|
163
|
+
|
|
164
|
+
### 用户可见变化(谁受影响 / 怎么改 / 不改会怎样)
|
|
165
|
+
|
|
166
|
+
1. **实现了 kit 适配器的消费方**(server、管理面)—— `legs` 少任何一条腿的表态 ⇒ **tsc 红**(`Partial<Record<LegName, ConformanceLeg>>` 不再可赋给
|
|
167
|
+
`legs`)。改:每条没有的腿写 `{ declined: "no-production-path", why: "<你仓的事实>" }`。不改(用 `as` 绕过类型)⇒ release 门违例
|
|
168
|
+
「leg `<腿名>` has no stance」,`report.ok = false`。
|
|
169
|
+
2. **持 `Record<LegName, …>` / 对 `LegName` 穷举 `switch` 的消费方** —— 编译红,要为 `strictWrite` 表态(实现或声明)。
|
|
170
|
+
3. **读 `report.adapter.legs` 的** —— 语义从「登记的腿」变成「**实现**的腿」(声明的腿在新字段 `adapter.declined` 里);读 `skipped` 格 detail
|
|
171
|
+
文案做判断的 —— `no adapter: …` 那句不再出现,换成 `consumer declines this leg (…)` 或 `no stance: …`。
|
|
172
|
+
4. **按 `migrate-workers --check` 做闸的脚本** —— 整域会被拒的文档(同名 / 同路由 / replicas 与 autoscaling 并存)现在 `--check` 也是
|
|
173
|
+
`outcome=refused` / 退出 1(以前 `pending` / 3)。退出码 3 仍只表示「会改、且写得进去」。读 `refused[]` 的 —— 这种文档上它不再是空数组,
|
|
174
|
+
项里 `name` 可以是 `null`(条目本身没有名字)。
|
|
175
|
+
5. **捕 `parseDomain` 抛错文案的**(只在传入非域名时)—— 文案从 `invalid "x" config (a value could not be read during validation)` 变成
|
|
176
|
+
`"x" is not a config domain`;抛的时机不变。
|
|
177
|
+
6. **装了包、只用 schema / 读写 helper 的消费方**:零变化(契约 schema、默认值、读腿读数一字未动)。
|
|
178
|
+
|
|
179
|
+
### 红先 / 门
|
|
180
|
+
|
|
181
|
+
- A:新腿前置格(`conformance-strict-write-8.0.0`)与表态律格(`conformance-legs-and-ledger` ①)。表态律的改前读数 = 7.0.1 同一构造
|
|
182
|
+
`violations=[]`、`ok=true`、detail `no adapter: this consumer registered no such read leg`(旧格「适配器缺一条腿 ⇒ 不算违例」钉的正是它,
|
|
183
|
+
本版按意图翻向);`parseDomain("hooks")` 改前抛 `a value could not be read`。
|
|
184
|
+
- C:先只改测试、对 7.0.1 的 `cli.ts` 跑 —— 4 格红:四态表新行 `--check` 整域拒 `expected 3 to be 1`;翻钉格
|
|
185
|
+
`[3, 'outcome=pending written=false']` ≠ `[1, 'outcome=refused written=false']`;`refused[]` `expected 0 to be greater than 0`;`--check` 的 stderr 为空。
|
|
186
|
+
修后 25/25。原 🚧 特征钉(`--check` 先于整域终判)按它自己的翻转句翻成新读数。
|
|
187
|
+
- B:`test/api-surface.test.ts`(五格)。
|
|
188
|
+
- 全量:见收车读数(`npm test` / `npm run build` / `npm pack --dry-run`)。
|
|
189
|
+
|
|
190
|
+
### 消费方(迁移句)
|
|
191
|
+
|
|
192
|
+
- **server**(今天精确钉 6.4.0;一致性门实现 `wire` + `localRoot`):提货 8.x 时 `serverAdapter` 的 `legs` 改成
|
|
193
|
+
`{ domains: { declined: "no-production-path", why: "…" }, strictWrite: { declined: "no-production-path", why: "worker 只读不写:写入只经中心" },
|
|
194
|
+
localRoot: …, wire: … }` —— `Partial<Record<LegName, ConformanceLeg>>` 那一行换成 `ConformanceAdapter["legs"]`。语料新增的 82 格严写读数在你这边
|
|
195
|
+
记 `skipped`(声明不承担),既有账本不受影响。`./migrate`:零读点(你的提货门已钉零 import),删不删都不碰你。
|
|
196
|
+
- **管理面(config-center 产品,今天钉 `^6.4.0`)**:提货 8.x 时 `legs` 四条表态 —— `domains` = 现在那条往返腿;**`strictWrite` = 域写受理那一段**
|
|
197
|
+
(对文档集的每个键跑写受理,任一份不受理 ⇒ `{ outcome: "rejected", stage: "validate", diagnostics }`,全部受理 ⇒
|
|
198
|
+
`{ outcome: "effective", effective: { <键>: <受理后的域文档> }, warnings: [], version: input.version, updatedAt: input.updatedAt }`);
|
|
199
|
+
`wire` / `localRoot` 声明 `no-production-path`(你生产 wire、不消费它;不读 `config.d/`)。两处已知差先看清再接:写受理剥未知键时回报的那份
|
|
200
|
+
名单在 kit 里没有位置(严写腿告警面恒空,按 `warnings: []` 交);写受理里的跨域引用完整性 `parseDomain` 不做(今天 40 条受理 case 跑过
|
|
201
|
+
`refIntegrityIssues` 零命中,所以暂不分叉)。`./migrate` 三处 import 不受影响(本版不删)。
|
|
202
|
+
- **cli / client-core**(钉 `^7`,脱字号不跨 major):不提货就零动作。提货时:不实现 kit 适配器(cli 已显式拒接 kit 读腿;client-core 零 import)⇒ 零改动;
|
|
203
|
+
cli 那一格「`conformance.projectWarning === configFns.projectWarning`」同一只函数,不受影响;`./migrate` 零 import。
|
|
204
|
+
- **sdk**:不依赖本包,零动作。
|
|
205
|
+
|
|
206
|
+
### 订正 7.0.1 披露(test d327-schema701 验收 32 格 PASS 31 / INFO 1 / FAIL 0 后补)
|
|
207
|
+
|
|
208
|
+
- **用户可见变化补一形(比字段改名更硬)**:7.0.1 起 `migrate-workers` 的 stdout **首行是结论行**(`outcome=… written=…`),整段 `JSON.parse(stdout)` 的旧脚本会抛错(test 实测 25/25 抛;7.0.0 为 0/24)—— 机读请取**最后一行**解析,或改读 `outcome` / `written` / `refused` 三个契约字段。
|
|
209
|
+
- 7.0.1 披露的「已知未修」两形(`--check` 先于整域终判、整域终判拒时 `refused[]` 为空)本版已修,见上第三节。
|
|
210
|
+
|
|
211
|
+
### 未闭环(如实)
|
|
212
|
+
|
|
213
|
+
- 严写腿没有 historical 读数(`conformance/historical/` 是三腿时代跑的,53 条 × 三条读腿);重跑会改写一批 `firstRedOn`,本版没做。
|
|
214
|
+
- 严写腿告警面恒空(`parseDomain` 不铸告警),写受理回报的剥键名单在 kit 里没有位置;严写不含跨域引用完整性。两样都是契约层的下一步,候裁。
|
|
215
|
+
- 语料 c06 / c20 的严写金样是 🚧 特征格(S-1020,已定性为缺陷、下一版源头修):写入席受理 runtime 旧文档时三闸旧键被 runtime schema 静默剥掉(读腿是抬升),管理面若按受理形落库,读侧抬升此后找不到它们 = 运维护栏静默消失。修掉(写侧对这三个旧键响亮拒)时两格翻 `rejected{validate}`;在那之前,一个**正确拒收**的管理面会在这两格与金样分歧,请登记进 `knownDivergences`。
|
|
10
216
|
|
|
11
217
|
## 7.0.1 — 2026-10-03 — 7.0.0 验收三件(文案 / 报告形):拒因补迁移指路、CLI 报告「没写」机读不可误读、指路写 `npx` 全名形(patch)
|
|
12
218
|
|
package/README.md
CHANGED
|
@@ -48,6 +48,37 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
48
48
|
> a zod-3 host (today the config-center product) still dedups to one instance. Moving this package to zod 4 is a major
|
|
49
49
|
> of its own (S-777); 6.2.0 does not touch the dependency or peer range.
|
|
50
50
|
|
|
51
|
+
## 升级指引:8.0.0 → 8.1.0(全部 additive,零动作可升)
|
|
52
|
+
|
|
53
|
+
8.1.0 只在 `./fleet`(与主入口)上**加**型与契约,零行为:既有字段的语义、`InstanceHealth` / `InstanceAnnouncement` 的形、两个就绪谓词一字未动。新增清单:
|
|
54
|
+
|
|
55
|
+
- `RestartSignal.withheld?` —— 可选布尔,引擎自判「这一拍不该被换代」;缺席 = `false`,读出形不铸键。
|
|
56
|
+
- `EngineSignal`(+ `EngineDraining`)与 `readEngineSignal(health)` —— 引擎自铸信号的**闭形**(`ready` 必填,`draining?` / `restart?` / `generation?` / `instanceId?` /
|
|
57
|
+
`leaseUntil?` / `configTargetVersion?` / `configAppliedVersion?` 可选),键平铺在 `/health` 与心跳体上;与既有读器走同一只受保护读口。
|
|
58
|
+
`undefined` = 没有这只信号,**不是就绪**;布尔 `draining` 的旧方言体读不出它(两个就绪谓词的输入仍是布尔方言,下一个 major 收)。
|
|
59
|
+
- 代际 —— `generation` 是非负整数的**规范十进制串**(`"7"`,不是 `"07"`、不是数字型),判别器 `isCanonicalGeneration`、数值比较 `compareGeneration`;三处 wire 形共用这一条判据。
|
|
60
|
+
- 服务租约 API 型 —— `ServingLeaseRequest`、`ServingLeaseResponse`(`granted { ttlMs }` / `denied { reason: superseded | retired }` / `unknown` 三词闭集,
|
|
61
|
+
**`unknown` 不是 `denied` 的一种**,`denied` 只凭正面证据;读器按 `verdict` 键判,`denied` 的原因词读不出时仍是 `denied`,原因读成 `unknown_reason`)、`SERVING_LEASE_TTL_DEFAULT_MS` / `SERVING_LEASE_TTL_FLOOR_MS` / `SERVING_LEASE_DRIFT_MARGIN_MS`、读器 `readServingLeaseRequest` / `readServingLeaseResponse`;
|
|
62
|
+
中心能力位 `CenterCapabilities { servingLease?: true }` + `readCenterCapabilities` —— 租约客户端只在位在场时启用,缺席 = 中心不支持租约(不续租、不围栏)。
|
|
63
|
+
- 排空口型 —— `DrainRequest`、`DrainReceipt`(`state: draining | already_draining`)、读器 `readDrainRequest` / `readDrainReceipt`。
|
|
64
|
+
- `FLEET_GENERATION_ENV` —— 配置渲染器与引擎共用的 generation env 名常量(实例身份 `instanceId` 由引擎每进程自铸,没有 env 名);它同时进了 `RESERVED_WORKER_ENV`,`worker.env` / `secretEnv` 设不了它。
|
|
65
|
+
- `clampServingLeaseTtlMs(n)` —— 配置的租约 TTL 的唯一夹取点(读不出 ⇒ 缺省 600 000;低于地板 ⇒ 60 000;无上限)。
|
|
66
|
+
|
|
67
|
+
提货成本只有一处:对 `RestartSignal` 的键做穷举的消费方会因多一个键编译红。逐条(谁受影响 / 怎么改)见 CHANGELOG 8.1.0。
|
|
68
|
+
|
|
69
|
+
## 升级指引:7.x → 8.0.0
|
|
70
|
+
|
|
71
|
+
8.0.0 的破坏面只在**一致性门 kit**(`./conformance`)的类型与 release 律、以及 CLI `migrate-workers --check` 的一种结局上;契约 schema、域默认值、
|
|
72
|
+
读写 helper 的读数一字未动。逐条(谁受影响 / 怎么改 / 不改会怎样)见 CHANGELOG 8.0.0「用户可见变化」,要点:
|
|
73
|
+
|
|
74
|
+
- **实现了 kit 适配器的**:`legs` 是全量 `Record<LegName, LegStance>` —— 四条腿(`domains` / `strictWrite` / `wire` / `localRoot`)每条要么实现、
|
|
75
|
+
要么写 `{ declined: "no-production-path", why: "<你仓的事实>" }`。少一条 ⇒ tsc 红;用 `as` 绕过 ⇒ release 门违例 `has no stance`。
|
|
76
|
+
只读的 worker:`domains` 与 `strictWrite` 声明不承担;写入席:实现 `strictWrite`(域写受理,任一份不受理 ⇒ `rejected{validate}`)。
|
|
77
|
+
- **持 `Record<LegName, …>` / 对 `LegName` 穷举的**:为新腿 `strictWrite` 表态。
|
|
78
|
+
- **按 `migrate-workers --check` 做闸的脚本**:整域会被拒的文档 `--check` 现在报 `outcome=refused` / 退出 1(以前 `pending` / 3),`refused[]`
|
|
79
|
+
逐条带出被拒项(`name` 可为 `null`)。
|
|
80
|
+
- **只用 schema / helper 的**:零动作。`./migrate` 本版**没有**删(管理面仍在用)。
|
|
81
|
+
|
|
51
82
|
## What's in it
|
|
52
83
|
|
|
53
84
|
- **The zod contract** (`types.ts`): `DOMAIN_SCHEMAS` (17 domains — 0.10.0 adds `projects`, the project
|
|
@@ -114,6 +145,11 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
114
145
|
| `dropped` (always) | `dropped` when `written=true`, else `wouldDrop` |
|
|
115
146
|
| — | `outcome`, `written` |
|
|
116
147
|
|
|
148
|
+
**8.0.0**: `--check` and a real run go through ONE verdict (per-worker refusals → nothing to change ⇒ `noop` → the whole-domain final judgment →
|
|
149
|
+
changes); `--check` only decides whether the "changes" branch writes (`pending`) or not. A document the whole-domain judgment refuses (two workers
|
|
150
|
+
with one name …) is `outcome=refused` / exit 1 under `--check` too (7.x: `pending` / exit 3), and `refused[]` names the refused items in the
|
|
151
|
+
per-worker shape (`name` / `field` / `reason`; `name` is `null` for an entry that carries no string name) instead of being empty.
|
|
152
|
+
|
|
117
153
|
Rejections that a migration can lift carry ONE signpost sentence (a constant, `MIGRATION_SIGNPOST`): the seven deleted 6.x fields, and an
|
|
118
154
|
`env` / `secretEnv` key the migration moves or drops (the execution-lane keys incl. `REMOTE_EXEC` / `REQUIRE_EXECUTION_LANE`, and the store keys
|
|
119
155
|
`DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND`). A reserved name the migration cannot move (`PORT`, `MODEL_*`, `MYSQL_*` …) carries none.
|
|
@@ -254,7 +290,20 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
254
290
|
sends it (UNKNOWN, not "none"). A name that cannot ride out is left out by the engine, judged by ONE predicate,
|
|
255
291
|
`isReportableRefusalName` (the wire can carry the key — `__proto__` it cannot — and `isDisclosableIdentity`, the
|
|
256
292
|
rule the dropped-entry identity on read warnings already uses: the `DISCLOSABLE_IDENTITY_RE` allowlist ∧ not
|
|
257
|
-
secret-shaped, 6.3.0).
|
|
293
|
+
secret-shaped, 6.3.0). **8.1.0** (additive) puts the engine's own state in one closed shape: `RestartSignal` gains the
|
|
294
|
+
optional `withheld` ("the engine says this beat must not be handed to a new generation"; absent ≡ false);
|
|
295
|
+
`EngineSignal` (`{ ready, draining?: EngineDraining, restart?, generation?, instanceId?, leaseUntil?,
|
|
296
|
+
configTargetVersion?, configAppliedVersion? }`) is the closed form of the engine-minted subset of the /health
|
|
297
|
+
self-description (keys flat on the /health and heartbeat bodies; `InstanceHealth` stays the open record) and
|
|
298
|
+
`readEngineSignal(health)` reads it through the same guarded reader as `readRestartSignal` / `readModelRefusals` —
|
|
299
|
+
`undefined` means "no such signal", never "ready", and a body carrying the boolean `draining` of the 8.0 dialect is not
|
|
300
|
+
one. The serving-lease wire (`ServingLeaseRequest` / `ServingLeaseResponse`: `granted { ttlMs }` | `denied { reason:
|
|
301
|
+
superseded | retired }` | `unknown` — a closed three-word set in which `unknown` is NOT a kind of `denied`; plus
|
|
302
|
+
`SERVING_LEASE_TTL_DEFAULT_MS` 600 000, `SERVING_LEASE_TTL_FLOOR_MS` 60 000 and `SERVING_LEASE_DRIFT_MARGIN_MS`, derived
|
|
303
|
+
from `DEFAULT_HEARTBEAT_MS`; `denied` rests on positive evidence only), the center capability bit `CenterCapabilities`
|
|
304
|
+
(`servingLease?: true` — absent means the center has no lease, so the engine neither renews nor is fenced), the drain
|
|
305
|
+
endpoint's `DrainRequest` / `DrainReceipt`, one reader per wire shape, and the env name `FLEET_GENERATION_ENV` are defined
|
|
306
|
+
here once for the renderer and the engine (an instance's id is minted by the engine per process — it has no env name).
|
|
258
307
|
- **Declared-but-unvalidated keys** (`PARSE_TRANSPARENT_KEYS`, 2.0.0): the ONE registry of keys this package
|
|
259
308
|
carries verbatim WITHOUT checking their shape, each row naming who does check it (the engine's ingest side or
|
|
260
309
|
a consumer's enforcement point). Today: `models[].compat` (1.10.0), `models[].headers` (6.4.0; the header-name
|
|
@@ -269,9 +318,11 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
269
318
|
|
|
270
319
|
## Cross-repo conformance gate (`./conformance`, 3.0.0)
|
|
271
320
|
|
|
272
|
-
契约仓自带**金样语料 + 比对器**,随包发(`conformance/cases/**`)
|
|
321
|
+
契约仓自带**金样语料 + 比对器**,随包发(`conformance/cases/**`)。每个消费方把**自己的生产入口**塞给
|
|
273
322
|
kit 跑一遍 ⇒ 同一份 settings 语料在配置中心、worker、管理面、契约仓自身读出的 effective 逐字节一致、
|
|
274
|
-
|
|
323
|
+
告警多重集一致;写入席对同一份文档收不收也一致。**四条腿**(8.0.0):`domains`(各域原始文档 → effective)、
|
|
324
|
+
`strictWrite`(同一份文档 → 严写受理;契约仓 = `parseDomain`,任一份不受理 ⇒ `rejected{validate}`,读腿抢救处严写拒)、
|
|
325
|
+
`wire`、`localRoot`。
|
|
275
326
|
|
|
276
327
|
```ts
|
|
277
328
|
import { runConformance } from "@sema-agent/settings-schema/conformance";
|
|
@@ -283,13 +334,15 @@ if (!report.ok) throw new Error(report.violations.join("\n"));
|
|
|
283
334
|
(**生产代码**的入口 —— 在测试里把契约包函数本身当读腿,门证明的只是「包等于包」)。4.0.0 起这句
|
|
284
335
|
**有牙**:入口落在**本包自己的 `dist/`**(拿自己比自己)⇒ `createContractAdapter` **当场拒**;落在
|
|
285
336
|
**另一份**装好的契约包里(`historical` 模式的正当目标)⇒ 工厂不拒,由 `release` 门点名。
|
|
286
|
-
-
|
|
287
|
-
|
|
337
|
+
- **每条腿都要表态**(8.0.0):实现它,或 `{ declined: "no-production-path", why }`(你的生产代码结构上没有这种读法 / 写法)。
|
|
338
|
+
缺表态 = release 违例;声明的腿逐格 `skipped`(带理由),报告 `adapter.declined` 列出。
|
|
339
|
+
- **两种模式**:`release`(版本三方对齐 + 每条腿有表态 + 生产入口核验 + 禁 `only`/归一器 + 至少实现一条腿 + **实现的腿
|
|
340
|
+
必须跑**)与 `historical`(对旧版包跑,只记行为 diff)。
|
|
288
341
|
- **四律**:对抗律 / 变异律 / 覆盖律 / 来源律 —— 缺一不算「门」。
|
|
289
342
|
- **已知分歧账本** `knownDivergences`:消费方对金样的**合法**差异(临时欠账 / 成文语义)登记成机器可核的
|
|
290
343
|
一行,命中记 `diff-ledgered` 不阻塞;**告警差与元数据差永不可入账**,而**从未命中的一条登记本身就是
|
|
291
344
|
违例**(棘轮只降不升,逼着到期翻转)。契约仓自证不许挂账本。
|
|
292
|
-
-
|
|
345
|
+
- 契约形、报告形、语料贡献形、以及**如实登记的边界**见 `docs/CONFORMANCE.md`。
|
|
293
346
|
- Node-only(语料在磁盘上,local-root 腿要落盘),不进主 barrel。
|
|
294
347
|
|
|
295
348
|
## Secret boundary
|
|
@@ -317,6 +370,7 @@ defaults. Each `config.d/<domain>.json` is exactly that domain's zod object.
|
|
|
317
370
|
自 `1.0.0`(改名)起本包按**标准 semver** 发版;`0.x` 时代的「破坏性变更必 bump minor」规矩**已撤**
|
|
318
371
|
(末条仅作历史注记)。判档看**消费方可见性**,不看 diff 大小:
|
|
319
372
|
|
|
373
|
+
- **CLI 报告形的契约边界(7.0.1 起)**:`migrate-workers` 的 stdout = 结论行 + JSON;其中 `outcome` / `written` / `refused` 三字段是机读契约(再改按 minor),其余键与行形是诊断面,patch 可变(test 认定口径 2026-10-03:CLI 报告形不在 C-R197 包导出面)。机读请取最后一行解析。
|
|
320
374
|
- **破坏性变更必 bump major**:字段/导出改名、改形(retype)、删除、默认值变更、校验收紧或放宽
|
|
321
375
|
(例:`.passthrough()` → 默认 strip、宽读 → 严判)、枚举**重排**——凡是能让一个按上一版契约写的
|
|
322
376
|
消费方在**运行期**变红、或让同一份配置解析出不同结果的改动;编译期同判,**唯一裁定例外** = 下一条的
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8.1.0
|
|
@@ -98,6 +98,11 @@
|
|
|
98
98
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
99
99
|
}
|
|
100
100
|
},
|
|
101
|
+
"strictWrite": {
|
|
102
|
+
"outcome": "rejected",
|
|
103
|
+
"stage": "validate",
|
|
104
|
+
"why": "拒:读腿逐条抢救 models(丢掉缺 id 的那一条 + entry-dropped);写入席不替运维删条目 —— 同一份文档里有一条坏条目,整次不受理。"
|
|
105
|
+
},
|
|
101
106
|
"wire": {
|
|
102
107
|
"derive": true
|
|
103
108
|
},
|
|
@@ -89,6 +89,11 @@
|
|
|
89
89
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
|
+
"strictWrite": {
|
|
93
|
+
"outcome": "rejected",
|
|
94
|
+
"stage": "validate",
|
|
95
|
+
"why": "拒:读腿对 hosts 先做存量夹紧(超长名剔除,hosts-grandfathered)再逐条抢救(entry-dropped);两样都只活在读路径,严写按现行 schema 判:名长 > 63 与名字形不合法都拒。"
|
|
96
|
+
},
|
|
92
97
|
"wire": {
|
|
93
98
|
"derive": true
|
|
94
99
|
},
|
|
@@ -102,6 +102,47 @@
|
|
|
102
102
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
|
+
"strictWrite": {
|
|
106
|
+
"outcome": "effective",
|
|
107
|
+
"effective": {
|
|
108
|
+
"models": {
|
|
109
|
+
"models": [
|
|
110
|
+
{
|
|
111
|
+
"api": "openai-completions",
|
|
112
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
113
|
+
"enabled": true,
|
|
114
|
+
"id": "m0-id",
|
|
115
|
+
"name": "m0",
|
|
116
|
+
"provider": "openai",
|
|
117
|
+
"quotaWeight": 1,
|
|
118
|
+
"reasoning": false,
|
|
119
|
+
"tier": "standard",
|
|
120
|
+
"vision": false
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"api": "openai-completions",
|
|
124
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
125
|
+
"enabled": true,
|
|
126
|
+
"id": "m1-id",
|
|
127
|
+
"name": "m1",
|
|
128
|
+
"provider": "openai",
|
|
129
|
+
"quotaWeight": 1,
|
|
130
|
+
"reasoning": false,
|
|
131
|
+
"tier": "standard",
|
|
132
|
+
"vision": false
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"roles": {},
|
|
136
|
+
"tierGroups": []
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"warnings": [],
|
|
140
|
+
"metadata": {
|
|
141
|
+
"version": 7,
|
|
142
|
+
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
143
|
+
},
|
|
144
|
+
"why": "受理:models 文档里字面叫 `models.1.k` 的键与条目里的未知键 `k` 都被 schema 剥掉(读腿报 unknown-keys-dropped);parseDomain 同样剥、不铸告警 —— 受理形逐字等于读腿 models 那一格,告警面为空。"
|
|
145
|
+
},
|
|
105
146
|
"wire": {
|
|
106
147
|
"derive": true
|
|
107
148
|
},
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
"outcome": "rejected",
|
|
4
4
|
"stage": "validate"
|
|
5
5
|
},
|
|
6
|
+
"strictWrite": {
|
|
7
|
+
"outcome": "rejected",
|
|
8
|
+
"stage": "validate",
|
|
9
|
+
"why": "拒:rosters 是 gate 域,读腿对坏条目(name 是数字)本就整域拒;严写同一份文档同样过不了 rosters 的 schema。"
|
|
10
|
+
},
|
|
6
11
|
"wire": {
|
|
7
12
|
"outcome": "rejected",
|
|
8
13
|
"stage": "validate",
|
|
@@ -89,6 +89,18 @@
|
|
|
89
89
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
|
+
"strictWrite": {
|
|
93
|
+
"outcome": "effective",
|
|
94
|
+
"effective": {
|
|
95
|
+
"runtime": {}
|
|
96
|
+
},
|
|
97
|
+
"warnings": [],
|
|
98
|
+
"metadata": {
|
|
99
|
+
"version": 7,
|
|
100
|
+
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
101
|
+
},
|
|
102
|
+
"why": "🚧 S-1020 特征格(已裁 = 缺陷,源头修排下一版):受理,受理形 = { runtime: {} }。读腿从 runtime 旧文档抬升 governance 三闸(legacy-governance-lifted);写入席不抬升 —— 受理的只有提交的 runtime 一份,三闸旧键被 runtime schema 静默剥掉(不拒、不告警,读腿的剥键扫描本就豁免这三个键)。写入席若按受理形落库,读侧抬升此后找不到它们。S-1020 修掉(写侧拒 runtime 上的三闸旧键)时本格翻 rejected{validate}。"
|
|
103
|
+
},
|
|
92
104
|
"wire": {
|
|
93
105
|
"derive": true
|
|
94
106
|
},
|
|
@@ -62,6 +62,11 @@
|
|
|
62
62
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
+
"strictWrite": {
|
|
66
|
+
"outcome": "rejected",
|
|
67
|
+
"stage": "validate",
|
|
68
|
+
"why": "拒:`hooks` 不是域名(它是 settings.json 的键,不是配置域)—— 写入席没有这个域可受理;读腿各按各的处置(domains 腿按域名取文档故无声、wire 逐字承运、本地报 not-a-domain),严写不替运维把它丢掉。"
|
|
69
|
+
},
|
|
65
70
|
"wire": {
|
|
66
71
|
"derive": true
|
|
67
72
|
},
|
|
@@ -326,6 +326,83 @@
|
|
|
326
326
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
327
327
|
}
|
|
328
328
|
},
|
|
329
|
+
"strictWrite": {
|
|
330
|
+
"outcome": "effective",
|
|
331
|
+
"effective": {
|
|
332
|
+
"limits": {
|
|
333
|
+
"zzzUnknown000": 0,
|
|
334
|
+
"zzzUnknown001": 1,
|
|
335
|
+
"zzzUnknown002": 2,
|
|
336
|
+
"zzzUnknown003": 3,
|
|
337
|
+
"zzzUnknown004": 4,
|
|
338
|
+
"zzzUnknown005": 5,
|
|
339
|
+
"zzzUnknown006": 6,
|
|
340
|
+
"zzzUnknown007": 7,
|
|
341
|
+
"zzzUnknown008": 8,
|
|
342
|
+
"zzzUnknown009": 9,
|
|
343
|
+
"zzzUnknown010": 10,
|
|
344
|
+
"zzzUnknown011": 11,
|
|
345
|
+
"zzzUnknown012": 12,
|
|
346
|
+
"zzzUnknown013": 13,
|
|
347
|
+
"zzzUnknown014": 14,
|
|
348
|
+
"zzzUnknown015": 15,
|
|
349
|
+
"zzzUnknown016": 16,
|
|
350
|
+
"zzzUnknown017": 17,
|
|
351
|
+
"zzzUnknown018": 18,
|
|
352
|
+
"zzzUnknown019": 19,
|
|
353
|
+
"zzzUnknown020": 20,
|
|
354
|
+
"zzzUnknown021": 21,
|
|
355
|
+
"zzzUnknown022": 22,
|
|
356
|
+
"zzzUnknown023": 23,
|
|
357
|
+
"zzzUnknown024": 24,
|
|
358
|
+
"zzzUnknown025": 25,
|
|
359
|
+
"zzzUnknown026": 26,
|
|
360
|
+
"zzzUnknown027": 27,
|
|
361
|
+
"zzzUnknown028": 28,
|
|
362
|
+
"zzzUnknown029": 29,
|
|
363
|
+
"zzzUnknown030": 30,
|
|
364
|
+
"zzzUnknown031": 31,
|
|
365
|
+
"zzzUnknown032": 32,
|
|
366
|
+
"zzzUnknown033": 33,
|
|
367
|
+
"zzzUnknown034": 34,
|
|
368
|
+
"zzzUnknown035": 35,
|
|
369
|
+
"zzzUnknown036": 36,
|
|
370
|
+
"zzzUnknown037": 37,
|
|
371
|
+
"zzzUnknown038": 38,
|
|
372
|
+
"zzzUnknown039": 39,
|
|
373
|
+
"zzzUnknown040": 40,
|
|
374
|
+
"zzzUnknown041": 41,
|
|
375
|
+
"zzzUnknown042": 42,
|
|
376
|
+
"zzzUnknown043": 43,
|
|
377
|
+
"zzzUnknown044": 44,
|
|
378
|
+
"zzzUnknown045": 45,
|
|
379
|
+
"zzzUnknown046": 46,
|
|
380
|
+
"zzzUnknown047": 47,
|
|
381
|
+
"zzzUnknown048": 48,
|
|
382
|
+
"zzzUnknown049": 49,
|
|
383
|
+
"zzzUnknown050": 50,
|
|
384
|
+
"zzzUnknown051": 51,
|
|
385
|
+
"zzzUnknown052": 52,
|
|
386
|
+
"zzzUnknown053": 53,
|
|
387
|
+
"zzzUnknown054": 54,
|
|
388
|
+
"zzzUnknown055": 55,
|
|
389
|
+
"zzzUnknown056": 56,
|
|
390
|
+
"zzzUnknown057": 57,
|
|
391
|
+
"zzzUnknown058": 58,
|
|
392
|
+
"zzzUnknown059": 59,
|
|
393
|
+
"zzzUnknown060": 60,
|
|
394
|
+
"zzzUnknown061": 61,
|
|
395
|
+
"zzzUnknown062": 62,
|
|
396
|
+
"zzzUnknown063": 63
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
"warnings": [],
|
|
400
|
+
"metadata": {
|
|
401
|
+
"version": 7,
|
|
402
|
+
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
403
|
+
},
|
|
404
|
+
"why": "受理:limits 是 open-world,64 个未知键原样承运(读腿报 unknown-keys-carried);严写同样承运,受理形等于读腿 limits 那一格,告警面为空。"
|
|
405
|
+
},
|
|
329
406
|
"wire": {
|
|
330
407
|
"derive": true
|
|
331
408
|
},
|