@sema-agent/settings-schema 6.1.0 → 6.3.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 +265 -0
- package/README.md +40 -9
- package/conformance/VERSION +1 -1
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +166 -0
- package/conformance/cases/c41-s792-encoded-pem-identity/input.json +62 -0
- package/conformance/cases/c41-s792-encoded-pem-identity/meta.json +12 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +120 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/input.json +25 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/meta.json +11 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +137 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/input.json +44 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/meta.json +12 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +115 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/input.json +30 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/meta.json +10 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +185 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/input.json +78 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/meta.json +11 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/expected.json +88 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/input.json +18 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/meta.json +6 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/expected.json +98 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/input.json +26 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/meta.json +6 -0
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +0 -8
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/meta.json +2 -1
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +0 -8
- package/conformance/cases/cli02-control-and-zero-width-chars/meta.json +2 -1
- package/dist/config-fns.d.ts +62 -14
- package/dist/config-fns.js +105 -30
- package/dist/cross-session.d.ts +8 -5
- package/dist/cross-session.js +8 -5
- package/dist/fleet.d.ts +163 -0
- package/dist/fleet.js +126 -5
- package/dist/secret-scan.d.ts +39 -0
- package/dist/secret-scan.js +110 -0
- package/dist/types.d.ts +79 -72
- package/dist/types.js +17 -31
- package/package.json +2 -2
package/dist/config-fns.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
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 { MODEL_ENTRY_REFUSAL_CODES, ModelRefusalsWire } from "./fleet.js";
|
|
20
|
+
import { carriesUserinfo, secretScanForms } from "./secret-scan.js"; // package-private display-side normalization + the userinfo judgement (not in the barrel)
|
|
19
21
|
import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, InfraCostRates, LimitsConfig, PRIVATE_KEY_PEM_HEADER, SkillsManifestSchema, containsSecretToken } from "./types.js";
|
|
20
22
|
/** Redact secret-shaped tokens from a validation MESSAGE — a zod error echoes the offending value (e.g.
|
|
21
23
|
* `received 'sk-live-…'`), so a secret pasted into the WRONG field would leak via the error string. Redacts
|
|
@@ -23,20 +25,32 @@ import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX,
|
|
|
23
25
|
* any secret-shaped run (`%` kept in the run so a percent-encoded secret stays whole for the percent-aware
|
|
24
26
|
* containsSecretToken). invariant ①: a secret VALUE never rides an error. R13/R14.
|
|
25
27
|
*
|
|
26
|
-
* **Output invariant** (S-712 verification pass N1 + codex review [high] ×2): a string holding a private-key
|
|
27
|
-
* header ANYWHERE ({@link PRIVATE_KEY_PEM_HEADER}) is withheld WHOLE,
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* **Output invariant** (S-712 verification pass N1 + codex review [high] ×2; 6.3.0): a string holding a private-key
|
|
29
|
+
* PEM header ANYWHERE ({@link PRIVATE_KEY_PEM_HEADER}) in ANY of its {@link secretScanForms} `forms` is withheld WHOLE, a
|
|
30
|
+
* string whose normalization did not finish (`unresolved`) is withheld WHOLE, and no result trips `containsSecretToken` on
|
|
31
|
+
* any of its forms.
|
|
32
|
+
* The whole-string judgement is ONE rule — both detectors run over the normalized forms: the PEM header on every form of
|
|
33
|
+
* the ORIGINAL text, `containsSecretToken` on every form of the targeted result; what could not be normalized does not go
|
|
34
|
+
* out (6.3.0, codex r1 F1: `-----BEGIN+PRIVATE+KEY` form-encoded headers and four-layer encodings both rode out).
|
|
35
|
+
* 6.3.0 (codex review of v6.1.0 [high]): `key=` + a percent-encoded PEM showed its header only on a decoded form
|
|
36
|
+
* (`-----BEGIN%20PRIVATE%20KEY` — `%20` is not in `[A-Z0-9 ]`), and the detector's anchored decode missed it behind
|
|
37
|
+
* the `key=` prefix, so the whole key rode out through coordinates, zod `received` / `message` and entry identity.
|
|
38
|
+
* The PEM header is judged BEFORE any replacement: the token pass can rewrite the header itself away
|
|
39
|
+
* (`sk-note:-----BEGIN` is one token-charset run that starts with `sk-`, so it becomes `[redacted-secret]` and the
|
|
40
|
+
* key body after it would ride out untouched — codex verification round). A PEM header carries spaces, so the
|
|
41
|
+
* targeted passes can never isolate it, and the detector only sees it at the very start of a string. Every
|
|
42
|
+
* free-text warning face that goes through here (coordinates, error text) inherits the same guarantee; the entry
|
|
43
|
+
* identity face additionally has to be an identifier ({@link isDisclosableIdentity}). The validation-side detector
|
|
44
|
+
* `containsSecretToken` is deliberately NOT widened here: making it reject a mid-string PEM would tighten every field
|
|
45
|
+
* it validates (a major). */
|
|
35
46
|
export function redactSecrets(text) {
|
|
36
47
|
const targeted = text
|
|
37
48
|
.replace(/\b(bearer|basic)(\s+)\S+/gi, "$1$2[redacted-secret]")
|
|
38
49
|
.replace(/[A-Za-z0-9_./:+=%-]{6,}/g, (tok) => (containsSecretToken(tok) ? "[redacted-secret]" : tok));
|
|
39
|
-
|
|
50
|
+
const scan = secretScanForms(text);
|
|
51
|
+
const targetedForms = targeted === text ? scan.forms : secretScanForms(targeted).forms; // 没替换过(最常见)⇒ 同一组形,不再归一化一遍
|
|
52
|
+
const whole = scan.unresolved || scan.forms.some((form) => PRIVATE_KEY_PEM_HEADER.test(form)) || targetedForms.some((form) => containsSecretToken(form));
|
|
53
|
+
return whole ? "[redacted-secret]" : targeted;
|
|
40
54
|
}
|
|
41
55
|
/** Sanitize a ZodError IN PLACE so no surfaced error (message / `received` / nested `unionErrors`) echoes a
|
|
42
56
|
* pasted secret. RECURSES into `unionErrors` (a `z.union` failure nests a ZodError per member — e.g. RoleTarget
|
|
@@ -184,10 +198,62 @@ const PLAIN_PATH_SEGMENT = /^(?!(?:0|[1-9]\d*)$)[^."\u0000-\u001f]+$/;
|
|
|
184
198
|
export function formatConfigPath(p) {
|
|
185
199
|
return p.map((seg) => (typeof seg === "number" ? String(seg) : PLAIN_PATH_SEGMENT.test(seg) ? seg : JSON.stringify(seg))).join(".");
|
|
186
200
|
}
|
|
187
|
-
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
|
|
201
|
+
/** 本文件铸的、带用户写的键段的告警坐标出门前的**唯一**一道(6.3.0,codex r1 F2):字符串段过 {@link outgoingSegment} —— 与 `error` 文案
|
|
202
|
+
* ({@link sanitizeZodError})、身份否决({@link isDisclosableIdentity})**同一只**脱敏器,不另设判据;数字段(下标)原样。
|
|
203
|
+
* 键段是用户写的,完全可能就是一条粘错位置的凭据:被丢 Record 条目的键、未知键告警(`unknown-keys-dropped` /
|
|
204
|
+
* `-carried`)的键名都一样(后者 0.19.0 起从不过脱敏器,`sk-…` 形的未知键名原样进告警与投影)。schema 已知的字段名
|
|
205
|
+
* 脱敏器原样放行,所以坐标里「它是哪个字段」照旧可读。经它的坐标就是出门形 —— {@link droppedKeys} /
|
|
206
|
+
* {@link carriedUnknownKeys} 这两只导出函数给消费方的 `keys` 同样是它的产物。`legacy-governance-lifted.keys` 不经它:那些键只来自
|
|
207
|
+
* 闭集 {@link GOVERNANCE_MIRROR_KEYS},按构造没有用户写的串(opus 腿 I1)。 */
|
|
208
|
+
function outgoingPath(p) {
|
|
209
|
+
return p.map((seg) => (typeof seg === "string" ? outgoingSegment(seg) : seg));
|
|
210
|
+
}
|
|
211
|
+
/** 一段用户写的串(坐标键段、`hosts-grandfathered` 被夹紧的主机名)出门前的那一道:长过 {@link DISCLOSABLE_IDENTITY_MAX}(与身份白名单同一只上界)
|
|
212
|
+
* ⇒ 不扫描、整段 `[redacted-secret]`(标识符不会那么长;fail-closed 且 O(1) —— opus 独立腿 M1:6.3.0 首稿让每个未知键名
|
|
213
|
+
* 不论多长都过脱敏器,64 个 16 KB 的键名把一次读拖到 20 s 以上);否则过 {@link redactSecrets}。 */
|
|
214
|
+
function outgoingSegment(seg) {
|
|
215
|
+
return seg.length > DISCLOSABLE_IDENTITY_MAX ? "[redacted-secret]" : redactSecrets(seg);
|
|
216
|
+
}
|
|
217
|
+
/** 可披露身份与出门坐标段的**长度上界**(一只常量,两处用:{@link DISCLOSABLE_IDENTITY_RE} 的上限、{@link outgoingSegment}
|
|
218
|
+
* 的「超长不扫描」界)。与 web-admin 读侧名长上限 256 对齐。 */
|
|
219
|
+
const DISCLOSABLE_IDENTITY_MAX = 256;
|
|
220
|
+
/** 可披露身份的**闭形**(6.3.0):字符集 `A-Z a-z 0-9 . _ : / @ + -`、长度 1–256(上限与 web-admin 读侧名长上限 256 对齐;`ModelEntry.name`
|
|
221
|
+
* 本身不设上限,超 256 字的合法名按无名处置)。身份是**标识符**不是自由文本 ——
|
|
222
|
+
* 允许集才是它的语义:上游模型 id 的真实形(`accounts/fireworks/models/…`、`Qwen/Qwen2.5-72B-Instruct`、
|
|
223
|
+
* `claude-3-5-sonnet@20240620`、`deepseek-v4-flash:free`、`o3-mini+high`)都在集内;`%` / `=` / 空白 / 引号 / 反斜杠 /
|
|
224
|
+
* 控制与零宽字符 / 非 ASCII 一律在集外 —— 编码过的、带前缀的、多行的、会在日志里伪造行的串,不可能是一个标识符。
|
|
225
|
+
* 无旗标(无 `g` ⇒ `.test` 无状态,可放心共享这一只)。判据只有 {@link isDisclosableIdentity} 一只,消费方要
|
|
226
|
+
* 「这个名能不能写出去」问它,不要拿本常量自拼(集内仍有密钥形,见那一只的否决项)。 */
|
|
227
|
+
export const DISCLOSABLE_IDENTITY_RE = new RegExp(`^[A-Za-z0-9._:/@+-]{1,${DISCLOSABLE_IDENTITY_MAX}}$`);
|
|
228
|
+
/** 身份串 —— 「一个用户写的身份串(条目的 `name` / `id`)能不能**原样**出门」的**唯一**判据(6.3.0 起两项合取):
|
|
229
|
+
* ① **形**:落在 {@link DISCLOSABLE_IDENTITY_RE} 白名单闭形里(不再是「不像密钥就可披露」—— 6.1.0 / 6.2.0 的黑名单
|
|
230
|
+
* 判据放行过 `key=` + 百分号编码的整把 PEM 私钥,codex 对 v6.1.0 复审 [high]);
|
|
231
|
+
* ② **否决**(「无密钥形」):坐标脱敏器 {@link redactSecrets} 原样放行 —— 集内照样有密钥形(`sk-…` / `ghp_…` /
|
|
232
|
+
* `AKIA…` / 三段 JWT 的字符全在集内),而且判它的必须是坐标段、`error` 文案({@link sanitizeZodError})用的**同一只**
|
|
233
|
+
* 脱敏器:两只判据会在同一条告警里给出两种答案(对抗轮 #2 真复现;集内的残例 = `@` 之后的单点 `eyJ…` 串,裸探测器
|
|
234
|
+
* 判阴、脱敏器会改,语料 c41 钉着);∧ 不是带 userinfo 的 URL —— 校验侧 `NON_SECRET_URL` 拒写的同一只判定
|
|
235
|
+
* (`carriesUserinfo`,包内私有;opus 腿 L1:`postgres://svc:pw@h` 的字符全在集内)。
|
|
236
|
+
* 入参是 `string`、返回 `boolean`,**刻意不是**类型谓词(`v is string`):它对一些字符串答 false,谓词形会让调用方的
|
|
237
|
+
* 否定分支把一个 `string` 收窄成 `never`;「是不是字符串」由调用点自己判(运行期收到非字符串照答 false、不抛)。
|
|
238
|
+
* 不可披露的串按**无名**处置(不带,而不是打码:打过码的串不再是身份)。
|
|
239
|
+
* 执法面(同一条律):被丢条目的身份({@link DroppedEntryIdentity},本文件铸);`ModelRefusalsWire` 的 `entries` 键
|
|
240
|
+
* (经 {@link isReportableRefusalName},S-768);引擎自己的日志 / 拒因句里要不要写出一个名字,同样问这一只。 */
|
|
241
|
+
export function isDisclosableIdentity(v) {
|
|
242
|
+
// 非字符串(JS / any 调用方)⇒ false,不抛(秘密轴 fail-closed)
|
|
243
|
+
return typeof v === "string" && DISCLOSABLE_IDENTITY_RE.test(v) && redactSecrets(v) === v && !carriesUserinfo(v);
|
|
244
|
+
}
|
|
245
|
+
/** S-768 —— 「一个目录条目名能不能当 {@link ModelRefusalsWire} `entries` 的键」的**唯一**判据;引擎铸键前只调这一只,
|
|
246
|
+
* 不过 ⇒ 按无名处置(不进键、不计数)。两条律的合取,不手抄任何一条:
|
|
247
|
+
* ① 这只 schema **自己载得住**这个键 —— 就地拿 schema 判(空串不过键规则;`__proto__` 这类名被 zod 的 record 静默丢掉,
|
|
248
|
+
* 载不住),不写名单;
|
|
249
|
+
* ② {@link isDisclosableIdentity}(不是白名单闭形里的标识符,或是秘密形 ⇒ 不出门)。
|
|
250
|
+
* 非字符串 ⇒ false。住在本文件而不是 `./fleet`:`fleet.ts` 被 `types.ts` 引,它再引本文件会成环。 */
|
|
251
|
+
export function isReportableRefusalName(name) {
|
|
252
|
+
if (!isDisclosableIdentity(name))
|
|
253
|
+
return false;
|
|
254
|
+
const probe = ModelRefusalsWire.safeParse({ entries: { [name]: MODEL_ENTRY_REFUSAL_CODES[0] } });
|
|
255
|
+
return probe.success && Object.hasOwn(probe.data.entries, name);
|
|
256
|
+
}
|
|
191
257
|
/** 一条被丢条目原文的身份({@link DroppedEntryIdentity} 的**唯一**读法)。只看纯 JSON 对象的**自有数据
|
|
192
258
|
* 属性**;任何反射/陷阱抛错 ⇒ 当作读不出(身份是诊断面的附件,绝不许把抢救拖下水)。 */
|
|
193
259
|
function readEntryIdentity(entry) {
|
|
@@ -202,8 +268,8 @@ function readEntryIdentity(entry) {
|
|
|
202
268
|
const id = own("id");
|
|
203
269
|
const enabled = own("enabled");
|
|
204
270
|
const identity = {
|
|
205
|
-
...(
|
|
206
|
-
...(
|
|
271
|
+
...(typeof name === "string" && isDisclosableIdentity(name) ? { name } : {}),
|
|
272
|
+
...(typeof id === "string" && isDisclosableIdentity(id) ? { id } : {}),
|
|
207
273
|
...(typeof enabled === "boolean" ? { enabled } : {}),
|
|
208
274
|
};
|
|
209
275
|
return Object.keys(identity).length > 0 ? identity : undefined;
|
|
@@ -253,7 +319,7 @@ function judgeEntry(schema, value) {
|
|
|
253
319
|
* 造出一个空集合**。
|
|
254
320
|
*
|
|
255
321
|
* 🔴 **告警不许比它诊断的东西更危险**(复审 R1 两条,红先复现):① Record 的键是用户写的,完全可能就是
|
|
256
|
-
* 一条粘错位置的凭据,所以路径里的键段过 {@link redactSecrets} 才出门(
|
|
322
|
+
* 一条粘错位置的凭据,所以路径里的键段过 {@link redactSecrets} 才出门(6.3.0 起经 `outgoingPath` 这一道;不变量①:secret 值永不随错误外流,
|
|
257
323
|
* 与 parseDomain 的 R13/R14 同一条);② 整段遍历包 try/catch —— 读字段、枚举 Record 都会触发访问器,
|
|
258
324
|
* 抛出来的文案可能带 secret。抢救是**尽力而为**的:抢不动就当没抢救(返回原引用),让既有的整域档位去裁,
|
|
259
325
|
* 绝不把解析拖下水、更不回显它的错误(与同文件 {@link droppedKeys} 逐字同纪律)。
|
|
@@ -299,7 +365,7 @@ function salvageEntriesUnsafe(schema, raw) {
|
|
|
299
365
|
keptFrom.push(i); // 压缩后第 keptFrom.length-1 条 = 原文档第 i 条
|
|
300
366
|
return true;
|
|
301
367
|
}
|
|
302
|
-
bad.push(droppedEntry([field, i], error, entry));
|
|
368
|
+
bad.push(droppedEntry(outgoingPath([field, i]), error, entry));
|
|
303
369
|
return false;
|
|
304
370
|
});
|
|
305
371
|
// 零幸存者 ⇒ 没有要抢救的东西(见头注:绝不凭空造出一个空集合)。
|
|
@@ -319,7 +385,7 @@ function salvageEntriesUnsafe(schema, raw) {
|
|
|
319
385
|
if (error === undefined)
|
|
320
386
|
kept[key] = entry;
|
|
321
387
|
else
|
|
322
|
-
bad.push(droppedEntry([field,
|
|
388
|
+
bad.push(droppedEntry(outgoingPath([field, key]), error, entry)); // 键是用户写的,可能就是一条粘错位置的凭据
|
|
323
389
|
}
|
|
324
390
|
if (bad.length > 0 && Object.keys(kept).length > 0) {
|
|
325
391
|
dropped.push(...bad);
|
|
@@ -399,7 +465,7 @@ export const WARNING_KINDS = [
|
|
|
399
465
|
export function projectWarning(w) {
|
|
400
466
|
switch (w.kind) {
|
|
401
467
|
case "hosts-grandfathered":
|
|
402
|
-
return { kind: w.kind, domain: w.domain, droppedNames: w.droppedNames, ...(w.truncatedFrom === undefined ? {} : { truncatedFrom: w.truncatedFrom }) };
|
|
468
|
+
return { kind: w.kind, domain: w.domain, droppedNames: w.droppedNames, ...(w.droppedTotal === undefined ? {} : { droppedTotal: w.droppedTotal }), ...(w.truncatedFrom === undefined ? {} : { truncatedFrom: w.truncatedFrom }) };
|
|
403
469
|
case "domain-defaulted":
|
|
404
470
|
return { kind: w.kind, domain: w.domain };
|
|
405
471
|
// 两臂字段相同但**不合并**:合并后 `kind` 塌成两员的联合,TS 就再也认不出它属于投影联合的哪一员
|
|
@@ -576,7 +642,7 @@ function collectDropped(domain, raw, parsed, prefix, depth, spared, acc) {
|
|
|
576
642
|
continue; // 扩展命名空间:按契约放的键,不是未知键
|
|
577
643
|
acc.total += 1;
|
|
578
644
|
if (acc.keys.length < DROP_SCAN_MAX_KEYS)
|
|
579
|
-
acc.keys.push([...prefix, k]);
|
|
645
|
+
acc.keys.push(outgoingPath([...prefix, k]));
|
|
580
646
|
continue;
|
|
581
647
|
}
|
|
582
648
|
collectDropped(domain, raw[k], parsed[k], [...prefix, k], depth + 1, spared, acc);
|
|
@@ -662,7 +728,7 @@ export function carriedUnknownKeys(domain, parsed) {
|
|
|
662
728
|
continue;
|
|
663
729
|
acc.total += 1;
|
|
664
730
|
if (acc.keys.length < DROP_SCAN_MAX_KEYS)
|
|
665
|
-
acc.keys.push([...at, k]);
|
|
731
|
+
acc.keys.push(outgoingPath([...at, k]));
|
|
666
732
|
}
|
|
667
733
|
}
|
|
668
734
|
return acc;
|
|
@@ -746,15 +812,23 @@ export const DOMAIN_READ_FALLBACK = {
|
|
|
746
812
|
* observable: a deployment that has not seen that warning across a full read cycle no longer needs the arm. */
|
|
747
813
|
export function grandfatherHostsRead(raw) {
|
|
748
814
|
if (raw === null || typeof raw !== "object" || !Array.isArray(raw.hosts)) {
|
|
749
|
-
return { raw, droppedNames: [], keptIndex: NO_ENTRIES_DROPPED }; // not the expected shape — let parseDomain rule on it
|
|
815
|
+
return { raw, droppedNames: [], droppedTotal: 0, keptIndex: NO_ENTRIES_DROPPED }; // not the expected shape — let parseDomain rule on it
|
|
750
816
|
}
|
|
751
817
|
const original = raw.hosts;
|
|
752
818
|
const droppedNames = [];
|
|
819
|
+
let droppedTotal = 0;
|
|
753
820
|
const keptFrom = [];
|
|
754
821
|
let hosts = original.filter((h, i) => {
|
|
755
822
|
const name = h !== null && typeof h === "object" ? h.name : undefined;
|
|
756
823
|
if (typeof name === "string" && name.length > HOST_NAME_MAX) {
|
|
757
|
-
|
|
824
|
+
droppedTotal += 1;
|
|
825
|
+
// 先钳后扫(S-813):只给前 DROP_SCAN_MAX_KEYS 个铸标签,其余只计数 —— 出门那一道每个名都要归一化一遍,不能随文档条数放大。
|
|
826
|
+
if (droppedNames.length >= DROP_SCAN_MAX_KEYS)
|
|
827
|
+
return false;
|
|
828
|
+
// 出门前先过同一道(6.3.0,opus 腿 M2):用户写的主机名完全可能就是一条粘错位置的凭据,夹紧只管载荷上界、不管秘密 ——
|
|
829
|
+
// 此前 `sk-live-…` 形 / 编码 PEM 形以 63 字原文前缀出门。判在夹紧**之前**(截断会把秘密形截成探测器认不出的半截)。
|
|
830
|
+
const out = outgoingSegment(name);
|
|
831
|
+
droppedNames.push(out.length > HOST_NAME_MAX ? out.slice(0, HOST_NAME_MAX) + "…" : out); // audit label — clipped so the warning itself stays bounded
|
|
758
832
|
return false;
|
|
759
833
|
}
|
|
760
834
|
keptFrom.push(i); // 夹紧后第 keptFrom.length-1 条 = 原文档第 i 条
|
|
@@ -765,11 +839,11 @@ export function grandfatherHostsRead(raw) {
|
|
|
765
839
|
truncatedFrom = hosts.length;
|
|
766
840
|
hosts = hosts.slice(0, HOSTS_ARRAY_MAX);
|
|
767
841
|
}
|
|
768
|
-
if (
|
|
769
|
-
return { raw, droppedNames, keptIndex: NO_ENTRIES_DROPPED }; // identity — byte-stable
|
|
842
|
+
if (droppedTotal === 0 && truncatedFrom === undefined)
|
|
843
|
+
return { raw, droppedNames, droppedTotal, keptIndex: NO_ENTRIES_DROPPED }; // identity — byte-stable
|
|
770
844
|
// 位移**只**来自按名剔条目;纯截断留下的是原数组前缀,幸存者下标不动 ⇒ 不登记(见 EntryKeptIndex)。
|
|
771
|
-
const keptIndex =
|
|
772
|
-
return { raw: { ...raw, hosts }, droppedNames, truncatedFrom, keptIndex };
|
|
845
|
+
const keptIndex = droppedTotal > 0 ? { hosts: keptFrom.slice(0, hosts.length) } : NO_ENTRIES_DROPPED;
|
|
846
|
+
return { raw: { ...raw, hosts }, droppedNames, droppedTotal, truncatedFrom, keptIndex };
|
|
773
847
|
}
|
|
774
848
|
export function buildEffective(version, getRaw, updatedAt, onWarning) {
|
|
775
849
|
// 0.19.0 静默剥键/静默承运门:解析**成功**的域,把 schema 丢掉的键、以及 open-world 域里承运但本包
|
|
@@ -846,8 +920,9 @@ export function buildEffective(version, getRaw, updatedAt, onWarning) {
|
|
|
846
920
|
// hosts: the PRECISE grandfather for the 0.8.0 tightening runs BEFORE parse (0.7-era legal data over the
|
|
847
921
|
// new bounds reads on, clamped + warned); any residual corruption falls to the generic guardrail above.
|
|
848
922
|
const hostsGf = grandfatherHostsRead(getRaw("hosts"));
|
|
849
|
-
if (hostsGf.
|
|
850
|
-
|
|
923
|
+
if (hostsGf.droppedTotal > 0 || hostsGf.truncatedFrom !== undefined) {
|
|
924
|
+
const clipped = hostsGf.droppedTotal > hostsGf.droppedNames.length ? { droppedTotal: hostsGf.droppedTotal } : {};
|
|
925
|
+
onWarning?.({ domain: "hosts", kind: "hosts-grandfathered", droppedNames: hostsGf.droppedNames, ...clipped, truncatedFrom: hostsGf.truncatedFrom });
|
|
851
926
|
}
|
|
852
927
|
return {
|
|
853
928
|
version,
|
package/dist/cross-session.d.ts
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* HELD cross-session message awaits approval」。**注意它有两个消费面**:远端停靠的审批对话框,与
|
|
16
16
|
* 停审的跨会话消息;本模块只声明键形,哪个宿主消费哪一面由宿主自陈。
|
|
17
17
|
*
|
|
18
|
-
* ## `"default"`
|
|
18
|
+
* ## `"default"` 这第五个拼写
|
|
19
19
|
*
|
|
20
|
-
* 设置 UI 的选择器需要一个「不设」的可写值,CC 用字面量 `"default"
|
|
20
|
+
* 设置 UI 的选择器需要一个「不设」的可写值,CC 用字面量 `"default"`。它**不是第五种策略**:读取器
|
|
21
21
|
* (`readCrossSessionInbound` / `readDialogExpiry`)把它与 `null`/缺席一同折成 `undefined`。所以
|
|
22
|
-
* `CrossSessionInboundSetting`
|
|
22
|
+
* `CrossSessionInboundSetting` 的受理面是五拼写,而**值域闭集**(`CROSS_SESSION_INBOUND_VALUES`)只有四个。
|
|
23
23
|
*
|
|
24
24
|
* ## 坏值方向 = 响亮,不静默
|
|
25
25
|
*
|
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
*
|
|
30
30
|
* ## 与引擎词表的锁步
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* 两张词表与 dialogExpiry 缺省的语义属主是引擎(判定纯函数的输入域;缺省也铸在引擎),本模块的三只常量是
|
|
33
|
+
* 它们的**登记镜像** —— 保留镜像是因为本包不能运行期依赖引擎,删掉它们两键的 schema 就只剩 `z.string()`。
|
|
34
|
+
* 两份**必须逐字同序相等**;等值由**本仓自证**:`test/upstream-drift.test.ts` ④ 在 dev 期读引擎的运行期导出,
|
|
35
|
+
* 登记表恒空(零豁免)、顺序另钉 —— 任一侧加词/改序/改缺省,那一格先红。消费方不再替本仓守这面镜像,只钉
|
|
36
|
+
* 「自己读的是引擎那份」。本包不 import 引擎(纯度不变量;引擎只在 devDependency)。
|
|
34
37
|
*/
|
|
35
38
|
import { z } from "zod";
|
|
36
39
|
/**
|
package/dist/cross-session.js
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* HELD cross-session message awaits approval」。**注意它有两个消费面**:远端停靠的审批对话框,与
|
|
16
16
|
* 停审的跨会话消息;本模块只声明键形,哪个宿主消费哪一面由宿主自陈。
|
|
17
17
|
*
|
|
18
|
-
* ## `"default"`
|
|
18
|
+
* ## `"default"` 这第五个拼写
|
|
19
19
|
*
|
|
20
|
-
* 设置 UI 的选择器需要一个「不设」的可写值,CC 用字面量 `"default"
|
|
20
|
+
* 设置 UI 的选择器需要一个「不设」的可写值,CC 用字面量 `"default"`。它**不是第五种策略**:读取器
|
|
21
21
|
* (`readCrossSessionInbound` / `readDialogExpiry`)把它与 `null`/缺席一同折成 `undefined`。所以
|
|
22
|
-
* `CrossSessionInboundSetting`
|
|
22
|
+
* `CrossSessionInboundSetting` 的受理面是五拼写,而**值域闭集**(`CROSS_SESSION_INBOUND_VALUES`)只有四个。
|
|
23
23
|
*
|
|
24
24
|
* ## 坏值方向 = 响亮,不静默
|
|
25
25
|
*
|
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
*
|
|
30
30
|
* ## 与引擎词表的锁步
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* 两张词表与 dialogExpiry 缺省的语义属主是引擎(判定纯函数的输入域;缺省也铸在引擎),本模块的三只常量是
|
|
33
|
+
* 它们的**登记镜像** —— 保留镜像是因为本包不能运行期依赖引擎,删掉它们两键的 schema 就只剩 `z.string()`。
|
|
34
|
+
* 两份**必须逐字同序相等**;等值由**本仓自证**:`test/upstream-drift.test.ts` ④ 在 dev 期读引擎的运行期导出,
|
|
35
|
+
* 登记表恒空(零豁免)、顺序另钉 —— 任一侧加词/改序/改缺省,那一格先红。消费方不再替本仓守这面镜像,只钉
|
|
36
|
+
* 「自己读的是引擎那份」。本包不 import 引擎(纯度不变量;引擎只在 devDependency)。
|
|
34
37
|
*/
|
|
35
38
|
import { z } from "zod";
|
|
36
39
|
// ── crossSessionInbound ────────────────────────────────────────────────────────────────────────────
|
package/dist/fleet.d.ts
CHANGED
|
@@ -81,6 +81,107 @@ export type RestartSignal = z.infer<typeof RestartSignal>;
|
|
|
81
81
|
* Presence law: only `restart` counts; the top-level `restartRequired` boolean the server also ships is not read.
|
|
82
82
|
*/
|
|
83
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)` (an identifier in the closed allowlist
|
|
136
|
+
* `DISCLOSABLE_IDENTITY_RE` — `A-Z a-z 0-9 . _ : / @ + -`, 1–256 chars — AND not secret-shaped, a URL userinfo included; the
|
|
137
|
+
* rule the dropped-entry identity on read warnings already uses, 6.3.0). A producer calls it before keying; this schema deliberately
|
|
138
|
+
* does not re-judge secrecy (see the 6.2.0 notes).
|
|
139
|
+
* - `plane` — present ⇔ the model plane of the latest catalog the engine was handed was refused as a group (the engine
|
|
140
|
+
* keeps serving the model plane it had); ONE code, never a list — one candidate has exactly one group verdict (the
|
|
141
|
+
* engine judges the group invariants in a fixed order and stops at the first that fails; a refusal from the Runner
|
|
142
|
+
* swap step carries no code and is outside this signal).
|
|
143
|
+
* - `entries` and `plane` describe the same latest candidate catalog — the one the engine was handed last; with `plane`
|
|
144
|
+
* present, that is the refused candidate (its per-entry verdicts are usually why it has no default). The engine does
|
|
145
|
+
* not swap its own refusal table while the group stays refused (its selection channels still refuse by the previous
|
|
146
|
+
* generation), so with `plane` present read `entries` as "why this catalog cannot land", not "what a pick is refused
|
|
147
|
+
* right now".
|
|
148
|
+
*
|
|
149
|
+
* PRESENCE is the signal (same law as {@link RestartSignal}): present ⇔ at least one reportable refusal (an entry refusal
|
|
150
|
+
* under a name that rides out, or a group refusal). An empty signal (no entries and no `plane`) is refused by the shape
|
|
151
|
+
* law — "none" has exactly one spelling, the absent key. Inside, "no entry refusals" is spelled `entries: {}` (the key is
|
|
152
|
+
* required) — one spelling at each level.
|
|
153
|
+
*/
|
|
154
|
+
export declare const ModelRefusalsWire: z.ZodEffects<z.ZodObject<{
|
|
155
|
+
entries: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
156
|
+
plane: z.ZodOptional<z.ZodString>;
|
|
157
|
+
}, "strip", z.ZodTypeAny, {
|
|
158
|
+
entries: Record<string, string>;
|
|
159
|
+
plane?: string | undefined;
|
|
160
|
+
}, {
|
|
161
|
+
entries: Record<string, string>;
|
|
162
|
+
plane?: string | undefined;
|
|
163
|
+
}>, {
|
|
164
|
+
entries: Record<string, string>;
|
|
165
|
+
plane?: string | undefined;
|
|
166
|
+
}, {
|
|
167
|
+
entries: Record<string, string>;
|
|
168
|
+
plane?: string | undefined;
|
|
169
|
+
}>;
|
|
170
|
+
export type ModelRefusalsWire = z.infer<typeof ModelRefusalsWire>;
|
|
171
|
+
/** {@link ModelRefusalsWire} narrowed to the closed code sets — the return type of {@link readModelRefusals}. */
|
|
172
|
+
export type ModelRefusalsSignal = {
|
|
173
|
+
entries: Record<string, ModelEntryRefusalCode>;
|
|
174
|
+
plane?: ModelPlaneRefusalCode;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* The ONE reader of the model-refusals signal, for BOTH exits: pass a /health body or a parsed
|
|
178
|
+
* {@link InstanceAnnouncement} — it reads the `modelRefusals` key of whatever it is given. Shape mismatch, a missing key, a
|
|
179
|
+
* non-object body, even a throwing accessor ⇒ `undefined` — never throws. Then it narrows to the closed sets: an unknown
|
|
180
|
+
* entry code drops that entry, an unknown plane code drops `plane`; narrowed to nothing ⇒ `undefined`. So `undefined`
|
|
181
|
+
* means "nothing this reader can say"; whether that is "none" or "unknown" depends on whether the engine reports this key
|
|
182
|
+
* at all — it answers the key only and never judges the engine's version (see {@link InstanceAnnouncement}`.modelRefusals`).
|
|
183
|
+
*/
|
|
184
|
+
export declare function readModelRefusals(body: unknown): ModelRefusalsSignal | undefined;
|
|
84
185
|
/** The `/health` self-description every orchestrator reads (shipped shape, server 1.96.0). Liveness and
|
|
85
186
|
* readiness are deliberately SPLIT: `status` stays ok while draining (the process is alive); routability is
|
|
86
187
|
* `isReadyForTraffic`. Unknown fields pass through — health is a self-description, not a closed record. */
|
|
@@ -150,6 +251,32 @@ export declare const InstanceAnnouncement: z.ZodObject<{
|
|
|
150
251
|
version: number;
|
|
151
252
|
since: number;
|
|
152
253
|
}>>;
|
|
254
|
+
/** S-768: present ⇔ this instance has at least one reportable model refusal — the SAME {@link ModelRefusalsWire} /health
|
|
255
|
+
* carries, minted by the same getter; read it with {@link readModelRefusals}`(announcement)` (never off this field
|
|
256
|
+
* directly: the field is the open wire form, the reader narrows to the closed sets). Every heartbeat re-announces the
|
|
257
|
+
* full row, so a later heartbeat WITHOUT it means "no reportable refusals any more" (the stored row is replaced, never
|
|
258
|
+
* merged). Reading absence: from an engine that reports this key, absent = none reportable; an engine that predates
|
|
259
|
+
* the key never sends it, so there absence is UNKNOWN, not "none" — tell the two apart by the announced `version`.
|
|
260
|
+
* Failure direction = meaning direction (this is a diagnostic key): a code this package version does not know passes
|
|
261
|
+
* (it is "not said" to the reader), and a value that breaks the {@link ModelRefusalsWire} shape law is dropped to
|
|
262
|
+
* ABSENT (`.catch`) — it never fails the announcement, so it can never make a healthy instance unroutable (unlike
|
|
263
|
+
* `restart`, whose shape still gates the row). The key may then be present with the value `undefined`. */
|
|
264
|
+
modelRefusals: z.ZodCatch<z.ZodOptional<z.ZodEffects<z.ZodObject<{
|
|
265
|
+
entries: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
266
|
+
plane: z.ZodOptional<z.ZodString>;
|
|
267
|
+
}, "strip", z.ZodTypeAny, {
|
|
268
|
+
entries: Record<string, string>;
|
|
269
|
+
plane?: string | undefined;
|
|
270
|
+
}, {
|
|
271
|
+
entries: Record<string, string>;
|
|
272
|
+
plane?: string | undefined;
|
|
273
|
+
}>, {
|
|
274
|
+
entries: Record<string, string>;
|
|
275
|
+
plane?: string | undefined;
|
|
276
|
+
}, {
|
|
277
|
+
entries: Record<string, string>;
|
|
278
|
+
plane?: string | undefined;
|
|
279
|
+
}>>>;
|
|
153
280
|
}, "strip", z.ZodTypeAny, {
|
|
154
281
|
draining: boolean;
|
|
155
282
|
version: string;
|
|
@@ -163,6 +290,10 @@ export declare const InstanceAnnouncement: z.ZodObject<{
|
|
|
163
290
|
version: number;
|
|
164
291
|
since: number;
|
|
165
292
|
} | undefined;
|
|
293
|
+
modelRefusals?: {
|
|
294
|
+
entries: Record<string, string>;
|
|
295
|
+
plane?: string | undefined;
|
|
296
|
+
} | undefined;
|
|
166
297
|
}, {
|
|
167
298
|
version: string;
|
|
168
299
|
instanceId: string;
|
|
@@ -175,6 +306,7 @@ export declare const InstanceAnnouncement: z.ZodObject<{
|
|
|
175
306
|
version: number;
|
|
176
307
|
since: number;
|
|
177
308
|
} | undefined;
|
|
309
|
+
modelRefusals?: unknown;
|
|
178
310
|
heartbeatMs?: number | undefined;
|
|
179
311
|
}>;
|
|
180
312
|
export type InstanceAnnouncement = z.infer<typeof InstanceAnnouncement>;
|
|
@@ -223,6 +355,32 @@ export declare const InstanceRegistration: z.ZodObject<{
|
|
|
223
355
|
version: number;
|
|
224
356
|
since: number;
|
|
225
357
|
}>>;
|
|
358
|
+
/** S-768: present ⇔ this instance has at least one reportable model refusal — the SAME {@link ModelRefusalsWire} /health
|
|
359
|
+
* carries, minted by the same getter; read it with {@link readModelRefusals}`(announcement)` (never off this field
|
|
360
|
+
* directly: the field is the open wire form, the reader narrows to the closed sets). Every heartbeat re-announces the
|
|
361
|
+
* full row, so a later heartbeat WITHOUT it means "no reportable refusals any more" (the stored row is replaced, never
|
|
362
|
+
* merged). Reading absence: from an engine that reports this key, absent = none reportable; an engine that predates
|
|
363
|
+
* the key never sends it, so there absence is UNKNOWN, not "none" — tell the two apart by the announced `version`.
|
|
364
|
+
* Failure direction = meaning direction (this is a diagnostic key): a code this package version does not know passes
|
|
365
|
+
* (it is "not said" to the reader), and a value that breaks the {@link ModelRefusalsWire} shape law is dropped to
|
|
366
|
+
* ABSENT (`.catch`) — it never fails the announcement, so it can never make a healthy instance unroutable (unlike
|
|
367
|
+
* `restart`, whose shape still gates the row). The key may then be present with the value `undefined`. */
|
|
368
|
+
modelRefusals: z.ZodCatch<z.ZodOptional<z.ZodEffects<z.ZodObject<{
|
|
369
|
+
entries: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
370
|
+
plane: z.ZodOptional<z.ZodString>;
|
|
371
|
+
}, "strip", z.ZodTypeAny, {
|
|
372
|
+
entries: Record<string, string>;
|
|
373
|
+
plane?: string | undefined;
|
|
374
|
+
}, {
|
|
375
|
+
entries: Record<string, string>;
|
|
376
|
+
plane?: string | undefined;
|
|
377
|
+
}>, {
|
|
378
|
+
entries: Record<string, string>;
|
|
379
|
+
plane?: string | undefined;
|
|
380
|
+
}, {
|
|
381
|
+
entries: Record<string, string>;
|
|
382
|
+
plane?: string | undefined;
|
|
383
|
+
}>>>;
|
|
226
384
|
} & {
|
|
227
385
|
registeredMs: z.ZodNumber;
|
|
228
386
|
lastSeenMs: z.ZodNumber;
|
|
@@ -242,6 +400,10 @@ export declare const InstanceRegistration: z.ZodObject<{
|
|
|
242
400
|
version: number;
|
|
243
401
|
since: number;
|
|
244
402
|
} | undefined;
|
|
403
|
+
modelRefusals?: {
|
|
404
|
+
entries: Record<string, string>;
|
|
405
|
+
plane?: string | undefined;
|
|
406
|
+
} | undefined;
|
|
245
407
|
observedReady?: boolean | undefined;
|
|
246
408
|
}, {
|
|
247
409
|
version: string;
|
|
@@ -257,6 +419,7 @@ export declare const InstanceRegistration: z.ZodObject<{
|
|
|
257
419
|
version: number;
|
|
258
420
|
since: number;
|
|
259
421
|
} | undefined;
|
|
422
|
+
modelRefusals?: unknown;
|
|
260
423
|
heartbeatMs?: number | undefined;
|
|
261
424
|
observedReady?: boolean | undefined;
|
|
262
425
|
}>;
|