@sema-agent/settings-schema 6.0.0 → 6.2.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.
@@ -16,16 +16,28 @@
16
16
  // zod is a VALUE import here (not just a type): {@link salvageEntries} introspects a domain schema to find
17
17
  // its entry collections. zod is this package's one runtime dependency, so this costs nothing new.
18
18
  import { z } from "zod";
19
- import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, InfraCostRates, LimitsConfig, SkillsManifestSchema, containsSecretToken } from "./types.js";
19
+ import { MODEL_ENTRY_REFUSAL_CODES, ModelRefusalsWire } from "./fleet.js";
20
+ import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, InfraCostRates, LimitsConfig, PRIVATE_KEY_PEM_HEADER, SkillsManifestSchema, containsSecretToken } from "./types.js";
20
21
  /** Redact secret-shaped tokens from a validation MESSAGE — a zod error echoes the offending value (e.g.
21
22
  * `received 'sk-live-…'`), so a secret pasted into the WRONG field would leak via the error string. Redacts
22
23
  * space-spanning auth credentials (`Bearer <token>`) FIRST (the token pass can't see across the space), then
23
24
  * any secret-shaped run (`%` kept in the run so a percent-encoded secret stays whole for the percent-aware
24
- * containsSecretToken). invariant ①: a secret VALUE never rides an error. R13/R14. */
25
+ * containsSecretToken). invariant ①: a secret VALUE never rides an error. R13/R14.
26
+ *
27
+ * **Output invariant** (S-712 verification pass N1 + codex review [high] ×2): a string holding a private-key PEM
28
+ * header ANYWHERE ({@link PRIVATE_KEY_PEM_HEADER}) is withheld WHOLE, and no result trips `containsSecretToken`.
29
+ * The PEM header is judged on the ORIGINAL text, before any replacement: the token pass can rewrite the header
30
+ * itself away (`sk-note:-----BEGIN` is one token-charset run that starts with `sk-`, so it becomes
31
+ * `[redacted-secret]` and the key body after it would ride out untouched — codex verification round). A PEM header
32
+ * carries spaces, so the targeted passes can never isolate it, and the detector only sees it at the very start of
33
+ * a string. Every warning face that goes through here (coordinates, error text, entry identity) inherits the same
34
+ * guarantee. The validation-side detector `containsSecretToken` is deliberately NOT widened here: making it reject
35
+ * a mid-string PEM would tighten every field it validates (a major). */
25
36
  export function redactSecrets(text) {
26
- return text
37
+ const targeted = text
27
38
  .replace(/\b(bearer|basic)(\s+)\S+/gi, "$1$2[redacted-secret]")
28
39
  .replace(/[A-Za-z0-9_./:+=%-]{6,}/g, (tok) => (containsSecretToken(tok) ? "[redacted-secret]" : tok));
40
+ return PRIVATE_KEY_PEM_HEADER.test(text) || containsSecretToken(targeted) ? "[redacted-secret]" : targeted;
29
41
  }
