@sema-agent/settings-schema 2.1.0 → 3.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 (183) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +35 -4
  3. package/conformance/VERSION +1 -0
  4. package/conformance/baseline.json +55 -0
  5. package/conformance/cases/c01-entry-salvage-then-typo/expected.json +105 -0
  6. package/conformance/cases/c01-entry-salvage-then-typo/input.json +22 -0
  7. package/conformance/cases/c01-entry-salvage-then-typo/meta.json +10 -0
  8. package/conformance/cases/c02-hosts-clamp-stacked/expected.json +96 -0
  9. package/conformance/cases/c02-hosts-clamp-stacked/input.json +21 -0
  10. package/conformance/cases/c02-hosts-clamp-stacked/meta.json +10 -0
  11. package/conformance/cases/c03-literal-dotted-top-key/expected.json +112 -0
  12. package/conformance/cases/c03-literal-dotted-top-key/input.json +26 -0
  13. package/conformance/cases/c03-literal-dotted-top-key/meta.json +5 -0
  14. package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +77 -0
  15. package/conformance/cases/c04-all-bad-collection-not-salvaged/input.json +18 -0
  16. package/conformance/cases/c04-all-bad-collection-not-salvaged/meta.json +5 -0
  17. package/conformance/cases/c05-gate-domain-bad-entry-throws/expected.json +16 -0
  18. package/conformance/cases/c05-gate-domain-bad-entry-throws/input.json +18 -0
  19. package/conformance/cases/c05-gate-domain-bad-entry-throws/meta.json +5 -0
  20. package/conformance/cases/c06-legacy-governance-lift/expected.json +99 -0
  21. package/conformance/cases/c06-legacy-governance-lift/input.json +20 -0
  22. package/conformance/cases/c06-legacy-governance-lift/meta.json +9 -0
  23. package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +72 -0
  24. package/conformance/cases/c07-hooks-is-not-a-config-domain/input.json +43 -0
  25. package/conformance/cases/c07-hooks-is-not-a-config-domain/meta.json +6 -0
  26. package/conformance/cases/c08-limits-unknown-carried-64/expected.json +336 -0
  27. package/conformance/cases/c08-limits-unknown-carried-64/input.json +74 -0
  28. package/conformance/cases/c08-limits-unknown-carried-64/meta.json +5 -0
  29. package/conformance/cases/c09-limits-unknown-carried-65/expected.json +338 -0
  30. package/conformance/cases/c09-limits-unknown-carried-65/input.json +75 -0
  31. package/conformance/cases/c09-limits-unknown-carried-65/meta.json +7 -0
  32. package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +106 -0
  33. package/conformance/cases/c10-record-key-secret-redacted-twice/input.json +29 -0
  34. package/conformance/cases/c10-record-key-secret-redacted-twice/meta.json +13 -0
  35. package/conformance/cases/c11-unicode-unknown-keys/expected.json +88 -0
  36. package/conformance/cases/c11-unicode-unknown-keys/input.json +13 -0
  37. package/conformance/cases/c11-unicode-unknown-keys/meta.json +5 -0
  38. package/conformance/cases/c12-prototype-name-top-keys/expected.json +72 -0
  39. package/conformance/cases/c12-prototype-name-top-keys/input.json +20 -0
  40. package/conformance/cases/c12-prototype-name-top-keys/meta.json +11 -0
  41. package/conformance/cases/c13-models-field-null/expected.json +77 -0
  42. package/conformance/cases/c13-models-field-null/input.json +11 -0
  43. package/conformance/cases/c13-models-field-null/meta.json +5 -0
  44. package/conformance/cases/c14-models-field-empty-array/expected.json +72 -0
  45. package/conformance/cases/c14-models-field-empty-array/input.json +11 -0
  46. package/conformance/cases/c14-models-field-empty-array/meta.json +5 -0
  47. package/conformance/cases/c15-models-field-number/expected.json +77 -0
  48. package/conformance/cases/c15-models-field-number/input.json +11 -0
  49. package/conformance/cases/c15-models-field-number/meta.json +5 -0
  50. package/conformance/cases/c16-deep-nesting-205/expected.json +83 -0
  51. package/conformance/cases/c16-deep-nesting-205/input.json +421 -0
  52. package/conformance/cases/c16-deep-nesting-205/meta.json +5 -0
  53. package/conformance/cases/c17-empty-domain-file/expected.json +73 -0
  54. package/conformance/cases/c17-empty-domain-file/input.json +10 -0
  55. package/conformance/cases/c17-empty-domain-file/meta.json +5 -0
  56. package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +74 -0
  57. package/conformance/cases/c18-wire-envelope-not-an-object/input.json +8 -0
  58. package/conformance/cases/c18-wire-envelope-not-an-object/meta.json +5 -0
  59. package/conformance/cases/c19-wire-envelope-no-version/expected.json +74 -0
  60. package/conformance/cases/c19-wire-envelope-no-version/input.json +13 -0
  61. package/conformance/cases/c19-wire-envelope-no-version/meta.json +5 -0
  62. package/conformance/cases/c20-governance-absent-lifts/expected.json +84 -0
  63. package/conformance/cases/c20-governance-absent-lifts/input.json +11 -0
  64. package/conformance/cases/c20-governance-absent-lifts/meta.json +9 -0
  65. package/conformance/cases/c21-governance-empty-object-wins/expected.json +72 -0
  66. package/conformance/cases/c21-governance-empty-object-wins/input.json +12 -0
  67. package/conformance/cases/c21-governance-empty-object-wins/meta.json +5 -0
  68. package/conformance/cases/c22-governance-null-throws/expected.json +16 -0
  69. package/conformance/cases/c22-governance-null-throws/input.json +9 -0
  70. package/conformance/cases/c22-governance-null-throws/meta.json +5 -0
  71. package/conformance/cases/c23-mixed-validity-models/expected.json +106 -0
  72. package/conformance/cases/c23-mixed-validity-models/input.json +27 -0
  73. package/conformance/cases/c23-mixed-validity-models/meta.json +9 -0
  74. package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +100 -0
  75. package/conformance/cases/c24-valid-entries-with-unknown-keys/input.json +25 -0
  76. package/conformance/cases/c24-valid-entries-with-unknown-keys/meta.json +5 -0
  77. package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +89 -0
  78. package/conformance/cases/c25-published-roundtrip-zero-warnings/input.json +24 -0
  79. package/conformance/cases/c25-published-roundtrip-zero-warnings/meta.json +5 -0
  80. package/conformance/cases/c26-hosts-array-1024/expected.json +7241 -0
  81. package/conformance/cases/c26-hosts-array-1024/input.json +3084 -0
  82. package/conformance/cases/c26-hosts-array-1024/meta.json +5 -0
  83. package/conformance/cases/c27-hosts-array-1025/expected.json +7248 -0
  84. package/conformance/cases/c27-hosts-array-1025/input.json +3087 -0
  85. package/conformance/cases/c27-hosts-array-1025/meta.json +5 -0
  86. package/conformance/cases/c28-large-document-over-1mb/expected.json +82 -0
  87. package/conformance/cases/c28-large-document-over-1mb/input.json +11 -0
  88. package/conformance/cases/c28-large-document-over-1mb/meta.json +5 -0
  89. package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +77 -0
  90. package/conformance/cases/c29-salvage-then-domain-refine-fails/input.json +27 -0
  91. package/conformance/cases/c29-salvage-then-domain-refine-fails/meta.json +5 -0
  92. package/conformance/cases/c30-version-triple-a-first/expected.json +85 -0
  93. package/conformance/cases/c30-version-triple-a-first/input.json +18 -0
  94. package/conformance/cases/c30-version-triple-a-first/meta.json +5 -0
  95. package/conformance/cases/c31-version-triple-b-changed/expected.json +85 -0
  96. package/conformance/cases/c31-version-triple-b-changed/input.json +18 -0
  97. package/conformance/cases/c31-version-triple-b-changed/meta.json +5 -0
  98. package/conformance/cases/c32-version-triple-a-restored/expected.json +85 -0
  99. package/conformance/cases/c32-version-triple-a-restored/input.json +18 -0
  100. package/conformance/cases/c32-version-triple-a-restored/meta.json +5 -0
  101. package/conformance/cases/c33-limits-nested-carried-key/expected.json +91 -0
  102. package/conformance/cases/c33-limits-nested-carried-key/input.json +14 -0
  103. package/conformance/cases/c33-limits-nested-carried-key/meta.json +5 -0
  104. package/conformance/cases/c34-skills-content-vs-manifest/expected.json +160 -0
  105. package/conformance/cases/c34-skills-content-vs-manifest/input.json +18 -0
  106. package/conformance/cases/c34-skills-content-vs-manifest/meta.json +5 -0
  107. package/conformance/cases/c35-portable-domain-file-is-null/expected.json +76 -0
  108. package/conformance/cases/c35-portable-domain-file-is-null/input.json +9 -0
  109. package/conformance/cases/c35-portable-domain-file-is-null/meta.json +5 -0
  110. package/conformance/cases/c36-skills-entry-without-content/expected.json +144 -0
  111. package/conformance/cases/c36-skills-entry-without-content/input.json +16 -0
  112. package/conformance/cases/c36-skills-entry-without-content/meta.json +5 -0
  113. package/conformance/cases/c37-skills-field-null/expected.json +134 -0
  114. package/conformance/cases/c37-skills-field-null/input.json +9 -0
  115. package/conformance/cases/c37-skills-field-null/meta.json +5 -0
  116. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +72 -0
  117. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/input.json +11 -0
  118. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/meta.json +5 -0
  119. package/conformance/cases/c39-hidden-file-in-config-d/expected.json +72 -0
  120. package/conformance/cases/c39-hidden-file-in-config-d/input.json +11 -0
  121. package/conformance/cases/c39-hidden-file-in-config-d/meta.json +5 -0
  122. package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +112 -0
  123. package/conformance/cases/cc01-mcp-toolfaces-non-object/input.json +39 -0
  124. package/conformance/cases/cc01-mcp-toolfaces-non-object/meta.json +8 -0
  125. package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +85 -0
  126. package/conformance/cases/cc02-mcp-source-key-is-transported/input.json +20 -0
  127. package/conformance/cases/cc02-mcp-source-key-is-transported/meta.json +8 -0
  128. package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +89 -0
  129. package/conformance/cases/cc03-model-compat-unknown-subkey/input.json +22 -0
  130. package/conformance/cases/cc03-model-compat-unknown-subkey/meta.json +8 -0
  131. package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +102 -0
  132. package/conformance/cases/cli01-model-name-nfc-vs-nfd/input.json +30 -0
  133. package/conformance/cases/cli01-model-name-nfc-vs-nfd/meta.json +9 -0
  134. package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +115 -0
  135. package/conformance/cases/cli02-control-and-zero-width-chars/input.json +42 -0
  136. package/conformance/cases/cli02-control-and-zero-width-chars/meta.json +9 -0
  137. package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +92 -0
  138. package/conformance/cases/cli03-credential-pasted-into-unknown-key/input.json +16 -0
  139. package/conformance/cases/cli03-credential-pasted-into-unknown-key/meta.json +9 -0
  140. package/conformance/cases/cli04-env-value-with-newline/expected.json +93 -0
  141. package/conformance/cases/cli04-env-value-with-newline/input.json +23 -0
  142. package/conformance/cases/cli04-env-value-with-newline/meta.json +5 -0
  143. package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +109 -0
  144. package/conformance/cases/cli05-long-and-prefix-colliding-names/input.json +30 -0
  145. package/conformance/cases/cli05-long-and-prefix-colliding-names/meta.json +5 -0
  146. package/conformance/cases/z01-every-domain-empty-object/expected.json +72 -0
  147. package/conformance/cases/z01-every-domain-empty-object/input.json +25 -0
  148. package/conformance/cases/z01-every-domain-empty-object/meta.json +5 -0
  149. package/conformance/cases/z02-every-domain-unknown-key/expected.json +228 -0
  150. package/conformance/cases/z02-every-domain-unknown-key/input.json +59 -0
  151. package/conformance/cases/z02-every-domain-unknown-key/meta.json +5 -0
  152. package/conformance/historical/settings-schema@1.0.0.json +999 -0
  153. package/conformance/historical/settings-schema@1.10.0.json +999 -0
  154. package/conformance/historical/settings-schema@1.5.0.json +999 -0
  155. package/conformance/historical/settings-schema@2.0.0.json +999 -0
  156. package/conformance/historical/settings-schema@2.1.0.json +999 -0
  157. package/dist/api/auth.d.ts +42 -1
  158. package/dist/api/auth.js +71 -4
  159. package/dist/api/scopes.d.ts +9 -1
  160. package/dist/api/scopes.js +8 -2
  161. package/dist/config-fns.d.ts +56 -24
  162. package/dist/config-fns.js +89 -72
  163. package/dist/conformance/canonical.d.ts +36 -0
  164. package/dist/conformance/canonical.js +114 -0
  165. package/dist/conformance/contract-adapter.d.ts +20 -0
  166. package/dist/conformance/contract-adapter.js +74 -0
  167. package/dist/conformance/corpus.d.ts +73 -0
  168. package/dist/conformance/corpus.js +99 -0
  169. package/dist/conformance/derive.d.ts +48 -0
  170. package/dist/conformance/derive.js +136 -0
  171. package/dist/conformance/index.d.ts +22 -0
  172. package/dist/conformance/index.js +22 -0
  173. package/dist/conformance/run.d.ts +27 -0
  174. package/dist/conformance/run.js +424 -0
  175. package/dist/conformance/types.d.ts +288 -0
  176. package/dist/conformance/types.js +4 -0
  177. package/dist/conformance/warnings.d.ts +88 -0
  178. package/dist/conformance/warnings.js +144 -0
  179. package/dist/file-store.d.ts +7 -0
  180. package/dist/file-store.js +6 -2
  181. package/package.json +7 -5
  182. package/dist/api/wire.d.ts +0 -60
  183. package/dist/api/wire.js +0 -89
