@sema-agent/settings-schema 14.7.0 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +306 -0
  2. package/README.md +80 -3
  3. package/conformance/VERSION +1 -1
  4. package/conformance/cases/c59-s784-g3-k8s-missing-image/input.json +4 -1
  5. package/conformance/cases/c61-s784-g3-other-lane-key/input.json +1 -0
  6. package/conformance/cases/c64-s784-multitenant-local-store/input.json +2 -1
  7. package/dist/atomic-write.d.ts +15 -0
  8. package/dist/atomic-write.js +109 -5
  9. package/dist/catalog-manages.d.ts +15 -0
  10. package/dist/catalog-manages.js +60 -0
  11. package/dist/center-refs.d.ts +68 -0
  12. package/dist/center-refs.js +70 -0
  13. package/dist/config-dir-migration.js +1 -0
  14. package/dist/config-fns.js +5 -3
  15. package/dist/config-root-documents.d.ts +106 -0
  16. package/dist/config-root-documents.js +93 -0
  17. package/dist/config-root-node.d.ts +117 -0
  18. package/dist/config-root-node.js +260 -0
  19. package/dist/conformance/run.d.ts +3 -0
  20. package/dist/conformance/run.js +10 -6
  21. package/dist/conformance/temp-roots.d.ts +31 -0
  22. package/dist/conformance/temp-roots.js +184 -0
  23. package/dist/default-model.d.ts +1 -1
  24. package/dist/default-model.js +5 -4
  25. package/dist/env-model-table.d.ts +163 -0
  26. package/dist/env-model-table.js +257 -0
  27. package/dist/execution-lane-index.d.ts +6 -0
  28. package/dist/execution-lane-index.js +8 -0
  29. package/dist/execution-lanes.d.ts +2 -1
  30. package/dist/execution-lanes.js +6 -4
  31. package/dist/fleet.d.ts +25 -7
  32. package/dist/fleet.js +24 -6
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.js +5 -0
  35. package/dist/mcp-request-entry.d.ts +58 -6
  36. package/dist/mcp-request-entry.js +45 -13
  37. package/dist/mcp-server-name.d.ts +17 -0
  38. package/dist/mcp-server-name.js +19 -0
  39. package/dist/migration-receipt.d.ts +10 -6
  40. package/dist/migration-receipt.js +5 -3
  41. package/dist/node.d.ts +1 -0
  42. package/dist/node.js +2 -0
  43. package/dist/one-time-migrations.d.ts +4 -2
  44. package/dist/one-time-migrations.js +29 -1
  45. package/dist/own-key-read.d.ts +1 -1
  46. package/dist/remote-exec.d.ts +1 -1
  47. package/dist/remote-exec.js +4 -2
  48. package/dist/retired-form.d.ts +19 -0
  49. package/dist/retired-form.js +24 -1
  50. package/dist/safety-merge-spec.d.ts +6 -6
  51. package/dist/tier-words.d.ts +33 -0
  52. package/dist/tier-words.js +43 -0
  53. package/dist/types.d.ts +6 -0
  54. package/dist/types.js +29 -5
  55. package/dist/worker-row-verdict.js +7 -1
  56. package/docs/API-SURFACE.md +68 -4
  57. package/docs/CONSUMER-INTEGRATION.md +72 -2
  58. package/llms-full.txt +8 -3
  59. package/llms.txt +9 -3
  60. package/package.json +2 -2
@@ -11,7 +11,13 @@
11
11
  * **迁移只认形、不认义**:每一步的输入形在新契约下非法、输出形合法 ⇒ 重跑恒等,新写入的数据永远不会被当成旧数据再改一次,不需要版本围栏;
12
12
  * 同一串字节换意思的改动一律**换表示**(SS-M6 因此是「`atModelAllowlist` → `atMention`」换字段,而不是翻 `[]` 的义)。
13
13
  *
14
- * ## 入口(纯函数:不读环境、不读墙钟、不改入参、**不抬任何版本号**)
14
+ * ## 第二种机制:在配置根上铸一次事实(只有 SS-M11)
15
+ * 上面的「只认形」律管的是**改写存储文档**的迁移。SS-M11 不改写任何存储形:它在一份文档**缺席**处铸一份部署决定(配置根的 `config.env-models.json`,收据词 `minted`),
16
+ * 而「缺席」本身没有形可认 —— 所以它不归上面的律,另有三条:① 它读 env、碰文件系统,住 `./node`(`migrateEnvModelsDecision`),不在下面的纯函数入口里;
17
+ * ② 它靠**按 id 一份的运行记录**(`config.migration.SS-M11.json`,「不存在才建」)跑恰一次,建成运行记录的进程是本根唯一的执行者;
18
+ * ③ 它的旧形待遇是 `read-as-default`(缺席按读者的缺省 `kept` 读,没有拒句可点名)。窗、删法与删后由谁接住照样只写在登记表那一行。
19
+ *
20
+ * ## 入口(纯函数:不读环境、不读墙钟、不改入参、**不抬任何版本号**;SS-M11 除外,见上一节)
15
21
  * · {@link migrateStoredDocuments} —— 一组**存储文档**(域名 → 文档;还有本机的 `remote-exec` —— `config.d/remote-exec.json`,
16
22
  * 只有 SS-M7 的座位走查读它)。一个 scope 的全部草稿、一个 principal 层(个人 models / mcp / skills / plugins)、回滚史里的一条单域行,各调一次;`coverage` 说这组文档是不是一个 scope 的全集(只有 SS-M1 要知道:缺席的
17
23
  * `governance` 是「从未设过」还是「没给」)。缺的域 = 不动。