30
42
  /** Sanitize a ZodError IN PLACE so no surfaced error (message / `received` / nested `unionErrors`) echoes a
31
43
  * pasted secret. RECURSES into `unionErrors` (a `z.union` failure nests a ZodError per member — e.g. RoleTarget
@@ -173,6 +185,64 @@ const PLAIN_PATH_SEGMENT = /^(?!(?:0|[1-9]\d*)$)[^."\u0000-\u001f]+$/;
173
185
  export function formatConfigPath(p) {
174
186
  return p.map((seg) => (typeof seg === "number" ? String(seg) : PLAIN_PATH_SEGMENT.test(seg) ? seg : JSON.stringify(seg))).join(".");
175
187
  }
188
+ /** 身份串 —— 「一个用户写的身份串(条目的 `name` / `id`)能不能**原样**出门」的**唯一**判据:
189
+ * {@link redactSecrets} 原样放行。入参是 `string`、返回 `boolean`,**刻意不是**类型谓词(`v is string`):它对一些
190
+ * 字符串答 false,谓词形会让调用方的否定分支把一个 `string` 收窄成 `never`;「是不是字符串」由调用点自己判
191
+ * (运行期收到非字符串照答 false、不抛)。秘密形的串按**无名**处置(不带,而不是打码:打过码的串不再是身份)。
192
+ * 与坐标段脱敏、`error` 文案脱敏({@link sanitizeZodError})**同一只**判据:告警面上任何一处用户写的串,要么过这一只
193
+ * 脱敏器出门,要么(可缺的身份)不出门。两只判据会在同一条告警里给出两种答案(对抗轮 #2 真复现:`Bearer` 后接 NBSP
194
+ * 或短令牌时,旧判据放行、坐标却打码)。
195
+ * 执法面(同一条律):被丢条目的身份({@link DroppedEntryIdentity},本文件铸);`ModelRefusalsWire` 的 `entries` 键
196
+ * (经 {@link isReportableRefusalName},S-768);引擎自己的日志 / 拒因句里要不要写出一个名字,同样问这一只。 */
197
+ export function isDisclosableIdentity(v) {
198
+ return typeof v === "string" && redactSecrets(v) === v; // 非字符串(JS / any 调用方)⇒ false,不抛(秘密轴 fail-closed)
199
+ }
200
+ /** S-768 —— 「一个目录条目名能不能当 {@link ModelRefusalsWire} `entries` 的键」的**唯一**判据;引擎铸键前只调这一只,
201
+ * 不过 ⇒ 按无名处置(不进键、不计数)。两条律的合取,不手抄任何一条:
202
+ * ① 这只 schema **自己载得住**这个键 —— 就地拿 schema 判(空串不过键规则;`__proto__` 这类名被 zod 的 record 静默丢掉,
203
+ * 载不住),不写名单;
204
+ * ② {@link isDisclosableIdentity}(秘密形 ⇒ 不出门)。
205
+ * 非字符串 ⇒ false。住在本文件而不是 `./fleet`:`fleet.ts` 被 `types.ts` 引,它再引本文件会成环。 */
206
+ export function isReportableRefusalName(name) {
207
+ if (!isDisclosableIdentity(name))
208
+ return false;
209
+ const probe = ModelRefusalsWire.safeParse({ entries: { [name]: MODEL_ENTRY_REFUSAL_CODES[0] } });
210
+ return probe.success && Object.hasOwn(probe.data.entries, name);
211
+ }
212
+ /** 一条被丢条目原文的身份({@link DroppedEntryIdentity} 的**唯一**读法)。只看纯 JSON 对象的**自有数据
213
+ * 属性**;任何反射/陷阱抛错 ⇒ 当作读不出(身份是诊断面的附件,绝不许把抢救拖下水)。 */
214
+ function readEntryIdentity(entry) {
215
+ try {
216
+ if (!isPlainJsonObject(entry))
217
+ return undefined;
218
+ const own = (k) => {
219
+ const d = Object.getOwnPropertyDescriptor(entry, k);
220
+ return d !== undefined && "value" in d ? d.value : undefined;
221
+ };
222
+ const name = own("name");
223
+ const id = own("id");
224
+ const enabled = own("enabled");
225
+ const identity = {
226
+ ...(typeof name === "string" && isDisclosableIdentity(name) ? { name } : {}),
227
+ ...(typeof id === "string" && isDisclosableIdentity(id) ? { id } : {}),
228
+ ...(typeof enabled === "boolean" ? { enabled } : {}),
229
+ };
230
+ return Object.keys(identity).length > 0 ? identity : undefined;
231
+ }
232
+ catch {
233
+ return undefined;
234
+ }
235
+ }
236
+ /** 一条记账行 → 它的 `entry-dropped` 告警。域面({@link buildEffective})与 wire 面(skills manifest)共用
237
+ * 这**一只** —— 告警形只在这里拼,新字段不会在一条腿上漏铸。 */
238
+ function entryDroppedWarning(domain, d) {
239
+ return d.identity === undefined ? { domain, kind: "entry-dropped", path: d.path, error: d.error } : { domain, kind: "entry-dropped", path: d.path, error: d.error, identity: d.identity };
240
+ }
241
+ /** 一条被丢条目的记账行 —— `salvageEntries` 两种集合形(数组 / Record)共用的**唯一**铸点。 */
242
+ function droppedEntry(path, error, entry) {
243
+ const identity = readEntryIdentity(entry);
244
+ return identity === undefined ? { path, error } : { path, error, identity };
245
+ }
176
246
  /** 一次「什么都没丢」的抢救的位移表(冻结的共享空表 —— {@link rebaseWarningPaths} 对它恒等)。 */
177
247
  const NO_ENTRIES_DROPPED = Object.freeze({});
