@sema-agent/settings-schema 6.2.0 → 6.4.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 +197 -0
- package/README.md +21 -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/c48-s935-model-headers-transported/expected.json +115 -0
- package/conformance/cases/c48-s935-model-headers-transported/input.json +37 -0
- package/conformance/cases/c48-s935-model-headers-transported/meta.json +6 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +98 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/input.json +28 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/meta.json +9 -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 +53 -22
- package/dist/config-fns.js +87 -33
- package/dist/cross-session.d.ts +8 -5
- package/dist/cross-session.js +8 -5
- package/dist/fleet.d.ts +3 -2
- package/dist/fleet.js +3 -2
- package/dist/hash.d.ts +3 -3
- package/dist/hash.js +3 -3
- package/dist/hooks.d.ts +8 -8
- package/dist/secret-scan.d.ts +39 -0
- package/dist/secret-scan.js +110 -0
- package/dist/types.d.ts +65 -7
- package/dist/types.js +57 -44
- package/package.json +1 -1
package/dist/config-fns.d.ts
CHANGED
|
@@ -21,15 +21,24 @@ import { type DomainConfig, type DomainName, type EffectiveConfig, type Effectiv
|
|
|
21
21
|
* any secret-shaped run (`%` kept in the run so a percent-encoded secret stays whole for the percent-aware
|
|
22
22
|
* containsSecretToken). invariant ①: a secret VALUE never rides an error. R13/R14.
|
|
23
23
|
*
|
|
24
|
-
* **Output invariant** (S-712 verification pass N1 + codex review [high] ×2): a string holding a private-key
|
|
25
|
-
* header ANYWHERE ({@link PRIVATE_KEY_PEM_HEADER}) is withheld WHOLE,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
24
|
+
* **Output invariant** (S-712 verification pass N1 + codex review [high] ×2; 6.3.0): a string holding a private-key
|
|
25
|
+
* PEM header ANYWHERE ({@link PRIVATE_KEY_PEM_HEADER}) in ANY of its {@link secretScanForms} `forms` is withheld WHOLE, a
|
|
26
|
+
* string whose normalization did not finish (`unresolved`) is withheld WHOLE, and no result trips `containsSecretToken` on
|
|
27
|
+
* any of its forms.
|
|
28
|
+
* The whole-string judgement is ONE rule — both detectors run over the normalized forms: the PEM header on every form of
|
|
29
|
+
* the ORIGINAL text, `containsSecretToken` on every form of the targeted result; what could not be normalized does not go
|
|
30
|
+
* out (6.3.0, codex r1 F1: `-----BEGIN+PRIVATE+KEY` form-encoded headers and four-layer encodings both rode out).
|
|
31
|
+
* 6.3.0 (codex review of v6.1.0 [high]): `key=` + a percent-encoded PEM showed its header only on a decoded form
|
|
32
|
+
* (`-----BEGIN%20PRIVATE%20KEY` — `%20` is not in `[A-Z0-9 ]`), and the detector's anchored decode missed it behind
|
|
33
|
+
* the `key=` prefix, so the whole key rode out through coordinates, zod `received` / `message` and entry identity.
|
|
34
|
+
* The PEM header is judged BEFORE any replacement: the token pass can rewrite the header itself away
|
|
35
|
+
* (`sk-note:-----BEGIN` is one token-charset run that starts with `sk-`, so it becomes `[redacted-secret]` and the
|
|
36
|
+
* key body after it would ride out untouched — codex verification round). A PEM header carries spaces, so the
|
|
37
|
+
* targeted passes can never isolate it, and the detector only sees it at the very start of a string. Every
|
|
38
|
+
* free-text warning face that goes through here (coordinates, error text) inherits the same guarantee; the entry
|
|
39
|
+
* identity face additionally has to be an identifier ({@link isDisclosableIdentity}). The validation-side detector
|
|
40
|
+
* `containsSecretToken` is deliberately NOT widened here: making it reject a mid-string PEM would tighten every field
|
|
41
|
+
* it validates (a major). */
|
|
33
42
|
export declare function redactSecrets(text: string): string;
|
|
34
43
|
/** Sanitize a ZodError IN PLACE so no surfaced error (message / `received` / nested `unionErrors`) echoes a
|
|
35
44
|
* pasted secret. RECURSES into `unionErrors` (a `z.union` failure nests a ZodError per member — e.g. RoleTarget
|
|
@@ -164,9 +173,10 @@ export declare function formatConfigPath(p: ConfigPath): string;
|
|
|
164
173
|
/** 一条被丢掉的条目的**身份**(S-712):只从被丢条目**原文**里取,三键各自可缺。
|
|
165
174
|
*
|
|
166
175
|
* 律(一条,不分域):`name` / `id` 取**字符串**形、`enabled` 取**布尔**形,类型不对 ⇒ 不带该键(不猜、
|
|
167
|
-
* 不 `String()` 转换);只认**数据属性**(不触发访问器);{@link
|
|
168
|
-
*
|
|
169
|
-
*
|
|
176
|
+
* 不 `String()` 转换);只认**数据属性**(不触发访问器);{@link isDisclosableIdentity} 说不可披露 ⇒ 不带该键
|
|
177
|
+
* (6.3.0 起:身份是**标识符**,须落在 {@link DISCLOSABLE_IDENTITY_RE} 白名单闭形里,且坐标脱敏器
|
|
178
|
+
* {@link redactSecrets} 原样放行 —— 坐标段必须在场,所以打码出门;身份是可缺的,打过码的串不再是任何条目的身份,
|
|
179
|
+
* 所以是**不带**而不是打码)。三键都读不出 ⇒ 整个身份缺席(不铸空对象)。
|
|
170
180
|
*
|
|
171
181
|
* **缺席 = 读不出**(不是字符串,或是字符串但不能原样出门),两种原因刻意不分:对消费方的正确处置相同
|
|
172
182
|
* (身份未知 ⇒ 按未知处置,例如 fail-closed),而一个「被扣下」的标记本身就在说「这里有一段像凭据的东西」。
|
|
@@ -186,13 +196,25 @@ export interface DroppedEntry {
|
|
|
186
196
|
error: unknown;
|
|
187
197
|
identity?: DroppedEntryIdentity;
|
|
188
198
|
}
|
|
189
|
-
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
199
|
+
/** 可披露身份的**闭形**(6.3.0):字符集 `A-Z a-z 0-9 . _ : / @ + -`、长度 1–256(上限与 web-admin 读侧名长上限 256 对齐;`ModelEntry.name`
|
|
200
|
+
* 本身不设上限,超 256 字的合法名按无名处置)。身份是**标识符**不是自由文本 ——
|
|
201
|
+
* 允许集才是它的语义:上游模型 id 的真实形(`accounts/fireworks/models/…`、`Qwen/Qwen2.5-72B-Instruct`、
|
|
202
|
+
* `claude-3-5-sonnet@20240620`、`deepseek-v4-flash:free`、`o3-mini+high`)都在集内;`%` / `=` / 空白 / 引号 / 反斜杠 /
|
|
203
|
+
* 控制与零宽字符 / 非 ASCII 一律在集外 —— 编码过的、带前缀的、多行的、会在日志里伪造行的串,不可能是一个标识符。
|
|
204
|
+
* 无旗标(无 `g` ⇒ `.test` 无状态,可放心共享这一只)。判据只有 {@link isDisclosableIdentity} 一只,消费方要
|
|
205
|
+
* 「这个名能不能写出去」问它,不要拿本常量自拼(集内仍有密钥形,见那一只的否决项)。 */
|
|
206
|
+
export declare const DISCLOSABLE_IDENTITY_RE: RegExp;
|
|
207
|
+
/** 身份串 —— 「一个用户写的身份串(条目的 `name` / `id`)能不能**原样**出门」的**唯一**判据(6.3.0 起两项合取):
|
|
208
|
+
* ① **形**:落在 {@link DISCLOSABLE_IDENTITY_RE} 白名单闭形里(不再是「不像密钥就可披露」—— 6.1.0 / 6.2.0 的黑名单
|
|
209
|
+
* 判据放行过 `key=` + 百分号编码的整把 PEM 私钥,codex 对 v6.1.0 复审 [high]);
|
|
210
|
+
* ② **否决**(「无密钥形」):坐标脱敏器 {@link redactSecrets} 原样放行 —— 集内照样有密钥形(`sk-…` / `ghp_…` /
|
|
211
|
+
* `AKIA…` / 三段 JWT 的字符全在集内),而且判它的必须是坐标段、`error` 文案({@link sanitizeZodError})用的**同一只**
|
|
212
|
+
* 脱敏器:两只判据会在同一条告警里给出两种答案(对抗轮 #2 真复现;集内的残例 = `@` 之后的单点 `eyJ…` 串,裸探测器
|
|
213
|
+
* 判阴、脱敏器会改,语料 c41 钉着);∧ 不是带 userinfo 的 URL —— 校验侧 `NON_SECRET_URL` 拒写的同一只判定
|
|
214
|
+
* (`carriesUserinfo`,包内私有;opus 腿 L1:`postgres://svc:pw@h` 的字符全在集内)。
|
|
215
|
+
* 入参是 `string`、返回 `boolean`,**刻意不是**类型谓词(`v is string`):它对一些字符串答 false,谓词形会让调用方的
|
|
216
|
+
* 否定分支把一个 `string` 收窄成 `never`;「是不是字符串」由调用点自己判(运行期收到非字符串照答 false、不抛)。
|
|
217
|
+
* 不可披露的串按**无名**处置(不带,而不是打码:打过码的串不再是身份)。
|
|
196
218
|
* 执法面(同一条律):被丢条目的身份({@link DroppedEntryIdentity},本文件铸);`ModelRefusalsWire` 的 `entries` 键
|
|
197
219
|
* (经 {@link isReportableRefusalName},S-768);引擎自己的日志 / 拒因句里要不要写出一个名字,同样问这一只。 */
|
|
198
220
|
export declare function isDisclosableIdentity(v: string): boolean;
|
|
@@ -200,7 +222,7 @@ export declare function isDisclosableIdentity(v: string): boolean;
|
|
|
200
222
|
* 不过 ⇒ 按无名处置(不进键、不计数)。两条律的合取,不手抄任何一条:
|
|
201
223
|
* ① 这只 schema **自己载得住**这个键 —— 就地拿 schema 判(空串不过键规则;`__proto__` 这类名被 zod 的 record 静默丢掉,
|
|
202
224
|
* 载不住),不写名单;
|
|
203
|
-
* ② {@link isDisclosableIdentity}(
|
|
225
|
+
* ② {@link isDisclosableIdentity}(不是白名单闭形里的标识符,或是秘密形 ⇒ 不出门)。
|
|
204
226
|
* 非字符串 ⇒ false。住在本文件而不是 `./fleet`:`fleet.ts` 被 `types.ts` 引,它再引本文件会成环。 */
|
|
205
227
|
export declare function isReportableRefusalName(name: string): boolean;
|
|
206
228
|
/** 抢救造成的**下标位移表**:数组字段名 → 该字段压缩后每个位置的**原**下标(`kept[j]` = 压缩后第 j 条
|
|
@@ -239,7 +261,7 @@ export interface EntrySalvageResult {
|
|
|
239
261
|
* 造出一个空集合**。
|
|
240
262
|
*
|
|
241
263
|
* 🔴 **告警不许比它诊断的东西更危险**(复审 R1 两条,红先复现):① Record 的键是用户写的,完全可能就是
|
|
242
|
-
* 一条粘错位置的凭据,所以路径里的键段过 {@link redactSecrets} 才出门(
|
|
264
|
+
* 一条粘错位置的凭据,所以路径里的键段过 {@link redactSecrets} 才出门(6.3.0 起经 `outgoingPath` 这一道;不变量①:secret 值永不随错误外流,
|
|
243
265
|
* 与 parseDomain 的 R13/R14 同一条);② 整段遍历包 try/catch —— 读字段、枚举 Record 都会触发访问器,
|
|
244
266
|
* 抛出来的文案可能带 secret。抢救是**尽力而为**的:抢不动就当没抢救(返回原引用),让既有的整域档位去裁,
|
|
245
267
|
* 绝不把解析拖下水、更不回显它的错误(与同文件 {@link droppedKeys} 逐字同纪律)。
|
|
@@ -286,10 +308,14 @@ export declare function legacyGovernanceFromRuntime(rawRuntime: unknown): unknow
|
|
|
286
308
|
* 0.19.0 加了两个 kind(additive union member —— 用 `switch` 穷举本类型的消费方需补臂):
|
|
287
309
|
* `"unknown-keys-dropped"` / `"unread-config-file"`,成案见 {@link droppedKeys}(消费方
|
|
288
310
|
* #322:配置写了、进程跑着、值没到,而没有任何一个字说出来)。 */
|
|
289
|
-
export type EffectiveReadWarning =
|
|
311
|
+
export type EffectiveReadWarning =
|
|
312
|
+
/** `droppedNames` 有界(6.3.0,S-813:与未知键同一只 `DROP_SCAN_MAX_KEYS`),超界时带 `droppedTotal`(= 按名剔掉的真实条数)——
|
|
313
|
+
* 缺席 = 列表是穷举。`truncatedFrom` 另是一件事:hosts 数组超 `HOSTS_ARRAY_MAX` 被切片前的长度。 */
|
|
314
|
+
{
|
|
290
315
|
domain: "hosts";
|
|
291
316
|
kind: "hosts-grandfathered";
|
|
292
317
|
droppedNames: string[];
|
|
318
|
+
droppedTotal?: number;
|
|
293
319
|
truncatedFrom?: number;
|
|
294
320
|
} | {
|
|
295
321
|
domain: DomainName;
|
|
@@ -559,6 +585,11 @@ export interface GrandfatherHostsReadResult {
|
|
|
559
585
|
/** Host names DROPPED because they exceed HOST_NAME_MAX (63) — truncating would forge a NEW identity that
|
|
560
586
|
* matches no ORCH_HOST_ID (and could collide into the duplicate-name refine), so the entry is skipped whole. */
|
|
561
587
|
droppedNames: string[];
|
|
588
|
+
/** How many hosts were dropped by name — the TRUE count. `droppedNames` carries labels for at most `DROP_SCAN_MAX_KEYS` (64,
|
|
589
|
+
* the same bound as the unknown-key scan) of them; each label goes through the outgoing redaction, and a document with
|
|
590
|
+
* thousands of over-long names must not cost thousands of scans on every read (6.3.0, S-813). Same shape as
|
|
591
|
+
* {@link UnknownKeyScan}'s `{ keys, total }`. */
|
|
592
|
+
droppedTotal: number;
|
|
562
593
|
/** Set when the hosts array (after drops) exceeded HOSTS_ARRAY_MAX (1024) and was sliced — the pre-slice length. */
|
|
563
594
|
truncatedFrom?: number;
|
|
564
595
|
/** 这次夹紧造成的**下标位移表**(与 {@link EntrySalvageResult.keptIndex} 同一种账、同一条律):本函数
|
package/dist/config-fns.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
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
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)
|
|
20
21
|
import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, InfraCostRates, LimitsConfig, PRIVATE_KEY_PEM_HEADER, SkillsManifestSchema, containsSecretToken } from "./types.js";
|
|
21
22
|
/** Redact secret-shaped tokens from a validation MESSAGE — a zod error echoes the offending value (e.g.
|
|
22
23
|
* `received 'sk-live-…'`), so a secret pasted into the WRONG field would leak via the error string. Redacts
|
|
@@ -24,20 +25,32 @@ import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX,
|
|
|
24
25
|
* any secret-shaped run (`%` kept in the run so a percent-encoded secret stays whole for the percent-aware
|
|
25
26
|
* containsSecretToken). invariant ①: a secret VALUE never rides an error. R13/R14.
|
|
26
27
|
*
|
|
27
|
-
* **Output invariant** (S-712 verification pass N1 + codex review [high] ×2): a string holding a private-key
|
|
28
|
-
* header ANYWHERE ({@link PRIVATE_KEY_PEM_HEADER}) is withheld WHOLE,
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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). */
|
|
36
46
|
export function redactSecrets(text) {
|
|
37
47
|
const targeted = text
|
|
38
48
|
.replace(/\b(bearer|basic)(\s+)\S+/gi, "$1$2[redacted-secret]")
|
|
39
49
|
.replace(/[A-Za-z0-9_./:+=%-]{6,}/g, (tok) => (containsSecretToken(tok) ? "[redacted-secret]" : tok));
|
|
40
|
-
|
|
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;
|
|
41
54
|
}
|
|
42
55
|
/** Sanitize a ZodError IN PLACE so no surfaced error (message / `received` / nested `unionErrors`) echoes a
|
|
43
56
|
* pasted secret. RECURSES into `unionErrors` (a `z.union` failure nests a ZodError per member — e.g. RoleTarget
|
|
@@ -185,23 +198,55 @@ const PLAIN_PATH_SEGMENT = /^(?!(?:0|[1-9]\d*)$)[^."\u0000-\u001f]+$/;
|
|
|
185
198
|
export function formatConfigPath(p) {
|
|
186
199
|
return p.map((seg) => (typeof seg === "number" ? String(seg) : PLAIN_PATH_SEGMENT.test(seg) ? seg : JSON.stringify(seg))).join(".");
|
|
187
200
|
}
|
|
188
|
-
/**
|
|
189
|
-
* {@link
|
|
190
|
-
*
|
|
191
|
-
* (
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
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
|
+
* 不可披露的串按**无名**处置(不带,而不是打码:打过码的串不再是身份)。
|
|
195
239
|
* 执法面(同一条律):被丢条目的身份({@link DroppedEntryIdentity},本文件铸);`ModelRefusalsWire` 的 `entries` 键
|
|
196
240
|
* (经 {@link isReportableRefusalName},S-768);引擎自己的日志 / 拒因句里要不要写出一个名字,同样问这一只。 */
|
|
197
241
|
export function isDisclosableIdentity(v) {
|
|
198
|
-
|
|
242
|
+
// 非字符串(JS / any 调用方)⇒ false,不抛(秘密轴 fail-closed)
|
|
243
|
+
return typeof v === "string" && DISCLOSABLE_IDENTITY_RE.test(v) && redactSecrets(v) === v && !carriesUserinfo(v);
|
|
199
244
|
}
|
|
200
245
|
/** S-768 —— 「一个目录条目名能不能当 {@link ModelRefusalsWire} `entries` 的键」的**唯一**判据;引擎铸键前只调这一只,
|
|
201
246
|
* 不过 ⇒ 按无名处置(不进键、不计数)。两条律的合取,不手抄任何一条:
|
|
202
247
|
* ① 这只 schema **自己载得住**这个键 —— 就地拿 schema 判(空串不过键规则;`__proto__` 这类名被 zod 的 record 静默丢掉,
|
|
203
248
|
* 载不住),不写名单;
|
|
204
|
-
* ② {@link isDisclosableIdentity}(
|
|
249
|
+
* ② {@link isDisclosableIdentity}(不是白名单闭形里的标识符,或是秘密形 ⇒ 不出门)。
|
|
205
250
|
* 非字符串 ⇒ false。住在本文件而不是 `./fleet`:`fleet.ts` 被 `types.ts` 引,它再引本文件会成环。 */
|
|
206
251
|
export function isReportableRefusalName(name) {
|
|
207
252
|
if (!isDisclosableIdentity(name))
|
|
@@ -274,7 +319,7 @@ function judgeEntry(schema, value) {
|
|
|
274
319
|
* 造出一个空集合**。
|
|
275
320
|
*
|
|
276
321
|
* 🔴 **告警不许比它诊断的东西更危险**(复审 R1 两条,红先复现):① Record 的键是用户写的,完全可能就是
|
|
277
|
-
* 一条粘错位置的凭据,所以路径里的键段过 {@link redactSecrets} 才出门(
|
|
322
|
+
* 一条粘错位置的凭据,所以路径里的键段过 {@link redactSecrets} 才出门(6.3.0 起经 `outgoingPath` 这一道;不变量①:secret 值永不随错误外流,
|
|
278
323
|
* 与 parseDomain 的 R13/R14 同一条);② 整段遍历包 try/catch —— 读字段、枚举 Record 都会触发访问器,
|
|
279
324
|
* 抛出来的文案可能带 secret。抢救是**尽力而为**的:抢不动就当没抢救(返回原引用),让既有的整域档位去裁,
|
|
280
325
|
* 绝不把解析拖下水、更不回显它的错误(与同文件 {@link droppedKeys} 逐字同纪律)。
|
|
@@ -320,7 +365,7 @@ function salvageEntriesUnsafe(schema, raw) {
|
|
|
320
365
|
keptFrom.push(i); // 压缩后第 keptFrom.length-1 条 = 原文档第 i 条
|
|
321
366
|
return true;
|
|
322
367
|
}
|
|
323
|
-
bad.push(droppedEntry([field, i], error, entry));
|
|
368
|
+
bad.push(droppedEntry(outgoingPath([field, i]), error, entry));
|
|
324
369
|
return false;
|
|
325
370
|
});
|
|
326
371
|
// 零幸存者 ⇒ 没有要抢救的东西(见头注:绝不凭空造出一个空集合)。
|
|
@@ -340,7 +385,7 @@ function salvageEntriesUnsafe(schema, raw) {
|
|
|
340
385
|
if (error === undefined)
|
|
341
386
|
kept[key] = entry;
|
|
342
387
|
else
|
|
343
|
-
bad.push(droppedEntry([field,
|
|
388
|
+
bad.push(droppedEntry(outgoingPath([field, key]), error, entry)); // 键是用户写的,可能就是一条粘错位置的凭据
|
|
344
389
|
}
|
|
345
390
|
if (bad.length > 0 && Object.keys(kept).length > 0) {
|
|
346
391
|
dropped.push(...bad);
|
|
@@ -420,7 +465,7 @@ export const WARNING_KINDS = [
|
|
|
420
465
|
export function projectWarning(w) {
|
|
421
466
|
switch (w.kind) {
|
|
422
467
|
case "hosts-grandfathered":
|
|
423
|
-
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 }) };
|
|
424
469
|
case "domain-defaulted":
|
|
425
470
|
return { kind: w.kind, domain: w.domain };
|
|
426
471
|
// 两臂字段相同但**不合并**:合并后 `kind` 塌成两员的联合,TS 就再也认不出它属于投影联合的哪一员
|
|
@@ -597,7 +642,7 @@ function collectDropped(domain, raw, parsed, prefix, depth, spared, acc) {
|
|
|
597
642
|
continue; // 扩展命名空间:按契约放的键,不是未知键
|
|
598
643
|
acc.total += 1;
|
|
599
644
|
if (acc.keys.length < DROP_SCAN_MAX_KEYS)
|
|
600
|
-
acc.keys.push([...prefix, k]);
|
|
645
|
+
acc.keys.push(outgoingPath([...prefix, k]));
|
|
601
646
|
continue;
|
|
602
647
|
}
|
|
603
648
|
collectDropped(domain, raw[k], parsed[k], [...prefix, k], depth + 1, spared, acc);
|
|
@@ -683,7 +728,7 @@ export function carriedUnknownKeys(domain, parsed) {
|
|
|
683
728
|
continue;
|
|
684
729
|
acc.total += 1;
|
|
685
730
|
if (acc.keys.length < DROP_SCAN_MAX_KEYS)
|
|
686
|
-
acc.keys.push([...at, k]);
|
|
731
|
+
acc.keys.push(outgoingPath([...at, k]));
|
|
687
732
|
}
|
|
688
733
|
}
|
|
689
734
|
return acc;
|
|
@@ -767,15 +812,23 @@ export const DOMAIN_READ_FALLBACK = {
|
|
|
767
812
|
* observable: a deployment that has not seen that warning across a full read cycle no longer needs the arm. */
|
|
768
813
|
export function grandfatherHostsRead(raw) {
|
|
769
814
|
if (raw === null || typeof raw !== "object" || !Array.isArray(raw.hosts)) {
|
|
770
|
-
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
|
|
771
816
|
}
|
|
772
817
|
const original = raw.hosts;
|
|
773
818
|
const droppedNames = [];
|
|
819
|
+
let droppedTotal = 0;
|
|
774
820
|
const keptFrom = [];
|
|
775
821
|
let hosts = original.filter((h, i) => {
|
|
776
822
|
const name = h !== null && typeof h === "object" ? h.name : undefined;
|
|
777
823
|
if (typeof name === "string" && name.length > HOST_NAME_MAX) {
|
|
778
|
-
|
|
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
|
|
779
832
|
return false;
|
|
780
833
|
}
|
|
781
834
|
keptFrom.push(i); // 夹紧后第 keptFrom.length-1 条 = 原文档第 i 条
|
|
@@ -786,11 +839,11 @@ export function grandfatherHostsRead(raw) {
|
|
|
786
839
|
truncatedFrom = hosts.length;
|
|
787
840
|
hosts = hosts.slice(0, HOSTS_ARRAY_MAX);
|
|
788
841
|
}
|
|
789
|
-
if (
|
|
790
|
-
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
|
|
791
844
|
// 位移**只**来自按名剔条目;纯截断留下的是原数组前缀,幸存者下标不动 ⇒ 不登记(见 EntryKeptIndex)。
|
|
792
|
-
const keptIndex =
|
|
793
|
-
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 };
|
|
794
847
|
}
|
|
795
848
|
export function buildEffective(version, getRaw, updatedAt, onWarning) {
|
|
796
849
|
// 0.19.0 静默剥键/静默承运门:解析**成功**的域,把 schema 丢掉的键、以及 open-world 域里承运但本包
|
|
@@ -867,8 +920,9 @@ export function buildEffective(version, getRaw, updatedAt, onWarning) {
|
|
|
867
920
|
// hosts: the PRECISE grandfather for the 0.8.0 tightening runs BEFORE parse (0.7-era legal data over the
|
|
868
921
|
// new bounds reads on, clamped + warned); any residual corruption falls to the generic guardrail above.
|
|
869
922
|
const hostsGf = grandfatherHostsRead(getRaw("hosts"));
|
|
870
|
-
if (hostsGf.
|
|
871
|
-
|
|
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 });
|
|
872
926
|
}
|
|
873
927
|
return {
|
|
874
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
|
@@ -132,8 +132,9 @@ export type ModelPlaneRefusalCode = z.infer<typeof ModelPlaneRefusalCode>;
|
|
|
132
132
|
* broken row held, and the sentence is prose, not contract. "Which name, which reason" is the whole wire. A name that
|
|
133
133
|
* cannot ride out is treated as NAMELESS — the engine leaves it out of this map entirely (not keyed, not counted). The
|
|
134
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)` (
|
|
136
|
-
*
|
|
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
|
|
137
138
|
* does not re-judge secrecy (see the 6.2.0 notes).
|
|
138
139
|
* - `plane` — present ⇔ the model plane of the latest catalog the engine was handed was refused as a group (the engine
|
|
139
140
|
* keeps serving the model plane it had); ONE code, never a list — one candidate has exactly one group verdict (the
|
package/dist/fleet.js
CHANGED
|
@@ -139,8 +139,9 @@ export const ModelPlaneRefusalCode = z.enum(MODEL_PLANE_REFUSAL_CODES);
|
|
|
139
139
|
* broken row held, and the sentence is prose, not contract. "Which name, which reason" is the whole wire. A name that
|
|
140
140
|
* cannot ride out is treated as NAMELESS — the engine leaves it out of this map entirely (not keyed, not counted). The
|
|
141
141
|
* ONE predicate is `isReportableRefusalName(name)` (main barrel): this schema can carry the key (non-empty; `__proto__`
|
|
142
|
-
* is silently dropped by the record, so it cannot) AND `isDisclosableIdentity(name)` (
|
|
143
|
-
*
|
|
142
|
+
* is silently dropped by the record, so it cannot) AND `isDisclosableIdentity(name)` (an identifier in the closed allowlist
|
|
143
|
+
* `DISCLOSABLE_IDENTITY_RE` — `A-Z a-z 0-9 . _ : / @ + -`, 1–256 chars — AND not secret-shaped, a URL userinfo included; the
|
|
144
|
+
* rule the dropped-entry identity on read warnings already uses, 6.3.0). A producer calls it before keying; this schema deliberately
|
|
144
145
|
* does not re-judge secrecy (see the 6.2.0 notes).
|
|
145
146
|
* - `plane` — present ⇔ the model plane of the latest catalog the engine was handed was refused as a group (the engine
|
|
146
147
|
* keeps serving the model plane it had); ONE code, never a list — one candidate has exactly one group verdict (the
|
package/dist/hash.d.ts
CHANGED
|
@@ -23,9 +23,9 @@ export declare const MAX_HASH_WRAPPER_RESERVE = 32;
|
|
|
23
23
|
/** Depth cap for a standalone FREE-FORM config value, reconciled with the hash cap MINUS the wrapper reserve so
|
|
24
24
|
* that ANYTHING the validator accepts is guaranteed hashable in every context it's later hashed in. 168 ≫ any
|
|
25
25
|
* real free-form value (OpenAI-style request params nest <10 deep).
|
|
26
|
-
* Scope: the ONE cap for EVERY free-form field vetted by `vetFreeFormJson` (`ModelEntry.extraBody
|
|
27
|
-
*
|
|
28
|
-
* serves
|
|
26
|
+
* Scope: the ONE cap for EVERY free-form field vetted by `vetFreeFormJson` (`ModelEntry.extraBody`, since 1.10.0
|
|
27
|
+
* `ModelEntry.compat`, since 6.4.0 `ModelEntry.headers`) — they sit at the SAME wrapper depth (`{models:{models:[{…}]}}`),
|
|
28
|
+
* so one cap serves them all. The `EXTRA_BODY` spelling is kept because this is a published export on the `./hash` subpath
|
|
29
29
|
* (renaming it would be a BREAKING change for a purely cosmetic gain). */
|
|
30
30
|
export declare const MAX_EXTRA_BODY_DEPTH: number;
|
|
31
31
|
/** Deterministic JSON with sorted object keys (so key order never affects the hash). */
|
package/dist/hash.js
CHANGED
|
@@ -23,9 +23,9 @@ export const MAX_HASH_WRAPPER_RESERVE = 32;
|
|
|
23
23
|
/** Depth cap for a standalone FREE-FORM config value, reconciled with the hash cap MINUS the wrapper reserve so
|
|
24
24
|
* that ANYTHING the validator accepts is guaranteed hashable in every context it's later hashed in. 168 ≫ any
|
|
25
25
|
* real free-form value (OpenAI-style request params nest <10 deep).
|
|
26
|
-
* Scope: the ONE cap for EVERY free-form field vetted by `vetFreeFormJson` (`ModelEntry.extraBody
|
|
27
|
-
*
|
|
28
|
-
* serves
|
|
26
|
+
* Scope: the ONE cap for EVERY free-form field vetted by `vetFreeFormJson` (`ModelEntry.extraBody`, since 1.10.0
|
|
27
|
+
* `ModelEntry.compat`, since 6.4.0 `ModelEntry.headers`) — they sit at the SAME wrapper depth (`{models:{models:[{…}]}}`),
|
|
28
|
+
* so one cap serves them all. The `EXTRA_BODY` spelling is kept because this is a published export on the `./hash` subpath
|
|
29
29
|
* (renaming it would be a BREAKING change for a purely cosmetic gain). */
|
|
30
30
|
export const MAX_EXTRA_BODY_DEPTH = MAX_HASH_DEPTH - MAX_HASH_WRAPPER_RESERVE;
|
|
31
31
|
/** Deterministic JSON with sorted object keys (so key order never affects the hash). */
|
package/dist/hooks.d.ts
CHANGED
|
@@ -5545,20 +5545,20 @@ export declare const HttpHookEntry: z.ZodObject<{
|
|
|
5545
5545
|
}, "strip", z.ZodTypeAny, {
|
|
5546
5546
|
type: "http";
|
|
5547
5547
|
url: string;
|
|
5548
|
+
headers?: Record<string, string> | undefined;
|
|
5548
5549
|
if?: string | undefined;
|
|
5549
5550
|
timeout?: number | undefined;
|
|
5550
5551
|
statusMessage?: string | undefined;
|
|
5551
5552
|
once?: boolean | undefined;
|
|
5552
|
-
headers?: Record<string, string> | undefined;
|
|
5553
5553
|
allowedEnvVars?: string[] | undefined;
|
|
5554
5554
|
}, {
|
|
5555
5555
|
type: "http";
|
|
5556
5556
|
url: string;
|
|
5557
|
+
headers?: Record<string, string> | undefined;
|
|
5557
5558
|
if?: string | undefined;
|
|
5558
5559
|
timeout?: number | undefined;
|
|
5559
5560
|
statusMessage?: string | undefined;
|
|
5560
5561
|
once?: boolean | undefined;
|
|
5561
|
-
headers?: Record<string, string> | undefined;
|
|
5562
5562
|
allowedEnvVars?: string[] | undefined;
|
|
5563
5563
|
}>;
|
|
5564
5564
|
/** One configured hook (CC settings shape + shell superset fields; discriminated on `type`). */
|
|
@@ -5655,20 +5655,20 @@ export declare const HookEntry: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
|
5655
5655
|
}, "strip", z.ZodTypeAny, {
|
|
5656
5656
|
type: "http";
|
|
5657
5657
|
url: string;
|
|
5658
|
+
headers?: Record<string, string> | undefined;
|
|
5658
5659
|
if?: string | undefined;
|
|
5659
5660
|
timeout?: number | undefined;
|
|
5660
5661
|
statusMessage?: string | undefined;
|
|
5661
5662
|
once?: boolean | undefined;
|
|
5662
|
-
headers?: Record<string, string> | undefined;
|
|
5663
5663
|
allowedEnvVars?: string[] | undefined;
|
|
5664
5664
|
}, {
|
|
5665
5665
|
type: "http";
|
|
5666
5666
|
url: string;
|
|
5667
|
+
headers?: Record<string, string> | undefined;
|
|
5667
5668
|
if?: string | undefined;
|
|
5668
5669
|
timeout?: number | undefined;
|
|
5669
5670
|
statusMessage?: string | undefined;
|
|
5670
5671
|
once?: boolean | undefined;
|
|
5671
|
-
headers?: Record<string, string> | undefined;
|
|
5672
5672
|
allowedEnvVars?: string[] | undefined;
|
|
5673
5673
|
}>]>;
|
|
5674
5674
|
export type HookEntry = z.infer<typeof HookEntry>;
|
|
@@ -5773,20 +5773,20 @@ export declare const HookMatcherEntry: z.ZodObject<{
|
|
|
5773
5773
|
}, "strip", z.ZodTypeAny, {
|
|
5774
5774
|
type: "http";
|
|
5775
5775
|
url: string;
|
|
5776
|
+
headers?: Record<string, string> | undefined;
|
|
5776
5777
|
if?: string | undefined;
|
|
5777
5778
|
timeout?: number | undefined;
|
|
5778
5779
|
statusMessage?: string | undefined;
|
|
5779
5780
|
once?: boolean | undefined;
|
|
5780
|
-
headers?: Record<string, string> | undefined;
|
|
5781
5781
|
allowedEnvVars?: string[] | undefined;
|
|
5782
5782
|
}, {
|
|
5783
5783
|
type: "http";
|
|
5784
5784
|
url: string;
|
|
5785
|
+
headers?: Record<string, string> | undefined;
|
|
5785
5786
|
if?: string | undefined;
|
|
5786
5787
|
timeout?: number | undefined;
|
|
5787
5788
|
statusMessage?: string | undefined;
|
|
5788
5789
|
once?: boolean | undefined;
|
|
5789
|
-
headers?: Record<string, string> | undefined;
|
|
5790
5790
|
allowedEnvVars?: string[] | undefined;
|
|
5791
5791
|
}>]>, "many">;
|
|
5792
5792
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -5819,11 +5819,11 @@ export declare const HookMatcherEntry: z.ZodObject<{
|
|
|
5819
5819
|
} | {
|
|
5820
5820
|
type: "http";
|
|
5821
5821
|
url: string;
|
|
5822
|
+
headers?: Record<string, string> | undefined;
|
|
5822
5823
|
if?: string | undefined;
|
|
5823
5824
|
timeout?: number | undefined;
|
|
5824
5825
|
statusMessage?: string | undefined;
|
|
5825
5826
|
once?: boolean | undefined;
|
|
5826
|
-
headers?: Record<string, string> | undefined;
|
|
5827
5827
|
allowedEnvVars?: string[] | undefined;
|
|
5828
5828
|
})[];
|
|
5829
5829
|
matcher?: string | undefined;
|
|
@@ -5857,11 +5857,11 @@ export declare const HookMatcherEntry: z.ZodObject<{
|
|
|
5857
5857
|
} | {
|
|
5858
5858
|
type: "http";
|
|
5859
5859
|
url: string;
|
|
5860
|
+
headers?: Record<string, string> | undefined;
|
|
5860
5861
|
if?: string | undefined;
|
|
5861
5862
|
timeout?: number | undefined;
|
|
5862
5863
|
statusMessage?: string | undefined;
|
|
5863
5864
|
once?: boolean | undefined;
|
|
5864
|
-
headers?: Record<string, string> | undefined;
|
|
5865
5865
|
allowedEnvVars?: string[] | undefined;
|
|
5866
5866
|
})[];
|
|
5867
5867
|
matcher?: string | undefined;
|