@sema-agent/settings-schema 6.3.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 CHANGED
@@ -8,6 +8,54 @@
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 6.4.0 — 2026-10-01 — `models.models[].headers` 解析透明键(S-935;与 `compat` 同一只变换)
12
+
13
+ > **判档 = minor**(新键 = additive,README「发版纪律」)。缺席 = 键不铸 = 6.3.0 行为,零迁移;不带 `headers` 的文档读出来逐字节不变
14
+ > (conformance 全语料重加冕:既有 61 条 case、`baseline.json`、`VERSION` 零 diff)。
15
+
16
+ ### 病(6.3.0 及更早)
17
+
18
+ sema-server 7.108.0(S-931)给模型目录条目加了 `headers`(部署 ↔ 供应商的 per-model 附加请求头,例 `{"anthropic-beta":"context-1m-2025-08-07"}`
19
+ 开 1M 上下文)。本包 `ModelEntry` 没有这个键 ⇒ 每一条按域 schema 解析的读口都把它剥掉并报 `unknown-keys-dropped`
20
+ (实测:`[{"domain":"models","kind":"unknown-keys-dropped","keys":[["models",0,"headers"]]}]`)—— 远端 `readEffectiveWire`、`buildEffective`、
21
+ 文件腿 `FileConfigStore.getEffective()`(严格 / `{tolerant:true}` 两形,`loadLocalConfig` 走严格形)、`FileConfigStore.getDomain`
22
+ (`exportBundle` 的读法);写腿 `writeDomainFile` / `validateDomain` 同样剥,且连告警都没有。经生产配置通道写下的 `headers`
23
+ 到不了 server 目录,只有绕过读腿的输入(盘上 LKG / 手写 effective)到得了。
24
+
25
+ ### 修
26
+
27
+ - `ModelEntry.headers?`(`src/types.ts`):`z.unknown().optional().transform(createFreeFormTransform("headers"))` —— 与 `compat` **同一只**
28
+ 变换(本版把 `compat` 那段内联闭包提成包内私有的 `createFreeFormTransform(field)`,两键共用,不再各写一份):缺席不铸键;其余只过
29
+ `vetFreeFormJson` 自由形安全判(密钥形键 / 值、访问器、环、非 JSON、深于 `MAX_EXTRA_BODY_DEPTH`),拒句与 `compat` 逐字同形、只换字段名。
30
+ - **不做形校验**:字符串 / 数组 / 数字 / `null` / 非规范大小写的名(`Anthropic-Beta`)/ 白名单外的名 / 非字符串值 / 写在不读它的线路上,
31
+ 本包一律逐字过。头名闭集白名单 × 读它的线路 × 值形的判据**唯一属主是 sema-server**(坏声明 ⇒ 该条目整条不可用),本包复述一份必漂。
32
+ - 登记进 `PARSE_TRANSPARENT_KEYS`(`models[].headers`,`since: "6.4.0"`,`validatedBy: "consumer"`)。`ParseTransparentValidator` 的
33
+ `consumer` 词注去掉了「丢掉坏的那一个键、保留宿主条目」这句处置描述 —— 坏值怎么处置是执法点自己的契约(`headers` 的执法点整条
34
+ 条目不可用,`mcp.servers[].source` 的只丢标签),写在各登记行的 `why` 里,本包不替它定(规则少一条,不加新词)。
35
+ - 同深度上限:`src/hash.ts` `MAX_EXTRA_BODY_DEPTH` 注文补 `headers`(与 `extraBody` / `compat` 同一个包裹深度,同一个上限)。
36
+ - 文档:README「Declared-but-unvalidated keys」、`llms.txt` 解析透明键登记表段(4 员 → 5 员)、`llms-full.txt` 补 `headers`。README 同句
37
+ 顺手订正:它把 `models[].extraBody` 列成解析透明键、把「the key's own guard」列成第三种校验方 —— 两处都与实现不符(`extraBody`
38
+ 有自己的形门,不在登记表里;校验方闭集只有 `engine-ingest` / `consumer` 两词),改成与 `src/types.ts` 一致。
39
+
40
+ ### 红先 / 门
41
+
42
+ - `test/model-headers-6.4.0.test.ts` 16 格,修前 15 红 1 绿(绿的是 ③ 负控):① 五条读腿逐字交出、零告警 ①b 写腿逐字落盘
43
+ ② 密钥形值 / 键(`sk-ant-…` / 私钥 PEM / `Bearer …` / 密钥形键 / 数组里的嵌套值)拒,拒句 = `compat` 拒句换字段名 ②b 五条读腿上与
44
+ `compat` 同一个处置(经 `buildEffective` 的四条腿逐条丢 `entry-dropped`、兄弟幸存;`getDomain` 整域净化后抛)、零回显
45
+ ③ 负控:不带 `headers` 的文档 effective / wire 的 `stableHash` 与 6.3.0 实测金样逐位相同 ④ 不做形校验 ⑤ `stableHash` 键序无关、
46
+ 声明 / 换值改变哈希、恰在 `MAX_EXTRA_BODY_DEPTH` 收且包进 effective 可哈希、超一层拒(与 `compat` 同层)⑥ 登记行。
47
+ - 解析透明键登记表门(`test/parse-transparent-keys.test.ts`,从 `DOMAIN_SCHEMAS` 行为派生、双向对拍)随新键自动覆盖。
48
+ - conformance 两条新语料(`npm run conformance:bless` 加冕):`c48-s935-model-headers-transported`(三腿逐字交出、零告警;openai 线路上的
49
+ 数组形 `headers` 照样过;无 `headers` 的条目不铸键)、`c49-s935-model-headers-secret-entry-dropped`(密钥形值 ⇒ 条目逐条丢、零回显,
50
+ `secrets` 已登记)。对 6.3.0 源码实测三腿同红(c48:2 处 effective 差 + 1 条多出的 `unknown-keys-dropped`;c49:6 处 effective 差、
51
+ 缺 1 条 `entry-dropped`、多 1 条 `unknown-keys-dropped`);`firstRedOn` 留空(historical 阶梯证据真跑于入库之前,不手填,c41 同口径)。
52
+
53
+ ### 消费方
54
+
55
+ - **sema-server 7.109.0 提货**:`config-provider.ts` `mapToServiceEffective` 加一行过境 `headers`,并把 `test/catalog-entry-headers.test.ts`
56
+ 末段 🚧 S-931-schema 两格(「两条读腿今天剥掉 `headers`」)翻成「读腿逐字交出 `headers`」的正向钉、删该段 🚧。
57
+ - center / TOC:`ModelEntry` 的推断类型多一个 `headers?: unknown`;不读它的消费方零改动。
58
+
11
59
  ## 6.3.0 — 2026-09-27 — 安全修(codex gpt-6-astra 对 v6.1.0 复审 [high],回填 D-4):可披露身份改白名单闭形 `DISCLOSABLE_IDENTITY_RE` · `redactSecrets` 整串判套在一处归一化上 · 告警坐标出门一道