178
248
  /** 一条条目的 parse —— 坏就返回它的(脱敏)错误。恶意 getter/Proxy 在遍历中抛错时**绝不**外泄其文案
@@ -250,7 +320,7 @@ function salvageEntriesUnsafe(schema, raw) {
250
320
  keptFrom.push(i); // 压缩后第 keptFrom.length-1 条 = 原文档第 i 条
251
321
  return true;
252
322
  }
253
- bad.push({ path: [field, i], error });
323
+ bad.push(droppedEntry([field, i], error, entry));
254
324
  return false;
255
325
  });
256
326
  // 零幸存者 ⇒ 没有要抢救的东西(见头注:绝不凭空造出一个空集合)。
@@ -270,7 +340,7 @@ function salvageEntriesUnsafe(schema, raw) {
270
340
  if (error === undefined)
271
341
  kept[key] = entry;
272
342
  else
273
- bad.push({ path: [field, redactSecrets(key)], error }); // 键是用户写的,可能就是一条粘错位置的凭据
343
+ bad.push(droppedEntry([field, redactSecrets(key)], error, entry)); // 键是用户写的,可能就是一条粘错位置的凭据
274
344
  }
275
345
  if (bad.length > 0 && Object.keys(kept).length > 0) {
276
346
  dropped.push(...bad);
@@ -362,7 +432,7 @@ export function projectWarning(w) {
362
432
  case "legacy-governance-lifted":
363
433
  return { kind: w.kind, domain: w.domain, keys: w.keys };
364
434
  case "entry-dropped":
365
- return { kind: w.kind, domain: w.domain, path: w.path };
435
+ return { kind: w.kind, domain: w.domain, path: w.path, ...(w.identity === undefined ? {} : { identity: w.identity }) };
366
436
  case "unread-config-file":
367
437
  return { kind: w.kind, file: w.file, ...(w.domain === undefined ? {} : { domain: w.domain }), why: w.why };
368
438
  default: {
@@ -764,7 +834,7 @@ export function buildEffective(version, getRaw, updatedAt, onWarning) {
764
834
  // 不过本层自己的表,直接交给出口(出口会补上更外层的换算);`buffered` 里的是对**本层压缩后**
765
835
  // 文档的诊断,是从下方上来的 ⇒ 先过本层的表,再交给出口。scan 函数一字不动。
766
836
  for (const d of salvage.dropped)
767
- emit?.({ domain, kind: "entry-dropped", path: d.path, error: d.error });
837
+ emit?.(entryDroppedWarning(domain, d));
768
838
  for (const w of buffered)
769
839
  emit?.(rebaseWarningPaths(w, salvage.keptIndex));
770
840
  return parsed;
@@ -875,7 +945,7 @@ export function readEffectiveWire(raw, onWarning) {
875
945
  const retry = salvage.dropped.length > 0 ? SkillsManifestSchema.safeParse(salvage.raw) : undefined;
876
946
  if (retry?.success) {
877
947
  for (const d of salvage.dropped)
878
- onWarning?.({ domain: "skills", kind: "entry-dropped", path: d.path, error: d.error });
948
+ onWarning?.(entryDroppedWarning("skills", d));
879
949
  skills = retry.data;
880
950
  }
881
951
  else {
@@ -9,7 +9,7 @@
9
9
  * 规范化(canonical JSON、告警投影、坐标深相等)全部在 kit 内**单铸** —— 消费方零复述,因为一份被各自
10
10
  * 复述的规范化就是第二份真源,而两份真源的分歧正是本门要抓的那类病。
11
11
  */
12
- import type { EffectiveReadWarning } from "../config-fns.js";
12
+ import type { EffectiveReadWarning, WarningProjection } from "../config-fns.js";
13
13
  /** 三条读腿的名字。**闭集** —— 新开一条腿要同时在语料派生表(`derive.ts` 的 `DERIVATION_TRANSFORMS`)里表态。
14
14
  *
15
15
  * · `domains`:各域**原始文档**(center 侧存储形)→ effective。契约仓的 `buildEffective`。
@@ -192,6 +192,11 @@ export interface ConformanceNormalizer {
192
192
  why: string;
193
193
  /** 把一条坐标归一到共同空间。有损归一器在这里把段拼成串。 */
194
194
  path(p: readonly (string | number)[]): unknown;
195
+ /** 把一条**告警投影**归一到共同空间(可选;先于 {@link ConformanceNormalizer.path} 作用,期望与实际两侧同过)。
196
+ * 用途:剥掉旧版**铸不出**的 additive 字段(S-712 的 `entry-dropped.identity`)—— 旧版没有这个字段不是读法
197
+ * 分歧,不许被记成「旧版行为红」。剥掉的字段同样是有损的一部分,`why` 里写明。release 模式禁归一器,故
198
+ * 这只钩子只作用于 historical 读数。 */
199
+ warning?(p: WarningProjection): WarningProjection;
195
200
  }
196
201
  /**
197
202
  * 一格结局。`pass` / `expected-rejected` / `diff-ledgered` 不阻塞,`diff` / `harness-error` 红,
@@ -51,13 +51,20 @@ export function normalizeProjection(p, f) {
51
51
  }
52
52
  /** 恒等坐标映射(无归一器时的那一只)。 */
53
53
  const identityPath = (path) => path;
