@sema-agent/settings-schema 13.0.0 → 14.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 (141) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +20 -2
  3. package/conformance/VERSION +1 -1
  4. package/conformance/baseline.json +1 -3
  5. package/conformance/cases/c01-entry-salvage-then-typo/expected.json +1 -3
  6. package/conformance/cases/c02-hosts-clamp-stacked/expected.json +1 -3
  7. package/conformance/cases/c03-literal-dotted-top-key/expected.json +1 -3
  8. package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +1 -3
  9. package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +1 -3
  10. package/conformance/cases/c08-limits-unknown-carried-64/expected.json +1 -3
  11. package/conformance/cases/c09-limits-unknown-carried-65/expected.json +1 -3
  12. package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +1 -3
  13. package/conformance/cases/c11-unicode-unknown-keys/expected.json +1 -3
  14. package/conformance/cases/c12-prototype-name-top-keys/expected.json +1 -3
  15. package/conformance/cases/c13-models-field-null/expected.json +1 -3
  16. package/conformance/cases/c14-models-field-empty-array/expected.json +1 -3
  17. package/conformance/cases/c15-models-field-number/expected.json +1 -3
  18. package/conformance/cases/c16-deep-nesting-205/expected.json +1 -3
  19. package/conformance/cases/c17-empty-domain-file/expected.json +1 -3
  20. package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +1 -3
  21. package/conformance/cases/c19-wire-envelope-no-version/expected.json +1 -3
  22. package/conformance/cases/c21-governance-empty-object-wins/expected.json +1 -3
  23. package/conformance/cases/c23-mixed-validity-models/expected.json +1 -3
  24. package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +1 -3
  25. package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +1 -3
  26. package/conformance/cases/c26-hosts-array-1024/expected.json +1 -3
  27. package/conformance/cases/c27-hosts-array-1025/expected.json +1 -3
  28. package/conformance/cases/c28-large-document-over-1mb/expected.json +1 -3
  29. package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +1 -3
  30. package/conformance/cases/c30-version-triple-a-first/expected.json +1 -3
  31. package/conformance/cases/c31-version-triple-b-changed/expected.json +1 -3
  32. package/conformance/cases/c32-version-triple-a-restored/expected.json +1 -3
  33. package/conformance/cases/c33-limits-nested-carried-key/expected.json +1 -3
  34. package/conformance/cases/c34-skills-content-vs-manifest/expected.json +1 -3
  35. package/conformance/cases/c35-portable-domain-file-is-null/expected.json +1 -3
  36. package/conformance/cases/c36-skills-entry-without-content/expected.json +1 -3
  37. package/conformance/cases/c37-skills-field-null/expected.json +1 -3
  38. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +1 -3
  39. package/conformance/cases/c39-hidden-file-in-config-d/expected.json +1 -3
  40. package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +1 -3
  41. package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +1 -3
  42. package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +1 -3
  43. package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +1 -3
  44. package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +1 -3
  45. package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +1 -3
  46. package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/expected.json +1 -3
  47. package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/expected.json +1 -3
  48. package/conformance/cases/c48-s935-model-headers-transported/expected.json +1 -3
  49. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +1 -3
  50. package/conformance/cases/c50-s784-lane-host-legal/expected.json +1 -3
  51. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +1 -3
  52. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +1 -3
  53. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +1 -3
  54. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +1 -3
  55. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +1 -3
  56. package/conformance/cases/c56-s784-lane-device-legal/expected.json +1 -3
  57. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +1 -3
  58. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +1 -3
  59. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +1 -3
  60. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +1 -3
  61. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +1 -3
  62. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +1 -3
  63. package/conformance/cases/c63-s784-host-two-replicas/expected.json +1 -3
  64. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +1 -3
  65. package/conformance/cases/c65-s784-pg-without-db/expected.json +1 -3
  66. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +1 -3
  67. package/conformance/cases/c67-s784-retired-field-loud/expected.json +1 -3
  68. package/conformance/cases/c68-s784-device-local-store/expected.json +1 -3
  69. package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +1 -3
  70. package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +1 -3
  71. package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +1 -3
  72. package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +1 -3
  73. package/conformance/cases/c73-projects-extended-registration-reads/expected.json +1 -3
  74. package/conformance/cases/c74-projects-over-bound-refused/expected.json +1 -3
  75. package/conformance/cases/c75-projects-patch-remote-uniqueness/expected.json +1 -3
  76. package/conformance/cases/c76-projects-entry-salvage-nested/expected.json +1 -3
  77. package/conformance/cases/c77-prompts-center-artifact-carried/expected.json +252 -0
  78. package/conformance/cases/c77-prompts-center-artifact-carried/input.json +93 -0
  79. package/conformance/cases/c77-prompts-center-artifact-carried/meta.json +5 -0
  80. package/conformance/cases/c78-prompts-empty-bindings-refused/expected.json +82 -0
  81. package/conformance/cases/c78-prompts-empty-bindings-refused/input.json +11 -0
  82. package/conformance/cases/c78-prompts-empty-bindings-refused/meta.json +5 -0
  83. package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/expected.json +82 -0
  84. package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/input.json +129 -0
  85. package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/meta.json +5 -0
  86. package/conformance/cases/c80-prompts-wire-v2-verbatim/expected.json +195 -0
  87. package/conformance/cases/c80-prompts-wire-v2-verbatim/input.json +65 -0
  88. package/conformance/cases/c80-prompts-wire-v2-verbatim/meta.json +5 -0
  89. package/conformance/cases/c81-prompts-declaration-bindings-refused/expected.json +82 -0
  90. package/conformance/cases/c81-prompts-declaration-bindings-refused/input.json +23 -0
  91. package/conformance/cases/c81-prompts-declaration-bindings-refused/meta.json +5 -0
  92. package/conformance/cases/c82-prompts-v1-catalog-refused/expected.json +82 -0
  93. package/conformance/cases/c82-prompts-v1-catalog-refused/input.json +35 -0
  94. package/conformance/cases/c82-prompts-v1-catalog-refused/meta.json +5 -0
  95. package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/expected.json +82 -0
  96. package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/input.json +56 -0
  97. package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/meta.json +5 -0
  98. package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +1 -3
  99. package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +1 -3
  100. package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +1 -3
  101. package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +1 -3
  102. package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +1 -3
  103. package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +1 -3
  104. package/conformance/cases/cli04-env-value-with-newline/expected.json +1 -3
  105. package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +1 -3
  106. package/conformance/cases/t01-absent-domain-key-not-null/expected.json +1 -3
  107. package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +1 -3
  108. package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +1 -3
  109. package/conformance/cases/z01-every-domain-empty-object/expected.json +2 -6
  110. package/conformance/cases/z02-every-domain-unknown-key/expected.json +2 -6
  111. package/dist/config-fns.d.ts +7 -3
  112. package/dist/config-fns.js +19 -16
  113. package/dist/conformance/derive.js +2 -2
  114. package/dist/hooks-config.d.ts +9 -1
  115. package/dist/hooks-config.js +23 -15
  116. package/dist/index.d.ts +1 -0
  117. package/dist/index.js +1 -0
  118. package/dist/mcp-request-entry.d.ts +93 -0
  119. package/dist/mcp-request-entry.js +157 -0
  120. package/dist/migration-receipt.d.ts +1 -1
  121. package/dist/migration-receipt.js +1 -1
  122. package/dist/node.d.ts +0 -1
  123. package/dist/node.js +0 -1
  124. package/dist/one-time-migrations.d.ts +1 -1
  125. package/dist/one-time-migrations.js +39 -0
  126. package/dist/safety-merge-spec.d.ts +6 -6
  127. package/dist/types.d.ts +438 -355
  128. package/dist/types.js +161 -136
  129. package/dist/url-shape.d.ts +8 -0
  130. package/dist/url-shape.js +10 -0
  131. package/docs/API-SURFACE.md +1409 -0
  132. package/docs/CC-DIVERGENCE.md +42 -0
  133. package/docs/CONFORMANCE.md +344 -0
  134. package/docs/CONSUMER-INTEGRATION.md +283 -0
  135. package/docs/openapi-auth.yaml +357 -0
  136. package/docs/openapi-scopes.yaml +281 -0
  137. package/llms-full.txt +659 -0
  138. package/llms.txt +179 -0
  139. package/package.json +5 -2
  140. package/dist/prompts-digest.d.ts +0 -16
  141. package/dist/prompts-digest.js +0 -49