@@ -0,0 +1,288 @@
1
+ /**
2
+ * 跨仓一致性门(conformance kit)的**形**:一套语料、一个比对器、N 条读腿。
3
+ *
4
+ * 成案:2.0.0 / 2.1.0 两版的正确性靠「表态 + 黑盒测试 + 提货车各自红先」,三样都是**单仓视角** ——
5
+ * 真跨仓分歧是被提货车**碰巧**撞出来的。碰巧不是门。本 kit 让每个消费方把**自己的生产读腿**塞进来跑
6
+ * 同一份语料:同一份 settings 在 center / worker / 管理面 / 契约仓自身读出的 effective 逐字节一致、
7
+ * 告警多重集一致,否则红。
8
+ *
9
+ * 规范化(canonical JSON、告警投影、坐标深相等)全部在 kit 内**单铸** —— 消费方零复述,因为一份被各自
10
+ * 复述的规范化就是第二份真源,而两份真源的分歧正是本门要抓的那类病。
11
+ */
12
+ import type { EffectiveReadWarning } from "../config-fns.js";
13
+ /** 三条读腿的名字。**闭集** —— 新开一条腿要同时在语料派生表(`derive.ts` 的 `DERIVATION_TRANSFORMS`)里表态。
14
+ *
15
+ * · `domains`:各域**原始文档**(center 侧存储形)→ effective。契约仓的 `buildEffective`。
16
+ * · `wire`:`GET /effective` 载荷 → effective。契约仓的 `readEffectiveWire`。
17
+ * · `localRoot`:`config.d/` 目录快照(文件名 → 文件内容)→ effective。契约仓的 `loadLocalConfig`。 */
18
+ export type LegName = "domains" | "wire" | "localRoot";
19
+ /** 三条腿的名字表(运行期枚举用;与 {@link LegName} 同源 —— 多一员少一员编译红)。 */
20
+ export declare const LEG_NAMES: readonly ["domains", "wire", "localRoot"];
21
+ /** 一条读腿的入参。腿名决定形,`domains` 是真源,另两形由 harness 派生(见 derive.ts 的变换表)。 */
22
+ export type LegInput = {
23
+ leg: "domains";
24
+ docs: Readonly<Record<string, unknown>>;
25
+ version: number;
26
+ updatedAt: string;
27
+ } | {
28
+ leg: "wire";
29
+ payload: unknown;
30
+ } | {
31
+ leg: "localRoot";
32
+ files: Readonly<Record<string, string>>;
33
+ mtime: string;
34
+ };
35
+ /**
36
+ * 一条腿跑完的**结局**。两类,分开报:读出了 effective,或者**拒绝**了。
37
+ *
38
+ * `rejected` 不是失败 —— 一份 gate 域坏掉的文档**就该**被拒,那是期望结局。真正的失败是
39
+ * 「harness 自己炸了」({@link ConformanceStatus} 的 `harness-error`):适配器抛了未受控异常,
40
+ * 我们连它的表态都没拿到。两件事混在一起报,就再也分不出「契约按设计拒收」与「消费方接线坏了」。
41
+ *
42
+ * `stage` 是**闭集**:拒绝发生在哪一层 —— 信封(不是一份 effective)/ 解析 / 校验 / 哈希 / 存储。
43
+ * `diagnostics` 是人读的一句话,**不参与比对**(它可经脱敏,比文案 = 比实现);比的是 `stage` 与 `code`。
44
+ */
45
+ export type LegOutcome = {
46
+ outcome: "effective";
47
+ effective: unknown;
48
+ warnings: readonly EffectiveReadWarning[];
49
+ version?: number;
50
+ updatedAt?: string;
51
+ } | {
52
+ outcome: "rejected";
53
+ stage: RejectStage;
54
+ code?: string;
55
+ diagnostics: string;
56
+ };
57
+ /** 拒绝发生的层。闭集 —— 新层要来这里登记(语料的 `stage` 字段按本表校验)。 */
58
+ export type RejectStage = "envelope" | "parse" | "validate" | "hash" | "store";
59
+ /** {@link RejectStage} 的运行期表(语料校验用;与类型同源)。 */
60
+ export declare const REJECT_STAGES: readonly ["envelope", "parse", "validate", "hash", "store"];
61
+ /**
62
+ * 适配器**实际加载到的那个契约模块**的出处 —— kit 核的是它,不是核自己那份 import。
63
+ *
64
+ * 成案:kit 与消费方装的可能根本不是同一份包(monorepo 软链、预发 tarball、忘了重装)。让 kit 核
65
+ * 「我自己这份包的版本」等于核了个寂寞;适配器必须报**它加载的那份**的 package.json 路径、版号与
66
+ * 完整性串,release 模式三者与语料版本对齐才算数。
67
+ */
68
+ export interface ConformanceProvenance {
69
+ /** 适配器加载到的契约包的 `package.json` **绝对路径**。 */
70
+ packageJsonPath: string;
71
+ /** 那份 package.json 里的 `version`。 */
72
+ version: string;
73
+ /** 它的完整性串 —— 预发 tarball 的 `sha256:<hex>`,或 npm 装包时记下的 `integrity`。语料不校验其值,
74
+ * 只要求**在场且非空**:它是人工核对「三仓装的是同一份 tarball」的那一行。 */
75
+ integrity: string;
76
+ }
77
+ /**
78
+ * 一条腿的**生产入口**坐标(同义反复护栏,§2d-2)。
79
+ *
80
+ * 成案:如果消费方在测试里把契约包的函数本身注册成读腿,这道门证明的是「包等于包」—— 一次纯粹的
81
+ * 同义反复,而且看起来全绿。`module` 必须是消费方**生产代码**里那个真入口文件的绝对路径,`export`
82
+ * 是它导出的那个名字。
83
+ *
84
+ * **门的诚实边界**:kit 能核的是「这个文件真的存在」「它不在契约包的 node_modules 目录之下」。它**不能**
85
+ * 证明这个文件内部没有直接转调契约函数 —— 那由消费方半场的**变异自证**负责(消费方对自己的折叠/投影层
86
+ * 注入一处变异 ⇒ kit 必红)。本字段是护栏,不是证明,docs/CONFORMANCE.md 逐字写明这一点。
87
+ */
88
+ export interface LegEntry {
89
+ /** 生产入口文件的**绝对路径**(消费方自己 `fileURLToPath(import.meta.url)` 出来的那个,不是手抄串)。 */
90
+ module: string;
91
+ /** 该文件里被当作读腿入口的导出名。 */
92
+ export: string;
93
+ }
94
+ /** 一条登记进 kit 的读腿:它的生产入口 + 真正跑它的那只函数。 */
95
+ export interface ConformanceLeg {
96
+ entry: LegEntry;
97
+ run(input: LegInput): Promise<LegOutcome> | LegOutcome;
98
+ }
99
+ /**
100
+ * 消费方对金样的一条**已知合法差异**的登记位 —— 临时欠账,或成文语义。
101
+ *
102
+ * 成案:消费方对金样的合法差异今天只能写在测试注里,而散文不是判据 —— 谁也说不清「这条差异还在不在」。
103
+ * 登记之后它变成机器可核的一行:命中即 `diff-ledgered`(不阻塞),**从未命中即违例**(`stale ledger entry`)。
104
+ * 这条反向律是本形的重点:与本仓 silent-degradation 棘轮同律,只降不升 —— 债还了就得把账本删掉,
105
+ * 否则它自己会把门顶红。
106
+ *
107
+ * **只收 effective 的差**:告警与元数据差永不可入账(见 {@link ConformanceStatus} 的 `diff-ledgered`)。
108
+ * **逐坐标**白名单而不是整格豁免:同一格里多一条没登记的坐标,整格仍然是 `diff`。
109
+ */
110
+ export type KnownDivergence = PathDivergence | OutcomeDivergence;
111
+ /** 一条**坐标形**登记:effective 的某一片值与金样不同。判别位 = 有 `pathPrefix`、没有 `outcome`。 */
112
+ export interface PathDivergence {
113
+ /** 这条登记管哪条腿。 */
114
+ leg: LegName;
115
+ /** 只管这几个 case;缺席 = 这条腿上的所有 case。 */
116
+ cases?: readonly string[];
117
+ /** effective diff 的 **JSON Pointer 段前缀**:盖住 `pathPrefix` 本身与 `pathPrefix + "/"` 之下的整棵子树,
118
+ * **不**盖名字更长的相邻坐标(登记 `/limits/infraCostRates` 不豁免 `/limits/infraCostRatesInjected`,
119
+ * 登记 `/models/1` 不豁免 `/models/10`)。必须以 `/` 开头并**点名至少一个顶层键** —— `/` 或空串是
120
+ * 一个 `ok=true` 开关,不是账本。 */
121
+ pathPrefix: string;
122
+ /** `"debt"` = 临时欠账(`ref` 是台账号);`"declared"` = 消费方成文语义(`ref` 是契约 / 文档锚)。 */
123
+ kind: "debt" | "declared";
124
+ /** 台账号或文档锚 —— 报告按它分组,人照它去查这笔差异的出处。 */
125
+ ref: string;
126
+ /** 什么时候该把这一条删掉(债还了 / 语义改了)。人读,非空。 */
127
+ flipWhen: string;
128
+ outcome?: never;
129
+ }
130
+ /**
131
+ * 一条**结局形**登记:契约期望这条腿**拒绝**,而消费方读出了 effective(降级 + 诊断),或反过来。
132
+ *
133
+ * 成案(接线实跑):这类差根本没有 effective patch —— 比对停在「结局形就不一样」那一步,坐标形账本
134
+ * 按 `pathPrefix` 够不着它,于是消费方 `ok` 恒 false,而它恰恰是门最该点名、也最该逼着修的一类。
135
+ *
136
+ * 两条硬约束,都是为了不让它变成一张万能豁免:
137
+ * · **`cases` 必填且非空** —— 结局形分歧不许按前缀大包一整条腿的拒绝面;
138
+ * · **只能是 `kind:"debt"`** —— 契约说拒、消费方说不拒,这不是「成文语义」盖得住的分歧,只能是债,
139
+ * 带台账号、带翻转条件,由 stale 律逼着到期删掉。
140
+ */
141
+ export interface OutcomeDivergence {
142
+ kind: "debt";
143
+ outcome: {
144
+ leg: LegName;
145
+ /** **必填且非空**:结局形登记只管点名的这几个 case。 */
146
+ cases: readonly string[];
147
+ /** 金样说这条腿该是什么结局。 */
148
+ expected: "rejected" | "effective";
149
+ /** 消费方实际给出的结局。 */
150
+ actual: "rejected" | "effective";
151
+ };
152
+ ref: string;
153
+ flipWhen: string;
154
+ leg?: never;
155
+ pathPrefix?: never;
156
+ }
157
+ /** 一个消费方交给 kit 的东西:它是谁、它加载的契约包是哪一份、它有哪几条生产读腿。 */
158
+ export interface ConformanceAdapter {
159
+ /** 消费方名(语料 `contributor` 同词表:`server` / `web-admin` / `cli` / `client-core` / `test` / `contract`)。 */
160
+ name: string;
161
+ provenance: ConformanceProvenance;
162
+ /**
163
+ * **只登记你真有的腿**。未登记 = 这个消费方结构上没有这条读法(例:worker 服务的生产代码里没有
164
+ * `buildEffective` 调用点,它只有 wire 腿与 local-root 腿)—— 报告逐格记 `skipped: no adapter`
165
+ * (覆盖矩阵看得见它没跑),**不算违例**。
166
+ *
167
+ * release 模式的两条律都指向**登记过的东西**:①至少登记一条腿;②**登记的腿必须跑** —— 登记了却在
168
+ * 整套语料上一格真读数都没有,那条登记是空的,违例。
169
+ */
170
+ legs: Partial<Record<LegName, ConformanceLeg>>;
171
+ /** 已知的**合法**差异账本(见 {@link KnownDivergence})。缺席 = 零容忍,任何 diff 都阻塞。 */
172
+ knownDivergences?: readonly KnownDivergence[];
173
+ }
174
+ /**
175
+ * historical 模式的**有损归一器**:把旧版包的读数与今天的语料放进同一个空间比。
176
+ *
177
+ * 它必须**自陈有损**(`lossy`):2.1.0 的告警坐标是点号串,3.0.0 是段数组 —— 归一就是把段数组按
178
+ * `.` 拼成串,而那正是 3.0.0 要消掉的那个有损编码(`["a.b"]` 与 `["a","b"]` 归一后同串)。自陈的
179
+ * 意义在于:`firstRedOn` 的读数要能说出「这条没红是因为归一器把它抹平了」这种可能,而不是假装
180
+ * 旧版与新版一致。
181
+ */
182
+ export interface ConformanceNormalizer {
183
+ /** 归一器身份(写进报告:`settings-schema@2.1.0`)。 */
184
+ id: string;
185
+ /** 自陈有损与否 —— 有损的归一器,其「零 diff」读数**不是**「行为相同」的证明。 */
186
+ lossy: boolean;
187
+ /** 有损在哪一处(人读,进报告)。 */
188
+ why: string;
189
+ /** 把一条坐标归一到共同空间。有损归一器在这里把段拼成串。 */
190
+ path(p: readonly (string | number)[]): unknown;
191
+ }
192
+ /**
193
+ * 一格结局。`pass` / `expected-rejected` / `diff-ledgered` 不阻塞,`diff` / `harness-error` 红,
194
+ * `skipped` 既不绿也不红(没跑)。
195
+ *
196
+ * `diff-ledgered`(3.0.0 车 FT):这一格的 effective 差**逐坐标**落在适配器的
197
+ * {@link KnownDivergence} 账本里 —— 一笔登记在案的合法差异,不阻塞,但单独计数、按 `ref` 列出来。
198
+ * **告警差与元数据差永不进这一格**:它们是这道门的承重面,一条被「已知」掉的告警差正是本门要抓的病。
199
+ */
200
+ export type ConformanceStatus = "pass" | "diff" | "diff-ledgered" | "expected-rejected" | "harness-error" | "skipped";
201
+ /** effective 的一处差:JSON Pointer 坐标 + 两侧的值(`undefined` = 该侧没有这个位置)。 */
202
+ export interface EffectivePatch {
203
+ op: "add" | "remove" | "replace";
204
+ /** RFC 6901 JSON Pointer —— 这是**诊断输出**,不是比对输入(比对走结构化深相等)。 */
205
+ path: string;
206
+ expected?: unknown;
207
+ actual?: unknown;
208
+ }
209
+ /** 告警多重集的差:期望里有而实际没有的、实际里有而期望没有的(各自带重数)。 */
210
+ export interface WarningMultisetDiff {
211
+ missing: readonly unknown[];
212
+ extra: readonly unknown[];
213
+ }
214
+ /** 一个 case × 一条腿的读数。 */
215
+ export interface ConformanceResult {
216
+ caseId: string;
217
+ leg: LegName;
218
+ status: ConformanceStatus;
219
+ /** 一句话:为什么是这个 status(skipped 说哪条腿没登记;harness-error 说抛了什么)。 */
220
+ detail: string;
221
+ /** `status === "diff"` / `"diff-ledgered"` 时才在场。`outcome` 只在**结局形**不一致时在场 ——
222
+ * 那种差没有 effective patch(比对停在「结局形就不一样」那一步),靠 `detail` 文案分辨等于没有判据。 */
223
+ diff?: {
224
+ effective: readonly EffectivePatch[];
225
+ warnings: WarningMultisetDiff;
226
+ metadata: readonly EffectivePatch[];
227
+ outcome?: {
228
+ expected: "rejected" | "effective";
229
+ actual: "rejected" | "effective";
230
+ stage?: RejectStage;
231
+ };
232
+ };
233
+ /** `status === "diff-ledgered"` 时在场:盖住这一格的账本条目 `ref`(逐坐标,按 diff 顺序)。 */
234
+ ledgerRefs?: readonly string[];
235
+ }
236
+ /** 报告里的一行账本:登记内容 + 它这一跑**命中了几条 diff**。`hits === 0` ⇒ 陈腐(release 模式违例)。 */
237
+ export interface LedgerRow {
238
+ ref: string;
239
+ kind: KnownDivergence["kind"];
240
+ leg: LegName;
241
+ /** 坐标形登记的段前缀(见 {@link PathDivergence.pathPrefix});**结局形**登记没有这一项。 */
242
+ pathPrefix?: string;
243
+ /** 结局形登记盯的那对结局;**坐标形**登记没有这一项。 */
244
+ outcome?: {
245
+ expected: "rejected" | "effective";
246
+ actual: "rejected" | "effective";
247
+ };
248
+ cases?: readonly string[];
249
+ flipWhen: string;
250
+ hits: number;
251
+ }
252
+ /** 一次跑的报告。 */
253
+ export interface ConformanceReport {
254
+ mode: ConformanceMode;
255
+ adapter: {
256
+ name: string;
257
+ provenance: ConformanceProvenance;
258
+ legs: readonly LegName[];
259
+ };
260
+ /** 语料版本(`conformance/VERSION`)。 */
261
+ corpusVersion: string;
262
+ /** kit 自己所在包的版本(= 语料应当对齐的那个)。 */
263
+ kitVersion: string;
264
+ normalizer?: {
265
+ id: string;
266
+ lossy: boolean;
267
+ why: string;
268
+ };
269
+ results: readonly ConformanceResult[];
270
+ summary: Readonly<Record<ConformanceStatus, number>>;
271
+ /** 适配器账本逐条的命中数(按 `ref` 读)。空数组 = 这个适配器没挂账本。 */
272
+ ledger: readonly LedgerRow[];
273
+ /** 四律里**不按 case 计**的违例(来源律 / 版本护栏 / release 禁 only 与禁 skipped)。非空 ⇒ `ok:false`。 */
274
+ violations: readonly string[];
275
+ /** 全绿 = 零**未入账** diff / 零 harness-error / 零 violations(未登记的腿的 `skipped` 不算红)。 */
276
+ ok: boolean;
277
+ }
278
+ /** 两种模式。 */
279
+ export type ConformanceMode = "release" | "historical";
280
+ export interface ConformanceOptions {
281
+ mode: ConformanceMode;
282
+ /** 只跑这几个 case(调试用)。**release 模式禁用** —— 一个能挑 case 的发版门不是门。 */
283
+ only?: readonly string[];
284
+ /** 语料目录;默认 = 本包的 `conformance/`。 */
285
+ corpusDir?: string;
286
+ /** historical 模式的归一器。release 模式传了即违例(归一器只用来看旧版,不用来放宽今天的门)。 */
287
+ normalizer?: ConformanceNormalizer;
288
+ }
@@ -0,0 +1,4 @@
1
+ /** 三条腿的名字表(运行期枚举用;与 {@link LegName} 同源 —— 多一员少一员编译红)。 */
2
+ export const LEG_NAMES = ["domains", "wire", "localRoot"];
3
+ /** {@link RejectStage} 的运行期表(语料校验用;与类型同源)。 */
4
+ export const REJECT_STAGES = ["envelope", "parse", "validate", "hash", "store"];
@@ -0,0 +1,88 @@
1
+ /**
2
+ * 告警的**穷举 typed 投影**与**多重集**比对 —— kit 的第二个单铸点。
3
+ *
4
+ * 为什么是穷举投影而不是「把告警对象整个比一遍」:`error` 字段带的是 zod 的错误对象/文案,它可经脱敏、
5
+ * 可随 zod 小版本改字 —— 比文案就是比实现。所以投影**逐臂列全**每个 kind 的非 `error` 字段,`error`
6
+ * 一律不进比对;但它也不是就这么放过去了:{@link findSecretLeaks} 先断言它不含语料自带的秘密清单。
7
+ *
8
+ * 为什么是**多重集**而不是集合:同一个 kind 在同一个域上发两次(两条坏条目、两处未知键路径)是有信息的
9
+ * —— 去重会让「报了一条」与「报了两条」同形,而那恰是消费方最容易漏折的一类。
10
+ *
11
+ * 坐标({@link ConfigPath})按**结构**比:段数组进 JSON 编码,`["a.b"]` 与 `["a","b"]`、`["1"]` 与 `[1]`
12
+ * 在编码里都还分得开。kit 内**零** `formatConfigPath` 调用 —— 出门形是给人看的,不是比对用的。
13
+ */
14
+ import type { ConfigPath, EffectiveReadWarning } from "../config-fns.js";
15
+ import type { ConformanceNormalizer } from "./types.js";
16
+ /** {@link EffectiveReadWarning} 的 `kind` 闭集(覆盖律按它计数;新 kind 不登记 ⇒ 下面的 switch 编译红)。 */
17
+ export declare const WARNING_KINDS: readonly ["hosts-grandfathered", "domain-defaulted", "unknown-keys-dropped", "unknown-keys-carried", "legacy-governance-lifted", "entry-dropped", "unread-config-file"];
18
+ export type WarningKind = (typeof WARNING_KINDS)[number];
19
+ /** 一条告警的投影 —— 每臂列全该 kind 的**非 `error`** 字段。 */
20
+ export type WarningProjection = {
21
+ kind: "hosts-grandfathered";
22
+ domain: "hosts";
23
+ droppedNames: readonly string[];
24
+ truncatedFrom?: number;
25
+ } | {
26
+ kind: "domain-defaulted";
27
+ domain: string;
28
+ } | {
29
+ kind: "unknown-keys-dropped";
30
+ domain: string;
31
+ keys: readonly ConfigPath[];
32
+ truncatedFrom?: number;
33
+ } | {
34
+ kind: "unknown-keys-carried";
35
+ domain: string;
36
+ keys: readonly ConfigPath[];
37
+ truncatedFrom?: number;
38
+ } | {
39
+ kind: "legacy-governance-lifted";
40
+ domain: "governance";
41
+ keys: readonly ConfigPath[];
42
+ } | {
43
+ kind: "entry-dropped";
44
+ domain: string;
45
+ path: ConfigPath;
46
+ } | {
47
+ kind: "unread-config-file";
48
+ file: string;
49
+ domain?: string;
50
+ why: string;
51
+ };
52
+ /**
53
+ * 一条告警 → 它的投影。**闭集穷举**:新 kind 不在这里表态就编译红(#157 的闭集纪律)。
54
+ *
55
+ * `truncatedFrom` 缺席时**不铸这个键**(不是铸 `undefined`)—— 规范化会把 `undefined` 值剔掉,两种写法
56
+ * 最终同形,但不铸更诚实:「没有截断」与「截断了 0 条」不该在中间形上分成两件事。
57
+ */
58
+ export declare function projectWarning(w: EffectiveReadWarning): WarningProjection;
59
+ /**
60
+ * 一条投影的**比对归一形**:①坐标过一遍映射(historical 模式把段拼成旧版的点号串;无归一器时是恒等);
61
+ * ②`keys` / `droppedNames` 这两个**集合字段**按袋归一。第二处闭集穷举:哪些 kind 带坐标、带在哪个字段上,
62
+ * 写在这里一次。
63
+ *
64
+ * 为什么集合字段要按袋比(codex R1 [medium] 复现):一条告警里的 `keys` 是「这些键被剥了」的**一只袋**,
65
+ * 它的顺序来自文档的枚举次序,契约从没承诺过。照序列比,消费方只要换个扫描次序,就会在 effective、坐标
66
+ * 与重数全都相同的情况下把发版门顶红 —— 那是纯粹的假红。**重数仍然保留**(同一条坐标报两次 ≠ 报一次),
67
+ * 而一条 {@link ConfigPath} **内部**的段序当然仍是序列(`["a","b"]` 与 `["b","a"]` 是两条不同的坐标)。
68
+ */
69
+ export declare function normalizeProjection(p: WarningProjection, f: (path: ConfigPath) => unknown): unknown;
70
+ /** 一条投影的比对键。 */
71
+ export declare function projectionKey(p: WarningProjection, normalizer?: ConformanceNormalizer): string;
72
+ /** 一条投影的规范形(进报告的 `missing`/`extra` 用,人读)。 */
73
+ export declare function projectionValue(p: WarningProjection, normalizer?: ConformanceNormalizer): unknown;
74
+ /**
75
+ * 两个投影**多重集**的差。重数保留:期望两条、实际一条 ⇒ `missing` 里留一条。
76
+ */
77
+ export declare function diffWarningMultiset(expected: readonly WarningProjection[], actual: readonly WarningProjection[], normalizer?: ConformanceNormalizer): {
78
+ missing: unknown[];
79
+ extra: unknown[];
80
+ };
81
+ /**
82
+ * 告警面(投影 + `error` 文案)里有没有语料自带的秘密。
83
+ *
84
+ * 口径(诚实登记,docs/CONFORMANCE.md 同步写明):扫的是**告警**,不是 effective。effective 里逐字承运
85
+ * 运维自己写下的那份文档是契约(配置就是他写的那份),而告警是**本包自己铸的新文本** —— 一条把秘密
86
+ * 抄进坐标或错误文案的告警,会把秘密带进日志、审计流、管理面,那才是「告警比它诊断的东西更危险」。
87
+ */
88
+ export declare function findSecretLeaks(warnings: readonly EffectiveReadWarning[], secrets: readonly string[]): string[];
@@ -0,0 +1,144 @@
1
+ import { canonicalize, stableKey } from "./canonical.js";
2
+ /** {@link EffectiveReadWarning} 的 `kind` 闭集(覆盖律按它计数;新 kind 不登记 ⇒ 下面的 switch 编译红)。 */
3
+ export const WARNING_KINDS = [
4
+ "hosts-grandfathered",
5
+ "domain-defaulted",
6
+ "unknown-keys-dropped",
7
+ "unknown-keys-carried",
8
+ "legacy-governance-lifted",
9
+ "entry-dropped",
10
+ "unread-config-file",
11
+ ];
12
+ /**
13
+ * 一条告警 → 它的投影。**闭集穷举**:新 kind 不在这里表态就编译红(#157 的闭集纪律)。
14
+ *
15
+ * `truncatedFrom` 缺席时**不铸这个键**(不是铸 `undefined`)—— 规范化会把 `undefined` 值剔掉,两种写法
16
+ * 最终同形,但不铸更诚实:「没有截断」与「截断了 0 条」不该在中间形上分成两件事。
17
+ */
18
+ export function projectWarning(w) {
19
+ switch (w.kind) {
20
+ case "hosts-grandfathered":
21
+ return { kind: w.kind, domain: w.domain, droppedNames: w.droppedNames, ...(w.truncatedFrom === undefined ? {} : { truncatedFrom: w.truncatedFrom }) };
22
+ case "domain-defaulted":
23
+ return { kind: w.kind, domain: w.domain };
24
+ // 两臂字段相同但**不合并**:合并后 `kind` 塌成两员的联合,TS 就再也认不出它属于投影联合的哪一员
25
+ // (真编译红过一次)。逐 kind 一臂,是这张表本来的样子。
26
+ case "unknown-keys-dropped":
27
+ return { kind: w.kind, domain: w.domain, keys: w.keys, ...(w.truncatedFrom === undefined ? {} : { truncatedFrom: w.truncatedFrom }) };
28
+ case "unknown-keys-carried":
29
+ return { kind: w.kind, domain: w.domain, keys: w.keys, ...(w.truncatedFrom === undefined ? {} : { truncatedFrom: w.truncatedFrom }) };
30
+ case "legacy-governance-lifted":
31
+ return { kind: w.kind, domain: w.domain, keys: w.keys };
32
+ case "entry-dropped":
33
+ return { kind: w.kind, domain: w.domain, path: w.path };
34
+ case "unread-config-file":
35
+ return { kind: w.kind, file: w.file, ...(w.domain === undefined ? {} : { domain: w.domain }), why: w.why };
36
+ default: {
37
+ const unhandled = w;
38
+ // 运行期到不了(本联合由契约包铸造);到了就让它红 —— 一条不认识的告警被静默投影成空,
39
+ // 正是「门看起来全绿」的那种假绿。
40
+ throw new Error(`conformance: unknown EffectiveReadWarning kind — ${JSON.stringify(unhandled)}`);
41
+ }
42
+ }
43
+ }
44
+ /** 一只**袋**:元素按各自的稳定编码排序,重数保留。比对前归一用,不改变「报了几条」这件事。 */
45
+ const bag = (items) => [...items].sort((a, b) => (stableKey(a) < stableKey(b) ? -1 : stableKey(a) > stableKey(b) ? 1 : 0));
46
+ /**
47
+ * 一条投影的**比对归一形**:①坐标过一遍映射(historical 模式把段拼成旧版的点号串;无归一器时是恒等);
48
+ * ②`keys` / `droppedNames` 这两个**集合字段**按袋归一。第二处闭集穷举:哪些 kind 带坐标、带在哪个字段上,
49
+ * 写在这里一次。
50
+ *
51
+ * 为什么集合字段要按袋比(codex R1 [medium] 复现):一条告警里的 `keys` 是「这些键被剥了」的**一只袋**,
52
+ * 它的顺序来自文档的枚举次序,契约从没承诺过。照序列比,消费方只要换个扫描次序,就会在 effective、坐标
53
+ * 与重数全都相同的情况下把发版门顶红 —— 那是纯粹的假红。**重数仍然保留**(同一条坐标报两次 ≠ 报一次),
54
+ * 而一条 {@link ConfigPath} **内部**的段序当然仍是序列(`["a","b"]` 与 `["b","a"]` 是两条不同的坐标)。
55
+ */
56
+ export function normalizeProjection(p, f) {
57
+ switch (p.kind) {
58
+ case "unknown-keys-dropped":
59
+ case "unknown-keys-carried":
60
+ case "legacy-governance-lifted":
61
+ return { ...p, keys: bag(p.keys.map(f)) };
62
+ case "entry-dropped":
63
+ return { ...p, path: f(p.path) };
64
+ case "hosts-grandfathered":
65
+ return { ...p, droppedNames: bag(p.droppedNames) };
66
+ case "domain-defaulted":
67
+ case "unread-config-file":
68
+ return p;
69
+ default: {
70
+ const unhandled = p;
71
+ throw new Error(`conformance: unknown warning projection kind — ${JSON.stringify(unhandled)}`);
72
+ }
73
+ }
74
+ }
75
+ /** 恒等坐标映射(无归一器时的那一只)。 */
76
+ const identityPath = (path) => path;
77
+ /** 一条投影的比对键。 */
78
+ export function projectionKey(p, normalizer) {
79
+ return stableKey(normalizeProjection(p, normalizer === undefined ? identityPath : normalizer.path));
80
+ }
81
+ /** 一条投影的规范形(进报告的 `missing`/`extra` 用,人读)。 */
82
+ export function projectionValue(p, normalizer) {
83
+ return canonicalize(normalizeProjection(p, normalizer === undefined ? identityPath : normalizer.path));
84
+ }
85
+ /**
86
+ * 两个投影**多重集**的差。重数保留:期望两条、实际一条 ⇒ `missing` 里留一条。
87
+ */
88
+ export function diffWarningMultiset(expected, actual, normalizer) {
89
+ const count = new Map();
90
+ const sample = new Map();
91
+ for (const p of expected) {
92
+ const k = projectionKey(p, normalizer);
93
+ count.set(k, (count.get(k) ?? 0) + 1);
94
+ if (!sample.has(k))
95
+ sample.set(k, projectionValue(p, normalizer));
96
+ }
97
+ for (const p of actual) {
98
+ const k = projectionKey(p, normalizer);
99
+ count.set(k, (count.get(k) ?? 0) - 1);
100
+ if (!sample.has(k))
101
+ sample.set(k, projectionValue(p, normalizer));
102
+ }
103
+ const missing = [];
104
+ const extra = [];
105
+ for (const [k, n] of [...count.entries()].sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))) {
106
+ for (let i = 0; i < n; i++)
107
+ missing.push(sample.get(k));
108
+ for (let i = 0; i < -n; i++)
109
+ extra.push(sample.get(k));
110
+ }
111
+ return { missing, extra };
112
+ }
113
+ /** 秘密扫描的拼接分隔串 —— 刻意是一段不会出现在任何配置里的字面,且**不是控制字节**(测试/源码里
114
+ * 放真控制字节会被别的门扫到);它的作用只是不让两条告警的尾首拼出一个假阳性。 */
115
+ const JOINER = "\n<<conformance-scan-boundary>>\n";
116
+ /** 一条告警的 `error` 的人读文案(只给秘密扫描用,永不进比对)。 */
117
+ function errorText(w) {
118
+ if (!("error" in w))
119
+ return "";
120
+ const e = w.error;
121
+ if (e === undefined || e === null)
122
+ return "";
123
+ if (e instanceof Error)
124
+ return `${e.name}: ${e.message}${JOINER}${e.stack ?? ""}`;
125
+ try {
126
+ return typeof e === "string" ? e : JSON.stringify(e);
127
+ }
128
+ catch {
129
+ return String(e);
130
+ }
131
+ }
132
+ /**
133
+ * 告警面(投影 + `error` 文案)里有没有语料自带的秘密。
134
+ *
135
+ * 口径(诚实登记,docs/CONFORMANCE.md 同步写明):扫的是**告警**,不是 effective。effective 里逐字承运
136
+ * 运维自己写下的那份文档是契约(配置就是他写的那份),而告警是**本包自己铸的新文本** —— 一条把秘密
137
+ * 抄进坐标或错误文案的告警,会把秘密带进日志、审计流、管理面,那才是「告警比它诊断的东西更危险」。
138
+ */
139
+ export function findSecretLeaks(warnings, secrets) {
140
+ if (secrets.length === 0)
141
+ return [];
142
+ const hay = warnings.map((w) => `${stableKey(projectWarning(w))}${JOINER}${errorText(w)}`).join(JOINER);
143
+ return secrets.filter((s) => s.length > 0 && hay.includes(s));
144
+ }
@@ -17,6 +17,13 @@ import { type DomainConfig, type DomainName, type EffectiveConfig } from "./type
17
17
  * 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