54
+ /** 归一器对一条投影的**整体**作用:先过投影级钩子({@link ConformanceNormalizer.warning},可缺),再做坐标映射 +
55
+ * 集合字段按袋。比对键与报告值共用这一只,两处不会各归一各的。 */
56
+ function normalizedProjection(p, normalizer) {
57
+ if (normalizer === undefined)
58
+ return normalizeProjection(p, identityPath);
59
+ return normalizeProjection(normalizer.warning === undefined ? p : normalizer.warning(p), normalizer.path);
60
+ }
54
61
  /** 一条投影的比对键。 */
55
62
  export function projectionKey(p, normalizer) {
56
- return stableKey(normalizeProjection(p, normalizer === undefined ? identityPath : normalizer.path));
63
+ return stableKey(normalizedProjection(p, normalizer));
57
64
  }
58
65
  /** 一条投影的规范形(进报告的 `missing`/`extra` 用,人读)。 */
59
66
  export function projectionValue(p, normalizer) {
60
- return canonicalize(normalizeProjection(p, normalizer === undefined ? identityPath : normalizer.path));
67
+ return canonicalize(normalizedProjection(p, normalizer));
61
68
  }
62
69
  /**
63
70
  * 两个投影**多重集**的差。重数保留:期望两条、实际一条 ⇒ `missing` 里留一条。
package/dist/fleet.d.ts CHANGED
@@ -45,6 +45,142 @@ export type DrainVerdict = "wait" | "exit_clean" | "exit_forced";
45
45
  /** The drain state machine's single decision point: keep waiting, exit clean (drained), or force the exit
46
46
  * (grace exhausted with legs still in flight — durable legs resume cross-instance via resumeFromRunId). */
47
47
  export declare function drainVerdict(s: DrainSnapshot): DrainVerdict;