package/llms-full.txt ADDED
@@ -0,0 +1,659 @@
1
+ <!--
2
+ llms-full.txt — 自包含全文 · 消费方(使用者)视角 · @sema-agent/settings-schema
3
+ 重写日期: 2026-10-17 | 包版本: 14.0.0
4
+ 生成方式: 无生成脚本——按当前树 src/*.ts 导出面 + package.json + README/CHANGELOG 手写事实版。
5
+ 证据约定: 每条"它怎么工作"的断言附 (文件: 符号) 坐标,刻意不用行号(行号逐版腐烂;符号名由
6
+ typecheck 与 test/package-surface.test.ts、test/docs-hygiene.test.ts 等常驻门钉住)。无证据推断显式标【推断】。
7
+ 刷新触发: package.json 版号变更(test/docs-hygiene.test.ts D 组把「包版本:」钉到 package.json)。
8
+ 读者假设: 你只有这个文件、没有仓库访问权,也要能正确 import、调用、不踩陷阱。术语/标识符保留原文。
9
+ -->
10
+
11
+ # @sema-agent/settings-schema(全文)
12
+
13
+ ================================================================================
14
+ §1 定位与现状
15
+ ================================================================================
16
+
17
+ 这是什么:一个**纯配置契约 + resolver + 本地 loader** 的 npm 包,sema 栈配置域的"单一真值",被三方共同
18
+ 消费:web-admin(舰队管理台)、worker 服务(引擎侧)、TOC 桌面/CLI。
19
+ (package.json: description;README 顶部)
20
+
21
+ 它解决什么:让 fleet(从 web-admin HTTP 拉)与单机(读本地文件)**用同一份 schema + 同一个 resolver**,两边
22
+ 产出的 `EffectiveConfig` 不可能 drift;同时把跨仓共享的五族纯契约(hooks / fleet / scheduler-store /
23
+ safety-merge-spec / auth·scopes wire)固化成一个发布物,谁都不许再抄第二份。(README;CLAUDE.md 定位段)
24
+
25
+ 纯性:零 next/react/server import、零 DB 驱动;唯一运行时依赖 `zod`(dependencies + peerDependencies 双声明
26
+ `^4.4.0`(11.0.0 起)——deps 保独立可装,peer 声明宿主同段:zod-4 宿主(server / CLI / client-core / sdk)里 dedup 成单实例;
27
+ 仍在 zod 3 的宿主(管理面,升级前)peer 不满足,本包的 zod 4 在它树里嵌套安装 = 两实例,跨缝只传纯数据、不混 schema 对象);只用 node 内建,且仅 `./node` 子路径拉
28
+ `node:fs`/`node:path`,主 barrel 浏览器安全。`npm ls --prod` 恒只有 zod。devDependencies 里的
29
+ `@sema-agent/core` 只服务 dev-only conformance 测试,**不是**运行时依赖。(package.json;CLAUDE.md 纯度段)
30
+
31
+ 版本与改名:现版见 package.json(本档头部「包版本」行与之相等,由测试钉住)。`1.0.0` 起包名
32
+ `@sema-agent/settings-schema`;≤`0.19.0` 为旧名 `@sema-agent/registry-core`(已冻结)。1.0.0 是**纯改名**:
33
+ 零字段增删、零校验变更、出口表逐条 1:1 平移。发版纪律见 §7。(README 改名声明;CHANGELOG 1.0.0 段)
34
+
35
+ ================================================================================
36
+ §2 安装与引入
37
+ ================================================================================
38
+
39
+ registry:公网 npmjs,无需私有 registry 配置。
40
+
41
+ 安装:
42
+ npm install @sema-agent/settings-schema zod
43
+ (zod 是 peer + dep;zod-4 宿主装同段 `^4.4.0` 即单实例,zod-3 宿主里是两实例,见上;Node `>=20.3.0`;`type:"module"` 纯 ESM,无 CJS 入口。)
44
+
45
+ 最小可用示例 A —— 纯契约(浏览器安全,主 barrel):
46
+ import { DOMAIN_SCHEMAS, isDomainName, buildEffective, resolveEffectiveForWorker } from "@sema-agent/settings-schema";
47
+ const models = DOMAIN_SCHEMAS.models.parse({ models: [{ name: "m1", id: "GW-ID" }] });
48
+
49
+ 最小可用示例 B —— 本地文件加载(Node only,`./node` 子路径):
50
+ import { loadLocalConfig } from "@sema-agent/settings-schema/node";
51
+ const { effective, remoteExec, env, requiredEnv, warnings } = await loadLocalConfig("/path/to/root");
52
+ (root 下需有 `config.d/*.json` + 可选 `config.d/remote-exec.json` + 可选 `.env`;见 §5/§9。)
53
+
54
+ ================================================================================
55
+ §3 核心概念与心智模型
56
+ ================================================================================
57
+
58
+ 3.1 一份契约,两个源,一个形状
59
+ - 源①:web-admin HTTP `GET /effective[?worker=]`(web-admin 实现,本包不含 HTTP)。
60
+ - 源②:本地 `config.d/*.json`(`FileConfigStore` / `loadLocalConfig`)。
61
+ - 两者收敛到 `EffectiveConfig`(本地全量)或 `EffectiveWire`(远程线缆形)。
62
+
63
+ 3.2 EffectiveConfig ≠ EffectiveWire(最常被搞错,见 §8 T1)
64
+ - `EffectiveConfig`:全 16 域,skills 带全文 content 内联,rosters/entitlement 不清空。本地
65
+ `loadLocalConfig().effective` 就是它。(src/types.ts: `EffectiveConfig`)
66
+ - `EffectiveWire = Omit<EffectiveConfig,"skills"|"prompts"> & { skills: SkillsManifest; prompts?: unknown }`:
67
+ skills 的 content 换成 contentHash(manifest);wire 整形时 rosters/entitlement 被清空(隐私/收窄);`prompts` 在 wire 上是
68
+ `EffectivePromptsWire`(`{schemaVersion: 2, artifact}`,中心为拉取方选出的那一份、盖上发布出处),读腿按在场性原样承运、由消费方判。这是
69
+ 远程 worker 真正收到的。(src/types.ts: `EffectiveWire`;src/resolve-roster.ts)
70
+ - 转换发生在 `resolveEffectiveForWorker`(纯函数);本地路径不经过它,拿到的是全文 EffectiveConfig。
71
+
72
+ 3.3 数据流
73
+ - `buildEffective(version, getRaw, updatedAt, onWarning?)` → 逐域 `parseDomainLoud` 填默认,返回合并后的
74
+ `EffectiveConfig`;剥键/承运/降级/未读文件走 `onWarning`(§4.2)。(src/config-fns.ts: `buildEffective`)
75
+ - `readEffectiveWire(raw, onWarning?)` → **消费端边界**对 wire 载荷的容错读(逐域同源判据;未知顶层键
76
+ open-world 透传)。(src/config-fns.ts: `readEffectiveWire`)
77
+ - `resolveEffectiveForWorker(eff, workerName)` → `{ config: EffectiveWire, rosterLabel, etag }`。无 roster /
78
+ 悬空 → `global(fallback)`,etag=`"v<version>"`;有 roster → models 收窄、etag=内容 hash(不含
79
+ version/updatedAt)。(src/resolve-roster.ts)
80
+ - `loadLocalConfig(root, {onWarning}?)` → `{ effective, remoteExec, env, requiredEnv, warnings }`。
81
+ (src/local-load.ts: `LocalConfig`)
82
+
83
+ 3.4 secret 心智:本包只存 env-NAME,绝不存值。值由消费者从自己的 env / k8s secret / 本地 `.env` 解析。
84
+
85
+ ================================================================================
86
+ §4 公共 API / 对外契约
87
+ ================================================================================
88
+
89
+ 以下均为真实导出(主 barrel `.`、`./node` 或细分 subpath;见 §6)。签名/字段名核实自源码导出面。
90
+
91
+ --- 4.1 域 schema 与类型(src/types.ts,主 barrel / `./types`) ---
92
+ - `DOMAIN_SCHEMAS`:16 个配置域 → zod 对象:`{ models, rosters, skills, mcp, plugins, scenarios, systems,
93
+ collab, workers, hosts, governance, entitlement, execution, projects, prompts, limits }`。`runtime` 域 11.0.0 起 retired:
94
+ 墓碑 `RETIRED_DOMAINS` —— `parseDomain` / `buildEffective` / `readEffectiveWire` / `FileConfigStore` 遇带事实的 runtime 文档一律抛
95
+ `RetiredDomainError`(拒句点名一次性迁移 SS-M1),缺席或 `{}` 不理会。
96
+ `DOMAINS`(声明顺序)、`isDomainName(x)`;`"remote-exec"` **不是**域(独立契约,§4.6)。
97
+ - 各域要点(schema 同名类型 z.infer):
98
+ · models:`ModelEntry[]`(provider↔api 配对强制;`sealedApiKey` 密钥托管形;`autoCompactTokens`/
99
+ `charsPerToken`/`promptGuidance`;11.0.0 起 `sealedApiKey` 与 `apiKeyEnv` 二选一,同设拒)+ `roles`(`ModelRole → RoleTarget`;缺省模型唯一位 `roles.default`,11.0.0 起 `models.default` 墓碑)+
100
+ `atMention`(11.0.0 取代 `atModelAllowlist`:`{mode:"all"}` 缺省 / `{mode:"none"}` / `{mode:"only",models}`;roster 位缺席 = 继承;
101
+ 收窄读法一只 `narrowAtMention`;旧键是墓碑字段,在场即拒)+
102
+ 档位组 `tierGroups`/`activeTierGroup`(`resolveActiveTiers` 解析)。
103
+ 能力位 `reasoning?` / `vision?` 三态(S-1625):缺席 = 未声明,不再被填成 false;读者按自己的缺省。
104
+ --- 1.10.0:`ModelEntry.compat?`(`z.unknown().optional()` 解析透明,无默认;镜像引擎 `OpenAICompletionsCompat` 五键 `thinkingFormat`/`reasoningEffortLevels`/`maxTokensField`/`requiresReasoningContentOnAssistantMessages`/`supportsReasoningEffort`;形与词表校验归消费方摄入侧单点,登记在 `PARSE_TRANSPARENT_KEYS`——那张词表的属主是引擎,本包复述一份必漂;缺席不铸键)——真需求 = vLLM/Qwen 类缺省开思考的网关(`chat_template_kwargs.enable_thinking=false`),`extraBody` 被引擎保留键表拦下;唯一解析拒 = 与 `extraBody` 共用的 `vetFreeFormJson` 安全判据(secret 形键/值、访问器、循环、非 JSON、深于 `MAX_EXTRA_BODY_DEPTH`) ---
105
+ --- 6.4.0:`ModelEntry.headers?`(`z.unknown().optional()` 解析透明,无默认;与 `compat` 同一只变换 —— 缺席不铸键,其余只过与 `compat`/`extraBody` 共用的 `vetFreeFormJson` 安全判据,拒句同形只换字段名;登记在 `PARSE_TRANSPARENT_KEYS`,`validatedBy: "consumer"`)——部署 ↔ 供应商的 per-model 附加请求头,例 `{"anthropic-beta":"context-1m-2025-08-07"}`(开 1M 上下文);头名闭集白名单 × 读它的线路 × 值形的判据**唯一属主是 sema-server**(坏声明 ⇒ 该条目整条不可用,不是丢头照常),本包刻意不做形校验(字符串 / 数组 / 大小写不规范的名 / 白名单外的名一律逐字过,由 server 判)。修前(≤6.3.0)每条读腿都剥它并报 `unknown-keys-dropped` ---
106
+ · rosters:`RosterEntry[]`(`primaryModel`/`cheapModel` 标记角色;`ROSTER_PRIMARY_ROLES`/`ROSTER_CHEAP_ROLES`)。
107
+ · skills:`SkillSpec[]`(content 全文;wire 转 manifest 见 §4.5)。
108
+ · mcp:`McpServerSpec[]` —— `transport` 判别 stdio(command/args/envRefs)| http(url/headerRefs/
109
+ principalHeader/assertionHeader;后两者互斥);`allowTools?`、`scenarios`/`rosters` 过滤、`elicitation`(默认 false,inbound elicitation
110
+ 的 per-server opt-in)、`contentOrigin?`(信任出处声明,闭词表 `MCP_CONTENT_ORIGINS =
111
+ --- 12.2.0:`McpServerSpec.toolAxes?` / `authForm?` / `entryKind?`(三键均 `z.unknown().optional()` 解析透明,登记在 `PARSE_TRANSPARENT_KEYS`(`authForm` / `entryKind` `validatedBy: "consumer"`;`toolAxes` 自 13.0.0 起 `"engine-ingest"` —— 形判官只有引擎根导出 `readMcpToolAxes` 一只,消费方两腿都调它);连接器条目的声明键,词表与语义归读它的消费方,本包只承载、原样过任意 JSON 形;缺席不铸键)---
112
+ --- 12.2.0:`McpTransport`(http 形)`assertionHeader?`(HTTP 头名,过 `principalHeader` / `headerRefs` 键同一只头名判官;承载连接器断言;与 `principalHeader` **互斥** —— 同设 ⇒ 解析期拒,条目层 `superRefine` 判一处,拒句点名条目名与两键;stdio 形上与 `principalHeader` 同判:键被剥并报 `unknown-keys-dropped`;缺席不铸键;断言怎么铸 / 验归消费方)---
113
+ --- 1.9.0:`McpServerSpec.toolFaces?`(`z.unknown().optional()` 解析透明,登记在 `PARSE_TRANSPARENT_KEYS`;引擎的工具名册 face 覆盖层,按远端工具名 key `{family?, pathTarget?, renderHints?, ruleFace?}`;形校验归引擎摄入侧,不合 schema 的工具不挂并通告一次 `config.tool_face_invalid`;缺席不铸键)
114
+ --- 1.7.0:`McpServerSpec.source?: string`(`z.unknown().optional()` 解析透明,无默认;「非空 ≤80 字符串」规则单点在消费方的投影层,登记在 `PARSE_TRANSPARENT_KEYS`)——声明方自述的分组标签,开集(CC 的 plugin/user/project/local 是惯用词不是词表);引擎 core ≥7.5.0 在 `wiring_manifest.mcp[].source` 逐字回显;显示用,不进 contentOrigin 信任轴 ---
115
+ ["local","execution","external"]`,缺席=引擎 pre-declaration 行为;表外值 parse 拒)。
116
+ · plugins:`PluginSpec[]`(只存来源引用 `PluginSource`,https-only `PLUGIN_ORIGIN` + `GIT_COMMIT_SHA` pin,
117
+ 永不存内容)。
118
+ · scenarios:`ScenarioEntry[]`(toolset slug;prompt 字段已废弃——prompt 单入口在 prompts 域)。
119
+ · systems:接入/凭据元数据(nonce+路由,永不存凭据值;`SystemStatusEntry` 状态行)。
120
+ · collab:协作模板(0.7.0 取代 teams;slot 引用 models 目录名;预算 `maxTokens`/`maxCost`)。
121
+ · workers:`WorkerSpec[]`(fleet 声明;`image` 默认值为公开 worker 镜像名、reconciler 白名单强制;
122
+ 7.0.0 两个结构块 `runtime.execution { lane, coords, secretRefs }` / `runtime.store { backend, session, memory, db? }`
123
+ (+ `secretRefs.dbPasswordEnv`),enabled 的 worker 两块必填,按 §4.5b 的词表判;`RESERVED_WORKER_ENV` 拒结构化字段
124
+ (含车道与店产出的全部 env 名)走 env;`WORKER_RESOURCE_PRESETS`;状态形 `WorkerStatus`/`WorkerCondition`)。
125
+ · hosts:机器注册表(`HostEntry`,`approved` 门;`HostProbes`/`HostStatus`)。
126
+ · governance:治理三件 `autonomy`(read-only|ask|plan|auto)/ `commandPolicy`(精确 argv[0],DENY-WINS,
127
+ 拒 glob,见 §8 T5)/ `approvalRequire`。
128
+ · entitlement:`tiers`(`EntitlementTier`:roster/models/visibleSkills/visibleMcp/visiblePlugins/budget/
129
+ runtimeCaps/orgMemory)+ `subjects` + `groupBindings` + `budgetGroups` + `defaults` + `scenarioAssignments`。
130
+ `EntitlementRuntimeCaps` 9 键:allowBypassPermissions / allowAutoMode / allowWorkflows / allowFork /
131
+ allowObservers / autoMode / allowUltracode / allowMemoryOptOut / forceDurableGate——全部三态
132
+ (ABSENT ≠ false,缺席不下发键);合并极性单源 `RUNTIME_CAP_STRICT`(allow* 族 false 胜,
133
+ forceDurableGate true 胜)。读边界 `PrincipalCapsWire`(runtimeCaps/budget 严判已知键 + passthrough
134
+ 未知键;能力面宽读;orgMemory 单独由 `PrincipalOrgMemoryWire` 校验)。
135
+ · execution:执行车道策略 —— `sessionMirror`(org 默认镜像目标)+ `publicKeys`(`ExecutionPublicKey`
136
+ sealed-box 公钥登记,`workerId?` 多 worker 选钥)。
137
+ · projects:项目身份登记簿(`ProjectRegistration`,`PROJECT_ID_REGEX` 宽读严写;租户身份数据,不便携)。
138
+ · prompts(14.0.0 单轴):只装引擎当期 schema 的中心 artifact。存储 `PromptsConfig = { artifact?, workers? }`(artifact 的身份
139
+ `{payload, artifactDigest}`,`workers.<名>` 整份取代 scope 那份;`bindings` / `catalog` 是墓碑,存量经 SS-M10);下发 `EffectivePromptsWire =
140
+ { schemaVersion: 2, artifact }`;唯一选择读法 `promptArtifactFor(prompts, worker | null)`。v3 结构镜像 `PromptArtifact` / `PublishedPromptArtifact` /
141
+ `PromptArtifactPayload`(schemaVersion 3)/ `PromptOverlay`(无 `scope`)/ `PromptOverlayWhen` / `PromptBindingCondition`(`{anyOf}` 或 `{noneOf}` 恰一表)/
142
+ `PromptArtifactRequires` / `PromptArtifactEvidence`;上界镜像 `PROMPT_ARTIFACT_LIMITS`(引擎七键)、`PROMPT_BINDING_MAX_CHARS` / `PROMPT_BINDING_LIST_MAX`。
143
+ 引擎词表不镜像(语义只有引擎 verify);逐条抢救 none(digest 约束)。
144
+ · limits:cost/限流族 14 键的热更新发布位;**open-world `.passthrough()`**(未知键原样承运 +
145
+ `"unknown-keys-carried"` 点名;1.x 论证见源头注,行为钉在 test/limits-domain-0.19.test.ts)。
146
+ - 边界 schema(可直接复用):`ENV_NAME`(/^[A-Z_][A-Z0-9_]*$/ 且拒 AKIA 形)、`NON_SECRET_URL`(存量文档里的 URL 座位 —— 域文档与 `config.d/remote-exec.json`
147
+ 的两个端点;fragment 拒句带迁移指路)、`NON_SECRET_URL_NO_MIGRATION`(不是存量的座位 —— 凭据信封 `issuer`:同一判官、同样的规则句,fragment 拒句不带指路)、
148
+ `K8S_QUANTITY`、`TCP_PORT`、`HOST_NAME_MAX`/`HOSTS_ARRAY_MAX`。
149
+ - 工具谓词:`hasNul`、`containsSecretToken`(含 percent-decode/JWT/Bearer;读原文 + ≤3 层宽容百分号解码,到不动点即停;令牌形判定与 6.2.0 同,
150
+ 10.0.0 起另认私钥 PEM 头**在串中任意位置**(原文与前三层解码上都读;此前只认串首)——写入校验与展示侧 `redactSecrets` 对私钥头的位置同判,但读法宽度仍窄于展示侧(四层及更深的编码、`+` 代空格的头写侧仍放行,展示侧整串扣);
151
+ 6.3.0 起那几层与展示侧的边界读法 / `+` 读法 / 「没走完」一位都住在包内私有模块 `src/secret-scan.ts`,不是导出 ——
152
+ `redactSecrets` 见「没走完」即整串扣:归一化不完的东西不出门)、`extraBodyUnsafe`、
153
+ `SECRET_LITERAL_RE`(由 `PRIVATE_KEY_PEM_HEADER` 拼出私钥 PEM 那一支,串首锚定,源串逐字不变;它是导出的单令牌字面形,不是 `containsSecretToken` 的全部)、
154
+ `PRIVATE_KEY_PEM_HEADER`(私钥 PEM header 的唯一定义,无锚;`containsSecretToken`(10.0.0 起,每一层百分号解码上)与 `redactSecrets`(任一归一化形上)
155
+ 都在串内任意位置读它 —— 见到就拒写 / 整串扣留;6.3.0 起展示侧含解码形与 `+` 读法,`key=` + 百分号编码 / 形式编码的 PEM 不再漏)、`urlQueryFragmentHasSecret`、`workerRouteKey`、`RESERVED_WORKER_ENV`、`DELIVER_DENYLIST`。
156
+ - 溯源契约:`SettingSource` / `EffectiveKey`(TOC `--effective` 与 TOB `?principal=` step-5 trace 的统一形)。
157
+ - 顶层类型:`EffectiveConfig`、`EffectiveWire`、`DomainName`、`DomainConfig`、`SkillsManifest`、
158
+ `SkillManifestEntry`、`ConfigChange`、`RBAC_AUDIT`/`SECRETS_AUDIT`。
159
+
160
+ --- 4.2 纯配置函数(src/config-fns.ts,主 barrel / `./config-fns`) ---
161
+ - `buildEffective(version, getRaw, updatedAt, onWarning?)`;`parseDomain(domain, raw)`(**`undefined` 才填默认,
162
+ `null` = 损坏 → 抛 sanitized ZodError**);`parseDomainLoud`(**文档面**:同 + 剥键/承运告警,永不抢救 —— 写入席、导出、`getDomain` 读它);
163
+ `parseDomainSalvaged`(12.3.0,**视图面**:与 `parseDomainLoud` 同一只内部体,整域读不进时按 `DOMAIN_ENTRY_SALVAGE` 逐条抢救、递归到嵌套集合,
164
+ 救不活抛整域 parse 的原错;`buildEffective` 十六域一处调它;好文档上两函数逐字同值同告警);`emptyEffective()`;
165
+ `readEffectiveWire(raw, onWarning?)`。
166
+ - 告警类型 `EffectiveReadWarning`(union,消费方 switch 需穷举):`hosts-grandfathered`(`droppedNames` 6.3.0 起有界 64,超界带
167
+ `droppedTotal`)| `domain-defaulted` |
168
+ `unknown-keys-dropped`(strip 域的未知键,段坐标)| `unknown-keys-carried`(open-world 域已承运的未知键)
169
+ (6.3.0 起告警坐标一律出门前过 `redactSecrets`:秘密形键段读 `[redacted-secret]`)|
170
+ `unread-config-file`(config.d 里永不会被读的文件)| `entry-dropped`(2.0.0,逐条容错丢掉的那一条条目,
171
+ 段坐标 `["models",3]` / `["roles","summarize"]`;S-712 起可带 `identity?: { name?, id?, enabled?, nameWithheld?, idWithheld?, token? }` =
172
+ 被丢条目**原文**里读得出的身份(`DroppedEntryIdentity`:字符串 `name`/`id`、布尔 `enabled`,只认自有数据属性,
173
+ 类型不对 ⇒ 不带该键;是字符串但 `isDisclosableIdentity` 说不可披露 ⇒ 带 `nameWithheld` / `idWithheld: true`(11.0.0),扣下的名另带可比记号
174
+ `token`(12.0.0,`withheldNameToken(name)` 一只铸法:稳定、不可逆)—— 判据 6.3.0 起 = 白名单闭形 `DISCLOSABLE_IDENTITY_RE`
175
+ (`^[A-Za-z0-9._:/@+-]{1,256}$`,主 barrel 导出;身份是标识符不是自由文本,`%` / `=` / 空白 / 引号 / 控制 / 非 ASCII / 超长
176
+ 一律不可披露)∧ `redactSecrets` 原样放行(与坐标段 / `error` 文案同一只脱敏器,否决集内的密钥形);三键都读不出 ⇒ 不带
177
+ `identity`,缺席 = 读不出、不分原因;进投影,一致性门比它;historical 模式的归一器用 `ConformanceNormalizer.warning?`
178
+ 两侧剥掉它 —— 旧版铸不出,不计旧版行为红;10.1.0 起另带 `reasonCode?` = 为什么丢的机读闭集词(`DROPPED_ENTRY_REASON_CODES`:
179
+ `entry-schema-invalid` 条目 / Record 值没过自己的 schema · `entry-key-invalid` Record 键没过键 schema · `entry-unreadable`
180
+ 判的过程抛了;词与丢弃点一一对应、无 other 兜底;本包铸的每条都带,键可选只为不让手工构造该联合的消费方编译红;`DroppedEntry.reasonCode`
181
+ 同源;同样进投影、同样被 historical 归一器剥掉))。(11.0.0:读侧两只兼容臂的告警 `legacy-governance-lifted` / `hosts-grandfathered`
182
+ 随臂删,闭集五员 —— 旧形由 `./one-time-migrations` 的一次性迁移改写,读侧遇旧形响亮拒。)未知键告警的 `keys` 有界(告警载荷上界),超界时带 `truncatedFrom` =
183
+ 截断前的真实总数;背后的两只扫描 11.0.0 起模块私有,公开原语只有 `parseDomainLoud`(上界本身也私有)。告警的 `kind` 闭集 `WARNING_KINDS` 与**派生**投影
184
+ `WarningProjection`/`projectWarning`(每臂 = 该 kind 减掉 `error`)属主也在这里,`./conformance` 只再导出。
185
+ **坐标是结构化段**(3.0.0 BREAKING):`ConfigPath = readonly (string | number)[]`,字符串段 = 对象键(逐字,
186
+ 含 `.` 也逐字),数字段 = 数组下标。2.x 的点号串是**有损编码** —— 键名本来就允许含 `.`,于是一个字面叫
187
+ `models.1.k` 的顶层键与 `models[1]` 里的未知键 `k` 编码成同一个串,2.1.0 只能用两支文法的段数门挡住其中
188
+ 一半。出门(日志 / UI)由**单铸**的 `formatConfigPath(p)` stringify:段间 `.`,数字段十进制,字符串段含
189
+ `.` / `"` / 控制字符、为空串、或文本恰好是某个数字段的样子(规范十进制)时按 JSON 字面量加引号
190
+ (`models.1."a.b"` / `models."1".k`),其余逐字 —— 一条判据:逐字出门读回来只能是它自己;**刻意不提供
191
+ parse 反函数**(没有一处把坐标读回来,给反函数=给下一个人一条在串上做事的路)。
192
+ **坐标回写到原文档的下标**(2.1.0):读路径上有**两个压缩点** —— hosts 的 `grandfatherHostsRead`
193
+ (parse 之前按名剔超长名条目)与逐条容错的 `salvageEntries`(整域 parse 失败后剔坏条目)。数组被剔过条目
194
+ 之后,诊断看见的是压缩后的下标(原第 2 条报成第 1 条,运维照它改的是别人的行)。律只有一条:
195
+ **一条告警用铸它那一层所见文档的坐标表达;哪一层压缩了文档,哪一层就把从它下方上来的告警回写一次**
196
+ (纯函数 `rebaseWarningPaths(w, keptIndex)`,对告警联合做穷举 switch,新 kind 不表态即编译红)。两层同时
197
+ 压缩时内层先、外层后,不重不漏。坐标账 = `EntrySalvageResult.keptIndex` / `GrandfatherHostsReadResult.keptIndex`
198
+ (同一种 `EntryKeptIndex`),**只登记真发生过位移的数组字段**;没有一条路径真的变 ⇒ 返回**同一个**告警
199
+ 对象引用。Record 字段的条目按键寻址,不受影响。3.0.0 起回写判据只有一条(`p[0]` 是字符串且在位移表里
200
+ ∧ `p[1]` 是**数字段** ⇔ 数组路径):段形把「下标还是键名」写进了类型,2.1.0 为在串上猜回这个位而设的
201
+ `PathGrammar` 两支文法、段数门与规范十进制正则三件一并删除 —— 规则数变少,挡住的东西一个没少。12.3.0(抢救递归)把同一条律推到
202
+ 每一层:坐标里每个数字段,它前面那段前缀(已回写成原文档坐标)在位移表里 ⇔ 它是那个被压缩数组的下标;从左往右逐段做,两层都压缩时
203
+ 各回写一次。位移表的键 = 被压缩数组的原文档全路径(`formatConfigPath` 出门形,`"projects.<id>.contextItems"`;顶层数组就是字段名,旧表形与义不变)。
204
+ - 读降级表 `DOMAIN_READ_FALLBACK`(整域读不进时 default 或 throw,逐域档位)与逐条抢救表 `DOMAIN_ENTRY_SALVAGE`(12.3.0:读时能否逐条丢,
205
+ `salvage` / `none`,逐域档位)—— 两张表各答一个问题,谁也不读谁;`grandfatherHostsRead`。
206
+ --- 2.0.0 行为面 BREAKING:**逐条容错**。此前一条坏条目让**整域**退回 schema 默认(13 个模型坏一条 ⇒ 一条
207
+ 都不剩);现在 `buildEffective` 在整域降级之前先经 `salvageEntries(schema, raw)` 只丢掉真正坏掉的那几条条目
208
+ (条目集合从域 schema 自身派生:数组字段与 Record 字段),同域其余条目照常生效,并逐条发 `entry-dropped`。
209
+ 档位不新增:只发生在 `DOMAIN_READ_FALLBACK` 判 `"default"` 的域;gate 域(rosters/governance/entitlement/
210
+ execution/workers/limits/projects)坏条目仍然 throw。好配置的解析路径一字不动(容错只在整域解析
211
+ 失败后才跑);丢掉坏条目后整域仍解析不了(重名/悬空 ref)则只发 `domain-defaulted`,一域一个结论。
212
+ **零幸存者不算部分损坏**:一个集合如果所有条目都坏,一条都不丢,原样走整域档位 —— 本 schema 里有若干
213
+ 字段「空 = 不设限」,抢救成空集合等于静默删掉运维的限制(11.0.0 起 `@` 限制换成 `atMention` 三态,「一个都不许」是 `{mode:"none"}`)。
214
+ `readEffectiveWire` 上的 skills manifest 那条旁路走同一个抢救点(它是另一张 schema,不是域) ---
215
+ --- 12.3.0:「能否逐条丢」从 `DOMAIN_READ_FALLBACK` 的 `"default"` 档拆出,单列 `DOMAIN_ENTRY_SALVAGE`(闸域 rosters/workers/governance/
216
+ entitlement/execution/limits = `none`;`projects` = `salvage`,整域档位仍 `"throw"`);抢救进视图面读函数 `parseDomainSalvaged`(`buildEffective` 只剩
217
+ 整域读不进一臂);文档面(`parseDomain` / `parseDomainLoud`:写入席、导出、`getDomain`)永不抢救,视图面(`parseDomainSalvaged` / `buildEffective`)
218
+ 按表抢救;抢救按 schema 递归到幸存条目体内的集合,子级坐标以父路径为前缀,零幸存者律每层成立 ---
219
+ - 角色助手:`UserRole`(viewer<editor<publisher<admin)、`ROLE_RANK`/`USER_ROLES`、`roleAtLeast`、`isRole`、
220
+ `domainWriteRole(domain)`(默认 editor;skills/mcp/plugins/scenarios/systems/entitlement = publisher,
221
+ 表在 `DOMAIN_WRITE_ROLE`)。
222
+ - 纯数据类型:`User`、`UserAuth`、`PublishedSnapshot`、`PendingPublish`、`ApprovePublishResult`、
223
+ `CasWriteResult`、`WorkerLifecycleRecord`;错误脱敏 `redactSecrets` / `sanitizeZodError`;
224
+ `legacyGovernanceFromRuntime`;`statusRowsToPrune`(两 selector 都缺 → `[]`,防全删);`resolveActiveTiers`。
225
+
226
+ --- 4.3 roster 解析 / 跨域引用(主 barrel / `./resolve-roster` / `./cross-domain`) ---
227
+ - `resolveEffectiveForWorker(eff, worker)` → `ResolvedEffective`(§3.3;dangling ref 容错回退 global)。
228
+ - `refIntegrityIssues(domain, value, resolve)`:domain ∈ `RefCheckedDomain`,返回 `RefIssue[]`(空=OK,
229
+ save-time 守卫);`createSiblingResolver(store, pending?)`。
230
+ - 闭集是**运行期值**(4.0.0):`REF_CHECKED_DOMAINS`(六员常量,`satisfies readonly (keyof DomainConfig)[]`)
231
+ + `RefCheckedDomain = (typeof REF_CHECKED_DOMAINS)[number]`(类型从值派生)+ `isRefCheckedDomain(d)`
232
+ 判别式。消费方问「这个域要不要过闸」用这三只,不要自己抄域名清单。
233
+ - `RefIssue.path` 六臂**一律以域键起手**(4.0.0,值形 BREAKING;此前 `collab` 从 `"templates"`、
234
+ `entitlement` 从 `"tiers"` 起手)⇒ `path[0]` 恒 = 被校验的域,可直接前置批次下标。
235
+
236
+ --- 4.4 哈希 / 内容寻址 / secret 清单 / reader ---
237
+ - `stableHash`(FNV-1a 64)/`stableStringify`/`fnv1a64`、`MAX_HASH_DEPTH`(200)。(src/hash.ts)
238
+ - `sha256Hex`(纯 JS,字节等同 node:crypto)。(src/sha256.ts)
239
+ - `skillContentHash` → `"sha256:<hex>"`、`toSkillsManifest`、`findSkillContent`、`normalizeContentHash`。
240
+ (src/skills-manifest.ts)
241
+ - `collectEnvRefs(eff): EnvRef[]`、`workersReferencingSecret`。(src/secret-refs.ts)
242
+ - `ConfigReader` 接口(3 方法:getDomain/getEffective/getVersion)——本地源/测试桩实现它即可。(src/reader.ts)
243
+
244
+ --- 4.5 remoteExec 契约(src/remote-exec.ts,主 barrel / `./remote-exec`) ---
245
+ - `RemoteExecSpec`(discriminatedUnion on `provider`),6 个 provider(每个 ⊆ `EXECUTION_LANE_WORDS`;7.0.0 删
246
+ `remote-docker` —— 引擎零读点,远端 docker 守护进程 = `local-docker` + `DOCKER_HOST`):
247
+ - `host`:{ workdir?, allowedPaths?, shell?, commandTimeoutMs? } —— 无 secret。
248
+ - `local-docker`:{ image!, memory?, cpus?, network?("none"|"bridge"|"host"), workdir?, mountPath? }。
249
+ - `e2b`:{ apiKeyEnv!, template?, timeoutMs?, livenessMs?, allowInternetAccess?, sandboxEnv? }。
250
+ - `k8s`:{ image!, apiUrl?, tokenEnv?, caCertEnv?, insecureTls?, namespace?, runtimeClass?, mountPath?,
251
+ timeoutMs?, memory?, cpu?, s3Snapshot?{ endpoint, bucket?, accessKeyEnv, secretKeyEnv, … } }。
252
+ - `ssh`:{ host!, port?, username!, privateKeyEnv!, mountPath? }。
253
+ - `adb`:{ serial!, adbPath?, mountPath? }。
254
+ (secret 全 env-NAME ref;exec-bound 串拒 NUL。通过校验 ≠ 目标服务版本已从文件驱动该 provider。)
255
+
256
+ --- 4.5b 执行车道 + 存储词表正本(src/execution-lanes.ts,主 barrel + `./execution-lanes`,7.0.0,纯数据) ---
257
+ - `EXECUTION_LANE_WORDS` / `ExecutionLane`:host · e2b · k8s · ssh · adb · local-docker · device(= 引擎 `REMOTE_EXEC` 收的词,
258
+ 逐字同序;无 `in-process`、无 `remote-docker`)。
259
+ - `RETIRED_LANE_WORDS`(11.0.0):退役车道**值**的墓碑(`[旧词, 去向车道, 怎么配]`;永久、一跳;今一行 remote-docker → local-docker + DOCKER_HOST);
260
+ worker 规格 `runtime.execution.lane` 与车道文件 `provider` 读到退役词即拒,拒句 = `retiredLaneWordRefusal(word)`
261
+ (带出路;issue 码不变)。
262
+ - `EXECUTION_LANES: readonly ExecutionLaneRow[]`:每行 `{ lane, tenantSeparation: "supported"|"single-user-only", requiredEnv,
263
+ requiredSecretEnv, optionalEnv, optionalSecretEnv }`(深冻结;四栏 env 名两两不交、跨车道不交;行内容逐格抄自引擎,
264
+ 坐标在每行 JSDoc)。`TENANT_SEPARATION_WORDS`。
265
+ - `STORE_BACKENDS`(mysql/pg/local/memory)、`SESSION_BACKENDS`(memory/mysql/auto)、`MEMORY_ENGINE_BACKENDS`(file/pg/tidb)。
266
+ - 判据(都在 `WorkerSpec` schema 里,zod issue 的 `path` 指字段、message 带出路):enabled ⇒ 两块必填;coords / secretRefs 键 ⊆
267
+ 表[lane] 对应栏、必需键在场非空;别车道键点名车道;`requirePrincipal` ∧ single-user-only 车道拒(出路按表现算);host ∧ 副本 > 1
268
+ 拒;device ⇒ SQL 店且 session ≠ memory;memory 店 ∧ session mysql 拒;记忆引擎 pg/tidb ∧ session memory 拒;SQL 坐标 ⇔ SQL 店;
269
+ 记忆后端配店;多租户 ⇒ SQL 店;6.x 删除项(`sessionBackend` / `memoryBackend` / `runtime.db` /
270
+ `coords.tidb*` / `tidbPasswordEnv`)读到即拒并指路迁移。`WorkerSpec` 是 ZodEffects(无 `.shape`):名字判据单独导出 `WORKER_NAME`(src/types.ts)。
271
+
272
+ --- 4.5b 跨会话两键(src/cross-session.ts,主 barrel;1.6.0 新增) ---
273
+ - `settings.json` 面的两个 CC-parity 键,本包只出格式契约、不做判定:
274
+ - `crossSessionInbound`:显式闭集 `CROSS_SESSION_INBOUND_VALUES = ["wake","next-turn","hold","refuse"]`(6.0.0 起
275
+ 随 core 7.30.0 词表;顺序即收紧秩 wake < next-turn < hold < refuse;`accept` 退役,文件层响亮拒 —— core 只在
276
+ 自己的层里把它读成 next-turn 并 onError 一次),UI 第五拼写 `CROSS_SESSION_INBOUND_UNSET = "default"` = **未设**,
277
+ 不是第五种策略;未设 ⇒ core 基线 next-turn + mode-parity(发送方与收件方权限档位同类才自动投递)。
278
+ - `dialogExpiry`:`DIALOG_EXPIRY_VALUES = ["60s","5m","10m","never"]`,`DIALOG_EXPIRY_DEFAULT = "5m"`。
279
+ CC describe 明写它有**两个**消费面:远端停靠的审批对话框,与停审的跨会话消息;哪个宿主消费哪一面由宿主自陈。
280
+ - zod 受理面 `CrossSessionInboundSetting` / `DialogExpirySetting`(各含 `"default"`),片段 schema
281
+ `CrossSessionSettings`(两键可选 + `.passthrough()` —— 本片段不是整份 settings 的闭集门,那道门归宿主受理面)。
282
+ - 层读取器 `readCrossSessionInbound(raw)` / `readDialogExpiry(raw)` → `SettingLayerRead<T>` 三态:
283
+ `{ok:true}` = 未设(`"default"`/`null`/缺席)· `{ok:true,value}` = 显式值 · `{ok:false}` = **词表外**。
284
+ 坏值恒 `ok:false` 由调用方响亮拒(CC 文件层是 `.catch(undefined)` 静默吞成未设;本栈有意分岔——
285
+ 一个大小写拼错静默读成「未设」会把用户以为已关的入站车道悄悄打开)。
286
+ - 三只常量是引擎 `CROSS_SESSION_INBOUND_SETTINGS` / `CROSS_SESSION_DIALOG_EXPIRY_VALUES` / `CROSS_SESSION_DIALOG_EXPIRY_DEFAULT`
287
+ 的**登记镜像**(语义属主 = 引擎),**必须逐字同序相等**;等值由本仓自证(`test/upstream-drift.test.ts` ④,dev 期读引擎
288
+ 运行期导出,零豁免)。消费方不替本仓守镜像,只钉「自己读的是引擎那份」(本包不 import 引擎)。
289
+
290
+ --- 4.5c URL 名单判官 · http hook 两张名单 · MCP 名单判决(主 barrel;13.0.0) ---
291
+ - `urlAllowPatternMatches(url, pattern)` / `urlDenyPatternMatches(url, pattern)`(src/url-pattern.ts):管理员 URL 名单一条模式的唯一判法。律 = 名单读不准处一律判向「拦」:
292
+ 放行出口 ⊆ CC 2.1.296 `g9`(cli296.js:249195),拒绝出口 ⊇ `g9` ∪ 整串 glob。组件判:协议同或整段 `*`;主机名去尾点小写后 glob(`*` 不跨主机边界);
293
+ 端口(模式没写且主机名带 `*` ⇒ 不判,否则逐字);路径 + 查询串 glob(模式无路径无查询 ⇒ 任意路径);片段 / 用户信息不比。放行出口另收四处
294
+ (docs/CC-DIVERGENCE.md SS-CCD-01 … 04):写明缺省端口照判;`*` 协议只通配 http/https/ws/wss,`ws+unix:` 套接字段逐字、空或含 `..` 不放;
295
+ 解析不出 URL 形的模式不放;解码出的字面 `*` 不当通配。纯函数,全局只用 `URL`,占位记号按输入确定选取。
296
+ - `httpHookAllowlists`(src/http-hook-allowlists.ts):类目行 `SAFETY_MERGE_SPEC.httpHookAllowlists`(denyFirst / global / setNarrower / resolver / closed)。
297
+ `HTTP_HOOK_ALLOWLIST_KEYS`;片段 `HttpHookAllowlistSettings`;层 `HttpHookAllowlistLayer = { source: SettingsSource, 两张名单? }`(只带源名,不带档);
298
+ 层读法 `readHttpHookAllowlistLayer(source, settings)` → `{ layer, malformed }`(坏形 ⇒ `[]` 并点名);
299
+ `httpHookUrlVerdict(url, layers, workspace)` → `undefined` | `HttpHookUrlBlock{ list:"allowedHttpHookUrls", rule:"empty"|"url", sources }`:每张**准入**名单
300
+ (准入 ⇔ `layerAdmission(源, 工作区读数, "httpHookAllowlists") === "admitted"` —— 源表定档、类目行定地板;闭集外的源 / 读数 RangeError)都须放行 = 跨层取交;
301
+ `httpHookEnvVarsAllowed(hookVars, layers, workspace)` = hook 自己的 allowedEnvVars ∩ 每张准入名单。与 CC 差:拼接 → 取交、任意层 → 准入层(SS-CCD-06 / 07)。
302
+ - `mcpServerPolicyVerdict(server, layers)`(src/mcp-server-policy.ts)→ `undefined` | `McpPolicyBlock{ name, list, rule, target, written, sources }`;闭集 `MCP_SERVER_LIST_KEYS` /
303
+ `MCP_POLICY_RULES`(name / command / url / empty)。层 `McpPolicyLayer = { source: SettingsSource, … }`(只带源名)。拒绝绝对优先、各层都算(名字 → 命令整数组 → URL 拒绝出口);
304
+ 允许名单各层拼接(CC 形),托管层(`settingsSourceContract(源).trust === "managed"`)`allowManagedMcpServersOnly` ⇒ 只读托管层;缺席不限、空一台都不许;stdio 遇命令条目 / 远端遇 URL 条目须命中其一,否则按名字。`server.target` = 展开后连接目标
305
+ (只用来判),`server.written` = 展开前写法(唯一回显;stdio 只给可执行名 + 参数个数)。SDK 型等是否豁免归调用方。
306
+
307
+ --- 4.5d 界面语言设置片段(src/ui-language.ts,主 barrel;13.0.0) ---
308
+ - `UI_LANGUAGE_SETTING = { key: "semaUiLanguage", values: UI_LANGUAGE_VALUES, category: "ui", source: "userSettings" }`(`source` 受 `SettingsSource` 约束),`UI_LANGUAGE_VALUES = ["en","zh"]`;
309
+ 形门 `UiLanguageSetting`、片段 `UiLanguageSettings`(passthrough);读器 `readUiLanguage(raw)` → `SettingLayerRead`(合法 ⇒ 值;缺席 / null ⇒ 未设;
310
+ 词表外 ⇒ `ok:false`;两种无值都由消费方落宿主 locale,读器不猜)。界面语言 ≠ 模型回答的语言(CC `language` 键)。
311
+
312
+ --- 4.6 hooks 契约(src/hooks.ts,主 barrel / `./hooks`) ---
313
+ - 事件全集 `HOOK_EVENTS`(33 个 hook 事件 = CC 2.1.198 的 27 + post-198 的 CwdChanged/FileChanged/
314
+ MessageDisplay/DirectoryAdded/PreModelSwitch/PostModelSwitch);owner 表 `HOOK_EVENT_OWNER`(engine|shell,单属主)、`ENGINE_OWNED_HOOK_EVENTS`、
315
+ matcher 语义表 `HOOK_EVENT_MATCHER_FIELD`(null = 无 matcher 轴)。
316
+ - 逐事件输入形 `*HookInput`(union `HookInput`);输出:`SyncHookOutput`(exit 0 JSON;exit 2 = 阻断,stderr
317
+ 作为理由/附加上下文喂回模型;其余 exit/超时/spawn 失败/坏 JSON = 非阻断故障,消费方 hook-runner 铸
318
+ `hook_non_blocking_failure` 通知帧上任务/fleet 流,stderr 先脱敏后截断 512 字)、`AsyncHookOutput`、
319
+ 决策形 `HookDecision` / `HookPermissionRequestDecision` / `HookSpecificOutput`。
320
+ - 配置形(src/hooks-config.ts,经 `./hooks` 整体转出):`HookEntry`(`HOOK_ENTRY_TYPES` 五型 command|prompt|agent|http|mcp_tool)→ `HookMatcherEntry` → `HooksConfig`
321
+ (`Partial<Record<HookEventName, HookMatcherEntry[]>>`,事件名闭集);三者与逐型五只 zod 面都是 `classifyHooksConfig` 的投影(受理 ⇔ loaded,
322
+ issue = 码 + 坐标 + 坏字段名)。`classifyHooksConfig(settingsDoc)` = hooks 坏形的唯一读者:结局 `loaded`/`loaded-partial`/`rejected`;剔点五码
323
+ `HOOKS_DROPPED_PIECE_CODES`(非守卫事件的坏块逐个剔);拒点六码 `HOOKS_REJECTION_CODES`(守卫事件 PreToolUse / PermissionRequest 任何坏处、
324
+ 或守卫迹象出现在 hooks 外 / 未知事件下 / 匹配器或条目字段里 ⇒ 整份拒;守卫事件值 null = 剔);坐标原样(出门前 `outgoingPath`)、零句子;剔点与拒绝项同一条 `field` 律 —— 坏块是匹配器 / 条目(对象)⇒ `field` = 它上面出问题的键(契约字段值错,或装着守卫迹象的成员:
325
+ 契约不认识的未知字段 / 装得下更多东西的契约字段 / 条目自己是匹配器形时取 `hooks`),块不是对象才缺席;
326
+ 自含(只引用 `HOOK_EVENTS` 与 JS 内建;整函数抄走的生成器 —— client-core —— 须把本包运行期导出 `HOOK_EVENTS` 作为上游常量注入抄本作用域,
327
+ 不许在抄本里贴字面事件表:那是第二张事件表,漂移门看不见)。执行门契约(`./hooks` 头注):工作区 `declined` ⇒ 所有层 hooks 不跑,执行在 client-core。`HooksSlotSemantics`(保留词表,
328
+ 未消费);权限词表 `PermissionBehavior`/`PermissionMode`/`PermissionUpdate` 族;停止归因 `StoppedBy`/
329
+ `stopAttributionText`;`HOOK_INJECTED_ENV`、`DEFAULT_HOOK_TIMEOUT_SECONDS`(60)。
330
+ - http 钩子 header 展开:`interpolateHookEnvVars(headers, allowedEnvVars, env)` —— `$VAR` / `${VAR}`(名字形 `[A-Z_][A-Z0-9_]*`)只在 header **值**里读,
331
+ 名单内展开(env 无值 ⇒ 空串)、名单外与凭据名一律空串并进返回值 `blanked`(`{ header, name, reason: "not-allowed" | "credential" }`)、结果剥 CR/LF/NUL;
332
+ 纯函数,壳与服务器同调一只(读法照 CC 2.1.296)。结果类型 `HookEnvInterpolation` / `HookEnvBlank`。
333
+
334
+ --- 4.7 safety-merge-spec(主 barrel / `./safety-merge-spec`) ---
335
+ - `SAFETY_MERGE_SPEC`:逐类别合并/执法表(`mergeShape`/`minTrust`/`tighten`/`enforcement`/`fail`);成员级
336
+ 准入(1.1.0 机制):`permissions`(allow=loosen@global / deny·ask=tighten@local)与 `sandboxNetwork`
337
+ (11 员闭集=壳侧沙箱方向表上收:2 个 deny 数组 tighten@local;9 个布尔员**值向**——行 floor global +
338
+ `boolPole{strict,minTrust:"local"}`,严格极写=local、松极/非布尔/无值=global;放宽面/failIfUnavailable/
339
+ 未声明新子键回落类目 floor global = fail-closed);读法 `membersOf`/`memberAdmission`(可带候写值)/
340
+ `memberMinTrust`(禁自铸表)。
341
+ - 信任阶梯(1.2.0):`trustRank`(managed 6 > global 5 > project 4 > local 3 > session 2 > task 1)、
342
+ `isAtLeast`;`MinTrust` 含 `task`(声明序非阶梯,阶梯唯一真源是 `trustRank`)。
343
+ - 设置源表(src/settings-sources.ts,主 barrel):`SETTINGS_SOURCES`(合并序 user<project<local<flag<policy)、`SETTINGS_SOURCE_CONTRACT`
344
+ (user global / project project 仓库可控 / local project 仓库可控 / flag global / policy managed)、`WORKSPACE_TRUST_STATES`
345
+ (accepted/declined/no_trust_step)、`layerAdmission(source, workspace, category, member?, value?)` → admitted / trust_withheld / below_trust_floor
346
+ (地板先判;仓库可控层 × 非 accepted 只收 tighten 成员);闭集外抛 RangeError。`settingsSourceContract(source)` = 源表唯一查口(同一句 RangeError):
347
+ 本包要层档的函数(`layerAdmission`、http hook 名单判官、MCP 名单判决)都经它,不收调用方给的档。
348
+ - 13.0.0 尾插整键类目 `httpHookAllowlists`(minTrust global;判决见 4.5c)。`SafetyMergeCategory` 随之加宽 —— 持穷举表的消费方编译红 = 通知。
349
+
350
+ --- 4.8 fleet 契约(主 barrel / `./fleet`) ---
351
+ - drain 协议(`DRAIN_PHASES`/`DrainSnapshot`/`drainVerdict`/`DEFAULT_DRAIN_GRACE_MS`);实例发现
352
+ (`InstanceAnnouncement`/`InstanceRegistration`/`isInstanceStale`/`routableInstances`/`InstanceHealth`;
353
+ S-741 重启信号 `RestartSignal` **定义一次** = `{ reasons: string[](≥1 个非空词), version: number(与 boot 不同的
354
+ web-admin 配置版本,有限数,与 `readEffectiveWire` 读配置版本同一条判据), since: number(epoch ms,非负整数)}`,即 server
355
+ `/health` 已发的形;心跳键 `InstanceAnnouncement.restart?` 引它(`InstanceRegistration` 随 extend 同带);`/health` 一侧
356
+ `InstanceHealth` 上它不入类型(passthrough,与顶层冗余布尔 `restartRequired` 一起原样承运)。在场 ⇔ 需要重启,形里没有
357
+ `required` 布尔。心跳每拍整行重报 ⇒ 不带它的一拍 = 不再需要,存行整替不合并;11.0.0 起重启是引擎状态,两出口只在
358
+ `EngineSignal.restart`(`engineSignal` 键里)读:`readEngineSignal(body)?.restart`,平铺读口 `readRestartSignal` 删,两只体上平铺的
359
+ `restart` 是收缩窗里的方言、不读;S-768 模型拒因同法**定义一次**,两个面:wire 形 `ModelRefusalsWire`
360
+ (zod)= `{ entries: record(非空名 → 非空串), plane?: 非空串 }` + 空信号 refine(只判形律,码是开放串),读方型
361
+ `ModelRefusalsSignal` = `{ entries: Record<条目名, ModelEntryRefusalCode>, plane?: ModelPlaneRefusalCode }`;值只码(不带上游
362
+ id / provider / 坏字段键 / 句子);码闭集真源在本包:`MODEL_ENTRY_REFUSAL_CODES`(`config.model_no_brain` /
363
+ `config.model_entry_invalid`)、`MODEL_PLANE_REFUSAL_CODES`(`config.default_model_missing` / `config.model_refused_name_minted` /
364
+ `config.model_route_endpoint_missing` / `config.gateway_fallback_without_primary`,镜像引擎组拒码表整族),各配 z.enum + 型;
365
+ `readModelRefusals(body)` 一只读口两出口(`/health` 体或已 parse 的公告,与 `readEngineSignal` 同一只私有读口)先判形再按
366
+ 闭集收窄,集外码丢、丢到空 ⇒ `undefined`,只答键不判版本;心跳键 `InstanceAnnouncement.modelRefusals` =
367
+ `ModelRefusalsWire.optional().catch(undefined)` —— 集外码照过、形坏降为缺席,诊断键永不拖垮心跳;`/health` 一侧 `InstanceHealth`
368
+ 形不动;`plane` 单码(一个候选恰一个组拒判决),`entries` 与 `plane` 描述同一份最新候选目录;在场 ⇔ 至少一条**可报**的拒或
369
+ 组拒;缺席读 `version`:更早的引擎缺席 = 不可知;载不住 / 秘密形的名由引擎按 `isReportableRefusalName`(wire 载得住 ∧
370
+ `isDisclosableIdentity`,后者即被丢条目身份同一只判据:6.3.0 起白名单闭形 `DISCLOSABLE_IDENTITY_RE` ∧ 非秘密形)筛掉,不进键);
371
+ 8.1.0(additive,零行为):`RestartSignal.withheld?`(可选布尔,引擎自判「这一拍不该被换代」,缺席 = false,读出形不铸键);
372
+ `EngineSignal`(zod + 型)= 引擎自铸信号的闭形 `{ ready, draining?: EngineDraining { actionId, since, deadlineAt }, restart?: RestartSignal,
373
+ generation?, leaseState?(8.2.0), leaseUntil?, configTargetVersion?: number(9.0.0 起是数,原为串), configAppliedVersion?: number }`(`InstanceHealth` 仍是开放自描述记录,形不动)。
374
+ **11.0.0 起两出口(`/health` 与心跳)整只承载它,在一个键 `ENGINE_SIGNAL_KEY`(`"engineSignal"`)下**(嵌套不平铺:收缩窗内布尔 `draining` 照发,
375
+ 同一个键装不下布尔与对象);两只形都类型化该键(`InstanceAnnouncement.engineSignal` / `InstanceHealth.engineSignal`,形坏 ⇒ 缺席,体照收)。
376
+ `readEngineSignal(body)` 只读这个键,与 `readModelRefusals` 走同一只受保护读口:缺键 / 形坏 / 缺 `ready` / 非对象 ⇒ `undefined`
377
+ (= 没有这只信号,不是就绪;平铺在体上的同名键不读)。判官只读信号:`judgeReadiness(signal)` / `judgeRoutability(row, nowMs)` → `{ routable: true }` |
378
+ `{ routable: false, code, why }`(闭集 `NOT_ROUTABLE_CODES`:no-engine-signal / stale / observer-not-ready / draining / not-ready),
379
+ `isReadyForTraffic(signal)` / `isRoutable(row, nowMs)` / `routableInstances` 是布尔形;没有信号的行 / 体(旧引擎)= `no-engine-signal`,不可路由。
380
+ 一个事实一处:引擎状态只在信号里,行的身份(`instanceId` / `worker` / `address` / `version` / `heartbeatMs`)只在体的顶层(11.0.0 起 `EngineSignal`
381
+ 不再有 `instanceId`);两只形上与信号同名的平铺键(`ready`、布尔 `draining` / `drainingSince`、`restart`、`generation`、`leaseState`、`leaseUntil`)
382
+ 是收缩窗里的方言:形不动、`@deprecated`、本包零读点,退役已登记(`ONE_TIME_MIGRATIONS`);
383
+ 服务租约 API 型:`ServingLeaseRequest { worker, scope?, generation, instanceId }`、`ServingLeaseResponse` = 判别联合
384
+ `granted { ttlMs }`(时长,引擎按发请求时的本地单调钟起算;web-admin 按库时间记自己的到期,两边不比钟)| `denied { reason ∈ SERVING_LEASE_DENIED_REASONS(superseded | retired) }` | `unknown`(闭集 `SERVING_LEASE_VERDICTS`;`unknown` 不带 reason、不是 `denied`),
385
+ 常量 `SERVING_LEASE_TTL_DEFAULT_MS`(600 000)/ `SERVING_LEASE_TTL_FLOOR_MS`(60 000)/ `SERVING_LEASE_DRIFT_MARGIN_MS`(派生自 `DEFAULT_HEARTBEAT_MS`),
386
+ `generation` = 非负整数的规范十进制串(`"7"`,不许 `"07"` / 数字型;三处 wire 形同一条判据),判别器 `isCanonicalGeneration(value)`、数值比较 `compareGeneration(a, b)`(`"9"` < `"10"`;非规范入参抛 TypeError);
387
+ 读器 `readServingLeaseRequest` / `readServingLeaseResponse`;`denied` 只凭正面证据(web-admin 持久化的代际下限之下 ⇒ superseded),对象缺席 ⇒ `unknown`;`readServingLeaseResponse` 按 `verdict` 键判,返回 `ServingLeaseReading`(`denied` 的 reason 不在闭集 / 缺 ⇒ 仍是 denied,reason 读成 `SERVING_LEASE_UNKNOWN_REASON` = `"unknown_reason"`;判别词不在三词 ⇒ undefined);
388
+ web-admin 能力位 `WebAdminCapabilities { servingLease?: true }` + 三态读器 `readCapabilityReading(reply): CapabilityReading`(9.0.0 起取代 `readCenterCapabilities`;吃心跳 2xx 的整只答复体,答 `declared { capabilities }` | `undeclared`(体是对象、键缺席 = 老 web-admin ⇒ 不续租、不围栏)| `unreadable { why: not-an-object | invalid-capabilities }`(不是读数:键在场而形坏 ≠ 没宣告,引擎保留上一次状态与钟);租约客户端只在 `declared` 且 `servingLease === true` 时启用);排空口型 `DrainRequest { actionId, scope?, worker, expectedGeneration?, instanceId?, deadlineAt, term? }`、
389
+ `DrainReceipt = EngineDraining + state ∈ DRAIN_RECEIPT_STATES(draining | already_draining)`,读器 `readDrainRequest` / `readDrainReceipt`;
390
+ `clampServingLeaseTtlMs(n)`(配置 TTL 唯一夹取点:读不出 ⇒ 缺省,低于地板 ⇒ 地板,无上限;应答的 `ttlMs` 不夹);
391
+ 8.2.0(additive):`LEASE_STATES` = `["none", "granted", "expired", "denied"]` + zod `LeaseState`(引擎持有的租约四态闭集;只有 fleet 成员说它);
392
+ `EngineSignal.leaseState?`(`leaseUntil` 在场 ⇔ `granted`);`InstanceAnnouncement` 加 `generation?` / `leaseState?` / `leaseUntil?`(各引 `EngineSignal` 同名键那一只 schema,
393
+ 形坏的那一键 `.catch` 落成缺席、心跳照收;`InstanceRegistration` 随之);心跳体**没有** `scope` 键(scope 由凭证定:per-worker 令牌 = 绑定的 scope,共享令牌 = `?scope=`,缺席 ⇒ `"global"`);
394
+ `ServingLeaseRequest.scope`:续租只收 per-worker 令牌,缺席 = 令牌的 scope、在场必须相等,共享令牌不得续租(403);
395
+ `FLEET_PATHS = { instances: "/api/fleet/instances", usage: "/api/fleet/usage", quotaLeases: "/api/fleet/leases", servingLease: "/api/fleet/live/serving-lease",
396
+ hostPlanner: "/api/fleet/live/host-planner/{host}" }`(路径模板,参数整段 `{name}`;成员律 = 本包定形其体 ∪ 活性组成员;没有能力读口端点)、
397
+ `FLEET_LIVE_PATHS = [instances, servingLease, hostPlanner]`(活性路由组,不经就绪门 / 配置治理门;组是表不是前缀),两者另以 JSON 资产 `./fleet-paths.json` 随包出(构建时写);
398
+ `HEARTBEAT_REPLY_CAPABILITIES_KEY = "capabilities"`(`WebAdminCapabilities` 的唯一载体 = 心跳 2xx 答复体的这个键,键查找在 `readCapabilityReading` 里;位按调用方凭证答,共享令牌看不到 `servingLease`);
399
+ env 名常量 `FLEET_GENERATION_ENV` = `"FLEET_GENERATION"`(进 `RESERVED_WORKER_ENV`;渲染器与引擎共用,不各自拼;实例身份 `instanceId`(体的顶层)是引擎每进程自铸的,无 env 名)、
400
+ `FLEET_SCOPE_ENV` = `"FLEET_SCOPE"`(8.3.0,8.3.1 注改真;成员所属租户 scope,与该成员 per-worker 令牌绑定的 scope 同值,渲染器写、引擎读作自身身份的 scope、只用来比对 `DrainRequest.scope`(请求不带 scope ⇒ 读成默认 scope)、不从令牌格式反推;续租 / 心跳的 scope 由凭证定、不取这只 env,两者同值是渲染器的义务;缺席 = 默认 scope `GLOBAL_SCOPE`;同样进 `RESERVED_WORKER_ENV`);
401
+ 派发(`DISPATCH_STRATEGIES` round-robin|least-inflight|consistent-hash、`DispatchPolicy`、`pickInstance`、
402
+ `SUBMIT_IDEMPOTENCY_KEY`);配额租约(`QuotaLease`/`leaseRemaining`/`leaseExhausted`/`shouldRenewLease`/
403
+ `reconcileLease`);用量上报(`UsageEntry`/`UsageReport`/`aggregateUsage`/`QuotaResetEvent`)。
404
+
405
+ --- 4.9 scheduler-store 契约(主 barrel / `./scheduler-store`;node 绑定在 `./node`) ---
406
+ - 纯契约:`SchedulerRecord`/`SchedulerWhen`/`SchedulerStore` 接口、`parseSchedulerStore`(前向兼容,保未知
407
+ 字段)/`serializeSchedulerStore`/`isSchedulerRecord`。
408
+ - `SchedulerParseWarning`(union,2.0.0 起两员):`malformed-record`(坏记录逐条丢)|`durable-scope-rekeyed`
409
+ (durable 行 `scope` 被兼容臂重铸,带 `from`/`to`)。行为一字未变 —— 2.0.0 只是把这次重写从静默改成出声。
410
+ - node 绑定(`./node`):`loadSchedulerStore`/`saveSchedulerStore`/`createFileSchedulerStore`(4.0.0 改名,
411
+ 旧名 `fileSchedulerStore` 已删、零别名)、跨进程原子变更
412
+ `mutateSchedulerStore`。
413
+
414
+ --- 4.10 api 契约(主 barrel;细分 `./api/auth` / `./api/auth-bridge` / `./api/scopes`) ---
415
+ - `api/auth`:RFC 8628 设备流 + 旋转 refresh + logout 的 wire 形(snake_case 刻意保留);
416
+ `DeviceCodeRequest/Response`(`verification_uri_complete` 自 `5.0.0` 起**可选**,RFC 8628 §3.2;缺席 ⇒
417
+ 展示 `verification_uri` + `user_code`,在场但为空串仍拒)、`DeviceTokenRequest`、`TokenGrantResponse`、`TokenRefreshRequest`、
418
+ `LogoutRequest/Response`、`DeviceApprove*`、`OAuthErrorResponse`;端点路径与协议常量
419
+ `AUTH_V1_PATHS`、`DEVICE_GRANT_TYPE`、TTL 四件、`OAUTH_ERROR_CODES`、`DEVICE_AUTH_STATUSES` 与纯函数
420
+ `normalizeUserCode`/`nextPollInterval` 也在这里(`3.0.0` 起:曾拆去 `./api/wire` 的那份零依赖子集已删,
421
+ 四个消费仓对现包名自始至终零 import —— 仅 sdk 的同源锚测试经退役包名 `registry-core` 的冻结版仍取它)。`api/scopes` 同理持有 `SCOPES_V1_PATHS`/`SCOPE_CLAIM`。
422
+ - `api/auth-bridge`:worker 验 web-admin 签发用户 JWT 的契约——RS256-only、JWKS(`WEB_ADMIN_JWKS_PATH`,
423
+ kid-miss 强刷 + ≥60s 节流)、时钟偏移 60s(exp / nbf / iat 同一个容忍)、24h 离线宽限后 fail-closed;`VerifierConfig`/`VerifiedIdentity`
424
+ (携 `scope?` 与 `iat?`:签发时刻整数秒原样带出,缺席不铸键,在场而非非负整数 ⇒ 该令牌 malformed)/`AuthBridgeErrorCode` + 纯判定 `shouldForceJwksRefresh`/`jwksWithinOfflineGrace`/
425
+ `classifyTimeClaims`/`toVerifiedIdentity`/`looksLikeJwt`。
426
+ - `api/scopes`:多租户 scope 契约——`Scope`/`ScopeMember`(viewer<editor<publisher<admin)、保留
427
+ `GLOBAL_SCOPE="global"`、JWT `scope` claim(缺席=global);判定纯函数 `resolveTokenScope`/`canSwitchScope`/
428
+ `effectiveScopeRole`/`readTokenScope`(11.0.0:缺 scope 声明 ⇒ `{refused:"scope_claim_missing"}`,不再读成 global);wire 形 `ScopesListResponse`/`ScopeSwitchRequest/Response`/
429
+ `ScopeCreateRequest`/`ScopeMemberUpsert*`。
430
+
431
+ --- 4.10b web-admin 凭证文件契约 + 连接 env 名表(src/web-admin-credential.ts,主 barrel;S-698) ---
432
+ - `SEMA_WEB_ADMIN_TOKEN_FILE` 指向的文件有两种**种类**:裸行(一个静态令牌,与今天同一种文件:首行 = 静态 bearer,
433
+ 只收可打印 ASCII(首尾可见、中间可有空格);是用户 JWT 还是 worker 拉取令牌由读方用 `looksLikeJwt` 自判)与**信封** `WebAdminCredentialEnvelope`
434
+ (`.strict()` 的 JSON 对象:`kind: "user-session"`(常量 `WEB_ADMIN_CREDENTIAL_ENVELOPE_KIND`,闭集今一员)、
435
+ `accessToken`(JWT)/ `refreshToken`(与裸行同一条令牌律)、`expiresAt`(RFC 3339 date-time:
436
+ 秒必备,`Z` 或 `±hh:mm` 偏移且时 00–23 / 分 00–59,不收闰秒)、`issuer`(签发它的 web-admin 的基础 URL,判据 =
437
+ 不是存量的 URL 座位那一只判官 `NON_SECRET_URL_NO_MIGRATION`;同不同源由读方比))。worker 拉取令牌不进信封。
438
+ - `parseWebAdminCredentialFile(text)` → `{kind:"bare", bearer}` | `{kind:"envelope", envelope}` |
439
+ `{kind:"unreadable", reason}`,判别律的**唯一**实现:`trim()`(含 BOM)后空 ⇒ `unreadable("empty")`;以 `{` 开头
440
+ ⇒ 只按信封读,JSON 不成立或 schema 不过 ⇒ `unreadable`(**不回落**裸行);否则裸行,bearer = 第一个 CR / LF
441
+ 之前那段,它不是令牌(含空格 / 控制 / 非 ASCII 字符)⇒ `unreadable`,绝不当 bearer 发出去。`reason` 只点信封字段名 + zod 问题码 + 未知键**个数**,从不回显文件内容。纯函数零 IO:读文件、
442
+ 写文件(tmp + rename 原子替换,mode 0600)、续签都是消费方的事。
443
+ - 声明面(S-742;11.0.0 起并入一张表):`WEB_ADMIN_ENV` = 引擎连 web-admin 读的全部 env 名(`url` / `token` / `tokenFile` / `worker` / `liveUrl` / `webUrl` / `dryRun` / `adminToken` /
444
+ `assertionToken` / `assertionTokenFile`,值 `SEMA_WEB_ADMIN_*`)
445
+ (键名一处定义,管理面渲染 worker env、壳写本机引擎 env、server 读,三方 import 同一份);
446
+ `judgeWebAdminCredentialDeclaration(env, credential)`(`credential` ∈ `WEB_ADMIN_CREDENTIALS` = `access`(`token` / `tokenFile`)|
447
+ `assertion`(`assertionToken` / `assertionTokenFile`),必填、没有缺省;JS 漏传 ⇒ 响亮拒)→ `{from:"env", token}` | `{from:"file", path}` | `{from:"absent"}`,
448
+ 每份凭证二选一规则的唯一实现:空串 = 未设;**一对里两个都设(非空)⇒ throw**(拒句点名两键、不回显值,各份同一句式);`path` 原样(相对
449
+ 路径按 cwd 解析是读方的事);只读那一对键(另一份凭证的键也不看)。
450
+ - 会话网页链接(src/web-admin-session-url.ts,主 barrel):`webAdminSessionUrl(webBaseUrl, { worker, sessionId })` = web-admin 会话链接的唯一模板,形
451
+ `<webBaseUrl>/chat?agent=<worker>&session=<sessionId>`(聊天页只读这两个查询参数;只带 session 的链接会落到页面自己挑的 worker 上、可能找不到会话)。
452
+ 两位必填(空 / 缺位 / 非串 ⇒ 抛 TypeError)、逐位 `encodeURIComponent`(含 `& # = ?` 的 id 不串位;孤立代理项 ⇒ URIError)、基址须是干净的绝对 http(s) URL
453
+ (无 userinfo / query / fragment;可带路径前缀;尾斜杠折掉)、返回绝对 URL。配对解析 `parseWebAdminSessionUrl(link)`:收绝对 / 相对串、`URL`、`URLSearchParams`;
454
+ 宽读 —— 缺的或空的那一位不出现在结果里;重复参数取第一个;路径与别的参数不看;串不是 URL ⇒ 抛 TypeError。链接没有 scope 位;日后加 scope 作可选第三位、同一函数。
455
+ - 退役 env 名墓碑表(src/retired-envs.ts,主 barrel):`RETIRED_ENVS`(字面元组一行一个旧名;三族闭集 renamed / replaced / removed;冻结)
456
+ —— 旧名在场即拒启,替代名设没设都一样(空串 = 未设);永久,不设到期;链只有一跳(去向恒是现名)。拒启句唯一铸点
457
+ `retiredEnvRefusal(env)`(纯,读传入的 env;码 `RETIRED_ENV_REFUSAL_CODE` = `config.env_key_retired`;只点名键不回显值);
458
+ 目录 wire 投影 `buildRetiredEnvWire()`。引擎起服 / 壳起本机引擎 / web-admin 渲染 worker env 判同一张表。
459
+
460
+ --- 4.11 Node-only(`./node`:file-store / file-edit / bundle / local-load / scheduler-store-node / 目录迁移) ---
461
+ - `loadLocalConfig(root, {onWarning}?)` → `LocalConfig { effective, remoteExec, env, requiredEnv, warnings }`。
462
+ - `loadRemoteExec(root)`(三态,§8 T3)、`writeRemoteExec`(校验+原子写)、`remoteExecPath`、`loadEnvFile`
463
+ (`.env` merge over process.env,file 赢,支持 `KEY=file:<path>`)。
464
+ - `FileConfigStore`(实现 `ConfigReader`;`getEffective({tolerant:true})` → `TolerantEffective
465
+ { effective, domainErrors }`:catalog 域坏文件单列降级,gate 域 fail-loud);`PORTABLE_DOMAINS`(11 域:
466
+ models/rosters/skills/mcp/plugins/scenarios/collab/governance/execution/prompts/limits;`config.d/runtime.json` 只为墓碑读,带事实即拒)、
467
+ `NON_PORTABLE_DOMAIN_REASONS`(workers/hosts/systems/projects/entitlement 的排除理由,编译期双向咬死)。
468
+ - `validateDomain`/`validateRemoteExec` → `ValidateResult`(不 throw 用户数据)、`writeDomainFile`/
469
+ `domainFilePath`、`listRequiredEnvNames` → `RequiredEnvName[]`。
470
+ - `exportBundle`/`writeBundle`/`readBundle`(`ConfigBundle` 的键 = `PortableDomain`;11.0.0 起三只函数类型层只收可搬域,非便携域在运行期也拒)。
471
+ - 目录形一次性迁移:`migrateConfigDirectory(dir, { documents, io, check? })` → `ConfigDirectoryMigration`(闭集 `kind`:`unreadable`〔读不了目录 / 文件,或某份文档是坏文件;`reason`〕·
472
+ `no-documents`〔覆盖面内一份文档都没有;带回 `entries` 目录清单〕· `refused`〔迁移步拒或整域终判拒;`refused[]` + `why`〕· `noop` · `pending`〔`check: true` 有改动〕· `applied`〔`written` / `removed` 文件名〕)。
473
+ 语义 = 命令行 `migrate` 的那一条(读目录 → 迁 → 整域终判 → 先写后删 → 原子写),命令行是它的渲染器;`documents` = 调用方读的域名集(覆盖面外不读不判不写不删;拼错的名字抛 `TypeError`;
474
+ 迁 retired 的 `runtime` 要把 `governance` 与 `limits` 一并放进覆盖面,否则 `runtime` 按 `partial` 拒迁、一字不写;迁移新建覆盖面外的文档 ⇒ 抛);
475
+ `io: ConfigDirectoryIo`(`readDir` / `readFile` / `writeFileAtomic` / `removeFile`),真绑定 `nodeConfigDirectoryIo`(本包唯一的原子写器,`config-document` 模式规则;域文件是符号链接则写到真文件);写盘阶段的 I/O 错误原样抛出。
476
+
477
+ --- 4.12 迁移工具(主 barrel) ---
478
+ - 0.5 → 0.7 的迁移工具(`./migrate` 出口:hosts grandfathering、worker.model 归并计划 / 执行 / diff 门、teams → collab)
479
+ 11.0.0 起删除(窗口早过,全生态零生产读点)。
480
+ - `migrateWorkersDomain(rawDoc) → { doc, changed, dropped, refused }`(7.0.0,主 barrel,src/migrate-workers.ts):6.x workers 文档
481
+ 一次性抬升到两个结构块;纯、幂等(已迁形同一引用返回);`refused`(不进 `doc`,带 `index` / `field` / `original` 逐字原行 ——
482
+ 写回 `doc` 前须按 `index` 放回)= 迁不了的 worker
483
+ (enabled 而无 `env.REMOTE_EXEC` 不替运维猜车道);终判**逐行**(8.2.0 起迁过的行与没动的行同一只)= 只含这一行的 workers 域读不读得进
484
+ (读侧的 `WorkersConfig`,不另抄保留名表),读不进 ⇒ `refused`;整域读侧只剩认不出名字的条目与跨行唯一性两类。文件形 = bin
485
+ `npx @sema-agent/settings-schema migrate-workers <workers.json> [--check]`(bin/settings-schema.mjs;有 refused 一字不写)。有删除窗。
486
+ 7.0.1 起 CLI stdout 恰两行:结论行 `outcome=<refused|applied|noop|pending> written=<true|false>` + JSON(取最后一行);`changed` / `dropped`
487
+ 仅 `written=true` 时存在,没写时是 `wouldChange` / `wouldDrop`。拒因里的迁移指路 = 一个常量(`npx …` 全名形),只给迁移能搬的键(被删旧键、
488
+ 车道键、`DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND`),`PORT` / `MODEL_*` 之类不带。8.0.0 起 `--check` 与真跑过同一只
489
+ 判定(逐只 refused → noop → 整域终判 → 有改动;`--check` 只决定写不写):整域会被拒的文档 `--check` 也报 `refused` / 退出 1,
490
+ `refused[]` 逐条带出被拒项(条目没有字符串名字时 `name` 为 null)。(src/cli.ts;src/execution-lane-index.ts)
491
+
492
+ --- 4.13 错误契约 ---
493
+ - **throw**(ZodError 或 Error):`parseDomain`/`buildEffective`/`loadLocalConfig`/`FileConfigStore.get*`
494
+ (非 tolerant)/`loadRemoteExec`/`writeDomainFile`/`writeRemoteExec`/`stableStringify`(超深/循环)。
495
+ ZodError 一律经 `sanitizeZodError` 脱敏(不回显粘错位置的 secret)。
496
+ - **返回错误对象**:`validateDomain`/`validateRemoteExec` → `{ok:false, issues}`;`refIntegrityIssues` →
497
+ `RefIssue[]`;tolerant 读 → `domainErrors[]`;`CasWriteResult`(web-admin store 行为,本包只导出类型)。
498
+
499
+ ================================================================================
500
+ §5 配置与环境变量 / secret 边界
501
+ ================================================================================
502
+
503
+ 5.1 本包**自身**无运行时 env(纯 lib)。`CONFIG_PROVIDER`/`CONFIG_LOCAL_DIR`/`CONFIG_CENTER_URL` 等是
504
+ 消费方约定(docs/CONSUMER-INTEGRATION.md),本包源码不读它们。【文档级证据】
505
+
506
+ 5.2 secret 边界(schema 强制):
507
+ - 所有 `*Env`/`apiKeyEnv`/`tokenEnv` 字段 = env 变量 **NAME**(`ENV_NAME` 正则,拒 AKIA 形);粘值被拒。
508
+ - 自由值字段(worker env、model extraBody / compat / headers、mcp stdio command/args、URL query、插件源、ssh 登录、车道坐标、覆盖值)经 `containsSecretToken`
509
+ 拒 secret 形状(含 percent-decode/JWT/Bearer;私钥 PEM 头 10.0.0 起在串中任意位置都拒,公钥 / 证书头不拒)。拒句是固定句,不回显值(zod issue 的 `path` 指到字段)。
510
+ - 密钥托管:`ModelEntry.sealedApiKey`(`SEALED_BOX_ALG` = libsodium sealedbox x25519,浏览器端封箱,
511
+ write-only)+ `execution.publicKeys` 公钥登记——中央**不存可解明文**。
512
+ - 错误不回显值:`sanitizeZodError`/`redactSecrets`;文件解析错误不回显原文。
513
+ - 值住哪:`.env`(file 赢)或 `KEY=file:<path>`(缺/空文件抛错 fail-closed)。
514
+
515
+ ================================================================================
516
+ §6 对外入口地图(26 个 subpath)
517
+ ================================================================================
518
+
519
+ - `.`(主 barrel,浏览器安全):types / hash / sha256 / skills-manifest / secret-refs / resolve-roster /
520
+ config-fns / reader / cross-domain / remote-exec / safety-merge-spec / scheduler-store / hooks / fleet /
521
+ api·auth / api·auth-bridge / api·scopes / api·pull-token / api·feedback 的全部导出,7.0.0 加 execution-lanes,11.0.0 加 workers-rows(`readWorkersRows`,workers 域的
522
+ 逐行终判)、墓碑(`RETIRED_DOMAINS` / `RetiredDomainError`)与 web-admin-env / web-admin-credential / retired-envs。(src/index.ts)
523
+ - `./one-time-migrations`(11.0.0,不进主 barrel):一次性迁移族 —— `migrateStoredDocuments(docs, { coverage })` / `migrateEffectiveSnapshot` /
524
+ `migrateSchedulerRecords`、登记表 `ONE_TIME_MIGRATIONS`(每条的窗、删法、删后由谁接住)、收据形 `MigrationReceipt`(闭集 `appliedAs`)。
525
+ 纯函数,不抬任何版本号;按登记表的窗整族删。(src/one-time-migrations.ts)
526
+ - `./node`(Node-only,拉 node:fs):file-store / file-edit / bundle / local-load / scheduler-store-node / 目录形一次性迁移
527
+ (`migrateConfigDirectory` + `nodeConfigDirectoryIo`)。(prompts 内容摘要 `promptsContentDigest` / `sectionContentHash` 14.0.0 随声明轴删除。)
528
+ 浏览器 bundle 绝不从这里 import。(src/node.ts)
529
+ - `./conformance`(3.0.0,Node-only):跨仓一致性门 kit —— `runConformance(adapter, {mode})` 让消费方把
530
+ **自己的生产入口**塞进来跑随包发的金样语料(`conformance/cases/**`),证同一份 settings 在几方读出的
531
+ effective 与告警多重集逐字一致。四条腿:`domains` / `strictWrite`(8.0.0,严写受理:契约仓 = `parseDomainLoud` + 跨域引用完整性〔11.0.0;
532
+ 悬空引用 ⇒ `rejected{validate, code:"ref-integrity"}` 带 `refIssues` 坐标〕,
533
+ 任一份不受理 ⇒ `rejected{validate}`)/ `wire` / `localRoot`;适配器对每条腿都要表态 —— 实现,或
534
+ `{ declined: "no-production-path", why }`(缺表态 = release 违例)。`release` / `historical` 两模式;四律(对抗/变异/覆盖/来源)见
535
+ docs/CONFORMANCE.md。不进主 barrel(语料在磁盘上)。(src/conformance/index.ts)
536
+ - 细分(精确 import / tree-shake):`./types` `./hash` `./sha256` `./skills-manifest` `./secret-refs`
537
+ `./execution-lanes` `./resolve-roster` `./config-fns` `./reader` `./cross-domain` `./remote-exec` `./safety-merge-spec`
538
+ `./one-time-migrations` `./scheduler-store` `./hooks` `./fleet` `./conformance` `./api/auth` `./api/auth-bridge`
539
+ `./api/scopes` `./api/pull-token` `./api/feedback`。
540
+ - **资产出口** `./package.json`(3.0.0):exports 表一旦在场就**挡住所有没登记的子路径**,而消费方接 kit
541
+ 要 `require.resolve("@sema-agent/settings-schema/package.json")` 拿 provenance(装到的是哪一份包) ——
542
+ 不放行它,docs/CONFORMANCE.md §1 的接线配方第一行就抛 `ERR_PACKAGE_PATH_NOT_EXPORTED`。
543
+ - **资产出口** `./fleet-paths.json`(8.2.0)→ `dist/fleet-paths.json`:`{ FLEET_PATHS, FLEET_LIVE_PATHS }` 两只 fleet 端点常量的 JSON 形,
544
+ `npm run build` 在 tsc 之后由 `scripts/gen-fleet-paths.mjs` 从构建出的 `dist/fleet.js` 写出(不手改);给 import 不了 TS 的工具
545
+ (Helm 模板、冒烟脚本)对表用。`test/fleet-8.2.0.test.ts` 钉它与常量逐字同。
546
+ file-store/file-edit/bundle/local-load **没有**各自细分路径,只能经 `./node`。
547
+ (package.json: exports;test/package-surface.test.ts 钉表↔源一致)
548
+
549
+ ================================================================================
550
+ §7 发版纪律(1.x semver)与契约演进
551
+ ================================================================================
552
+
553
+ - **包版本即 schema 版本**。自 1.0.0 起标准 semver:破坏性(改名/改形/删除/默认值变更/校验收紧或放宽/
554
+ 枚举重排)= **major**;新键/新导出/词表尾插新词 = **minor**(穷举表消费方提货时可能编译红 = 提货成本,
555
+ 发布走显式表态制点名);文案/注释/测试/bug 修复 = **patch**。0.x 时代「破坏性必 bump minor」已撤,仅史注。
556
+ (README「发版纪律(semver,1.x)」)
557
+ - 加性字段一律 `.optional()` 无默认(默认值会静默覆盖部署 env 闸,§8 T7)。
558
+ - etag/version:任意写 bump `version`;`resolveEffectiveForWorker` 的 etag 见 §3.3。
559
+
560
+ ================================================================================
561
+ §8 陷阱 / 反直觉点 / 误用模式
562
+ ================================================================================
563
+
564
+ T1. **EffectiveConfig ≠ EffectiveWire**:本地是全文(skills.content 内联),远程是 manifest + rosters/
565
+ entitlement 剥离。别假设本地结果是 wire 形状。(§3.2)
566
+ T2. **`./node` 污染浏览器 bundle**:纯契约只 import `.`。
567
+ T3. **三态文件**(remote-exec.json / config.d/*.json):缺失→默认;存在→赢;空或损坏→抛错(fail-closed)。
568
+ "想要默认就删文件,不是留空文件"。
569
+ T4. **`null` ≠ absent**:`parseDomain(d, null)` = 损坏抛错,只有 `undefined`(文件缺失)才填默认。
570
+ T5. **`governance.commandPolicy` 的 command 是精确 argv[0] 名**,glob/path/带参数会被 fail-loud 拒收
571
+ (消费端引擎是精确 Set 匹配,glob 永不匹配 = 静默放行 fail-open,所以 schema 层就拒)。DENY-WINS。
572
+ T6. **governance 是 DECLARATION**:autonomy 经引擎收紧-only 展开;commandPolicy 必须 combine 进
573
+ toolPolicy,绝不裸赋值(会静默清部署护栏)。展开在消费侧,本包只给 schema + 拒收规则。
574
+ T7. **`.optional()` 无默认是有意的**:governance/limits 多数键 ABSENT = "web-admin 不管,保留你的 env";
575
+ 显式 0 与 absent hash-distinct(0 = block-all)。
576
+ T8. **roster 解析容错**(dangling → global fallback),PUT 时才用 `refIntegrityIssues` 严判。
577
+ T9. **`atMention` 的义由 `mode` 说**(11.0.0 取代 `atModelAllowlist`):`only` 收窄到一个都不剩 ⇒ `{mode:"none"}`(`narrowAtMention`,一只读法)。
578
+ T10. **worker.env 保留键**:`RESERVED_WORKER_ENV` 在 env/secretEnv 里被拒(用结构化字段)。7.0.0 起执行车道键
579
+ (REMOTE_EXEC / REQUIRE_EXECUTION_LANE / 车道表全部名)与店键(DB_BACKEND / MEMORY_ENGINE*)也在保留集 —— 写到
580
+ `runtime.execution` / `runtime.store`;别的域也读的名(MINIO_* / LOG_LEVEL)仍可透传。
581
+ T11. **`remote-exec` 不是域**:用 `writeRemoteExec`/`loadRemoteExec`,喂给 `writeDomainFile` 会 fail-loud。
582
+ T12. **provider↔api 配对强制**(ModelEntry)。
583
+ T13. **三态 cap(entitlement.runtimeCaps 全族)**:ABSENT ≠ false;缺席**不下发键**(引擎是唯一默认源,
584
+ 补 false = 自造"web-admin 说过话"的显式判决)。`allowObservers`/`autoMode` 引擎侧 `=== true` 显式
585
+ opt-in(极性与 allow* 相反);`allowMemoryOptOut` 隐私轴故障过境:故障 = 键缺席,绝不铸 false。
586
+ T14. **open-world 域只有 limits(+caps wire 的 runtimeCaps/budget)**:其余域默认 strip,未知键被剥并经
587
+ `"unknown-keys-dropped"` 点名;limits 的未知键**承运**并经 `"unknown-keys-carried"` 点名。
588
+ T15. **mcp.contentOrigin 是信任声明不是路由提示**:只对部署自己跑的进程/服务声明 `"local"`;表外值
589
+ parse 拒(大小写不折);缺席 = 引擎按协议命名空间判 external(fail-closed)。
590
+
591
+ ================================================================================
592
+ §9 运行时现实
593
+ ================================================================================
594
+
595
+ - 本地跑/引入:`npm run build` 后 import;参考闭环 `node examples/toc-e2e.mjs`(=`npm run e2e`),无需
596
+ web-admin/service/k8s。
597
+ - env 必填项:无;消费方的 secret 值经 `.env`/`process.env`/`file:` 提供,`requiredEnv[].present=false`
598
+ 标缺失。
599
+ - 本地 vs 生产:本地全文 EffectiveConfig;fleet 走 EffectiveWire(manifest + 懒拉 + 剥离)。同一份
600
+ schema 同一 resolver,不 drift。
601
+ - ESM only,Node `>=20.3.0`,zod `^4.4.0`(deps+peer)。
602
+
603
+ ================================================================================
604
+ §10 典型使用场景配方
605
+ ================================================================================
606
+
607
+ R1. 本地加载喂引擎:`loadLocalConfig(root)`(`./node`)→ effective 是全文形。常见错:从 `.` import 它;
608
+ 把 effective.skills 当 manifest。
609
+ R2. 解析某 worker 的 wire 载荷:`resolveEffectiveForWorker(eff, name)` → config 是 EffectiveWire。
610
+ 常见错:再去读 skills.content(已换 contentHash)。
611
+ R3. 校验用户输入不抛异常:`validateDomain("models", raw)`(`./node`)→ ValidateResult。
612
+ R4. doctor 面板列缺失 secret:`loadLocalConfig(root).requiredEnv` 渲染 `present=false`。
613
+ R5. fleet 配置种到本地/反向:`exportBundle`/`writeBundle`/`readBundle`(remote-exec 不进 bundle,用
614
+ `writeRemoteExec`)。
615
+ R6. 复用 secret 边界:直接用 `ENV_NAME`/`NON_SECRET_URL`/`containsSecretToken`,别自写正则。
616
+ R7. ETag/漂移检测:`stableHash`;skills 内容寻址:`skillContentHash`/`sha256Hex`(prompts 的身份是引擎铸的 `artifactDigest`,本包不铸)。
617
+ R8. 消费端读 wire:`readEffectiveWire(raw, onWarning)`(容错极性是裁定过的语义,别自己 parse 全量域)。
618
+ R9. settings resolver:类别合并律读 `SAFETY_MERGE_SPEC` + `trustRank`/`isAtLeast`/`memberAdmission`,层准入读 `layerAdmission` +
619
+ `SETTINGS_SOURCE_CONTRACT`(查口 `settingsSourceContract`),禁自铸 rank/成员/层档表;hooks 坏形读 `classifyHooksConfig`,禁自铸判官。
620
+
621
+ ================================================================================
622
+ §11 如何确认用对了
623
+ ================================================================================
624
+
625
+ - `loadLocalConfig` 成功:`{ effective, remoteExec, env, requiredEnv, warnings }`;
626
+ `effective.skills.skills[i].content` 是全文(不是 `sha256:` 前缀)。
627
+ - `resolveEffectiveForWorker` 成功:`config.skills.skills[i]` 有 `contentHash`(无 content);
628
+ `config.rosters.rosters === []`;`rosterLabel` ∈ {具体名, "global", "global(fallback)"}。
629
+ - 常见错误消息:
630
+ - "is empty/whitespace — delete it for the domain default" → 留了空文件(T3)。
631
+ - "must be an ENV var NAME (A-Z, 0-9, _; not a value)" → secret 值粘进 NAME 字段(§5.2)。
632
+ - "exact argv[0] command NAME … NO glob" → commandPolicy 写了通配/路径(T5)。
633
+ - "is not a config domain … use writeRemoteExec" → 把 remote-exec 当域(T11)。
634
+ - contentOrigin 的 enum 拒收 → 信任词拼写/大小写不在闭词表(T15)。
635
+
636
+ ================================================================================
637
+ §12 限制与不支持的用法
638
+ ================================================================================
639
+
640
+ - 不携带任何 secret 值——只存 env-NAME / `file:` ref / sealed-box 密文(write-only)。
641
+ - 无 CLI/bin、无 HTTP 服务端——`/effective`、`/api/v1/auth/*`、`/api/v1/scopes*`、publish gate、fleet
642
+ auto-placement 都在 web-admin;本包只给 wire 形 + 纯判定函数。
643
+ - `./node` 非浏览器安全。
644
+ - 单机本地为 v1 单根:无 managed>local>project 分层(分层语义走 safety-merge-spec 的消费方 resolver)。
645
+ - `remoteExec` 是前向超集:通过校验 ≠ 目标服务版本已实现该 provider。
646
+ - `stableHash`/`skillContentHash` 非 MAC/密码学用途。
647
+ - fleet-only 域(workers/hosts/systems)与 projects/entitlement 在单机不便携(理由表
648
+ `NON_PORTABLE_DOMAIN_REASONS`);缺席解析为 schema 默认空。
649
+
650
+ ================================================================================
651
+ 附:证据强度与建议人工核对
652
+ ================================================================================
653
+
654
+ - §5.1 的消费方 env 名来自 docs/CONSUMER-INTEGRATION.md,本包源码不读这些 env。
655
+ - §8 T6 的"如何展开"是消费侧(引擎)行为;本包只提供 schema + 拒收规则。
656
+ - HTTP 端点由 web-admin 实现,本包不含;路径常量的真源是 `api/auth`(`AUTH_V1_PATHS`)与 `api/scopes`
657
+ (`SCOPES_V1_PATHS`)。
658
+ - 本档由人手写,无生成脚本;域数/hook 事件数/subpath 数/版本行由 test/docs-hygiene.test.ts D 组与
659
+ 运行时表对钉,其余内容以 src 导出面为准。