@@ -176,6 +182,28 @@ export const ONE_TIME_MIGRATIONS = [
176
182
  afterRemoval: "the prompts `bindings` / `catalog` tombstone fields (permanent): a residual key is refused on read and write (the prompts domain degrades " +
177
183
  "to its default — no center layer is served)",
178
184
  },
185
+ {
186
+ id: "SS-M11",
187
+ oldForm: "read-as-default",
188
+ introducedIn: "14.8.0",
189
+ releasedOn: "2026-10-27",
190
+ removableAfter: "2026-11-26",
191
+ rewrites: "nothing stored is rewritten — the config root's env-model-table decision is minted once (migrateEnvModelsDecision, ./node): when the run " +
192
+ "marker config.migration.SS-M11.json is absent (a marker that does not read counts as run), the decision and whether config.d/models.json " +
193
+ "manages the model plane (catalogManagesModelPlane) are read first; then the marker is created exclusively — the one process that creates " +
194
+ "it is the only one that may mint on this root, any other stops for good — and that minter creates {\"envModels\":\"replaced\"} " +
195
+ "exclusively only when the decision it read before the election is absent and the catalog manages the model plane (whether an env model " +
196
+ "table is declared does not matter: the env only names the replaced env models) — the reading before the decision existed (a managing " +
197
+ "catalog replaced the env model table). Outcomes: minted / exists (only EEXIST) / failed (errno + a manual way out); an interruption after the marker is " +
198
+ "never retried. An absent root is created owner-only, holding only the marker; config.d is never created",
199
+ removal: "the next major after the window: delete migrateEnvModelsDecision (the SS-M11 body), this row, SS-M11 from ONE_TIME_MIGRATION_IDS, the " +
200
+ "`minted` receipt word and the `read-as-default` oldForm word; the marker then has no reader — readMigrationMarker and the marker's " +
201
+ "entry in CONFIG_ROOT_DOCUMENTS go with it (a marker left on disk is inert)",
202
+ afterRemoval: "an absent decision reads as kept — the reading the group-refusal code config.catalog_replaces_env_models is for: this package exports " +
203
+ "that code constant and the decision document for the engine to read, enforcing it is the engine's side, and the engine does not " +
204
+ "refuse on it today. Where the engine refuses, the way out is to write config.env-models.json = {\"envModels\":\"replaced\"} in the " +
205
+ "config root; no signpost in this package",
206
+ },
179
207
  {
180
208
  // 一条 wire 位的删码登记,不是数据迁移(id 在 WIRE_RETIREMENT_IDS,不在收据闭集)。
181
209
  id: "SS-W1",
@@ -24,7 +24,7 @@
24
24
  * 以及交回原引用 / 作类型守卫 / 对原值下判决的读口:`isStringArray` / `isStringRecord` / `isSchedulerRecord`、`readSettingsUnit` 的列表单元与环境变量表、比较器的串集合(`tightenVerdict` 的集合规则)。
25
25
  * 内核覆盖声明键读与整容器读;**仍自带读法的读者按名登记**(各自写明契约;普查钉按「文件 → 函数 → 读法 API」逐处数,登记外出现即红):
26
26
  * 既有、未经内核的数据读者:`tightenVerdict` 的 `none` 规则(`strictJsonCopy`:对象的不可枚举键跳过、访问器 ⇒ 整份 `unreadable`;数组按 `length` 逐位读描述符 —— `length` 走一次 `[[Get]]`、不可枚举的下标照读)·
27
- * 被丢条目的身份(`readEntryIdentity`:访问器 ⇒ 当缺席,从不调)· 传输记录读法(`readPlainStringRecord`:自己枚举成员名 `getOwnPropertyNames`,值经 {@link readKey});
27
+ * 被丢条目的身份(`readEntryIdentity` 读 `name` / `id`,它调的 `ownEnabledFlag`(`./catalog-manages`,与原文判官共用)读 `enabled` —— 同一读法:访问器 ⇒ 当缺席,从不调)· 传输记录读法(`readPlainStringRecord`:自己枚举成员名 `getOwnPropertyNames`,值经 {@link readKey});
28
28
  * 不是数据读的:安全判官 `extraBodyUnsafe` 的审计读(`./types`:问「任何自有键上有没有可疑」,含符号键与会抛的描述符,访问器 ⇒ 判不安全,从不调)。
29
29
  * 直接用 `[[Get]]` 的几处(不经内核,按名登记):hooks 分类器(`Object.keys` + 成员读;闭包可抄的内建写法,随闭包契约变更收)· `collectDropped` / `collectionSlots`(`./config-fns`:
30
30
  * 未知键扫描与条目集合的位置表,自己枚举容器再 `[[Get]]`;既有)· http hook 名单读器与判官的**普通读对拍**(安全轴的名单、判官的 `layers` / `hookVars` 要描述符视图
@@ -63,7 +63,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<[z.ZodObject<{
63
63
  tokenEnv: z.ZodOptional<z.ZodString>;
64
64
  caCertEnv: z.ZodOptional<z.ZodString>;
65
65
  insecureTls: z.ZodOptional<z.ZodBoolean>;
66
- namespace: z.ZodOptional<z.ZodString>;
66
+ namespace: z.ZodString;
67
67
  runtimeClass: z.ZodOptional<z.ZodString>;
68
68
  mountPath: z.ZodOptional<z.ZodString>;
69
69
  timeoutMs: z.ZodOptional<z.ZodNumber>;
@@ -84,7 +84,7 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
84
84
  /** Which of THIS process's env-NAMEs to forward into the sandbox (service: E2B_SANDBOX_ENV, a CSV of NAMEs). */
85
85
  sandboxEnv: z.array(ENV_NAME).optional(),
86
86
  }),
87
- // ── k8s: per-task Kata pod (service provider "k8s"; REMOTE_EXEC=k8s + K8S_SANDBOX_IMAGE).
87
+ // ── k8s: per-task Kata pod (service provider "k8s"; REMOTE_EXEC=k8s + K8S_SANDBOX_IMAGE + K8S_NAMESPACE).
88
88
  z.object({
89
89
  provider: z.literal("k8s"),
90
90
  /** Sandbox container image (service: K8S_SANDBOX_IMAGE). Required so a run is reproducible. */
@@ -97,7 +97,9 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
97
97
  /** env-NAME holding the base64 CA cert (service: K8S_CA_CERT_B64). NEVER the cert bytes. */
98
98
  caCertEnv: ENV_NAME.optional(),
99
99
  insecureTls: z.boolean().optional(),
100
- namespace: execStr.optional(),
100
+ /** The namespace the sandbox pods live in (service: K8S_NAMESPACE). Required — the deployment declares where its pods run and nothing here guesses one;
101
+ * the execution-lane table lists K8S_NAMESPACE as a required coordinate of the k8s lane, and this file contract says the same. */
102
+ namespace: execStr,
101
103
  runtimeClass: execStr.optional(),
102
104
  mountPath: execStr.optional(),
103
105
  timeoutMs: INTEGER.positive().optional(),
@@ -7,6 +7,9 @@
7
7
  *
8
8
  * 墓碑是**拒**,不是第二种读法:它不把旧形读成任何值,只说「这里有一份没迁的旧形」以及去哪儿迁。迁移族按登记表的窗整族删,
9
9
  * 墓碑留下 —— 到那时拒句里的迁移 id 由删码那一车改写(登记表那一行的 `removal` 点名了这件事)。
10
+ *
11
+ * **墓碑表**:每个 {@link retiredKey} 铸出的墓碑在铸造时登记一行({@link RetiredKeyRow}:键名、形、整句)。表的读者两个 —— 读面的终判
12
+ * ({@link retiredKeyOfSentence}:一条 zod issue 的 message 恰是某个墓碑的整句 ⇒ 这是一条墓碑 issue)与测试({@link retiredKeyTable}:从表读名单,不手抄)。
10
13
  */
11
14
  import { z } from "zod";
12
15
  /** 迁移命令行的唯一拼写:`npx` 全名形 —— 没装本包的运维粘贴即可跑(裸 bin 名在没装本包的机器上是 `command not found`)。参数是一个 `config.d` 目录。
@@ -29,8 +32,24 @@ export interface RetiredForm {
29
32
  readonly migration: string;
30
33
  }
31
34
  export declare function retiredFormSentence(form: string, t: RetiredForm): string;
35
+ /** 墓碑表的一行:`key` 是墓碑字段在它所在对象里的点路径(例:`runtime.sessionBackend`),`sentence` 是它的拒句整句(= 那条 zod issue 的 message)。 */
36
+ export interface RetiredKeyRow {
37
+ readonly key: string;
38
+ readonly form: RetiredForm;
39
+ readonly sentence: string;
40
+ }
32
41
  /**
33
42
  * retired**键**的墓碑字段:放在该键原来所在的对象形里,键缺席 = 合法,键在场(任何值)= 一条 issue(路径就是那个键、句子点名迁移)。
34
43
  * 输出形里它恒缺席(型上是 `?: undefined`),JSON Schema 上是 `not: {}`(「不许出现」)—— 墓碑写在旧键原来的位置上,schema 自己说明它retired了。
35
44
  */
36
45
  export declare function retiredKey(key: string, t: RetiredForm): z.ZodOptional<z.ZodNever>;
46
+ /** 墓碑表(一份副本,按铸造次序)。 */
47
+ export declare function retiredKeyTable(): readonly RetiredKeyRow[];
48
+ /** 一条 zod issue 的 message 恰是某个墓碑的整句 ⇒ 那一行;否则 `undefined`。 */
49
+ export declare function retiredKeyOfSentence(message: string): RetiredKeyRow | undefined;
50
+ /**
51
+ * 墓碑在**读面**(终判扣下一行时的拒句)的说法:不点名那个旧字段的名字 —— 它就在盘上那一行里,结构化的坐标(`field`)已经指到它;
52
+ * 文字说现行位、迁移是一条出路,以及「迁移拒过这一行」的出路。读面是纯函数、无 I/O,不知道这一行跑没跑过迁移,所以两种情形都要成立:
53
+ * 没跑过 ⇒ 跑迁移;跑过且拒了(迁移不写回半成品,原行留在盘上)⇒ 手写现行形、删掉旧字段。
54
+ */
55
+ export declare function retiredKeyHeldSentence(t: RetiredForm): string;
@@ -7,6 +7,9 @@
7
7
  *
8
8
  * 墓碑是**拒**,不是第二种读法:它不把旧形读成任何值,只说「这里有一份没迁的旧形」以及去哪儿迁。迁移族按登记表的窗整族删,
9
9
  * 墓碑留下 —— 到那时拒句里的迁移 id 由删码那一车改写(登记表那一行的 `removal` 点名了这件事)。
10
+ *
11
+ * **墓碑表**:每个 {@link retiredKey} 铸出的墓碑在铸造时登记一行({@link RetiredKeyRow}:键名、形、整句)。表的读者两个 —— 读面的终判
12
+ * ({@link retiredKeyOfSentence}:一条 zod issue 的 message 恰是某个墓碑的整句 ⇒ 这是一条墓碑 issue)与测试({@link retiredKeyTable}:从表读名单,不手抄)。
10
13
  */
11
14
  import { z } from "zod";
12
15
  import { shellQuote } from "./shell-quote.js";
@@ -27,10 +30,30 @@ export function migrationSignpost(migration, before) {
27
30
  export function retiredFormSentence(form, t) {
28
31
  return `${form} was retired in ${t.retiredIn} — now ${t.now}; ${migrationSignpost(t.migration, t.retiredIn)}`;
29
32
  }
33
+ /** 整句 → 行。同一个键在两处 schema 里以同一个形退役(整句逐字同)只占一行。 */
34
+ const TOMBSTONES = new Map();
30
35
  /**
31
36
  * retired**键**的墓碑字段:放在该键原来所在的对象形里,键缺席 = 合法,键在场(任何值)= 一条 issue(路径就是那个键、句子点名迁移)。
32
37
  * 输出形里它恒缺席(型上是 `?: undefined`),JSON Schema 上是 `not: {}`(「不许出现」)—— 墓碑写在旧键原来的位置上,schema 自己说明它retired了。
33
38
  */
34
39
  export function retiredKey(key, t) {
35
- return z.never({ error: retiredFormSentence(key, t) }).optional();
40
+ const sentence = retiredFormSentence(key, t);
41
+ TOMBSTONES.set(sentence, Object.freeze({ key, form: t, sentence }));
42
+ return z.never({ error: sentence }).optional();
43
+ }
44
+ /** 墓碑表(一份副本,按铸造次序)。 */
45
+ export function retiredKeyTable() {
46
+ return [...TOMBSTONES.values()];
47
+ }
48
+ /** 一条 zod issue 的 message 恰是某个墓碑的整句 ⇒ 那一行;否则 `undefined`。 */
49
+ export function retiredKeyOfSentence(message) {
50
+ return TOMBSTONES.get(message);
51
+ }
52
+ /**
53
+ * 墓碑在**读面**(终判扣下一行时的拒句)的说法:不点名那个旧字段的名字 —— 它就在盘上那一行里,结构化的坐标(`field`)已经指到它;
54
+ * 文字说现行位、迁移是一条出路,以及「迁移拒过这一行」的出路。读面是纯函数、无 I/O,不知道这一行跑没跑过迁移,所以两种情形都要成立:
55
+ * 没跑过 ⇒ 跑迁移;跑过且拒了(迁移不写回半成品,原行留在盘上)⇒ 手写现行形、删掉旧字段。
56
+ */
57
+ export function retiredKeyHeldSentence(t) {
58
+ return `a field retired in ${t.retiredIn} is still in the row — now ${t.now}; ${migrationSignpost(t.migration, t.retiredIn)}; if that migration already refused this row, write the current form by hand and delete the old field`;
36
59
  }
@@ -92,8 +92,8 @@ export declare const MIN_TRUST_WORDS: readonly ["local", "session", "project", "
92
92
  export declare const MinTrust: z.ZodEnum<{
93
93
  local: "local";
94
94
  session: "session";
95
- project: "project";
96
95
  global: "global";
96
+ project: "project";
97
97
  managed: "managed";
98
98
  task: "task";
99
99
  }>;
@@ -151,8 +151,8 @@ export declare const MemberAdmission: z.ZodObject<{
151
151
  minTrust: z.ZodOptional<z.ZodEnum<{
152
152
  local: "local";
153
153
  session: "session";
154
- project: "project";
155
154
  global: "global";
155
+ project: "project";
156
156
  managed: "managed";
157
157
  task: "task";
158
158
  }>>;
@@ -161,8 +161,8 @@ export declare const MemberAdmission: z.ZodObject<{
161
161
  minTrust: z.ZodEnum<{
162
162
  local: "local";
163
163
  session: "session";
164
- project: "project";
165
164
  global: "global";
165
+ project: "project";
166
166
  managed: "managed";
167
167
  task: "task";
168
168
  }>;
@@ -264,8 +264,8 @@ export declare const MergeCategorySpec: z.ZodObject<{
264
264
  minTrust: z.ZodEnum<{
265
265
  local: "local";
266
266
  session: "session";
267
- project: "project";
268
267
  global: "global";
268
+ project: "project";
269
269
  managed: "managed";
270
270
  task: "task";
271
271
  }>;
@@ -314,8 +314,8 @@ export declare const MergeCategorySpec: z.ZodObject<{
314
314
  minTrust: z.ZodOptional<z.ZodEnum<{
315
315
  local: "local";
316
316
  session: "session";
317
- project: "project";
318
317
  global: "global";
318
+ project: "project";
319
319
  managed: "managed";
320
320
  task: "task";
321
321
  }>>;
@@ -324,8 +324,8 @@ export declare const MergeCategorySpec: z.ZodObject<{
324
324
  minTrust: z.ZodEnum<{
325
325
  local: "local";
326
326
  session: "session";
327
- project: "project";
328
327
  global: "global";
328
+ project: "project";
329
329
  managed: "managed";
330
330
  task: "task";
331
331
  }>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 档位词(纯、零 I/O、浏览器安全;**包内叶子模块,不出包**):引擎的档序与 CC 档位别名、从某档起向低档的梯,与「一个目录词在档位展开之后落到哪条条目」。
3
+ * 本包里档位词的唯一一处:default 角色的档梯(`./default-model`)与写者计划助手的落点(`./env-model-table`)都从这里取。
4
+ * 镜像 core(`DEFAULT_TIER_ORDER` / `CC_MODEL_TIER_ALIASES` / `expandTiers`):`test/tier-words-parity-14.8.0` 对 devDependency 里精确钉住的那一版 core 对拍。
5
+ */
6
+ /** 引擎的档序(高 → 低)。 */
7
+ export declare const TIER_ORDER: readonly ["ultra", "max", "pro", "flash", "lite"];
8
+ /** 一个档位词。 */
9
+ export type TierWord = (typeof TIER_ORDER)[number];
10
+ /** CC 档位词 → 引擎档位。 */
11
+ export declare const CC_TIER_ALIASES: {
12
+ readonly fable: "max";
13
+ readonly mythos: "max";
14
+ readonly opus: "pro";
15
+ readonly sonnet: "flash";
16
+ readonly haiku: "lite";
17
+ };
18
+ /** 档序里从 `S` 起(含)向低档的那一段,字面元组型。 */
19
+ type LadderFrom<S extends TierWord, T extends readonly TierWord[] = typeof TIER_ORDER> = T extends readonly [infer H, ...infer R extends readonly TierWord[]] ? H extends S ? Readonly<T> : LadderFrom<S, R> : readonly [];
20
+ /** 一档的梯:档序里从它起(含)向低档的那一段(没绑的档沿它往下落)。 */
21
+ export declare function tierLadderFrom<S extends TierWord>(tier: S): LadderFrom<S>;
22
+ /**
23
+ * 一个目录词(条目名 / 档位词 / CC 档位别名 / `best`)在**档位展开之后**落到哪条目录条目名 —— 与引擎装配档位表的两步同答:
24
+ * 先摘掉活跃组里绑定目标不可服务的档(引擎的生效装配在交给 core 之前做这一步,并 warn 点名),再按 core `expandTiers` 展开
25
+ * (对拍 devDependency 那一版 core 的「摘后表」)。所以这里只有一条绑定规则:**一档的绑定只在它点名一条可服务条目时算数**,否则当作没绑。展开规则:
26
+ * · 目录自己的键先赢(`has(word)`;一条就叫 `flash` 的条目遮住档位词);
27
+ * · 档位词 ⇒ 它的梯({@link tierLadderFrom})上第一个有绑定的档;摘完没有任何绑定 ⇒ 只认目录键;
28
+ * · CC 档位别名 ⇒ 经它对应的档位;`best` ⇒ 档序里第一个有绑定的档;别的词 ⇒ 落不到。
29
+ * `has` = 调用方的「这个名是目录里一条可服务(启用)的条目」;`tiers` = `resolveActiveTiers` 的读数(原表,未摘)—— 它的键只有五个档位词
30
+ * (档位组的绑定表在 schema 里只收这五个键,别的键被剥掉),所以只按档位词读表。
31
+ */
32
+ export declare function landCatalogWord(word: string, has: (name: string) => boolean, tiers: Readonly<Partial<Record<TierWord, string>>> | undefined): string | undefined;
33
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 档位词(纯、零 I/O、浏览器安全;**包内叶子模块,不出包**):引擎的档序与 CC 档位别名、从某档起向低档的梯,与「一个目录词在档位展开之后落到哪条条目」。
3
+ * 本包里档位词的唯一一处:default 角色的档梯(`./default-model`)与写者计划助手的落点(`./env-model-table`)都从这里取。
4
+ * 镜像 core(`DEFAULT_TIER_ORDER` / `CC_MODEL_TIER_ALIASES` / `expandTiers`):`test/tier-words-parity-14.8.0` 对 devDependency 里精确钉住的那一版 core 对拍。
5
+ */
6
+ /** 引擎的档序(高 → 低)。 */
7
+ export const TIER_ORDER = ["ultra", "max", "pro", "flash", "lite"];
8
+ /** CC 档位词 → 引擎档位。 */
9
+ export const CC_TIER_ALIASES = { fable: "max", mythos: "max", opus: "pro", sonnet: "flash", haiku: "lite" };
10
+ /** 一档的梯:档序里从它起(含)向低档的那一段(没绑的档沿它往下落)。 */
11
+ export function tierLadderFrom(tier) {
12
+ return TIER_ORDER.slice(TIER_ORDER.indexOf(tier));
13
+ }
14
+ const isTierWord = (word) => TIER_ORDER.includes(word);
15
+ /**
16
+ * 一个目录词(条目名 / 档位词 / CC 档位别名 / `best`)在**档位展开之后**落到哪条目录条目名 —— 与引擎装配档位表的两步同答:
17
+ * 先摘掉活跃组里绑定目标不可服务的档(引擎的生效装配在交给 core 之前做这一步,并 warn 点名),再按 core `expandTiers` 展开
18
+ * (对拍 devDependency 那一版 core 的「摘后表」)。所以这里只有一条绑定规则:**一档的绑定只在它点名一条可服务条目时算数**,否则当作没绑。展开规则:
19
+ * · 目录自己的键先赢(`has(word)`;一条就叫 `flash` 的条目遮住档位词);
20
+ * · 档位词 ⇒ 它的梯({@link tierLadderFrom})上第一个有绑定的档;摘完没有任何绑定 ⇒ 只认目录键;
21
+ * · CC 档位别名 ⇒ 经它对应的档位;`best` ⇒ 档序里第一个有绑定的档;别的词 ⇒ 落不到。
22
+ * `has` = 调用方的「这个名是目录里一条可服务(启用)的条目」;`tiers` = `resolveActiveTiers` 的读数(原表,未摘)—— 它的键只有五个档位词
23
+ * (档位组的绑定表在 schema 里只收这五个键,别的键被剥掉),所以只按档位词读表。
24
+ */
25
+ export function landCatalogWord(word, has, tiers) {
26
+ if (has(word))
27
+ return word;
28
+ if (tiers === undefined)
29
+ return undefined;
30
+ /** 一档的绑定:只在它点名一条可服务条目时算数(否则引擎已摘掉它)。 */
31
+ const bound = (tier) => {
32
+ const target = Object.hasOwn(tiers, tier) ? tiers[tier] : undefined;
33
+ return target !== undefined && has(target) ? target : undefined;
34
+ };
35
+ const firstBound = (ladder) => ladder.map(bound).find((landed) => landed !== undefined);
36
+ if (isTierWord(word))
37
+ return firstBound(tierLadderFrom(word));
38
+ if (Object.hasOwn(CC_TIER_ALIASES, word))
39
+ return firstBound(tierLadderFrom(CC_TIER_ALIASES[word]));
40
+ if (word === "best")
41
+ return firstBound(TIER_ORDER);
42
+ return undefined;
43
+ }
package/dist/types.d.ts CHANGED
@@ -1885,6 +1885,8 @@ export declare const McpServerSpec: z.ZodObject<{
1885
1885
  local: "local";
1886
1886
  execution: "execution";
1887
1887
  }>>;
1888
+ sessionMetaKey: z.ZodOptional<z.ZodUnknown>;
1889
+ startupTimeoutMs: z.ZodOptional<z.ZodUnknown>;
1888
1890
  source: z.ZodOptional<z.ZodUnknown>;
1889
1891
  toolFaces: z.ZodOptional<z.ZodUnknown>;
1890
1892
  toolAxes: z.ZodOptional<z.ZodUnknown>;
@@ -1918,6 +1920,8 @@ export declare const McpConfig: z.ZodObject<{
1918
1920
  local: "local";
1919
1921
  execution: "execution";
1920
1922
  }>>;
1923
+ sessionMetaKey: z.ZodOptional<z.ZodUnknown>;
1924
+ startupTimeoutMs: z.ZodOptional<z.ZodUnknown>;
1921
1925
  source: z.ZodOptional<z.ZodUnknown>;
1922
1926
  toolFaces: z.ZodOptional<z.ZodUnknown>;
1923
1927
  toolAxes: z.ZodOptional<z.ZodUnknown>;
@@ -2520,6 +2524,8 @@ export declare const DOMAIN_SCHEMAS: {
2520
2524
  local: "local";
2521
2525
  execution: "execution";
2522
2526
  }>>;
2527
+ sessionMetaKey: z.ZodOptional<z.ZodUnknown>;
2528
+ startupTimeoutMs: z.ZodOptional<z.ZodUnknown>;
2523
2529
  source: z.ZodOptional<z.ZodUnknown>;
2524
2530
  toolFaces: z.ZodOptional<z.ZodUnknown>;
2525
2531
  toolAxes: z.ZodOptional<z.ZodUnknown>;
package/dist/types.js CHANGED
@@ -30,7 +30,7 @@ import { PREVIOUS_WEB_ADMIN_ENV } from "./previous-web-admin-env.js"; // the pre
30
30
  import { retiredEnvRefusal } from "./retired-envs.js"; // the worker env write gate asks the retired-env tombstone table too (one table, one refusal sentence)
31
31
  import { Sha256Digest } from "./sha256.js"; // the one `sha256:<hex>` digest form
32
32
  import { EXECUTION_LANES, EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS, retiredLaneWordRefusal } from "./execution-lanes.js"; // 7.0.0 M-R2: the lane / store vocabulary (one exported table)
33
- import { LANE_ENV_OWNER, isMigratableEnvKey, laneEnvSeat, laneRowOf } from "./execution-lane-index.js"; // package-private indexes derived from that table (not in the barrel)
33
+ import { LANE_ENV_OWNER, isBlankCoordinate, isMigratableEnvKey, laneEnvSeat, laneRowOf } from "./execution-lane-index.js"; // package-private indexes derived from that table (not in the barrel)
34
34
  import { migrationSignpost, retiredFormSentence, retiredKey } from "./retired-form.js"; // 11.0.0: the ONE retired-form sentence + key tombstone (package-private)
35
35
  import { retiredRoleWordsRefusal } from "./retired-role-words.js"; // 12.0.0: the retired role-word tombstone (package-private; SS-M8)
36
36
  import { buildQuotedIssue, fixed } from "./quoted-issue.js"; // THE minting point of custom issues (package-private)
@@ -456,6 +456,20 @@ export const PARSE_TRANSPARENT_KEYS = [
456
456
  validatedBy: "consumer",
457
457
  why: "它的形由同一条 override 的 `key` 决定(一个 key 一种形),执法点在读那个 key 的消费方;在这里写死形等于把所有可覆盖键的形表搬进本包。",
458
458
  },
459
+ {
460
+ domain: "mcp",
461
+ path: "servers[].sessionMetaKey",
462
+ since: "14.8.0",
463
+ validatedBy: "engine-ingest",
464
+ why: "引擎在发给这台服务器的请求 `_meta` 里放发送腿会话 id 的那把键;它只有一只判官 = 引擎根导出 `judgeSessionMetaKey`(类型、非空、上界、MCP `_meta` 键名文法、MCP 保留前缀、`progressToken`、引擎的 `sema/` 命名空间一并判;坏键在引擎的准备门整条拒、点名这把键),投影它的消费方在交给引擎之前可调同一只;本包只承载 —— 这里哪怕只判类型与长度,也是第二只判官的一部分,与引擎那只各答各的。",
465
+ },
466
+ {
467
+ domain: "mcp",
468
+ path: "servers[].startupTimeoutMs",
469
+ since: "14.8.0",
470
+ validatedBy: "engine-ingest",
471
+ why: "引擎的逐服务器启动界(握手与首次工具列举各带它);它只有一只判官 = 引擎根导出的 `mcpStartupTimeoutMs`(是不是数、整数性、范围一并判;坏值在引擎的拨号门拒这一台服务器、点名 `config.mcp_startup_timeout_invalid`,其余服务器照挂),投影它的消费方在交给引擎之前可调同一只;本包只承载 —— 这里哪怕只判「是不是数」,也是第二只判官的一部分,与引擎那只各答各的。",
472
+ },
459
473
  ];
460
474
  /** A catalog model's price / capability tier — fuels core 1.25 `selectModel({ tier })` role resolution. ONE closed set for both
461
475
  * places that name it (`ModelEntry.tier`, `RoleTarget.select.tier`). Not the tier-group ladder of {@link TierBindings}
@@ -1875,8 +1889,8 @@ const nonSecretLiteral = (minLength) => z
1875
1889
  /**
1876
1890
  * `runtime.execution` —— 这台 worker 的执行车道(渲染:`REMOTE_EXEC=<lane>` + `REQUIRE_EXECUTION_LANE=true` + coords / secretRefs 逐键)。
1877
1891
  * - `lane`:{@link EXECUTION_LANE_WORDS} 之一。没有「无手」词。
1878
- * - `coords`:该车道的**非密坐标**(服务变量名 → 字面值),键 ⊆ 表[lane] 的 `requiredEnv ∪ optionalEnv`;必需键在场且非空串
1879
- * (引擎把空串读作未设)。可选键的空串照收(例:`K8S_RUNTIME_CLASS=""` 在引擎里是「不用 Kata」)。
1892
+ * - `coords`:该车道的**非密坐标**(服务变量名 → 字面值),键 ⊆ 表[lane] 的 `requiredEnv ∪ optionalEnv`;必需键在场且不是空白
1893
+ * (空串或只含空白按缺 —— 唯一判官 `isBlankCoordinate`)。可选键的空串照收(例:`K8S_RUNTIME_CLASS=""` 在引擎里是「不用 Kata」)。
1880
1894
  * - `secretRefs`:该车道的**密钥**(服务变量名 → 宿主 env-NAME),键 ⊆ 表[lane] 的 `requiredSecretEnv ∪ optionalSecretEnv`,必需键在场。
1881
1895
  * 别车道的键 ⇒ 拒并点名它属于哪条车道;本车道的密钥写进 coords / 坐标写进 secretRefs ⇒ 拒并指路另一栏。
1882
1896
  */
@@ -1911,8 +1925,8 @@ const ExecutionBlock = z
1911
1925
  return true;
1912
1926
  };
1913
1927
  for (const [key, value] of Object.entries(x.coords)) {
1914
- if (judgeKey("coords", key) && value === "" && LANE_ENV_OWNER.get(key)?.required === true) {
1915
- issue("coords", key, buildQuotedIssue `required coordinate ${key} of lane "${x.lane}" is empty — the engine reads an empty value as unset and refuses to start`);
1928
+ if (judgeKey("coords", key) && isBlankCoordinate(value) && LANE_ENV_OWNER.get(key)?.required === true) {
1929
+ issue("coords", key, buildQuotedIssue `required coordinate ${key} of lane "${x.lane}" is empty or whitespace-only — it counts as absent, and the engine refuses to start without it`);
1916
1930
  }
1917
1931
  }
1918
1932
  for (const key of Object.keys(x.secretRefs))
@@ -2486,6 +2500,16 @@ export const McpServerSpec = z.object({
2486
2500
  * declared `"local"`, the engine read ABSENT. A value outside the table is REJECTED at parse — a mistyped trust
2487
2501
  * word must never be read as a legal one (the consumer's gate is equally severe: no case-folding). */
2488
2502
  contentOrigin: z.enum(MCP_CONTENT_ORIGINS).optional(),
2503
+ /** 引擎在发给这台服务器的每个 `tools/call` / `resources/read` 的 `params._meta[key]` 里放发送腿自己的会话 id 的那把键 —— 一一对应引擎的
2504
+ * `McpServerSpec.sessionMetaKey`。缺席 = 什么都不送(`.optional()`,不铸默认:会话 id 出进程是部署自己选的关联)。
2505
+ *
2506
+ * **解析透明** —— 形归谁校验、为什么不放这里:{@link PARSE_TRANSPARENT_KEYS}(`mcp.servers[].sessionMetaKey`)。 */
2507
+ sessionMetaKey: z.unknown().optional(),
2508
+ /** 这一台服务器自己的启动界(整数毫秒):握手(连接 + initialize)与首次工具列举各带它 —— 一一对应引擎的 `McpServerSpec.startupTimeoutMs`。
2509
+ * 缺席 = 引擎读进程级的缺省界(`.optional()`,不铸默认:没声明就是没声明);它只界启动阶段,不动单次调用的超时。
2510
+ *
2511
+ * **解析透明** —— 形归谁校验、为什么不放这里:{@link PARSE_TRANSPARENT_KEYS}(`mcp.servers[].startupTimeoutMs`)。 */
2512
+ startupTimeoutMs: z.unknown().optional(),
2489
2513
  /** Declaration-plane GROUPING LABEL for this server — maps 1:1 onto the engine's `McpServerSpec.source`
2490
2514
  * (core ≥7.5.0 echoes it verbatim on `wiring_manifest.mcp[].source`, single-line + 80-char cap, so a
2491
2515
  * consumer can group servers by where they were declared). An OPEN set: CC's `plugin` / `user` /
@@ -10,6 +10,7 @@ import { formatConfigPath, outgoingPath } from "./config-fns.js";
10
10
  import { issuePath } from "./issue-path.js";
11
11
  import { guardedSafeParse } from "./own-key-read.js";
12
12
  import { jsonShapeOf } from "./plain-json-object.js";
13
+ import { retiredKeyHeldSentence, retiredKeyOfSentence } from "./retired-form.js";
13
14
  import { WorkersConfig } from "./types.js";
14
15
  /** 每一形的拒句主语 —— 穷举映射型:加一形不补这里即编译红。 */
15
16
  const SUBJECT = {
@@ -63,6 +64,11 @@ export function safeIssueMessage(issue) {
63
64
  function rowRelative(path) {
64
65
  return path[0] === "workers" && path[1] === 0 ? path.slice(2) : path;
65
66
  }
67
+ /** 一条 issue 在拒句里的样子:`<坐标>: <句子>`;墓碑 issue(旧形字段还在盘上)换成读面的说法 —— 坐标就是退役键名,拒句的文字里不点名它(结构化的 `field` 已经指到它),指现行位与出路。 */
68
+ function issueText(i) {
69
+ const tombstone = retiredKeyOfSentence(i.message);
70
+ return tombstone !== undefined ? retiredKeyHeldSentence(tombstone.form) : `${formatConfigPath(outgoingPath(rowRelative(issuePath(i))))}: ${i.message}`;
71
+ }
66
72
  /** 一行的终判:读得进 ⇒ `undefined`;读不进 ⇒ 第一条 issue 的路径(相对该行)+ 拒句(见文件头)。 */
67
73
  export function workerRowVerdict(row, form) {
68
74
  // 校验过程抛(会抛的 getter / 陷阱:读侧交进来的是调用方的活对象;zod 4.6 首读 `error` 时拼句子抛 —— 被污染的运行时、另一 realm 的怪形值)⇒ 这一行判「读不出」
@@ -74,7 +80,7 @@ export function workerRowVerdict(row, form) {
74
80
  if (verdict.success)
75
81
  return undefined;
76
82
  const issues = verdict.error.issues;
77
- const shown = issues.slice(0, 3).map((i) => `${formatConfigPath(outgoingPath(rowRelative(issuePath(i))))}: ${i.message}`);
83
+ const shown = issues.slice(0, 3).map(issueText);
78
84
  const more = issues.length > shown.length ? ` (+${issues.length - shown.length} more)` : "";
79
85
  const first = issues[0];
80
86
  return { field: first === undefined ? [] : rowRelative(issuePath(first)), reason: `${SUBJECT[form]} is not a valid WorkerSpec — ${shown.join("; ")}${more}` };