12
60
 
13
61
  > **判档 = minor**(发版方裁,2026-09-27;原排 6.2.1 当日补丁):按 README「发版纪律」,新导出 `DISCLOSABLE_IDENTITY_RE` = minor
package/README.md CHANGED
@@ -209,9 +209,12 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
209
209
  rule the dropped-entry identity on read warnings already uses: the `DISCLOSABLE_IDENTITY_RE` allowlist ∧ not
210
210
  secret-shaped, 6.3.0).
211
211
  - **Declared-but-unvalidated keys** (`PARSE_TRANSPARENT_KEYS`, 2.0.0): the ONE registry of keys this package
212
- carries verbatim WITHOUT checking their shape, each row naming who does check it (the engine's ingest side,
213
- a consumer's projection, or the key's own guard). Today: `models[].extraBody`, `models[].compat` (1.10.0),
214
- `mcp.servers[].source` (1.7.2), `mcp.servers[].toolFaces` (1.9.0), and an entitlement override's `value`.
212
+ carries verbatim WITHOUT checking their shape, each row naming who does check it (the engine's ingest side or
213
+ a consumer's enforcement point). Today: `models[].compat` (1.10.0), `models[].headers` (6.4.0; the header-name
214
+ allowlist × lane is sema-server's, not restated here), `mcp.servers[].source` (1.7.2), `mcp.servers[].toolFaces`
215
+ (1.9.0), and an entitlement override's `value`. `models[].extraBody` is NOT one of them — it has its own shape
216
+ gate (a plain JSON object); what it leaves open is the key vocabulary inside. `compat` / `headers` / `extraBody`
217
+ still clear the one free-form safety judgment (secret-shaped key/value, accessor, cycle, non-JSON, too deep ⇒ rejected).
215
218
  - **Extension namespaces** (`EXTENSION_NAMESPACES`, 1.5.0): keys another writer puts in a domain document by
216
219
  contract and this package deliberately does not model (today: the shell's entry-level `sema*` fields on
217
220
  `models`). They are still stripped, but they are NOT reported as unknown keys — intent is declared at the
@@ -1 +1 @@
1
- 6.3.0
1
+ 6.4.0
@@ -0,0 +1,115 @@
1
+ {
2
+ "domains": {
3
+ "outcome": "effective",
4
+ "effective": {
5
+ "collab": {
6
+ "templates": []
7
+ },
8
+ "entitlement": {
9
+ "budgetGroups": [],
10
+ "groupBindings": [],
11
+ "subjects": [],
12
+ "tiers": []
13
+ },
14
+ "execution": {
15
+ "allowedLanes": [
16
+ "host"
17
+ ],
18
+ "required": false
19
+ },
20
+ "governance": {},
21
+ "hosts": {
22
+ "hosts": []
23
+ },
24
+ "limits": {},
25
+ "mcp": {
26
+ "servers": []
27
+ },
28
+ "models": {
29
+ "models": [
30
+ {
31
+ "api": "anthropic-messages",
32
+ "apiKeyEnv": "ANTHROPIC_API_KEY",
33
+ "enabled": true,
34
+ "headers": {
35
+ "anthropic-beta": "context-1m-2025-08-07"
36
+ },
37
+ "id": "claude-x",
38
+ "name": "claude-1m",
39
+ "provider": "anthropic",
40
+ "quotaWeight": 1,
41
+ "reasoning": false,
42
+ "tier": "standard",
43
+ "vision": false
44
+ },
45
+ {
46
+ "api": "openai-completions",
47
+ "apiKeyEnv": "OPENAI_API_KEY",
48
+ "enabled": true,
49
+ "headers": [
50
+ "Anthropic-Beta: not-a-header-bag"
51
+ ],
52
+ "id": "gw-id",
53
+ "name": "gw",
54
+ "provider": "openai",
55
+ "quotaWeight": 1,
56
+ "reasoning": false,
57
+ "tier": "standard",
58
+ "vision": false
59
+ },
60
+ {
61
+ "api": "openai-completions",
62
+ "apiKeyEnv": "OPENAI_API_KEY",
63
+ "enabled": true,
64
+ "id": "plain-id",
65
+ "name": "plain",
66
+ "provider": "openai",
67
+ "quotaWeight": 1,
68
+ "reasoning": false,
69
+ "tier": "standard",
70
+ "vision": false
71
+ }
72
+ ],
73
+ "roles": {},
74
+ "tierGroups": []
75
+ },
76
+ "plugins": {
77
+ "plugins": []
78
+ },
79
+ "projects": {
80
+ "projects": {}
81
+ },
82
+ "prompts": {
83
+ "bindings": []
84
+ },
85
+ "rosters": {
86
+ "rosters": []
87
+ },
88
+ "runtime": {},
89
+ "scenarios": {
90
+ "scenarios": []
91
+ },
92
+ "skills": {
93
+ "skills": []
94
+ },
95
+ "systems": {
96
+ "systems": []
97
+ },
98
+ "workers": {
99
+ "workers": []
100
+ }
101
+ },
102
+ "warnings": [],
103
+ "metadata": {
104
+ "version": 7,
105
+ "updatedAt": "2026-10-01T00:00:00.000Z"
106
+ }
107
+ },
108
+ "wire": {
109
+ "derive": true
110
+ },
111
+ "localRoot": {
112
+ "derive": true,
113
+ "localVersion": 1883250445565322
114
+ }
115
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "meta": {
3
+ "version": 7,
4
+ "updatedAt": "2026-10-01T00:00:00.000Z"
5
+ },
6
+ "domains": {
7
+ "models": {
8
+ "models": [
9
+ {
10
+ "name": "claude-1m",
11
+ "provider": "anthropic",
12
+ "api": "anthropic-messages",
13
+ "id": "claude-x",
14
+ "apiKeyEnv": "ANTHROPIC_API_KEY",
15
+ "headers": {
16
+ "anthropic-beta": "context-1m-2025-08-07"
17
+ }
18
+ },
19
+ {
20
+ "name": "gw",
21
+ "provider": "openai",
22
+ "id": "gw-id",
23
+ "apiKeyEnv": "OPENAI_API_KEY",
24
+ "headers": [
25
+ "Anthropic-Beta: not-a-header-bag"
26
+ ]
27
+ },
28
+ {
29
+ "name": "plain",
30
+ "provider": "openai",
31
+ "id": "plain-id",
32
+ "apiKeyEnv": "OPENAI_API_KEY"
33
+ }
34
+ ]
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "contributor": "contract",
3
+ "claim": "S-935(6.4.0):`models[].headers` 是解析透明键(与 `compat` 同一只变换)⇒ 三读腿**逐字交出、零告警**,不剥、不报 `unknown-keys-dropped`;并且本包**不做形校验** —— 写在 openai 线路上的数组形 `headers`(非规范大小写的名、根本不是头袋)照样逐字过,形与线路归 sema-server 的目录条目判据。任一腿把它剥掉,经生产配置通道写下的附加请求头(开 1M 上下文的 `anthropic-beta`)就到不了 server 目录且零告警;任一腿对它做形校验,就是本包复述了 server 的白名单(两个写者)。第三条不带 `headers` 的条目是负控:不铸键。",
4
+ "firstRedOn": [],
5
+ "note": "车SL(2026-10-01)红先:6.3.0 读腿在本条上三腿同红 —— 两条带 `headers` 的条目都被剥掉该键,`unknown-keys-dropped` 点名 `[\"models\",0,\"headers\"]` / `[\"models\",1,\"headers\"]`(实测读数见车SL 收车档)。firstRedOn 为空:`conformance/historical/` 的阶梯证据(2.1.0 及更早)真跑于本条入库之前,没有本条的读数,那一栏只收 historical 真跑,不手填(c41 同口径)。"
6
+ }
@@ -0,0 +1,98 @@
1
+ {
2
+ "domains": {
3
+ "outcome": "effective",
4
+ "effective": {
5
+ "collab": {
6
+ "templates": []
7
+ },
8
+ "entitlement": {
9
+ "budgetGroups": [],
10
+ "groupBindings": [],
11
+ "subjects": [],
12
+ "tiers": []
13
+ },
14
+ "execution": {
15
+ "allowedLanes": [
16
+ "host"
17
+ ],
18
+ "required": false
19
+ },
20
+ "governance": {},
21
+ "hosts": {
22
+ "hosts": []
23
+ },
24
+ "limits": {},
25
+ "mcp": {
26
+ "servers": []
27
+ },
28
+ "models": {
29
+ "models": [
30
+ {
31
+ "api": "openai-completions",
32
+ "apiKeyEnv": "OPENAI_API_KEY",
33
+ "enabled": true,
34
+ "id": "gw-id",
35
+ "name": "gw",
36
+ "provider": "openai",
37
+ "quotaWeight": 1,
38
+ "reasoning": false,
39
+ "tier": "standard",
40
+ "vision": false
41
+ }
42
+ ],
43
+ "roles": {},
44
+ "tierGroups": []
45
+ },
46
+ "plugins": {
47
+ "plugins": []
48
+ },
49
+ "projects": {
50
+ "projects": {}
51
+ },
52
+ "prompts": {
53
+ "bindings": []
54
+ },
55
+ "rosters": {
56
+ "rosters": []
57
+ },
58
+ "runtime": {},
59
+ "scenarios": {
60
+ "scenarios": []
61
+ },
62
+ "skills": {
63
+ "skills": []
64
+ },
65
+ "systems": {
66
+ "systems": []
67
+ },
68
+ "workers": {
69
+ "workers": []
70
+ }
71
+ },
72
+ "warnings": [
73
+ {
74
+ "domain": "models",
75
+ "identity": {
76
+ "id": "claude-x",
77
+ "name": "claude-1m"
78
+ },
79
+ "kind": "entry-dropped",
80
+ "path": [
81
+ "models",
82
+ 0
83
+ ]
84
+ }
85
+ ],
86
+ "metadata": {
87
+ "version": 7,
88
+ "updatedAt": "2026-10-01T00:00:00.000Z"
89
+ }
90
+ },
91
+ "wire": {
92
+ "derive": true
93
+ },
94
+ "localRoot": {
95
+ "derive": true,
96
+ "localVersion": 496785931148953
97
+ }
98
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "meta": {
3
+ "version": 7,
4
+ "updatedAt": "2026-10-01T00:00:00.000Z"
5
+ },
6
+ "domains": {
7
+ "models": {
8
+ "models": [
9
+ {
10
+ "name": "claude-1m",
11
+ "provider": "anthropic",
12
+ "api": "anthropic-messages",
13
+ "id": "claude-x",
14
+ "apiKeyEnv": "ANTHROPIC_API_KEY",
15
+ "headers": {
16
+ "x-api-key": "sk-ant-api03-FAKES935CORPUS000000000000000000"
17
+ }
18
+ },
19
+ {
20
+ "name": "gw",
21
+ "provider": "openai",
22
+ "id": "gw-id",
23
+ "apiKeyEnv": "OPENAI_API_KEY"
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "contributor": "contract",
3
+ "claim": "S-935(6.4.0):`models[].headers` 里粘了一把密钥形值(`x-api-key: sk-ant-…`)⇒ 与 `compat` 同一只自由形安全判据拒 ⇒ 该条目**逐条丢**(`entry-dropped`,坐标 `[\"models\",0]`)、兄弟条目幸存、告警面零回显那把钥匙。打的是两种读错:① 腿把 `headers` 当未知键剥掉(6.3.0 形)⇒ 条目带着「看起来干净」的形幸存,明文凭据却已经进了持久层且没人拒;② 腿对 `headers` 另起一套判据(或把拒句拼上值)⇒ 告警面带出密钥。",
4
+ "firstRedOn": [],
5
+ "secrets": [
6
+ "sk-ant-api03-FAKES935CORPUS000000000000000000"
7
+ ],
8
+ "note": "车SL(2026-10-01)红先:6.3.0 读腿在本条上三腿同红 —— 条目幸存(`headers` 被剥)+ `unknown-keys-dropped` 点名 `[\"models\",0,\"headers\"]`,没有 `entry-dropped`(实测读数见车SL 收车档)。firstRedOn 为空:同 c48 / c41 口径(historical 阶梯证据真跑于本条入库之前,不手填)。"
9
+ }
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` and, since
27
- * 1.10.0, `ModelEntry.compat`) — they sit at the SAME wrapper depth (`{models:{models:[{…}]}}`), so one cap
28
- * serves both. The `EXTRA_BODY` spelling is kept because this is a published export on the `./hash` subpath
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` and, since
27
- * 1.10.0, `ModelEntry.compat`) — they sit at the SAME wrapper depth (`{models:{models:[{…}]}}`), so one cap
28
- * serves both. The `EXTRA_BODY` spelling is kept because this is a published export on the `./hash` subpath
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;
package/dist/types.d.ts CHANGED
@@ -117,7 +117,8 @@ export declare const SealedApiKey: z.ZodObject<{
117
117
  export type SealedApiKey = z.infer<typeof SealedApiKey>;
118
118
  /** 谁来校验一个**解析透明键**的形。闭集:新增一员要在这里加词,穷举消费方即编译红。
119
119
  * · `engine-ingest` —— 引擎摄入侧单点校验(不合形的那一条不挂并通告一次);
120
- * · `consumer` —— 消费方的投影/执法点(丢掉坏的那一个键、保留其宿主条目、记账)。 */
120
+ * · `consumer` —— 消费方的投影/执法点。坏值怎么处置(只丢那一个键 / 整条宿主条目不可用)是那个执法点自己的
121
+ * 契约,写在登记行的 `why` 里 —— 本包不替它定。 */
121
122
  export type ParseTransparentValidator = "engine-ingest" | "consumer";
122
123
  /** {@link PARSE_TRANSPARENT_KEYS} 的一行。 */
123
124
  export interface ParseTransparentKey {
@@ -247,6 +248,16 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
247
248
  * deep to hash later. That is not shape validation — it is the boundary every free-form value must clear to
248
249
  * enter the persisted config and `stableHash` (R18). */
249
250
  compat: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
251
+ /** Per-model extra request headers, deployment ↔ provider (e.g. `{"anthropic-beta": "context-1m-2025-08-07"}` on an
252
+ * `anthropic-messages` entry). Real need: a beta that a shell passes as `--betas` / `ANTHROPIC_BETAS` never reaches the
253
+ * request the engine sends; the catalog entry is the deployment-level seat for it — and without this key the entry's
254
+ * declaration is stripped at parse on every read leg, so it never reaches the consumer at all.
255
+ * ABSENT = key not minted = today's behaviour, zero migration.
256
+ *
257
+ * **Parse-transparent** — who validates the shape, and why not here: {@link PARSE_TRANSPARENT_KEYS}
258
+ * (`models.models[].headers`). The ONE rejection that remains is the free-form SAFETY judgment shared with `compat` /
259
+ * `extraBody` ({@link vetFreeFormJson}). */
260
+ headers: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
250
261
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
251
262
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
252
263
  quotaWeight: z.ZodDefault<z.ZodNumber>;
@@ -283,6 +294,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
283
294
  } | undefined;
284
295
  extraBody?: Record<string, unknown> | undefined;
285
296
  compat?: unknown;
297
+ headers?: unknown;
286
298
  notes?: string | undefined;
287
299
  }, {
288
300
  id: string;
@@ -313,6 +325,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
313
325
  } | undefined;
314
326
  extraBody?: unknown;
315
327
  compat?: unknown;
328
+ headers?: unknown;
316
329
  quotaWeight?: number | undefined;
317
330
  enabled?: boolean | undefined;
318
331
  notes?: string | undefined;
@@ -347,6 +360,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
347
360
  } | undefined;
348
361
  extraBody?: Record<string, unknown> | undefined;
349
362
  compat?: unknown;
363
+ headers?: unknown;
350
364
  notes?: string | undefined;
351
365
  }, {
352
366
  id: string;
@@ -377,6 +391,7 @@ export declare const ModelEntry: z.ZodEffects<z.ZodObject<{
377
391
  } | undefined;
378
392
  extraBody?: unknown;
379
393
  compat?: unknown;
394
+ headers?: unknown;
380
395
  quotaWeight?: number | undefined;
381
396
  enabled?: boolean | undefined;
382
397
  notes?: string | undefined;
@@ -585,6 +600,16 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
585
600
  * deep to hash later. That is not shape validation — it is the boundary every free-form value must clear to
586
601
  * enter the persisted config and `stableHash` (R18). */
587
602
  compat: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
603
+ /** Per-model extra request headers, deployment ↔ provider (e.g. `{"anthropic-beta": "context-1m-2025-08-07"}` on an
604
+ * `anthropic-messages` entry). Real need: a beta that a shell passes as `--betas` / `ANTHROPIC_BETAS` never reaches the
605
+ * request the engine sends; the catalog entry is the deployment-level seat for it — and without this key the entry's
606
+ * declaration is stripped at parse on every read leg, so it never reaches the consumer at all.
607
+ * ABSENT = key not minted = today's behaviour, zero migration.
608
+ *
609
+ * **Parse-transparent** — who validates the shape, and why not here: {@link PARSE_TRANSPARENT_KEYS}
610
+ * (`models.models[].headers`). The ONE rejection that remains is the free-form SAFETY judgment shared with `compat` /
611
+ * `extraBody` ({@link vetFreeFormJson}). */
612
+ headers: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
588
613
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
589
614
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
590
615
  quotaWeight: z.ZodDefault<z.ZodNumber>;
@@ -621,6 +646,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
621
646
  } | undefined;
622
647
  extraBody?: Record<string, unknown> | undefined;
623
648
  compat?: unknown;
649
+ headers?: unknown;
624
650
  notes?: string | undefined;
625
651
  }, {
626
652
  id: string;
@@ -651,6 +677,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
651
677
  } | undefined;
652
678
  extraBody?: unknown;
653
679
  compat?: unknown;
680
+ headers?: unknown;
654
681
  quotaWeight?: number | undefined;
655
682
  enabled?: boolean | undefined;
656
683
  notes?: string | undefined;
@@ -685,6 +712,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
685
712
  } | undefined;
686
713
  extraBody?: Record<string, unknown> | undefined;
687
714
  compat?: unknown;
715
+ headers?: unknown;
688
716
  notes?: string | undefined;
689
717
  }, {
690
718
  id: string;
@@ -715,6 +743,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
715
743
  } | undefined;
716
744
  extraBody?: unknown;
717
745
  compat?: unknown;
746
+ headers?: unknown;
718
747
  quotaWeight?: number | undefined;
719
748
  enabled?: boolean | undefined;
720
749
  notes?: string | undefined;
@@ -839,6 +868,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
839
868
  } | undefined;