48
+ /**
49
+ * "This instance must restart to apply config" — the engine's OWN observation (S-741), defined ONCE for both exits that
50
+ * carry it: the fleet heartbeat ({@link InstanceAnnouncement}`.restart`, typed) and GET /health (`restart`, read through
51
+ * {@link readRestartSignal} — {@link InstanceHealth} keeps carrying it untyped, see there). One mint point, two exits —
52
+ * never a second judgement.
53
+ *
54
+ * Shape = what the server already ships on /health (`restart: { reasons, version, since }`). PRESENCE is the signal:
55
+ * present ⇔ a restart is required, absent ⇔ not; there is deliberately no `required` boolean inside (a boolean next
56
+ * to presence is redundant, and two spellings of one fact drift).
57
+ * - `reasons` — which restart-to-apply slices differ from the value baked at boot (the vocabulary is the engine's;
58
+ * the contract only guarantees non-empty words, at least one).
59
+ * - `version` — the config-center version that currently differs from boot (the LATEST seen — it moves forward with
60
+ * later publishes, so a "one restart per version" dedupe bounds restarts per config version, not per reason set).
61
+ * Judged exactly as `readEffectiveWire` judges a config version (a finite number) — one quantity, one rule.
62
+ * - `since` — epoch ms when this restart first became required (stable across refreshes while `reasons` holds).
63
+ */
64
+ export declare const RestartSignal: z.ZodObject<{
65
+ reasons: z.ZodArray<z.ZodString, "many">;
66
+ version: z.ZodNumber;
67
+ since: z.ZodNumber;
68
+ }, "strip", z.ZodTypeAny, {
69
+ reasons: string[];
70
+ version: number;
71
+ since: number;
72
+ }, {
73
+ reasons: string[];
74
+ version: number;
75
+ since: number;
76
+ }>;
77
+ export type RestartSignal = z.infer<typeof RestartSignal>;
78
+ /**
79
+ * The /health exit of {@link RestartSignal}: `health.restart` judged against the one shape. Shape mismatch, a missing
80
+ * key, a non-object body, even a throwing accessor ⇒ `undefined` — never throws (a probe must not die on one odd body).
81
+ * Presence law: only `restart` counts; the top-level `restartRequired` boolean the server also ships is not read.
82
+ */
83
+ export declare function readRestartSignal(health: unknown): RestartSignal | undefined;
84
+ /**
85
+ * The model-refusal cause codes (S-768) — CLOSED sets, owned HERE, never a hand-copied second list. Adding a code is
86
+ * one edit in this file; who goes red at compile time: a reader that switches on the code exhaustively (a center), and
87
+ * a producer only where it holds a table KEYED BY the code (an exhaustive `switch` over the code, or a
88
+ * `Record<ModelEntryRefusalCode, …>`) — a producer table that merely `satisfies` these codes as values proves a subset,
89
+ * not equality, and stays green.
90
+ *
91
+ * - {@link MODEL_ENTRY_REFUSAL_CODES} — why ONE catalog entry is not served on this host (the other entries keep serving):
92
+ * `config.model_no_brain` — the entry's provider rides a brain this host has not mounted;
93
+ * `config.model_entry_invalid` — the entry does not match the `ModelEntry` shape (dropped by the read, or re-judged at
94
+ * the engine's own second boundary).
95
+ * - {@link MODEL_PLANE_REFUSAL_CODES} — why the model plane of the catalog the engine was handed was refused AS A GROUP
96
+ * (the engine keeps serving the model plane it had). This set mirrors the engine's group-refusal code TABLE as a
97
+ * whole family — never picked by which invariant is reachable on the hot path today (reachability moves, the table
98
+ * does not):
99
+ * `config.default_model_missing` — the candidate has no default model;
100
+ * `config.model_refused_name_minted` — a refused entry's name would be re-minted onto another entry by the
101
+ * tier / alias expansion (or the dropped-entry carrier cannot prove it would not be);
102
+ * `config.model_route_endpoint_missing` — an openai-compatible route of the candidate resolves to no endpoint;
103
+ * `config.gateway_fallback_without_primary` — fallback gateways are declared without a primary.
104
+ * Source (engine): the first two in `src/model-entry-refusal.ts`, the last two in `src/model-route-endpoint.ts:40,42`
105
+ * (invariant `model-route-endpoint`, judged by the model-plane stage in `src/config-center/apply-effective.ts:664-665`).
106
+ */
107
+ export declare const MODEL_ENTRY_REFUSAL_CODES: readonly ["config.model_no_brain", "config.model_entry_invalid"];
108
+ export declare const ModelEntryRefusalCode: z.ZodEnum<["config.model_no_brain", "config.model_entry_invalid"]>;
109
+ export type ModelEntryRefusalCode = z.infer<typeof ModelEntryRefusalCode>;
110
+ export declare const MODEL_PLANE_REFUSAL_CODES: readonly ["config.default_model_missing", "config.model_refused_name_minted", "config.model_route_endpoint_missing", "config.gateway_fallback_without_primary"];
111
+ export declare const ModelPlaneRefusalCode: z.ZodEnum<["config.default_model_missing", "config.model_refused_name_minted", "config.model_route_endpoint_missing", "config.gateway_fallback_without_primary"]>;
112
+ export type ModelPlaneRefusalCode = z.infer<typeof ModelPlaneRefusalCode>;
113
+ /**
114
+ * "This instance does not serve part (or all) of the model catalog it was handed" — the engine's OWN per-host verdict
115
+ * (S-768), defined ONCE for both exits that carry it: the fleet heartbeat ({@link InstanceAnnouncement}`.modelRefusals`)
116
+ * and GET /health (`modelRefusals`; {@link InstanceHealth} keeps carrying it untyped, as it does `restart`). One mint point,
117
+ * two exits, ONE reader for both: {@link readModelRefusals}.
118
+ *
119
+ * Two faces, split on purpose — the failure direction must match the meaning (this is a DIAGNOSTIC key; an unknown word
120
+ * in it must never make a healthy instance unroutable):
121
+ * - {@link ModelRefusalsWire} — what rides the wire and what a stored heartbeat row holds: the SHAPE law only, codes as
122
+ * open non-empty strings. A code this package version does not know passes (it is "not said" to this reader, and the
123
+ * stored row still carries it for a reader that is upgraded later).
124
+ * - {@link ModelRefusalsSignal} — what {@link readModelRefusals} returns: the wire narrowed to the closed sets above (an
125
+ * unknown entry code or plane code is dropped; narrowed to nothing ⇒ `undefined`, rendered like "not said"). The
126
+ * closed-set types stay the compile-time red source for readers when a code is added.
127
+ *
128
+ * The wire shape:
129
+ * - `entries` — catalog entry NAME → its code. The value is ONLY the code, on purpose: the engine's refusal fact also
130
+ * holds the upstream `id`, the `provider` word, the bad field keys and an operator sentence, and none of them rides
131
+ * here — an `entry_invalid` row is by definition one nothing validated, so its `id` / `provider` may be whatever the
132
+ * broken row held, and the sentence is prose, not contract. "Which name, which reason" is the whole wire. A name that
133
+ * cannot ride out is treated as NAMELESS — the engine leaves it out of this map entirely (not keyed, not counted). The
134
+ * ONE predicate is `isReportableRefusalName(name)` (main barrel): this schema can carry the key (non-empty; `__proto__`
135
+ * is silently dropped by the record, so it cannot) AND `isDisclosableIdentity(name)` (not secret-shaped — the rule the
136
+ * dropped-entry identity on read warnings already uses). A producer calls it before keying; this schema deliberately
137
+ * does not re-judge secrecy (see the 6.2.0 notes).
138
+ * - `plane` — present ⇔ the model plane of the latest catalog the engine was handed was refused as a group (the engine
139
+ * keeps serving the model plane it had); ONE code, never a list — one candidate has exactly one group verdict (the
140
+ * engine judges the group invariants in a fixed order and stops at the first that fails; a refusal from the Runner
141
+ * swap step carries no code and is outside this signal).
142
+ * - `entries` and `plane` describe the same latest candidate catalog — the one the engine was handed last; with `plane`
143
+ * present, that is the refused candidate (its per-entry verdicts are usually why it has no default). The engine does
144
+ * not swap its own refusal table while the group stays refused (its selection channels still refuse by the previous
145
+ * generation), so with `plane` present read `entries` as "why this catalog cannot land", not "what a pick is refused
146
+ * right now".
147
+ *
148
+ * PRESENCE is the signal (same law as {@link RestartSignal}): present ⇔ at least one reportable refusal (an entry refusal
149
+ * under a name that rides out, or a group refusal). An empty signal (no entries and no `plane`) is refused by the shape
150
+ * law — "none" has exactly one spelling, the absent key. Inside, "no entry refusals" is spelled `entries: {}` (the key is
151
+ * required) — one spelling at each level.
152
+ */
153
+ export declare const ModelRefusalsWire: z.ZodEffects<z.ZodObject<{
154
+ entries: z.ZodRecord<z.ZodString, z.ZodString>;
155
+ plane: z.ZodOptional<z.ZodString>;
156
+ }, "strip", z.ZodTypeAny, {
157
+ entries: Record<string, string>;
158
+ plane?: string | undefined;
159
+ }, {
160
+ entries: Record<string, string>;
161
+ plane?: string | undefined;
162
+ }>, {
163
+ entries: Record<string, string>;
164
+ plane?: string | undefined;
165
+ }, {
166
+ entries: Record<string, string>;
167
+ plane?: string | undefined;
168
+ }>;
169
+ export type ModelRefusalsWire = z.infer<typeof ModelRefusalsWire>;
170
+ /** {@link ModelRefusalsWire} narrowed to the closed code sets — the return type of {@link readModelRefusals}. */
171
+ export type ModelRefusalsSignal = {
172
+ entries: Record<string, ModelEntryRefusalCode>;
173
+ plane?: ModelPlaneRefusalCode;
174
+ };
175
+ /**
176
+ * The ONE reader of the model-refusals signal, for BOTH exits: pass a /health body or a parsed
177
+ * {@link InstanceAnnouncement} — it reads the `modelRefusals` key of whatever it is given. Shape mismatch, a missing key, a
178
+ * non-object body, even a throwing accessor ⇒ `undefined` — never throws. Then it narrows to the closed sets: an unknown
179
+ * entry code drops that entry, an unknown plane code drops `plane`; narrowed to nothing ⇒ `undefined`. So `undefined`
180
+ * means "nothing this reader can say"; whether that is "none" or "unknown" depends on whether the engine reports this key
181
+ * at all — it answers the key only and never judges the engine's version (see {@link InstanceAnnouncement}`.modelRefusals`).
182
+ */
183
+ export declare function readModelRefusals(body: unknown): ModelRefusalsSignal | undefined;
48
184
  /** The `/health` self-description every orchestrator reads (shipped shape, server 1.96.0). Liveness and
49
185
  * readiness are deliberately SPLIT: `status` stays ok while draining (the process is alive); routability is
50
186
  * `isReadyForTraffic`. Unknown fields pass through — health is a self-description, not a closed record. */
