@sema-agent/settings-schema 8.3.1 → 9.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 +62 -0
- package/README.md +19 -2
- package/conformance/VERSION +1 -1
- package/dist/fleet.d.ts +55 -27
- package/dist/fleet.js +82 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,68 @@
|
|
|
6
6
|
> `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
|
|
7
7
|
> 包名是**历史记录**,如实保留不追改。
|
|
8
8
|
|
|
9
|
+
## 9.0.0 — 2026-10-09 — 能力读数三态读器 · 世代账两键由串改数(BREAKING)
|
|
10
|
+
|
|
11
|
+
> **判档 = major**(README「发版纪律」:导出删除 / 改名、类型 retype ⇒ major)。**硬 breaking:删改直接做,零别名,消费方 tsc 红即通知。** 两件:
|
|
12
|
+
> ① `readCenterCapabilities` 删,换 `readCapabilityReading` + `CapabilityReading`;② `EngineSignal.configTargetVersion` / `configAppliedVersion` 的类型由 `string` 改 `number`。
|
|
13
|
+
> 导出面登记表:`.` 与 `./fleet` 各 **−1**(`readCenterCapabilities`)**+2**(`readCapabilityReading` 值、`CapabilityReading` 类型),无改形。已知的消费方只有引擎侧(server)。
|
|
14
|
+
|
|
15
|
+
### 一、能力读数三态读器:`readCapabilityReading(reply): CapabilityReading`(替掉 `readCenterCapabilities`)
|
|
16
|
+
|
|
17
|
+
**病**:`capabilities` 键在场但形坏(例:`{ servingLease: "true" }`、`null`),`readCenterCapabilities` 回 `undefined`;而 `undefined` 在引擎侧的读法是「中心没宣告」⇒ 引擎撤围栏。
|
|
18
|
+
围栏这条轴上,「形坏」不许等于「缺席」—— 一份读不出的宣告可能是中心正在宣告租约,引擎却据此放掉了围栏。
|
|
19
|
+
|
|
20
|
+
**改**:读器吃心跳 **2xx 的整只答复体**(键查找进读器 —— 一只载体一只读器;不再让调用方自己取 `reply.capabilities`),答闭集三态:
|
|
21
|
+
|
|
22
|
+
- `{ kind: "declared"; capabilities: CenterCapabilities }` —— 键在场且过 `CenterCapabilities` schema(未知位被剥,缺席的位保持缺席;`{}` 也是 declared:宣告了、没有这一位);
|
|
23
|
+
- `{ kind: "undeclared" }` —— 体是对象、键缺席 = 老中心,什么都没说(键的值是 `undefined` 这种 JSON 载不了的形,读成键缺席);
|
|
24
|
+
- `{ kind: "unreadable"; why: "not-an-object" | "invalid-capabilities" }` —— 体不是对象(`null` / 数组 / 串 / 数 / 布尔 / `undefined`),或键在场但 schema 拒(含键上的 accessor 抛)。
|
|
25
|
+
|
|
26
|
+
**引擎侧契约(写进读器头注)**:只有 `declared` / `undeclared` 是读数;**`unreadable` 不是读数** —— 引擎当作没有这一拍答复:保留上一次状态(没有过读数前 = 无租约),
|
|
27
|
+
租约到期钟与不可达容忍钟照旧走,不因它启动、停止或延长任何东西。同步改真的注:`LEASE_STATES` 的 `none` 句(不再把「读不出」并进没宣告)、`CenterCapabilities` 头注(「读不出」不再算缺席)、
|
|
28
|
+
`HEARTBEAT_REPLY_CAPABILITIES_KEY` 头注(「没有键 = 老中心」保留;补「键在场而形坏 ≠ 没宣告,是 unreadable」)。旧读器的头注随它删除。
|
|
29
|
+
|
|
30
|
+
内部:`readSignalBody`(各 `read*` 共用的受保护读口)拆出同一个判决核心 `judgeSignalBody`,多答「为什么不是读数」(`not-an-object` / `invalid`);`readSignalBody` 是它的「读到或 `undefined`」面,
|
|
31
|
+
其余读器一字不变。仍是**一只** `safeParse`、**一只** `catch`(8.1.0 的单点钉不动)。
|
|
32
|
+
|
|
33
|
+
### 二、世代账两键由串改数:`EngineSignal.configTargetVersion` / `configAppliedVersion`
|
|
34
|
+
|
|
35
|
+
`z.string().min(1)` ⇒ **有限数**(与 `RestartSignal.version` 是同一个量、同一条规则,在 `fleet.ts` 里定义一次,内部常量 `ConfigVersion`,不导出)。理由:引擎 `/health` 铸的就是 number 世代账;
|
|
36
|
+
8.2.0 起头注就写着「引擎写成数、本形类型为串」并留给下一个 major —— 这里兑现,头注里的「types them as strings」「opaque strings」两句随之删掉,出口方言由三条减为两条
|
|
37
|
+
(布尔 `draining`、就绪时 `/health` 不带 `ready`)。
|
|
38
|
+
|
|
39
|
+
读数变化:带**数形**世代账的 `/health` 体,补上 `ready` 后 `readEngineSignal` 现在读得出(以前恒 `undefined`);带**串形**世代账的体现在读成 `undefined`(整只信号拒,不拿剩下的充数);
|
|
40
|
+
`NaN` / `±Infinity` 拒(JSON 本来也载不了)。`InstanceAnnouncement` 一直不收这两键(strip),未动。
|
|
41
|
+
|
|
42
|
+
### 用户可见变化(谁受影响 / 怎么改 / 不改会怎样)
|
|
43
|
+
|
|
44
|
+
1. **引擎侧租约客户端(server)**:调 `readCenterCapabilities` 的 ⇒ tsc 红(没有这个导出了)。迁移一句:
|
|
45
|
+
`readCenterCapabilities(reply.capabilities)?.servingLease` ⇒ `const r = readCapabilityReading(reply); r.kind === "declared" && r.capabilities.servingLease`;
|
|
46
|
+
并对 `unreadable` 按契约不动状态与钟(旧代码里它与 `undeclared` 是同一个 `undefined`,所以以前会撤围栏)。对 `r.kind` 做 `switch` 时加 `default: never` 臂,以后加态编译会红。
|
|
47
|
+
2. **读 `EngineSignal.configTargetVersion` / `configAppliedVersion` 的**:类型由 `string | undefined` 变 `number | undefined` ⇒ 把它当串用的 tsc 红;按数比较 / 展示即可(「equal ⇔ converged」不变)。
|
|
48
|
+
拿 `readEngineSignal` 读带串形世代账的体的,现在得 `undefined`。
|
|
49
|
+
3. **其它消费方**(管理面 / sdk / 只用 schema 与读写 helper 的):零变化 —— 据发版方核对,它们没有这两处的读点。
|
|
50
|
+
4. **持有导出面快照的测试 / 脚本**:`readCenterCapabilities` 行删,`readCapabilityReading`(值)与 `CapabilityReading`(类型)两行加。
|
|
51
|
+
|
|
52
|
+
### 红先 / 门
|
|
53
|
+
|
|
54
|
+
- 先写钉、对着改前的树跑红,再实现:`test/fleet-9.0.0.test.ts` 24 格(三态各一格 + 老中心 `{ ok: true }` ⇒ `undeclared`、`{ capabilities: { servingLease: "true" } }` ⇒ `unreadable` / `invalid-capabilities`、
|
|
55
|
+
`null` / `[]` / 串 ⇒ `unreadable` / `not-an-object`、`{ capabilities: { servingLease: true, extra: 1 } }` 按 `CenterCapabilities` 现行宽严(未知位被剥)⇒ `declared`;敌意 accessor / Proxy 不抛;
|
|
56
|
+
穷举 `switch` 编译钉;旧读器从两个入口都删;五处注释文本钉;世代账两键:数收、串 / 空串 / 非有限 / 空值拒,类型面,金样 `/health` 体,与 `RestartSignal.version` 同判,头注两句已删)⇒ 写钉时对改前的树 23 格里 21 红 2 绿
|
|
57
|
+
(2 绿是不依赖新读器的反向格),随后补一格(键的值是 `undefined` 读成键缺席);实装后 24 格全绿。
|
|
58
|
+
- 既有钉随之改真:`test/engine-signal-8.1.0.test.ts`(世代账夹具与类型面改数;「形坏读成 `undefined`,读方等同缺席」那格翻成 `unreadable`,不再等同缺席;读器三格改用新读器)、
|
|
59
|
+
`test/fleet-8.2.0.test.ts`(能力位金样改读整只答复体;「补上 ready 也仍因数形的世代账读不出」那格翻成读得出)。
|
|
60
|
+
- 变异自证(九个,每个都使预期那几格红):无效声明读成 `undeclared`(旧病)/ 非对象读成 `undeclared` / 载体不过 schema / `ConfigVersion` 丢 `.finite()` /
|
|
61
|
+
`configTargetVersion` 退回串 / 读器头注删「`unreadable` 不是读数」句 / 键缺席读成 `unreadable` / 旧读器别名回来 / `EngineSignal` 头注留着「types them as strings」。全量读数见发版链(本节不复述会过期的数)。
|
|
62
|
+
- 导出面登记表(`docs/API-SURFACE.md`)重生成:`.` 与 `./fleet` 各 −1 +2。
|
|
63
|
+
|
|
64
|
+
### 未闭环(如实)
|
|
65
|
+
|
|
66
|
+
- **引擎对 `unreadable` 的处置是契约不是本包能判的**:「保留上一次状态与钟」写在读器头注里,本包没有取引擎源码的通道,没有机器门看引擎是否照做。
|
|
67
|
+
- **`EngineSignal` 仍未被任何出口整只承载**:心跳的布尔 `draining`、`/health` 就绪时不带 `ready`,以及 `isReadyForTraffic` / `isRoutable` 仍吃布尔方言 —— 头注与 8.2.0 起都写「下一个 major」收;
|
|
68
|
+
本版只兑现了世代账两键,这几件仍待下一个 major。`InstanceHealth` 的 `restart` 仍未入形(同样留给下一个 major)。
|
|
69
|
+
- **读器不判中心与引擎的凭证对应**:`declared` 里的位按调用方凭证答(共享令牌看不到 `servingLease`)是中心的事,本读器只读答复体里有什么。
|
|
70
|
+
|
|
9
71
|
## 8.3.1 — 2026-10-08 — FLEET_SCOPE_ENV 注改真(patch)
|
|
10
72
|
|
|
11
73
|
> **判档 = patch**(README「发版纪律」:文案 / 注释 = patch)。只改 `src/fleet.ts` 里 `FLEET_SCOPE_ENV` 的头注(它随 `.d.ts` 发出);常量值不变,导出面登记表零 diff,零运行期行为,
|
package/README.md
CHANGED
|
@@ -48,6 +48,21 @@ 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.3.1 → 9.0.0(BREAKING:两件,硬 breaking —— 删改直接做,零别名)
|
|
52
|
+
|
|
53
|
+
**① 能力读数改三态读器**:`readCenterCapabilities(body)` 删,换 `readCapabilityReading(reply): CapabilityReading`(类型 `CapabilityReading` 同入)。输入改成心跳 **2xx 的整只答复体**(键查找进读器),
|
|
54
|
+
答三态:`{ kind: "declared"; capabilities }`(`capabilities` 键在场且过 schema)· `{ kind: "undeclared" }`(体是对象、键缺席 = 老中心)·
|
|
55
|
+
`{ kind: "unreadable"; why: "not-an-object" | "invalid-capabilities" }`(体不是对象 / 键在场但 schema 拒)。**只有 `declared` / `undeclared` 是读数;`unreadable` 不是读数 —— 引擎保留上一次状态与钟。**
|
|
56
|
+
|
|
57
|
+
- **谁需要**:引擎侧的租约客户端(server)—— 围栏这条轴上「形坏」不许等于「缺席」:以前 `capabilities` 键在场但形坏读成 `undefined` = 「没宣告」,引擎据此撤围栏。
|
|
58
|
+
- **谁受伤**:调 `readCenterCapabilities` 的(tsc 红:没有这个导出了)。其它消费方(管理面 / sdk)据发版方核对没有读点。
|
|
59
|
+
- **迁移一句**:`readCenterCapabilities(reply.capabilities)?.servingLease` ⇒ `const r = readCapabilityReading(reply); r.kind === "declared" && r.capabilities.servingLease`;
|
|
60
|
+
并对 `unreadable` 按契约不动状态与钟(旧代码里它和 `undeclared` 是同一个 `undefined`)。
|
|
61
|
+
|
|
62
|
+
**② `EngineSignal.configTargetVersion` / `configAppliedVersion` 由串改数**(有限数,与 `RestartSignal.version` 同一条规则):引擎 `/health` 铸的就是 number 世代账。
|
|
63
|
+
- **谁受伤**:把这两键当 `string` 用的(tsc 红);拿 `readEngineSignal` 读带串形世代账的体的(现在读成 `undefined`)。反过来,以前因数形世代账而读不出的 `/health` 体(补上 `ready` 后)现在读得出。
|
|
64
|
+
- **补偿**:按数用即可,没有别的动作。逐条见 CHANGELOG 9.0.0。
|
|
65
|
+
|
|
51
66
|
## 升级指引:8.3.0 → 8.3.1(注释改真,零动作可升)
|
|
52
67
|
|
|
53
68
|
`FLEET_SCOPE_ENV` 的头注改真:引擎读这只 env 只用来比对 `DrainRequest.scope`;续租体与心跳的 scope 由凭证定、不取这只 env。导出面与行为零变化,逐条见 CHANGELOG 8.3.1。
|
|
@@ -91,12 +106,12 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
91
106
|
- `RestartSignal.withheld?` —— 可选布尔,引擎自判「这一拍不该被换代」;缺席 = `false`,读出形不铸键。
|
|
92
107
|
- `EngineSignal`(+ `EngineDraining`)与 `readEngineSignal(health)` —— 引擎自铸信号的**闭形**(`ready` 必填,`draining?` / `restart?` / `generation?` / `instanceId?` /
|
|
93
108
|
`leaseUntil?` / `configTargetVersion?` / `configAppliedVersion?` 可选);与既有读器走同一只受保护读口。⚠️ 8.2.0 订正:今天的心跳与 `/health` 都不整只
|
|
94
|
-
承载它(心跳恒带布尔 `draining`、`/health` 就绪时不带 `ready`、世代账两键是数),`readEngineSignal` 对今天的心跳体恒 `undefined` —— 见 8.2.0
|
|
109
|
+
承载它(心跳恒带布尔 `draining`、`/health` 就绪时不带 `ready`、世代账两键是数),`readEngineSignal` 对今天的心跳体恒 `undefined` —— 见 8.2.0 升级指引(9.0.0 起这两键在 `EngineSignal` 里就是数,不再是读不出的原因)。
|
|
95
110
|
`undefined` = 没有这只信号,**不是就绪**;布尔 `draining` 的旧方言体读不出它(两个就绪谓词的输入仍是布尔方言,下一个 major 收)。
|
|
96
111
|
- 代际 —— `generation` 是非负整数的**规范十进制串**(`"7"`,不是 `"07"`、不是数字型),判别器 `isCanonicalGeneration`、数值比较 `compareGeneration`;三处 wire 形共用这一条判据。
|
|
97
112
|
- 服务租约 API 型 —— `ServingLeaseRequest`、`ServingLeaseResponse`(`granted { ttlMs }` / `denied { reason: superseded | retired }` / `unknown` 三词闭集,
|
|
98
113
|
**`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`;
|
|
99
|
-
中心能力位 `CenterCapabilities { servingLease?: true }` + `readCenterCapabilities` —— 租约客户端只在位在场时启用,缺席 = 中心不支持租约(不续租、不围栏)。
|
|
114
|
+
中心能力位 `CenterCapabilities { servingLease?: true }` + `readCenterCapabilities`(9.0.0 起删,由 `readCapabilityReading` 取代,见 9.0.0 升级指引)—— 租约客户端只在位在场时启用,缺席 = 中心不支持租约(不续租、不围栏)。
|
|
100
115
|
- 排空口型 —— `DrainRequest`、`DrainReceipt`(`state: draining | already_draining`)、读器 `readDrainRequest` / `readDrainReceipt`。
|
|
101
116
|
- `FLEET_GENERATION_ENV` —— 配置渲染器与引擎共用的 generation env 名常量(实例身份 `instanceId` 由引擎每进程自铸,没有 env 名);它同时进了 `RESERVED_WORKER_ENV`,`worker.env` / `secretEnv` 设不了它。
|
|
102
117
|
- `clampServingLeaseTtlMs(n)` —— 配置的租约 TTL 的唯一夹取点(读不出 ⇒ 缺省 600 000;低于地板 ⇒ 60 000;无上限)。
|
|
@@ -354,6 +369,8 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
354
369
|
the center's capability bits: `HEARTBEAT_REPLY_CAPABILITIES_KEY` (`"capabilities"`) on the heartbeat's 2xx reply.
|
|
355
370
|
**8.3.0** (additive) adds the env name `FLEET_SCOPE_ENV` (`"FLEET_SCOPE"`) — the tenant scope a fleet member belongs to, set by the
|
|
356
371
|
config renderer and read by the engine as its own identity's scope (absent ⇒ `GLOBAL_SCOPE`); a reserved worker env name.
|
|
372
|
+
**9.0.0** (BREAKING) replaces `readCenterCapabilities` with the three-state reader `readCapabilityReading(reply)` (`declared` |
|
|
373
|
+
`undeclared` | `unreadable` — a malformed declaration is not an absent one) and types `EngineSignal`'s two config-version keys as numbers.
|
|
357
374
|
- **Declared-but-unvalidated keys** (`PARSE_TRANSPARENT_KEYS`, 2.0.0): the ONE registry of keys this package
|
|
358
375
|
carries verbatim WITHOUT checking their shape, each row naming who does check it (the engine's ingest side or
|
|
359
376
|
a consumer's enforcement point). Today: `models[].compat` (1.10.0), `models[].headers` (6.4.0; the header-name
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
9.0.0
|
package/dist/fleet.d.ts
CHANGED
|
@@ -90,8 +90,10 @@ export declare function compareGeneration(a: string, b: string): -1 | 0 | 1;
|
|
|
90
90
|
/**
|
|
91
91
|
* The serving lease as the ENGINE holds it — a CLOSED set of four words, owned here (the engine's vocabulary, word for word;
|
|
92
92
|
* a reader that switches on it exhaustively is told by the compiler when a word is added):
|
|
93
|
-
* - `none` — the lease is not in effect: the center has
|
|
94
|
-
*
|
|
93
|
+
* - `none` — the lease is not in effect: the center has declared no {@link CenterCapabilities}`.servingLease` ({@link readCapabilityReading}
|
|
94
|
+
* says `undeclared` — an older center — or `declared` without the bit). The engine behaves exactly as an engine without a lease —
|
|
95
|
+
* no renewals, no fence. A reply that cannot be read (`unreadable`) is not a reading: it does not move the engine into or out of
|
|
96
|
+
* `none` — it keeps the state it had.
|
|
95
97
|
* - `granted` — the engine holds a lease; `leaseUntil` says until when (epoch ms, the engine's own computation from the
|
|
96
98
|
* granted `ttlMs`, see {@link ServingLeaseResponse}).
|
|
97
99
|
* - `expired` — the lease is in effect but no grant is valid right now (the center is unreachable, answered `unknown`, or has
|
|
@@ -118,7 +120,8 @@ export type LeaseState = z.infer<typeof LeaseState>;
|
|
|
118
120
|
* the contract only guarantees non-empty words, at least one).
|
|
119
121
|
* - `version` — the config-center version that currently differs from boot (the LATEST seen — it moves forward with
|
|
120
122
|
* later publishes, so a "one restart per version" dedupe bounds restarts per config version, not per reason set).
|
|
121
|
-
* Judged exactly as `readEffectiveWire` judges a config version (a finite number) — one quantity, one rule
|
|
123
|
+
* Judged exactly as `readEffectiveWire` judges a config version (a finite number, `ConfigVersion`) — one quantity, one rule;
|
|
124
|
+
* {@link EngineSignal}'s two version-ledger keys use the same one.
|
|
122
125
|
* - `since` — epoch ms when this restart first became required (stable across refreshes while `reasons` holds).
|
|
123
126
|
* - `withheld` — optional: the ENGINE's own verdict that this beat must not be handed to a new generation ("a restart is
|
|
124
127
|
* required, but replacing this instance right now would lose state it holds"). Absent ≡ `false` — an explicit `false`
|
|
@@ -584,13 +587,12 @@ export type EngineDraining = z.infer<typeof EngineDraining>;
|
|
|
584
587
|
* this shape off a body whose keys sit flat on it (no wrapper key).
|
|
585
588
|
*
|
|
586
589
|
* Where it rides today: on NEITHER exit whole. The heartbeat ({@link InstanceAnnouncement}) and `/health` ({@link InstanceHealth})
|
|
587
|
-
* still speak the 8.0 dialect for the keys they share with this shape, in
|
|
590
|
+
* still speak the 8.0 dialect for the keys they share with this shape, in two ways:
|
|
588
591
|
* 1. `draining` is a BOOLEAN there, not the {@link EngineDraining} object. The heartbeat always carries it (`false` included);
|
|
589
592
|
* `/health` carries `draining: true` only while draining.
|
|
590
593
|
* 2. `/health` carries `ready` only while NOT ready (`ready: false`); a ready engine's `/health` body has no `ready` key.
|
|
591
|
-
* 3. The engine writes `configTargetVersion` / `configAppliedVersion` as numbers; this shape types them as strings.
|
|
592
594
|
* So `readEngineSignal` over a heartbeat body is always `undefined` (point 1), and over a `/health` body it is `undefined` whenever
|
|
593
|
-
*
|
|
595
|
+
* either applies. What does ride both exits flat, under these names and with these values: `restart`, `instanceId`
|
|
594
596
|
* and the fleet-membership keys `generation` / `leaseState` / `leaseUntil` — on the heartbeat, read them off the parsed
|
|
595
597
|
* {@link InstanceAnnouncement} (which types all five). The next major retires the boolean pair (`draining` / `drainingSince`),
|
|
596
598
|
* has both exits carry this shape whole and moves {@link isReadyForTraffic} / {@link isRoutable} onto it; until then this is
|
|
@@ -612,7 +614,7 @@ export type EngineDraining = z.infer<typeof EngineDraining>;
|
|
|
612
614
|
* - `leaseUntil` — epoch ms until which the engine holds its serving lease, as the engine computed it from the last
|
|
613
615
|
* `ttlMs` it was granted ({@link ServingLeaseResponse}); present ⇔ `leaseState` is `granted`.
|
|
614
616
|
* - `configTargetVersion` / `configAppliedVersion` — the config-center version the engine was told to converge on, and the
|
|
615
|
-
* one it has applied (
|
|
617
|
+
* one it has applied: numbers, as the engine mints them (finite, the same rule as {@link RestartSignal}`.version`; equal ⇔ converged).
|
|
616
618
|
*
|
|
617
619
|
* Reading: `EngineSignal` is parsed with the default strip — a whole /health body goes in, only these keys come out. A
|
|
618
620
|
* shape violation in ANY key makes the whole signal `undefined` (it is never "the rest of it"). `undefined` means "no
|
|
@@ -655,8 +657,8 @@ export declare const EngineSignal: z.ZodObject<{
|
|
|
655
657
|
instanceId: z.ZodOptional<z.ZodString>;
|
|
656
658
|
leaseState: z.ZodOptional<z.ZodEnum<["none", "granted", "expired", "denied"]>>;
|
|
657
659
|
leaseUntil: z.ZodOptional<z.ZodNumber>;
|
|
658
|
-
configTargetVersion: z.ZodOptional<z.
|
|
659
|
-
configAppliedVersion: z.ZodOptional<z.
|
|
660
|
+
configTargetVersion: z.ZodOptional<z.ZodNumber>;
|
|
661
|
+
configAppliedVersion: z.ZodOptional<z.ZodNumber>;
|
|
660
662
|
}, "strip", z.ZodTypeAny, {
|
|
661
663
|
ready: boolean;
|
|
662
664
|
draining?: {
|
|
@@ -674,8 +676,8 @@ export declare const EngineSignal: z.ZodObject<{
|
|
|
674
676
|
generation?: string | undefined;
|
|
675
677
|
leaseState?: "none" | "granted" | "expired" | "denied" | undefined;
|
|
676
678
|
leaseUntil?: number | undefined;
|
|
677
|
-
configTargetVersion?:
|
|
678
|
-
configAppliedVersion?:
|
|
679
|
+
configTargetVersion?: number | undefined;
|
|
680
|
+
configAppliedVersion?: number | undefined;
|
|
679
681
|
}, {
|
|
680
682
|
ready: boolean;
|
|
681
683
|
draining?: {
|
|
@@ -693,8 +695,8 @@ export declare const EngineSignal: z.ZodObject<{
|
|
|
693
695
|
generation?: string | undefined;
|
|
694
696
|
leaseState?: "none" | "granted" | "expired" | "denied" | undefined;
|
|
695
697
|
leaseUntil?: number | undefined;
|
|
696
|
-
configTargetVersion?:
|
|
697
|
-
configAppliedVersion?:
|
|
698
|
+
configTargetVersion?: number | undefined;
|
|
699
|
+
configAppliedVersion?: number | undefined;
|
|
698
700
|
}>;
|
|
699
701
|
export type EngineSignal = z.infer<typeof EngineSignal>;
|
|
700
702
|
/**
|
|
@@ -881,13 +883,15 @@ export declare function readServingLeaseResponse(body: unknown): ServingLeaseRea
|
|
|
881
883
|
* What the CENTER says it can do: capability bits an engine reads before it starts using a center-side endpoint.
|
|
882
884
|
* - `servingLease` — the center answers serving-lease renewals ({@link ServingLeaseRequest} / {@link ServingLeaseResponse}).
|
|
883
885
|
*
|
|
884
|
-
* A bit is `true` or absent — one spelling for "not supported" (there is no `false`).
|
|
885
|
-
* the
|
|
886
|
-
* lease nor is fenced by one: this is the compatibility gate that lets an executor ship ahead of the center
|
|
887
|
-
* running engines into lease expiry. It is a gate, not a verdict — nothing in this shape says "the center refuses
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
886
|
+
* A bit is `true` or absent — one spelling for "not supported" (there is no `false`). An absent bit — in a declared object, or
|
|
887
|
+
* because the center declares nothing at all (an older center) — means the center does not support the lease, and the engine then
|
|
888
|
+
* neither renews a lease nor is fenced by one: this is the compatibility gate that lets an executor ship ahead of the center
|
|
889
|
+
* without pushing running engines into lease expiry. It is a gate, not a verdict — nothing in this shape says "the center refuses
|
|
890
|
+
* leases". Read a heartbeat reply with {@link readCapabilityReading} and start the lease client only on a `declared` reading whose
|
|
891
|
+
* `capabilities.servingLease === true`.
|
|
892
|
+
* Unknown bits are stripped (a newer center's extra bits never break an older reader); a known bit whose value is not `true` breaks
|
|
893
|
+
* the declaration — and a broken declaration is NOT an absent one: the reply reads as `unreadable`, which is not a reading (the engine
|
|
894
|
+
* keeps the state it had; see {@link readCapabilityReading}).
|
|
891
895
|
* It rides in ONE place: the heartbeat's 2xx reply ({@link FLEET_PATHS}`.instances`), under {@link HEARTBEAT_REPLY_CAPABILITIES_KEY}.
|
|
892
896
|
* The center answers the bits per CALLER CREDENTIAL: under a per-worker token, by what the center supports; under the shared token
|
|
893
897
|
* `servingLease` is always absent (the serving-lease endpoint takes per-worker tokens only, so announcing it there would only push
|
|
@@ -903,15 +907,39 @@ export declare const CenterCapabilities: z.ZodObject<{
|
|
|
903
907
|
export type CenterCapabilities = z.infer<typeof CenterCapabilities>;
|
|
904
908
|
/**
|
|
905
909
|
* The key of the heartbeat's 2xx reply body under which the center puts its {@link CenterCapabilities} — the object's only carrier.
|
|
906
|
-
* The center writes `{ …, [HEARTBEAT_REPLY_CAPABILITIES_KEY]: caps }`; the engine reads
|
|
907
|
-
* `
|
|
908
|
-
* bits). Nested, not flat: the reply's own keys and the capability bits never share a namespace.
|
|
909
|
-
* older center — no capability said.
|
|
910
|
+
* The center writes `{ …, [HEARTBEAT_REPLY_CAPABILITIES_KEY]: caps }`; the engine reads the whole reply with
|
|
911
|
+
* `readCapabilityReading(reply)` — the key lookup lives in the reader, one carrier one reader — and only on a 2xx (a failed heartbeat
|
|
912
|
+
* says nothing about the bits). Nested, not flat: the reply's own keys and the capability bits never share a namespace.
|
|
913
|
+
* A reply without the key is an older center — no capability said (`undeclared`). A reply with the key present with a value that
|
|
914
|
+
* breaks the shape is NOT "without the key" — it is `unreadable`, and an unreadable reply says nothing: neither declared nor undeclared.
|
|
910
915
|
*/
|
|
911
916
|
export declare const HEARTBEAT_REPLY_CAPABILITIES_KEY = "capabilities";
|
|
912
|
-
/**
|
|
913
|
-
|
|
914
|
-
|
|
917
|
+
/** What one heartbeat reply says about the center's capabilities — three readings of the reply, see {@link readCapabilityReading}. */
|
|
918
|
+
export type CapabilityReading = {
|
|
919
|
+
kind: "declared";
|
|
920
|
+
capabilities: CenterCapabilities;
|
|
921
|
+
} | {
|
|
922
|
+
kind: "undeclared";
|
|
923
|
+
} | {
|
|
924
|
+
kind: "unreadable";
|
|
925
|
+
why: "not-an-object" | "invalid-capabilities";
|
|
926
|
+
};
|
|
927
|
+
/**
|
|
928
|
+
* Engine side: what one heartbeat reply says about the center's capabilities. The input is the heartbeat's whole 2xx reply body
|
|
929
|
+
* (the key lookup lives here — one carrier, one reader), the output one of three readings:
|
|
930
|
+
* - `declared` — the key ({@link HEARTBEAT_REPLY_CAPABILITIES_KEY}) is present and its value passes {@link CenterCapabilities};
|
|
931
|
+
* `capabilities` is the parsed object (unknown bits stripped, an absent bit stays absent).
|
|
932
|
+
* - `undeclared` — the reply is an object and the key is absent: an older center, which has said nothing about capabilities.
|
|
933
|
+
* (`undefined` as the key's value is what JSON cannot carry; it reads as the key being absent.)
|
|
934
|
+
* - `unreadable` — the reply is not an object (`why: "not-an-object"`), or the key is present but its value is not a
|
|
935
|
+
* {@link CenterCapabilities} (`why: "invalid-capabilities"`; an accessor that throws reads the same way).
|
|
936
|
+
* Only `declared` and `undeclared` are readings. `unreadable` is not a reading: on the fence axis a malformed declaration must not
|
|
937
|
+
* read as "the center declared nothing", so the engine acts on it as on no reply at all — it keeps the state it last had (before
|
|
938
|
+
* any reading: no lease) and leaves its clocks, the lease expiry and the outage allowance, as they were. Start the lease client only
|
|
939
|
+
* on `declared` with `capabilities.servingLease === true`. Never throws; judged by the same one guarded body judgement as every
|
|
940
|
+
* other reader here.
|
|
941
|
+
*/
|
|
942
|
+
export declare function readCapabilityReading(reply: unknown): CapabilityReading;
|
|
915
943
|
/**
|
|
916
944
|
* The drain endpoint's request: "put this instance into draining, by this deadline". One persisted action, one validation,
|
|
917
945
|
* whether the center calls directly or an executor retries it. Two lanes, one shape: on the Docker lane (and for a direct
|
package/dist/fleet.js
CHANGED
|
@@ -79,6 +79,9 @@ export function drainVerdict(s) {
|
|
|
79
79
|
}
|
|
80
80
|
/** A wall-clock instant as epoch milliseconds — one quantity, one rule, wherever the fleet contract carries a point in time. */
|
|
81
81
|
const EpochMs = z.number().int().nonnegative();
|
|
82
|
+
/** A config-center version — a finite number, judged the way `readEffectiveWire` judges one. One quantity, one rule, wherever the
|
|
83
|
+
* fleet contract carries it: {@link RestartSignal}`.version` and {@link EngineSignal}'s version-ledger keys. */
|
|
84
|
+
const ConfigVersion = z.number().finite();
|
|
82
85
|
/**
|
|
83
86
|
* A generation is a non-negative integer written as its CANONICAL decimal string: `"0"`, `"7"`, `"10"` — no sign, no
|
|
84
87
|
* leading zero (`"07"` is not a generation), no fraction, no whitespace, ASCII digits only, and a string, never a number.
|
|
@@ -110,8 +113,10 @@ const Generation = z.string().refine(isCanonicalGeneration, { message: "a genera
|
|
|
110
113
|
/**
|
|
111
114
|
* The serving lease as the ENGINE holds it — a CLOSED set of four words, owned here (the engine's vocabulary, word for word;
|
|
112
115
|
* a reader that switches on it exhaustively is told by the compiler when a word is added):
|
|
113
|
-
* - `none` — the lease is not in effect: the center has
|
|
114
|
-
*
|
|
116
|
+
* - `none` — the lease is not in effect: the center has declared no {@link CenterCapabilities}`.servingLease` ({@link readCapabilityReading}
|
|
117
|
+
* says `undeclared` — an older center — or `declared` without the bit). The engine behaves exactly as an engine without a lease —
|
|
118
|
+
* no renewals, no fence. A reply that cannot be read (`unreadable`) is not a reading: it does not move the engine into or out of
|
|
119
|
+
* `none` — it keeps the state it had.
|
|
115
120
|
* - `granted` — the engine holds a lease; `leaseUntil` says until when (epoch ms, the engine's own computation from the
|
|
116
121
|
* granted `ttlMs`, see {@link ServingLeaseResponse}).
|
|
117
122
|
* - `expired` — the lease is in effect but no grant is valid right now (the center is unreachable, answered `unknown`, or has
|
|
@@ -137,7 +142,8 @@ export const LeaseState = z.enum(LEASE_STATES);
|
|
|
137
142
|
* the contract only guarantees non-empty words, at least one).
|
|
138
143
|
* - `version` — the config-center version that currently differs from boot (the LATEST seen — it moves forward with
|
|
139
144
|
* later publishes, so a "one restart per version" dedupe bounds restarts per config version, not per reason set).
|
|
140
|
-
* Judged exactly as `readEffectiveWire` judges a config version (a finite number) — one quantity, one rule
|
|
145
|
+
* Judged exactly as `readEffectiveWire` judges a config version (a finite number, `ConfigVersion`) — one quantity, one rule;
|
|
146
|
+
* {@link EngineSignal}'s two version-ledger keys use the same one.
|
|
141
147
|
* - `since` — epoch ms when this restart first became required (stable across refreshes while `reasons` holds).
|
|
142
148
|
* - `withheld` — optional: the ENGINE's own verdict that this beat must not be handed to a new generation ("a restart is
|
|
143
149
|
* required, but replacing this instance right now would lose state it holds"). Absent ≡ `false` — an explicit `false`
|
|
@@ -146,29 +152,39 @@ export const LeaseState = z.enum(LEASE_STATES);
|
|
|
146
152
|
*/
|
|
147
153
|
export const RestartSignal = z.object({
|
|
148
154
|
reasons: z.array(z.string().min(1)).min(1),
|
|
149
|
-
version:
|
|
155
|
+
version: ConfigVersion,
|
|
150
156
|
since: EpochMs,
|
|
151
157
|
withheld: z.boolean().optional(),
|
|
152
158
|
});
|
|
153
159
|
/**
|
|
154
|
-
* The ONE guarded body
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
+
* The ONE guarded body judgement behind every wire-shape read in this file: `pick(body)` — one key of the body, or the body itself —
|
|
161
|
+
* is judged against that shape's one schema, and the verdict says WHY when it is not a read: `not-an-object` (the body is null, an
|
|
162
|
+
* array or a scalar) or `invalid` (the shape rejected it, or an accessor threw). It never throws (a probe must not die on one odd
|
|
163
|
+
* body). Almost every reader wants only "a read or nothing" and goes through {@link readSignalBody}; the one reader whose
|
|
164
|
+
* failure direction matters ({@link readCapabilityReading}) takes the reason. A second hand-written guard anywhere else in this
|
|
165
|
+
* package is the defect, not a convenience.
|
|
160
166
|
*/
|
|
161
|
-
function
|
|
167
|
+
function judgeSignalBody(body, pick, schema) {
|
|
162
168
|
try {
|
|
163
169
|
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
164
|
-
return
|
|
170
|
+
return { kind: "not-an-object" };
|
|
165
171
|
const parsed = schema.safeParse(pick(body));
|
|
166
|
-
return parsed.success ? parsed.data :
|
|
172
|
+
return parsed.success ? { kind: "read", value: parsed.data } : { kind: "invalid" };
|
|
167
173
|
}
|
|
168
174
|
catch {
|
|
169
|
-
return
|
|
175
|
+
return { kind: "invalid" };
|
|
170
176
|
}
|
|
171
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* The reader behind every wire-shape read in this file that has no use for a reason: the engine-owned signals on /health
|
|
180
|
+
* ({@link readRestartSignal}, {@link readModelRefusals}, {@link readEngineSignal}) and the request / reply bodies of the
|
|
181
|
+
* fleet endpoints ({@link readServingLeaseRequest}, {@link readServingLeaseResponse}, {@link readDrainRequest},
|
|
182
|
+
* {@link readDrainReceipt}). Shape mismatch, a non-object body, even a throwing accessor ⇒ `undefined` — never throws.
|
|
183
|
+
*/
|
|
184
|
+
function readSignalBody(body, pick, schema) {
|
|
185
|
+
const judged = judgeSignalBody(body, pick, schema);
|
|
186
|
+
return judged.kind === "read" ? judged.value : undefined;
|
|
187
|
+
}
|
|
172
188
|
/** `pick` for a shape whose keys sit flat on the body itself (as opposed to under one key of it). */
|
|
173
189
|
const wholeBody = (body) => body;
|
|
174
190
|
/**
|
|
@@ -410,13 +426,12 @@ export const EngineDraining = z.object({
|
|
|
410
426
|
* this shape off a body whose keys sit flat on it (no wrapper key).
|
|
411
427
|
*
|
|
412
428
|
* Where it rides today: on NEITHER exit whole. The heartbeat ({@link InstanceAnnouncement}) and `/health` ({@link InstanceHealth})
|
|
413
|
-
* still speak the 8.0 dialect for the keys they share with this shape, in
|
|
429
|
+
* still speak the 8.0 dialect for the keys they share with this shape, in two ways:
|
|
414
430
|
* 1. `draining` is a BOOLEAN there, not the {@link EngineDraining} object. The heartbeat always carries it (`false` included);
|
|
415
431
|
* `/health` carries `draining: true` only while draining.
|
|
416
432
|
* 2. `/health` carries `ready` only while NOT ready (`ready: false`); a ready engine's `/health` body has no `ready` key.
|
|
417
|
-
* 3. The engine writes `configTargetVersion` / `configAppliedVersion` as numbers; this shape types them as strings.
|
|
418
433
|
* So `readEngineSignal` over a heartbeat body is always `undefined` (point 1), and over a `/health` body it is `undefined` whenever
|
|
419
|
-
*
|
|
434
|
+
* either applies. What does ride both exits flat, under these names and with these values: `restart`, `instanceId`
|
|
420
435
|
* and the fleet-membership keys `generation` / `leaseState` / `leaseUntil` — on the heartbeat, read them off the parsed
|
|
421
436
|
* {@link InstanceAnnouncement} (which types all five). The next major retires the boolean pair (`draining` / `drainingSince`),
|
|
422
437
|
* has both exits carry this shape whole and moves {@link isReadyForTraffic} / {@link isRoutable} onto it; until then this is
|
|
@@ -438,7 +453,7 @@ export const EngineDraining = z.object({
|
|
|
438
453
|
* - `leaseUntil` — epoch ms until which the engine holds its serving lease, as the engine computed it from the last
|
|
439
454
|
* `ttlMs` it was granted ({@link ServingLeaseResponse}); present ⇔ `leaseState` is `granted`.
|
|
440
455
|
* - `configTargetVersion` / `configAppliedVersion` — the config-center version the engine was told to converge on, and the
|
|
441
|
-
* one it has applied (
|
|
456
|
+
* one it has applied: numbers, as the engine mints them (finite, the same rule as {@link RestartSignal}`.version`; equal ⇔ converged).
|
|
442
457
|
*
|
|
443
458
|
* Reading: `EngineSignal` is parsed with the default strip — a whole /health body goes in, only these keys come out. A
|
|
444
459
|
* shape violation in ANY key makes the whole signal `undefined` (it is never "the rest of it"). `undefined` means "no
|
|
@@ -454,8 +469,8 @@ export const EngineSignal = z.object({
|
|
|
454
469
|
instanceId: z.string().min(1).optional(),
|
|
455
470
|
leaseState: LeaseState.optional(),
|
|
456
471
|
leaseUntil: EpochMs.optional(),
|
|
457
|
-
configTargetVersion:
|
|
458
|
-
configAppliedVersion:
|
|
472
|
+
configTargetVersion: ConfigVersion.optional(),
|
|
473
|
+
configAppliedVersion: ConfigVersion.optional(),
|
|
459
474
|
});
|
|
460
475
|
/**
|
|
461
476
|
* The reader of {@link EngineSignal}: the shape's keys, flat on the given body, judged by the same guarded body reader as
|
|
@@ -592,13 +607,15 @@ export function readServingLeaseResponse(body) {
|
|
|
592
607
|
* What the CENTER says it can do: capability bits an engine reads before it starts using a center-side endpoint.
|
|
593
608
|
* - `servingLease` — the center answers serving-lease renewals ({@link ServingLeaseRequest} / {@link ServingLeaseResponse}).
|
|
594
609
|
*
|
|
595
|
-
* A bit is `true` or absent — one spelling for "not supported" (there is no `false`).
|
|
596
|
-
* the
|
|
597
|
-
* lease nor is fenced by one: this is the compatibility gate that lets an executor ship ahead of the center
|
|
598
|
-
* running engines into lease expiry. It is a gate, not a verdict — nothing in this shape says "the center refuses
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
610
|
+
* A bit is `true` or absent — one spelling for "not supported" (there is no `false`). An absent bit — in a declared object, or
|
|
611
|
+
* because the center declares nothing at all (an older center) — means the center does not support the lease, and the engine then
|
|
612
|
+
* neither renews a lease nor is fenced by one: this is the compatibility gate that lets an executor ship ahead of the center
|
|
613
|
+
* without pushing running engines into lease expiry. It is a gate, not a verdict — nothing in this shape says "the center refuses
|
|
614
|
+
* leases". Read a heartbeat reply with {@link readCapabilityReading} and start the lease client only on a `declared` reading whose
|
|
615
|
+
* `capabilities.servingLease === true`.
|
|
616
|
+
* Unknown bits are stripped (a newer center's extra bits never break an older reader); a known bit whose value is not `true` breaks
|
|
617
|
+
* the declaration — and a broken declaration is NOT an absent one: the reply reads as `unreadable`, which is not a reading (the engine
|
|
618
|
+
* keeps the state it had; see {@link readCapabilityReading}).
|
|
602
619
|
* It rides in ONE place: the heartbeat's 2xx reply ({@link FLEET_PATHS}`.instances`), under {@link HEARTBEAT_REPLY_CAPABILITIES_KEY}.
|
|
603
620
|
* The center answers the bits per CALLER CREDENTIAL: under a per-worker token, by what the center supports; under the shared token
|
|
604
621
|
* `servingLease` is always absent (the serving-lease endpoint takes per-worker tokens only, so announcing it there would only push
|
|
@@ -609,16 +626,46 @@ export const CenterCapabilities = z.object({
|
|
|
609
626
|
});
|
|
610
627
|
/**
|
|
611
628
|
* The key of the heartbeat's 2xx reply body under which the center puts its {@link CenterCapabilities} — the object's only carrier.
|
|
612
|
-
* The center writes `{ …, [HEARTBEAT_REPLY_CAPABILITIES_KEY]: caps }`; the engine reads
|
|
613
|
-
* `
|
|
614
|
-
* bits). Nested, not flat: the reply's own keys and the capability bits never share a namespace.
|
|
615
|
-
* older center — no capability said.
|
|
629
|
+
* The center writes `{ …, [HEARTBEAT_REPLY_CAPABILITIES_KEY]: caps }`; the engine reads the whole reply with
|
|
630
|
+
* `readCapabilityReading(reply)` — the key lookup lives in the reader, one carrier one reader — and only on a 2xx (a failed heartbeat
|
|
631
|
+
* says nothing about the bits). Nested, not flat: the reply's own keys and the capability bits never share a namespace.
|
|
632
|
+
* A reply without the key is an older center — no capability said (`undeclared`). A reply with the key present with a value that
|
|
633
|
+
* breaks the shape is NOT "without the key" — it is `unreadable`, and an unreadable reply says nothing: neither declared nor undeclared.
|
|
616
634
|
*/
|
|
617
635
|
export const HEARTBEAT_REPLY_CAPABILITIES_KEY = "capabilities";
|
|
618
|
-
/**
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
636
|
+
/** The reply body as far as the capability carrier is concerned: the one key, optional, nothing else (the reply's other keys are stripped). */
|
|
637
|
+
const CapabilitiesCarrier = z.object({ [HEARTBEAT_REPLY_CAPABILITIES_KEY]: CenterCapabilities.optional() });
|
|
638
|
+
/**
|
|
639
|
+
* Engine side: what one heartbeat reply says about the center's capabilities. The input is the heartbeat's whole 2xx reply body
|
|
640
|
+
* (the key lookup lives here — one carrier, one reader), the output one of three readings:
|
|
641
|
+
* - `declared` — the key ({@link HEARTBEAT_REPLY_CAPABILITIES_KEY}) is present and its value passes {@link CenterCapabilities};
|
|
642
|
+
* `capabilities` is the parsed object (unknown bits stripped, an absent bit stays absent).
|
|
643
|
+
* - `undeclared` — the reply is an object and the key is absent: an older center, which has said nothing about capabilities.
|
|
644
|
+
* (`undefined` as the key's value is what JSON cannot carry; it reads as the key being absent.)
|
|
645
|
+
* - `unreadable` — the reply is not an object (`why: "not-an-object"`), or the key is present but its value is not a
|
|
646
|
+
* {@link CenterCapabilities} (`why: "invalid-capabilities"`; an accessor that throws reads the same way).
|
|
647
|
+
* Only `declared` and `undeclared` are readings. `unreadable` is not a reading: on the fence axis a malformed declaration must not
|
|
648
|
+
* read as "the center declared nothing", so the engine acts on it as on no reply at all — it keeps the state it last had (before
|
|
649
|
+
* any reading: no lease) and leaves its clocks, the lease expiry and the outage allowance, as they were. Start the lease client only
|
|
650
|
+
* on `declared` with `capabilities.servingLease === true`. Never throws; judged by the same one guarded body judgement as every
|
|
651
|
+
* other reader here.
|
|
652
|
+
*/
|
|
653
|
+
export function readCapabilityReading(reply) {
|
|
654
|
+
const judged = judgeSignalBody(reply, wholeBody, CapabilitiesCarrier);
|
|
655
|
+
switch (judged.kind) {
|
|
656
|
+
case "not-an-object":
|
|
657
|
+
return { kind: "unreadable", why: "not-an-object" };
|
|
658
|
+
case "invalid":
|
|
659
|
+
return { kind: "unreadable", why: "invalid-capabilities" };
|
|
660
|
+
case "read": {
|
|
661
|
+
const capabilities = judged.value[HEARTBEAT_REPLY_CAPABILITIES_KEY];
|
|
662
|
+
return capabilities === undefined ? { kind: "undeclared" } : { kind: "declared", capabilities };
|
|
663
|
+
}
|
|
664
|
+
default: {
|
|
665
|
+
const unhandled = judged;
|
|
666
|
+
return unhandled;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
622
669
|
}
|
|
623
670
|
/**
|
|
624
671
|
* The drain endpoint's request: "put this instance into draining, by this deadline". One persisted action, one validation,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/settings-schema",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|