840
869
  extraBody?: Record<string, unknown> | undefined;
841
870
  compat?: unknown;
871
+ headers?: unknown;
842
872
  notes?: string | undefined;
843
873
  }[];
844
874
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -894,6 +924,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
894
924
  } | undefined;
895
925
  extraBody?: unknown;
896
926
  compat?: unknown;
927
+ headers?: unknown;
897
928
  quotaWeight?: number | undefined;
898
929
  enabled?: boolean | undefined;
899
930
  notes?: string | undefined;
@@ -953,6 +984,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
953
984
  } | undefined;
954
985
  extraBody?: Record<string, unknown> | undefined;
955
986
  compat?: unknown;
987
+ headers?: unknown;
956
988
  notes?: string | undefined;
957
989
  }[];
958
990
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -1008,6 +1040,7 @@ export declare const ModelsConfig: z.ZodEffects<z.ZodObject<{
1008
1040
  } | undefined;
1009
1041
  extraBody?: unknown;
1010
1042
  compat?: unknown;
1043
+ headers?: unknown;
1011
1044
  quotaWeight?: number | undefined;
1012
1045
  enabled?: boolean | undefined;
1013
1046
  notes?: string | undefined;
@@ -9501,6 +9534,16 @@ export declare const DOMAIN_SCHEMAS: {
9501
9534
  * deep to hash later. That is not shape validation — it is the boundary every free-form value must clear to
9502
9535
  * enter the persisted config and `stableHash` (R18). */
9503
9536
  compat: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
9537
+ /** Per-model extra request headers, deployment ↔ provider (e.g. `{"anthropic-beta": "context-1m-2025-08-07"}` on an
9538
+ * `anthropic-messages` entry). Real need: a beta that a shell passes as `--betas` / `ANTHROPIC_BETAS` never reaches the
9539
+ * request the engine sends; the catalog entry is the deployment-level seat for it — and without this key the entry's
9540
+ * declaration is stripped at parse on every read leg, so it never reaches the consumer at all.
9541
+ * ABSENT = key not minted = today's behaviour, zero migration.
9542
+ *
9543
+ * **Parse-transparent** — who validates the shape, and why not here: {@link PARSE_TRANSPARENT_KEYS}
9544
+ * (`models.models[].headers`). The ONE rejection that remains is the free-form SAFETY judgment shared with `compat` /
9545
+ * `extraBody` ({@link vetFreeFormJson}). */
9546
+ headers: z.ZodEffects<z.ZodOptional<z.ZodUnknown>, unknown, unknown>;
9504
9547
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
9505
9548
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
9506
9549
  quotaWeight: z.ZodDefault<z.ZodNumber>;
@@ -9537,6 +9580,7 @@ export declare const DOMAIN_SCHEMAS: {
9537
9580
  } | undefined;
9538
9581
  extraBody?: Record<string, unknown> | undefined;
9539
9582
  compat?: unknown;
9583
+ headers?: unknown;
9540
9584
  notes?: string | undefined;
9541
9585
  }, {
9542
9586
  id: string;
@@ -9567,6 +9611,7 @@ export declare const DOMAIN_SCHEMAS: {
9567
9611
  } | undefined;
9568
9612
  extraBody?: unknown;
9569
9613
  compat?: unknown;
9614
+ headers?: unknown;
9570
9615
  quotaWeight?: number | undefined;
9571
9616
  enabled?: boolean | undefined;
9572
9617
  notes?: string | undefined;
@@ -9601,6 +9646,7 @@ export declare const DOMAIN_SCHEMAS: {
9601
9646
  } | undefined;
9602
9647
  extraBody?: Record<string, unknown> | undefined;
9603
9648
  compat?: unknown;
9649
+ headers?: unknown;
9604
9650
  notes?: string | undefined;
9605
9651
  }, {
9606
9652
  id: string;
@@ -9631,6 +9677,7 @@ export declare const DOMAIN_SCHEMAS: {
9631
9677
  } | undefined;
9632
9678
  extraBody?: unknown;
9633
9679
  compat?: unknown;
9680
+ headers?: unknown;
9634
9681
  quotaWeight?: number | undefined;
9635
9682
  enabled?: boolean | undefined;
9636
9683
  notes?: string | undefined;
@@ -9755,6 +9802,7 @@ export declare const DOMAIN_SCHEMAS: {
9755
9802
  } | undefined;
9756
9803
  extraBody?: Record<string, unknown> | undefined;
9757
9804
  compat?: unknown;
9805
+ headers?: unknown;
9758
9806
  notes?: string | undefined;
9759
9807
  }[];