@@ -93,25 +229,83 @@ export declare const InstanceAnnouncement: z.ZodObject<{
93
229
  /** reachable base URL for the deployment form: k8s pod IP:port / docker net alias / 127.0.0.1:<engine.port> */
94
230
  address: z.ZodString;
95
231
  version: z.ZodString;
96
- /** self-declared: bootDone && !draining */
232
+ /** self-declared readiness: model roster landed AND not draining (server ≥7.102.0; older engines: !draining only) */
97
233
  ready: z.ZodBoolean;
98
234
  draining: z.ZodDefault<z.ZodBoolean>;
99
235
  heartbeatMs: z.ZodDefault<z.ZodNumber>;
236
+ /** S-741: present ⇔ this instance must restart to apply config — the SAME {@link RestartSignal} /health carries,
237
+ * minted by the same getter (self-reported: a worker-scoped credential can only report for its own worker).
238
+ * Every heartbeat re-announces the full row, so a later heartbeat WITHOUT it means "no longer required" — the
239
+ * stored row ({@link InstanceRegistration}) is replaced, never merged. */
240
+ restart: z.ZodOptional<z.ZodObject<{
241
+ reasons: z.ZodArray<z.ZodString, "many">;
242
+ version: z.ZodNumber;
243
+ since: z.ZodNumber;
244
+ }, "strip", z.ZodTypeAny, {
245
+ reasons: string[];
246
+ version: number;
247
+ since: number;
248
+ }, {
249
+ reasons: string[];
250
+ version: number;
251
+ since: number;
252
+ }>>;
253
+ /** S-768: present ⇔ this instance has at least one reportable model refusal — the SAME {@link ModelRefusalsWire} /health
254
+ * carries, minted by the same getter; read it with {@link readModelRefusals}`(announcement)` (never off this field
255
+ * directly: the field is the open wire form, the reader narrows to the closed sets). Every heartbeat re-announces the
256
+ * full row, so a later heartbeat WITHOUT it means "no reportable refusals any more" (the stored row is replaced, never
257
+ * merged). Reading absence: from an engine that reports this key, absent = none reportable; an engine that predates
258
+ * the key never sends it, so there absence is UNKNOWN, not "none" — tell the two apart by the announced `version`.
259
+ * Failure direction = meaning direction (this is a diagnostic key): a code this package version does not know passes
260
+ * (it is "not said" to the reader), and a value that breaks the {@link ModelRefusalsWire} shape law is dropped to
261
+ * ABSENT (`.catch`) — it never fails the announcement, so it can never make a healthy instance unroutable (unlike
262
+ * `restart`, whose shape still gates the row). The key may then be present with the value `undefined`. */
263
+ modelRefusals: z.ZodCatch<z.ZodOptional<z.ZodEffects<z.ZodObject<{
264
+ entries: z.ZodRecord<z.ZodString, z.ZodString>;
265
+ plane: z.ZodOptional<z.ZodString>;
266
+ }, "strip", z.ZodTypeAny, {
267
+ entries: Record<string, string>;
268
+ plane?: string | undefined;
269
+ }, {
270
+ entries: Record<string, string>;
271
+ plane?: string | undefined;
272
+ }>, {
273
+ entries: Record<string, string>;
274
+ plane?: string | undefined;
275
+ }, {
276
+ entries: Record<string, string>;
277
+ plane?: string | undefined;
278
+ }>>>;
100
279
  }, "strip", z.ZodTypeAny, {
101
280
  draining: boolean;
102
- instanceId: string;
103
281
  version: string;
282
+ instanceId: string;
104
283
  worker: string;
105
284
  address: string;
106
285
  ready: boolean;
107
286
  heartbeatMs: number;
287
+ restart?: {
288
+ reasons: string[];
289
+ version: number;
290
+ since: number;
291
+ } | undefined;
292
+ modelRefusals?: {
293
+ entries: Record<string, string>;
294
+ plane?: string | undefined;
295
+ } | undefined;
108
296
  }, {
109
- instanceId: string;
110
297
  version: string;
298
+ instanceId: string;
111
299
  worker: string;
112
300
  address: string;
113
301
  ready: boolean;
114
302
  draining?: boolean | undefined;
303
+ restart?: {
304
+ reasons: string[];
305
+ version: number;
306
+ since: number;
307
+ } | undefined;
308
+ modelRefusals?: unknown;
115
309
  heartbeatMs?: number | undefined;
116
310
  }>;