18
18
  export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "runtime", "governance", "execution", "prompts", "limits"];
19
19
  export type PortableDomain = (typeof PORTABLE_DOMAINS)[number];
20
+ /** `config.d/` 里**合法存在但不是域文档**的文件名(不进 `"unread-config-file"` 告警)。`remote-exec`
21
+ * 不是 `DOMAIN_SCHEMAS` 成员(resolver 不读它),按 service 表决住在这个目录,由 loadRemoteExec 读
22
+ * ——**它有自己的读取与校验路径**,所以既不是「未知文件」也不是「域文件」。
23
+ *
24
+ * **导出**(3.0.0):一致性门的语料派生要按同一张表给本地腿的文件分类(域文件 / 专用文件 / 忽略),
25
+ * 手抄一份必漂 —— 漂了的那一天,门会把一个合法的 `remote-exec.json` 报成未知文件。 */
26
+ export declare const NON_DOMAIN_CONFIG_FILES: ReadonlySet<string>;
20
27
  /**
21
28
  * 非便携域的**排除理由表**(#282 件5 附带裁定:禁裸排除)。
22
29
  *
@@ -42,8 +42,12 @@ import { isDomainName } from "./types.js";
42
42
  * 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
43
43
  export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "runtime", "governance", "execution", "prompts", "limits"];
44
44
  /** `config.d/` 里**合法存在但不是域文档**的文件名(不进 `"unread-config-file"` 告警)。`remote-exec`
45
- * 不是 `DOMAIN_SCHEMAS` 成员(resolver 不读它),按 service 表决住在这个目录,由 loadRemoteExec 读。 */
46
- const NON_DOMAIN_CONFIG_FILES = new Set(["remote-exec.json"]);
45
+ * 不是 `DOMAIN_SCHEMAS` 成员(resolver 不读它),按 service 表决住在这个目录,由 loadRemoteExec 读
46
+ * ——**它有自己的读取与校验路径**,所以既不是「未知文件」也不是「域文件」。
47
+ *
48
+ * **导出**(3.0.0):一致性门的语料派生要按同一张表给本地腿的文件分类(域文件 / 专用文件 / 忽略),
49
+ * 手抄一份必漂 —— 漂了的那一天,门会把一个合法的 `remote-exec.json` 报成未知文件。 */
50
+ export const NON_DOMAIN_CONFIG_FILES = new Set(["remote-exec.json"]);
47
51
  /**
48
52
  * 非便携域的**排除理由表**(#282 件5 附带裁定:禁裸排除)。
49
53
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -51,6 +51,11 @@
51
51
  "types": "./dist/config-fns.d.ts",
52
52
  "default": "./dist/config-fns.js"
53
53
  },
54
+ "./package.json": "./package.json",
55
+ "./conformance": {
56
+ "types": "./dist/conformance/index.d.ts",
57
+ "default": "./dist/conformance/index.js"
58
+ },
54
59
  "./reader": {
55
60
  "types": "./dist/reader.d.ts",
56
61
  "default": "./dist/reader.js"
@@ -98,14 +103,11 @@
98
103
  "./api/scopes": {
99
104
  "types": "./dist/api/scopes.d.ts",
100
105
  "default": "./dist/api/scopes.js"
101
- },
102
- "./api/wire": {
103
- "types": "./dist/api/wire.d.ts",
104
- "default": "./dist/api/wire.js"
105
106
  }
106
107
  },
107
108
  "files": [
108
109
  "dist",
110
+ "conformance",
109
111
  "README.md",
110
112
  "CHANGELOG.md"
111
113
  ],
@@ -1,60 +0,0 @@
1
- /**
2
- * `api/wire` — the ZERO-DEPENDENCY wire subset of the auth/scopes contracts (server AI 接手首车,
3
- * 2026-07-27,跨仓 candidate 落地):endpoint paths, protocol constants and PURE helper functions,
4
- * with **no zod import** — so a zero-runtime-dependency client (e.g. `@sema-agent/sdk/registry`) can
5
- * import these at RUNTIME instead of value-copying them behind a same-source anchor test.
6
- *
7
- * ⚠️ 拆分纪律:这里只放**纯**件(常量/纯函数/字面量类型)。zod schema 留在 `api/auth.ts` /
8
- * `api/scopes.ts`(它们 re-export 本文件 ⇒ 既有导入面字节兼容,谁都不用改)。给本文件加东西前
9
- * 自问:它 import 了任何东西吗?——答案必须是 no(test/wire-purity 打包门盯着)。
10
- */
11
- /** The five auth endpoints, relative to the registry origin. */
12
- export declare const AUTH_V1_PATHS: {
13
- readonly deviceCode: "/api/v1/auth/device/code";
14
- readonly deviceToken: "/api/v1/auth/device/token";
15
- readonly deviceApprove: "/api/v1/auth/device/approve";
16
- readonly tokenRefresh: "/api/v1/auth/token/refresh";
17
- readonly logout: "/api/v1/auth/logout";
18
- };
19
- /** The RFC 8628 grant_type the device token endpoint requires. */
20
- export declare const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
21
- /** Minimum seconds between token-endpoint polls (RFC 8628 `interval`); polling faster → `slow_down`. */
22
- export declare const DEVICE_POLL_INTERVAL_SECONDS = 5;
23
- /** Device-code handshake lifetime (seconds) — the user has 15 minutes to open /activate and approve. */
24
- export declare const DEVICE_CODE_TTL_SECONDS: number;
25
- /** Access-token (RS256 JWT) lifetime in seconds — 1h, re-minted via refresh. */
26
- export declare const ACCESS_TOKEN_TTL_SECONDS: number;
27
- /** Refresh-grant lifetime (seconds) — 30d SLIDING: every rotation opens a fresh 30d window. */
28
- export declare const REFRESH_TOKEN_TTL_SECONDS: number;
29
- /** Every error code the /api/v1/auth/* endpoints answer (HTTP 400 unless noted). */
30
- export declare const OAUTH_ERROR_CODES: readonly ["authorization_pending", "slow_down", "expired_token", "access_denied", "invalid_request", "unsupported_grant_type", "invalid_grant", "server_error"];
31
- export type OAuthErrorCode = (typeof OAUTH_ERROR_CODES)[number];
32
- /** Handshake states a consent-screen lookup can see. (`approved` rows vanish once exchanged/consumed.)
33
- * `denied` is contract-reserved for an explicit deny button (pairs with the `access_denied` poll error);
34
- * the shipped M1 store carries pending|approved only — adding deny later is implementation, not schema. */
35
- export declare const DEVICE_AUTH_STATUSES: readonly ["pending", "approved", "denied"];
36
- export type DeviceAuthStatus = (typeof DEVICE_AUTH_STATUSES)[number];
37
- /**
38
- * Canonicalize a human-typed user code to the wire shape "XXXX-XXXX": uppercase, dashes/spaces optional.
39
- * null = not a plausible code (wrong length/charset after stripping separators). Same normalization the
40
- * server applies — run it client-side for instant feedback.
41
- */
42
- export declare function normalizeUserCode(input: unknown): string | null;
43
- /**
44
- * The RFC 8628 §3.5 poll-loop step: what interval to poll with next, and whether to keep going.
45
- * - authorization_pending → keep polling at the current interval
46
- * - slow_down → keep polling, interval + 5s (the RFC-mandated increment)
47
- * - anything else → stop (expired_token/access_denied = terminal; invalid_* = client bug)
48
- */
49
- export declare function nextPollInterval(currentIntervalSeconds: number, error: OAuthErrorCode): {
50
- continue: boolean;
51
- intervalSeconds: number;
52
- };
53
- export declare const SCOPES_V1_PATHS: {
54
- /** GET (list) + POST (create). */
55
- readonly scopes: "/api/v1/scopes";
56
- /** POST — mint a new access token bound to another scope. */
57
- readonly authScope: "/api/v1/auth/scope";
58
- };
59
- /** The JWT claim carrying the token's active scope. OPTIONAL — absent means GLOBAL_SCOPE (tokenScopeOf). */
60
- export declare const SCOPE_CLAIM = "scope";