9760
9808
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -9810,6 +9858,7 @@ export declare const DOMAIN_SCHEMAS: {
9810
9858
  } | undefined;
9811
9859
  extraBody?: unknown;
9812
9860
  compat?: unknown;
9861
+ headers?: unknown;
9813
9862
  quotaWeight?: number | undefined;
9814
9863
  enabled?: boolean | undefined;
9815
9864
  notes?: string | undefined;
@@ -9869,6 +9918,7 @@ export declare const DOMAIN_SCHEMAS: {
9869
9918
  } | undefined;
9870
9919
  extraBody?: Record<string, unknown> | undefined;
9871
9920
  compat?: unknown;
9921
+ headers?: unknown;
9872
9922
  notes?: string | undefined;
9873
9923
  }[];
9874
9924
  roles: Partial<Record<"default" | "summarize" | "subagent" | "team" | "synthesize" | "verifier" | "classifier" | "consolidate", {
@@ -9924,6 +9974,7 @@ export declare const DOMAIN_SCHEMAS: {
9924
9974
  } | undefined;
9925
9975
  extraBody?: unknown;
9926
9976
  compat?: unknown;
9977
+ headers?: unknown;
9927
9978
  quotaWeight?: number | undefined;
9928
9979
  enabled?: boolean | undefined;
9929
9980
  notes?: string | undefined;
package/dist/types.js CHANGED
@@ -201,9 +201,9 @@ export function extraBodyUnsafe(v) {
201
201
  }
202
202
  return false;
203
203
  }