117
311
  export type InstanceAnnouncement = z.infer<typeof InstanceAnnouncement>;
@@ -139,34 +333,92 @@ export declare const InstanceRegistration: z.ZodObject<{
139
333
  /** reachable base URL for the deployment form: k8s pod IP:port / docker net alias / 127.0.0.1:<engine.port> */
140
334
  address: z.ZodString;
141
335
  version: z.ZodString;
142
- /** self-declared: bootDone && !draining */
336
+ /** self-declared readiness: model roster landed AND not draining (server ≥7.102.0; older engines: !draining only) */
143
337
  ready: z.ZodBoolean;
144
338
  draining: z.ZodDefault<z.ZodBoolean>;
145
339
  heartbeatMs: z.ZodDefault<z.ZodNumber>;
340
+ /** S-741: present ⇔ this instance must restart to apply config — the SAME {@link RestartSignal} /health carries,
341
+ * minted by the same getter (self-reported: a worker-scoped credential can only report for its own worker).
342
+ * Every heartbeat re-announces the full row, so a later heartbeat WITHOUT it means "no longer required" — the
343
+ * stored row ({@link InstanceRegistration}) is replaced, never merged. */
344
+ restart: z.ZodOptional<z.ZodObject<{
345
+ reasons: z.ZodArray<z.ZodString, "many">;
346
+ version: z.ZodNumber;
347
+ since: z.ZodNumber;
348
+ }, "strip", z.ZodTypeAny, {
349
+ reasons: string[];
350
+ version: number;
351
+ since: number;
352
+ }, {
353
+ reasons: string[];
354
+ version: number;
355
+ since: number;
356
+ }>>;
357
+ /** S-768: present ⇔ this instance has at least one reportable model refusal — the SAME {@link ModelRefusalsWire} /health
358
+ * carries, minted by the same getter; read it with {@link readModelRefusals}`(announcement)` (never off this field
359
+ * directly: the field is the open wire form, the reader narrows to the closed sets). Every heartbeat re-announces the
360
+ * full row, so a later heartbeat WITHOUT it means "no reportable refusals any more" (the stored row is replaced, never
361
+ * merged). Reading absence: from an engine that reports this key, absent = none reportable; an engine that predates
362
+ * the key never sends it, so there absence is UNKNOWN, not "none" — tell the two apart by the announced `version`.
363
+ * Failure direction = meaning direction (this is a diagnostic key): a code this package version does not know passes
364
+ * (it is "not said" to the reader), and a value that breaks the {@link ModelRefusalsWire} shape law is dropped to
365
+ * ABSENT (`.catch`) — it never fails the announcement, so it can never make a healthy instance unroutable (unlike
366
+ * `restart`, whose shape still gates the row). The key may then be present with the value `undefined`. */
367
+ modelRefusals: z.ZodCatch<z.ZodOptional<z.ZodEffects<z.ZodObject<{
368
+ entries: z.ZodRecord<z.ZodString, z.ZodString>;
369
+ plane: z.ZodOptional<z.ZodString>;
370
+ }, "strip", z.ZodTypeAny, {
371
+ entries: Record<string, string>;
372
+ plane?: string | undefined;
373
+ }, {
374
+ entries: Record<string, string>;
375
+ plane?: string | undefined;
376
+ }>, {
377
+ entries: Record<string, string>;
378
+ plane?: string | undefined;
379
+ }, {
380
+ entries: Record<string, string>;
381
+ plane?: string | undefined;
382
+ }>>>;
146
383
  } & {
147
384
  registeredMs: z.ZodNumber;
148
385
  lastSeenMs: z.ZodNumber;
149
386
  observedReady: z.ZodOptional<z.ZodBoolean>;
150
387
  }, "strip", z.ZodTypeAny, {
151
388
  draining: boolean;
152
- instanceId: string;
153
389
  version: string;
390
+ instanceId: string;
154
391
  worker: string;
155
392
  address: string;
156
393
  ready: boolean;
157
394
  heartbeatMs: number;
158
395
  registeredMs: number;
159
396
  lastSeenMs: number;
397
+ restart?: {
398
+ reasons: string[];
399
+ version: number;
400
+ since: number;
401
+ } | undefined;
402
+ modelRefusals?: {
403
+ entries: Record<string, string>;
404
+ plane?: string | undefined;
405
+ } | undefined;
160
406
  observedReady?: boolean | undefined;
161
407
  }, {
162
- instanceId: string;
163
408
  version: string;
409
+ instanceId: string;
164
410
  worker: string;
165
411
  address: string;
166
412
  ready: boolean;
167
413
  registeredMs: number;
168
414
  lastSeenMs: number;
169
415
  draining?: boolean | undefined;
416
+ restart?: {
417
+ reasons: string[];
418
+ version: number;
419
+ since: number;
420
+ } | undefined;
421
+ modelRefusals?: unknown;
170
422
  heartbeatMs?: number | undefined;
171
423
  observedReady?: boolean | undefined;
172
424
  }>;
@@ -410,7 +662,6 @@ export declare const UsageReport: z.ZodObject<{
410
662
  quotaWeightAtUse?: number | undefined;
411
663
  }>, "many">;
412
664
  }, "strip", z.ZodTypeAny, {
413
- instanceId: string;
414
665
  entries: {
415
666
  principal: string;
416
667
  model: string;
@@ -421,11 +672,11 @@ export declare const UsageReport: z.ZodObject<{
421
672
  runs?: number | undefined;
422
673
  quotaWeightAtUse?: number | undefined;
423
674
  }[];
675
+ instanceId: string;
424
676
  worker: string;
425
677
  windowStartMs: number;
426
678
  windowEndMs: number;
427
679
  }, {
428
- instanceId: string;
429
680
  entries: {
430
681
  principal: string;
431
682
  model: string;
@@ -436,6 +687,7 @@ export declare const UsageReport: z.ZodObject<{
436
687
  runs?: number | undefined;
437
688
  quotaWeightAtUse?: number | undefined;
438
689
  }[];
690
+ instanceId: string;
439
691
  worker: string;
440
692
  windowStartMs: number;
441
693
  windowEndMs: number;