204
- /** THE vetting pipeline for every FREE-FORM JSON value that lands in the persisted config — `ModelEntry.extraBody`
205
- * and `ModelEntry.compat` share this ONE function (1.10.0; before that it was inline in `extraBody` and a second
206
- * free-form field would have copied it). The judgment is identical for every such field, so it lives in one place:
204
+ /** THE vetting pipeline for every FREE-FORM JSON value that lands in the persisted config — `ModelEntry.extraBody`,
205
+ * `ModelEntry.compat` and `ModelEntry.headers` share this ONE function (1.10.0; before that it was inline in `extraBody`
206
+ * and a second free-form field would have copied it). The judgment is identical for every such field, so it lives in one place:
207
207
  *
208
208
  * 1. getter-free descriptor walk ({@link extraBodyUnsafe}) over the RAW value — never invokes a getter, and any
209
209
  * reflective throw (a hostile Proxy trap could throw a secret-bearing message) collapses into a FIXED message;
@@ -234,6 +234,22 @@ function vetFreeFormJson(v, field) {
234
234
  return { ok: false, message: `${field}: its serialized form carries a secret` };
235
235
  return { ok: true, value: clone };
236
236
  }
237
+ /** The transform of every **parse-transparent free-form** key (`z.unknown().optional().transform(createFreeFormTransform(field))`:
238
+ * `ModelEntry.compat` / `ModelEntry.headers`) — ONE closure, so those keys cannot drift apart: absent stays absent (key not
239
+ * minted); anything else is the {@link vetFreeFormJson} safety judgment and nothing more (no SHAPE rule — whose job that is,
240
+ * per key, is {@link PARSE_TRANSPARENT_KEYS}). `extraBody` is not one of these: it has its own shape gate first. */
241
+ function createFreeFormTransform(field) {
242
+ return (v, ctx) => {
243
+ if (v === undefined)
244
+ return undefined;
245
+ const vetted = vetFreeFormJson(v, field);
246
+ if (!vetted.ok) {
247
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: vetted.message });
248
+ return z.NEVER;
249
+ }
250
+ return vetted.value;
251
+ };
252
+ }
237
253
  /** A k8s resource quantity ("250m", "1", "0.5", "512Mi", "1Gi", "128M") — catches garbage at config-submit
238
254
  * rather than surfacing as a reconcile ApplyError at the apiserver (consistent with ENV_NAME/DURATION guards). */
239
255
  export const K8S_QUANTITY = z.string().regex(/^\d+(\.\d+)?(m|[kKMGTPE]i?)?$/, 'must be a k8s quantity like "250m", "512Mi", "1Gi"');
@@ -313,6 +329,13 @@ export const PARSE_TRANSPARENT_KEYS = [
313
329
  validatedBy: "engine-ingest",
314
330
  why: "镜像引擎的 per-model wire-compat 声明(thinkingFormat 等五键);那张词表随引擎走,本包复述一份必漂。",
315
331
  },
332
+ {
333
+ domain: "models",
334
+ path: "models[].headers",
335
+ since: "6.4.0",
336
+ validatedBy: "consumer",
337
+ why: "部署 ↔ 供应商的 per-model 附加请求头;判据(头名闭集白名单 × 读它的线路 × 值形)的唯一属主是 server 的目录条目判据(坏声明 ⇒ 该条目整条不可用,不是丢头照常),本包复述一份那张表必漂、两处各一份就是两个写者。",
338
+ },
316
339
  {
317
340
  domain: "mcp",
318
341
  path: "servers[].source",
@@ -424,16 +447,20 @@ export const ModelEntry = z.object({
424
447
  compat: z
425
448
  .unknown()
426
449
  .optional()
427
- .transform((v, ctx) => {
428
- if (v === undefined)
429
- return undefined;
430
- const vetted = vetFreeFormJson(v, "compat");
431
- if (!vetted.ok) {
432
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: vetted.message });
433
- return z.NEVER;
434
- }
435
- return vetted.value;
436
- }),
450
+ .transform(createFreeFormTransform("compat")),
451
+ /** Per-model extra request headers, deployment ↔ provider (e.g. `{"anthropic-beta": "context-1m-2025-08-07"}` on an
452
+ * `anthropic-messages` entry). Real need: a beta that a shell passes as `--betas` / `ANTHROPIC_BETAS` never reaches the
453
+ * request the engine sends; the catalog entry is the deployment-level seat for it — and without this key the entry's
454
+ * declaration is stripped at parse on every read leg, so it never reaches the consumer at all.
455
+ * ABSENT = key not minted = today's behaviour, zero migration.
456
+ *
457
+ * **Parse-transparent** — who validates the shape, and why not here: {@link PARSE_TRANSPARENT_KEYS}
458
+ * (`models.models[].headers`). The ONE rejection that remains is the free-form SAFETY judgment shared with `compat` /
459
+ * `extraBody` ({@link vetFreeFormJson}). */
460
+ headers: z
461
+ .unknown()
462
+ .optional()
463
+ .transform(createFreeFormTransform("headers")),
437
464
  /** 配额倍率(EXPERT-REDESIGN §4-1):该模型烧 token 双窗额度的倍率,默认 1。折算时点=weight-at-burn
438
465
  * (记账时固化到 UsageEntry.quotaWeightAtUse/weightedTokens):改倍率只影响此后记账,不回算窗口内历史。 */
439
466
  quotaWeight: z.number().finite().nonnegative().default(1),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",