@sema-agent/settings-schema 7.0.1 → 8.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 (100) hide show
  1. package/CHANGELOG.md +123 -1
  2. package/README.md +28 -5
  3. package/conformance/VERSION +1 -1
  4. package/conformance/cases/c01-entry-salvage-then-typo/expected.json +5 -0
  5. package/conformance/cases/c02-hosts-clamp-stacked/expected.json +5 -0
  6. package/conformance/cases/c03-literal-dotted-top-key/expected.json +41 -0
  7. package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +5 -0
  8. package/conformance/cases/c05-gate-domain-bad-entry-throws/expected.json +5 -0
  9. package/conformance/cases/c06-legacy-governance-lift/expected.json +12 -0
  10. package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +5 -0
  11. package/conformance/cases/c08-limits-unknown-carried-64/expected.json +77 -0
  12. package/conformance/cases/c09-limits-unknown-carried-65/expected.json +78 -0
  13. package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +5 -0
  14. package/conformance/cases/c11-unicode-unknown-keys/expected.json +14 -0
  15. package/conformance/cases/c12-prototype-name-top-keys/expected.json +5 -0
  16. package/conformance/cases/c13-models-field-null/expected.json +5 -0
  17. package/conformance/cases/c14-models-field-empty-array/expected.json +16 -0
  18. package/conformance/cases/c15-models-field-number/expected.json +5 -0
  19. package/conformance/cases/c16-deep-nesting-205/expected.json +14 -0
  20. package/conformance/cases/c17-empty-domain-file/expected.json +10 -0
  21. package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +10 -0
  22. package/conformance/cases/c19-wire-envelope-no-version/expected.json +10 -0
  23. package/conformance/cases/c20-governance-absent-lifts/expected.json +12 -0
  24. package/conformance/cases/c21-governance-empty-object-wins/expected.json +13 -0
  25. package/conformance/cases/c22-governance-null-throws/expected.json +5 -0
  26. package/conformance/cases/c23-mixed-validity-models/expected.json +5 -0
  27. package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +29 -0
  28. package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +33 -0
  29. package/conformance/cases/c26-hosts-array-1024/expected.json +7183 -0
  30. package/conformance/cases/c27-hosts-array-1025/expected.json +5 -0
  31. package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -0
  32. package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +5 -0
  33. package/conformance/cases/c30-version-triple-a-first/expected.json +29 -0
  34. package/conformance/cases/c31-version-triple-b-changed/expected.json +29 -0
  35. package/conformance/cases/c32-version-triple-a-restored/expected.json +29 -0
  36. package/conformance/cases/c33-limits-nested-carried-key/expected.json +17 -0
  37. package/conformance/cases/c34-skills-content-vs-manifest/expected.json +23 -0
  38. package/conformance/cases/c35-portable-domain-file-is-null/expected.json +5 -0
  39. package/conformance/cases/c36-skills-entry-without-content/expected.json +23 -0
  40. package/conformance/cases/c37-skills-field-null/expected.json +5 -0
  41. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +5 -0
  42. package/conformance/cases/c39-hidden-file-in-config-d/expected.json +5 -0
  43. package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +5 -0
  44. package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +5 -0
  45. package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +33 -0
  46. package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +5 -0
  47. package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +5 -0
  48. package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +5 -0
  49. package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/expected.json +5 -0
  50. package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/expected.json +5 -0
  51. package/conformance/cases/c48-s935-model-headers-transported/expected.json +59 -0
  52. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +5 -0
  53. package/conformance/cases/c50-s784-lane-host-legal/expected.json +44 -0
  54. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +46 -0
  55. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +48 -0
  56. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +48 -0
  57. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +44 -0
  58. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +45 -0
  59. package/conformance/cases/c56-s784-lane-device-legal/expected.json +51 -0
  60. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +5 -0
  61. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +5 -0
  62. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +5 -0
  63. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +5 -0
  64. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +5 -0
  65. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +5 -0
  66. package/conformance/cases/c63-s784-host-two-replicas/expected.json +5 -0
  67. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +5 -0
  68. package/conformance/cases/c65-s784-pg-without-db/expected.json +5 -0
  69. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +5 -0
  70. package/conformance/cases/c67-s784-retired-field-loud/expected.json +5 -0
  71. package/conformance/cases/c68-s784-device-local-store/expected.json +5 -0
  72. package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +54 -0
  73. package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +27 -0
  74. package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +33 -0
  75. package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +5 -0
  76. package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +5 -0
  77. package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +22 -0
  78. package/conformance/cases/cli04-env-value-with-newline/expected.json +46 -0
  79. package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +53 -0
  80. package/conformance/cases/t01-absent-domain-key-not-null/expected.json +5 -0
  81. package/conformance/cases/t02-entry-salvage-vs-whole-domain-refusal/expected.json +5 -0
  82. package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -0
  83. package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +5 -0
  84. package/conformance/cases/z01-every-domain-empty-object/expected.json +64 -0
  85. package/conformance/cases/z02-every-domain-unknown-key/expected.json +66 -0
  86. package/dist/cli.js +58 -26
  87. package/dist/config-fns.js +7 -1
  88. package/dist/conformance/contract-adapter.d.ts +4 -2
  89. package/dist/conformance/contract-adapter.js +21 -7
  90. package/dist/conformance/corpus.js +12 -7
  91. package/dist/conformance/derive.d.ts +26 -0
  92. package/dist/conformance/derive.js +25 -2
  93. package/dist/conformance/index.d.ts +4 -3
  94. package/dist/conformance/index.js +4 -3
  95. package/dist/conformance/run.d.ts +3 -3
  96. package/dist/conformance/run.js +87 -17
  97. package/dist/conformance/types.d.ts +57 -17
  98. package/dist/conformance/types.js +4 -2
  99. package/dist/hooks.d.ts +4 -4
  100. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -6,7 +6,129 @@
6
6
  > `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
7
7
  > 包名是**历史记录**,如实保留不追改。
8
8
 
9
- ## Unreleased
9
+ ## 8.0.0 — 2026-10-04 — 一致性门第四条腿 `strictWrite`(严写受理)+ 每条腿都要表态(BREAKING,kit 类型面与 release 律)· `migrate-workers --check` 与真跑过同一只判定(BREAKING,CLI 退出码 / `refused[]`)· 导出面登记表机器门
10
+
11
+ > **判档 = major**(README「发版纪律」):kit 的闭集 `LegName` 加一员、`ConformanceAdapter.legs` 从 `Partial<Record<…>>` 改成全量
12
+ > `Record<LegName, LegStance>`(实现了 kit 适配器的消费方 tsc 红,即通知);release 律收紧(没表态的腿从「跳过、不算违例」变成违例);
13
+ > CLI 机读契约字段 `outcome` / `refused` 在一种文档上改了值(`--check` 对整域会被拒的文档从 `pending` / 退出 3 变成 `refused` / 退出 1,
14
+ > `refused[]` 从空变成逐条)。契约 schema、域默认值、三条读腿的读数**一字未动**:conformance 全语料重加冕,`domains` / `wire` / `localRoot`
15
+ > 三腿金样、`baseline.json`、迁移金样 `m01–m04` 零 diff(`expected.json` 只新增 `strictWrite` 一格,82 个文件 0 行删改)。
16
+
17
+ ### 一、一致性门第四条腿 `strictWrite`(严写受理)
18
+
19
+ **病**:写入席(管理面)判一份文档收不收,读侧(worker / CLI)逐条容错 —— 两侧此前没有任何一格放在同一份文档上对过。kit 的三条腿都是读语义:
20
+ 读腿抢救的地方(丢坏条目、整域降级、主机名夹紧)写入席**应该拒**,而这一点没有金样、没有门。
21
+
22
+ **修**:
23
+
24
+ - `LegName` 闭集 +1 `"strictWrite"`(`LEG_NAMES` 同步)。入参与 `domains` 腿**同一份字段表**(新导出类型 `DomainDocsInput`,两臂共用,
25
+ 不抄第二份)。结局只用既有两类(`LegOutcome` 不动):全部受理 ⇒ `effective` = 提交的每个键 → 受理后的域文档(**只含提交的键、不补缺席域**;
26
+ 空集受理成 `{}`),告警面为空,元数据回报 harness 注入的那一份;任何一份不受理 ⇒ `rejected{ stage: "validate" }`(写是一个动作,不逐份抢救)。
27
+ - 契约仓自己的适配器用 `parseDomain` 接这条腿(`ContractModule` 加 `parseDomain`,`createContractAdapter` 的 `entries` 要第四个入口)。
28
+ - **期望从哪来是一张表**:`derive.ts` 新导出 `LEG_EXPECTATION`(`Record<LegName, { source: "explicit" | "derivable"; why }>`,新开一条腿不表态
29
+ 即编译红)。`strictWrite` 入参恒等、**期望不派生**(严写结局不是读结局的表示变换;从 `domains` 期望派生会让一份金样的错同时改写两条腿)。
30
+ 语料加载器只按这张表判「能不能写 `{derive:true}`」—— 原来对 `domains` 的特判并进同一条规则。
31
+ - **语料**:82 条 case 逐条加 `strictWrite` 显式金样,每条带 `why`(为什么受理 / 为什么拒):40 受理、42 拒(全在 `validate` 层)。
32
+ 拒的 42 条 = 读腿抢救过的 22 条(全在逐条容错档的域上:models / skills / mcp / hosts)+ 读腿整条拒的 15 条 + 带非域名键的 5 条。
33
+ - **金样逐条核**(`test/conformance-strict-write-8.0.0.test.ts`):一条**不调 `parseDomain`** 的律 ——「严写拒 ⇔ 文档集里有非域名键 ∨ 读腿整条拒 ∨
34
+ 读腿在提交的某个域上抢救过(`entry-dropped` / `domain-defaulted` / `hosts-grandfathered`);否则受理,受理形 = 读腿 effective 里那几格」——
35
+ 对着已冻结的 `domains` 金样逐条推导,与入库的严写金样 82/82 一致。两份金样从此互相压着。
36
+ - **负控**(`test/conformance-mutation.test.ts` 第十一靶):把 `parseDomain` 换成读腿的宽松解析 ⇒ 严写腿**恰好**红在那 22 条读腿抢救 case 上
37
+ (别的腿、别的 case 一格不动);把非域名键静默跳过 ⇒ 恰好红在那 5 条上。
38
+ - **`parseDomain` 对非域名响亮拒**:`"x" is not a config domain`(名字出门前过与告警坐标同一道脱敏)。此前 `DOMAIN_SCHEMAS[x]` 是 undefined,
39
+ TypeError 被 getter 守卫吞掉、说成 `invalid "x" config (a value could not be read during validation)` —— 抛还是抛,原因说错了。
40
+ 严写腿把文档集的每个键都交给它,所以这句话现在是用户看得见的拒因。真域名的行为一字不变。
41
+
42
+ ### 二、每条腿都要表态:实现,或显式声明不承担(一条规则,四腿同律)
43
+
44
+ **病**:3.0.0 起「未登记的腿 ⇒ `skipped: no adapter`,不算违例」。闭集加一条腿时,这条律让每个没接新腿的消费方**静默跳过、默认当通过** ——
45
+ 「这个消费方结构上没有这条写法」与「这个消费方忘了接」在报告里长一个样。
46
+
47
+ **修**:
48
+
49
+ - `ConformanceAdapter.legs` 改为全量 `Record<LegName, LegStance>`;`LegStance = ConformanceLeg | LegDeclined`,
50
+ `LegDeclined = { declined: LegDeclineReason; why: string }`。两臂**互斥**(`ConformanceLeg.declined?: never`、`LegDeclined.entry?` / `run?: never`),
51
+ 运行期 release 门同一条律(带 `declined` 又带 `entry` 或 `run` = 形不对)—— 编译过了就证明表态形合法。收窄用 `stance.declined !== undefined`
52
+ (`"run" in stance` 收不窄)。理由词是闭集(`LEG_DECLINE_REASONS`),今天一个词 `"no-production-path"`
53
+ = 你的生产代码里结构上就没有这种读法 / 写法。刻意没有「有路径、还没接」的词:有路径就接,结局上的已知差走既有账本(带台账号、翻转条件、stale 律)。
54
+ - release 律:**每条腿都有表态**(缺表态 / 形不对 / 理由词不在闭集 / `why` 为空 / 既实现又声明 = 违例)· 至少**实现**一条腿 · **实现的腿必须跑**。
55
+ 声明的腿逐格记 `skipped`(detail = `consumer declines this leg (<理由词>): <why>`),报告新增 `adapter.declined` 逐条列出。historical 模式不跑这些律。
56
+ - 新导出(`./conformance`):`LEG_DECLINE_REASONS`、`LEG_EXPECTATION`、类型 `LegStance` / `LegDeclined` / `LegDeclineReason` / `DomainDocsInput`。
57
+
58
+ ### 三、`migrate-workers --check` 与真跑过同一只判定
59
+
60
+ **病**(7.0.0 既有,7.0.1 披露):`--check` 在整域终判**之前**就返回 —— 整域会被拒的文档(例:两只同名 worker)`--check` 报
61
+ `outcome=pending` / 退出 3,真跑报 `outcome=refused` / 退出 1,按 `--check` 做闸的脚本会放行一次必被拒的真跑;整域终判拒时 stdout 的
62
+ `refused[]` 为空,原因只在 stderr。
63
+
64
+ **修**:写之前只有**一只**判定 —— 逐只 refused → 无改动 ⇒ noop → 整域终判 → 有改动;`--check` 不参与判定,只决定「有改动」那一支写不写
65
+ (`pending` / `applied`)。整域终判拒时 `refused[]` 逐条带出被拒项,形与逐只 refused 同一套(`name` / `field` / `reason`;`field` 相对那只 worker;
66
+ 条目本身没有字符串名字时 `name` 为 `null`,不编名字)。stderr 两句文案、退出码的含义表、`outcome` 闭集都不变。`migrateWorkersDomain` 的输出不变。
67
+
68
+ ### 四、导出面登记表(API 面卫生的机器门,零新依赖)
69
+
70
+ - `scripts/api-surface.mjs`(`npm run api:surface [-- --check]`)用 TypeScript 检查器(本来就在 devDependencies)列出 exports 表里 22 条模块子路径的
71
+ 全部导出名(`export *` 展开,`value` / `type` / `value+type` 三形),写成 `docs/API-SURFACE.md`(不随包发)。
72
+ - `test/api-surface.test.ts`:登记表逐名逐形 ≡ 真实导出面;子路径集合与次序 = exports 表;登记表 = 生成器原样输出;判据自证(每条子路径「值」形的名字
73
+ ≡ 运行期模块对象的键)。加 / 删 / 改形一个导出而不重生成登记表 ⇒ 红 —— 导出面的每次变动从此以登记表 diff 出现在评审里。
74
+ - 编译器层的死代码门早已在位:`tsconfig.json` 的 `noUnusedLocals` / `noUnusedParameters`(`npm run typecheck` 覆盖 src + test),本版不重复造。
75
+ - **`./migrate` 本版不删**:五个消费仓 `origin/main` 普查,管理面仍有三处生产 import(hosts grandfather 路由、hosts status 路由、
76
+ workers migrate-model 路由:`grandfatherHosts` / `planWorkerModelMigration` / `applyModelMigrationPlan` / `migrationDiffViolations` /
77
+ `ApplyMigrationDocs` / `LegacyWorkerModel`);server / cli / client-core / sdk 零 import。零外部消费的前提不成立 ⇒ 不删(ROADMAP 条目已按普查改写)。
78
+ `./api/wire` 早在 `3.0.0` 删除;`createSiblingResolver` 的工厂命名在 `2.0.0` 已改(本版核过,无动作)。
79
+
80
+ ### 用户可见变化(谁受影响 / 怎么改 / 不改会怎样)
81
+
82
+ 1. **实现了 kit 适配器的消费方**(server、管理面)—— `legs` 少任何一条腿的表态 ⇒ **tsc 红**(`Partial<Record<LegName, ConformanceLeg>>` 不再可赋给
83
+ `legs`)。改:每条没有的腿写 `{ declined: "no-production-path", why: "<你仓的事实>" }`。不改(用 `as` 绕过类型)⇒ release 门违例
84
+ 「leg `<腿名>` has no stance」,`report.ok = false`。
85
+ 2. **持 `Record<LegName, …>` / 对 `LegName` 穷举 `switch` 的消费方** —— 编译红,要为 `strictWrite` 表态(实现或声明)。
86
+ 3. **读 `report.adapter.legs` 的** —— 语义从「登记的腿」变成「**实现**的腿」(声明的腿在新字段 `adapter.declined` 里);读 `skipped` 格 detail
87
+ 文案做判断的 —— `no adapter: …` 那句不再出现,换成 `consumer declines this leg (…)` 或 `no stance: …`。
88
+ 4. **按 `migrate-workers --check` 做闸的脚本** —— 整域会被拒的文档(同名 / 同路由 / replicas 与 autoscaling 并存)现在 `--check` 也是
89
+ `outcome=refused` / 退出 1(以前 `pending` / 3)。退出码 3 仍只表示「会改、且写得进去」。读 `refused[]` 的 —— 这种文档上它不再是空数组,
90
+ 项里 `name` 可以是 `null`(条目本身没有名字)。
91
+ 5. **捕 `parseDomain` 抛错文案的**(只在传入非域名时)—— 文案从 `invalid "x" config (a value could not be read during validation)` 变成
92
+ `"x" is not a config domain`;抛的时机不变。
93
+ 6. **装了包、只用 schema / 读写 helper 的消费方**:零变化(契约 schema、默认值、读腿读数一字未动)。
94
+
95
+ ### 红先 / 门
96
+
97
+ - A:新腿前置格(`conformance-strict-write-8.0.0`)与表态律格(`conformance-legs-and-ledger` ①)。表态律的改前读数 = 7.0.1 同一构造
98
+ `violations=[]`、`ok=true`、detail `no adapter: this consumer registered no such read leg`(旧格「适配器缺一条腿 ⇒ 不算违例」钉的正是它,
99
+ 本版按意图翻向);`parseDomain("hooks")` 改前抛 `a value could not be read`。
100
+ - C:先只改测试、对 7.0.1 的 `cli.ts` 跑 —— 4 格红:四态表新行 `--check` 整域拒 `expected 3 to be 1`;翻钉格
101
+ `[3, 'outcome=pending written=false']` ≠ `[1, 'outcome=refused written=false']`;`refused[]` `expected 0 to be greater than 0`;`--check` 的 stderr 为空。
102
+ 修后 25/25。原 🚧 特征钉(`--check` 先于整域终判)按它自己的翻转句翻成新读数。
103
+ - B:`test/api-surface.test.ts`(五格)。
104
+ - 全量:见收车读数(`npm test` / `npm run build` / `npm pack --dry-run`)。
105
+
106
+ ### 消费方(迁移句)
107
+
108
+ - **server**(今天精确钉 6.4.0;一致性门实现 `wire` + `localRoot`):提货 8.x 时 `serverAdapter` 的 `legs` 改成
109
+ `{ domains: { declined: "no-production-path", why: "…" }, strictWrite: { declined: "no-production-path", why: "worker 只读不写:写入只经中心" },
110
+ localRoot: …, wire: … }` —— `Partial<Record<LegName, ConformanceLeg>>` 那一行换成 `ConformanceAdapter["legs"]`。语料新增的 82 格严写读数在你这边
111
+ 记 `skipped`(声明不承担),既有账本不受影响。`./migrate`:零读点(你的提货门已钉零 import),删不删都不碰你。
112
+ - **管理面(config-center 产品,今天钉 `^6.4.0`)**:提货 8.x 时 `legs` 四条表态 —— `domains` = 现在那条往返腿;**`strictWrite` = 域写受理那一段**
113
+ (对文档集的每个键跑写受理,任一份不受理 ⇒ `{ outcome: "rejected", stage: "validate", diagnostics }`,全部受理 ⇒
114
+ `{ outcome: "effective", effective: { <键>: <受理后的域文档> }, warnings: [], version: input.version, updatedAt: input.updatedAt }`);
115
+ `wire` / `localRoot` 声明 `no-production-path`(你生产 wire、不消费它;不读 `config.d/`)。两处已知差先看清再接:写受理剥未知键时回报的那份
116
+ 名单在 kit 里没有位置(严写腿告警面恒空,按 `warnings: []` 交);写受理里的跨域引用完整性 `parseDomain` 不做(今天 40 条受理 case 跑过
117
+ `refIntegrityIssues` 零命中,所以暂不分叉)。`./migrate` 三处 import 不受影响(本版不删)。
118
+ - **cli / client-core**(钉 `^7`,脱字号不跨 major):不提货就零动作。提货时:不实现 kit 适配器(cli 已显式拒接 kit 读腿;client-core 零 import)⇒ 零改动;
119
+ cli 那一格「`conformance.projectWarning === configFns.projectWarning`」同一只函数,不受影响;`./migrate` 零 import。
120
+ - **sdk**:不依赖本包,零动作。
121
+
122
+ ### 订正 7.0.1 披露(test d327-schema701 验收 32 格 PASS 31 / INFO 1 / FAIL 0 后补)
123
+
124
+ - **用户可见变化补一形(比字段改名更硬)**:7.0.1 起 `migrate-workers` 的 stdout **首行是结论行**(`outcome=… written=…`),整段 `JSON.parse(stdout)` 的旧脚本会抛错(test 实测 25/25 抛;7.0.0 为 0/24)—— 机读请取**最后一行**解析,或改读 `outcome` / `written` / `refused` 三个契约字段。
125
+ - 7.0.1 披露的「已知未修」两形(`--check` 先于整域终判、整域终判拒时 `refused[]` 为空)本版已修,见上第三节。
126
+
127
+ ### 未闭环(如实)
128
+
129
+ - 严写腿没有 historical 读数(`conformance/historical/` 是三腿时代跑的,53 条 × 三条读腿);重跑会改写一批 `firstRedOn`,本版没做。
130
+ - 严写腿告警面恒空(`parseDomain` 不铸告警),写受理回报的剥键名单在 kit 里没有位置;严写不含跨域引用完整性。两样都是契约层的下一步,候裁。
131
+ - 语料 c06 / c20 的严写金样是 🚧 特征格(S-1020,已定性为缺陷、下一版源头修):写入席受理 runtime 旧文档时三闸旧键被 runtime schema 静默剥掉(读腿是抬升),管理面若按受理形落库,读侧抬升此后找不到它们 = 运维护栏静默消失。修掉(写侧对这三个旧键响亮拒)时两格翻 `rejected{validate}`;在那之前,一个**正确拒收**的管理面会在这两格与金样分歧,请登记进 `knownDivergences`。
10
132
 
11
133
  ## 7.0.1 — 2026-10-03 — 7.0.0 验收三件(文案 / 报告形):拒因补迁移指路、CLI 报告「没写」机读不可误读、指路写 `npx` 全名形(patch)
12
134
 
package/README.md CHANGED
@@ -48,6 +48,19 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
48
48
  > a zod-3 host (today the config-center product) still dedups to one instance. Moving this package to zod 4 is a major
49
49
  > of its own (S-777); 6.2.0 does not touch the dependency or peer range.
50
50
 
51
+ ## 升级指引:7.x → 8.0.0
52
+
53
+ 8.0.0 的破坏面只在**一致性门 kit**(`./conformance`)的类型与 release 律、以及 CLI `migrate-workers --check` 的一种结局上;契约 schema、域默认值、
54
+ 读写 helper 的读数一字未动。逐条(谁受影响 / 怎么改 / 不改会怎样)见 CHANGELOG 8.0.0「用户可见变化」,要点:
55
+
56
+ - **实现了 kit 适配器的**:`legs` 是全量 `Record<LegName, LegStance>` —— 四条腿(`domains` / `strictWrite` / `wire` / `localRoot`)每条要么实现、
57
+ 要么写 `{ declined: "no-production-path", why: "<你仓的事实>" }`。少一条 ⇒ tsc 红;用 `as` 绕过 ⇒ release 门违例 `has no stance`。
58
+ 只读的 worker:`domains` 与 `strictWrite` 声明不承担;写入席:实现 `strictWrite`(域写受理,任一份不受理 ⇒ `rejected{validate}`)。
59
+ - **持 `Record<LegName, …>` / 对 `LegName` 穷举的**:为新腿 `strictWrite` 表态。
60
+ - **按 `migrate-workers --check` 做闸的脚本**:整域会被拒的文档 `--check` 现在报 `outcome=refused` / 退出 1(以前 `pending` / 3),`refused[]`
61
+ 逐条带出被拒项(`name` 可为 `null`)。
62
+ - **只用 schema / helper 的**:零动作。`./migrate` 本版**没有**删(管理面仍在用)。
63
+
51
64
  ## What's in it
52
65
 
53
66
  - **The zod contract** (`types.ts`): `DOMAIN_SCHEMAS` (17 domains — 0.10.0 adds `projects`, the project
@@ -114,6 +127,11 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
114
127
  | `dropped` (always) | `dropped` when `written=true`, else `wouldDrop` |
115
128
  | — | `outcome`, `written` |
116
129
 
130
+ **8.0.0**: `--check` and a real run go through ONE verdict (per-worker refusals → nothing to change ⇒ `noop` → the whole-domain final judgment →
131
+ changes); `--check` only decides whether the "changes" branch writes (`pending`) or not. A document the whole-domain judgment refuses (two workers
132
+ with one name …) is `outcome=refused` / exit 1 under `--check` too (7.x: `pending` / exit 3), and `refused[]` names the refused items in the
133
+ per-worker shape (`name` / `field` / `reason`; `name` is `null` for an entry that carries no string name) instead of being empty.
134
+
117
135
  Rejections that a migration can lift carry ONE signpost sentence (a constant, `MIGRATION_SIGNPOST`): the seven deleted 6.x fields, and an
118
136
  `env` / `secretEnv` key the migration moves or drops (the execution-lane keys incl. `REMOTE_EXEC` / `REQUIRE_EXECUTION_LANE`, and the store keys
119
137
  `DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND`). A reserved name the migration cannot move (`PORT`, `MODEL_*`, `MYSQL_*` …) carries none.
@@ -269,9 +287,11 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
269
287
 
270
288
  ## Cross-repo conformance gate (`./conformance`, 3.0.0)
271
289
 
272
- 契约仓自带**金样语料 + 比对器**,随包发(`conformance/cases/**`)。每个消费方把**自己的生产读腿**塞给
290
+ 契约仓自带**金样语料 + 比对器**,随包发(`conformance/cases/**`)。每个消费方把**自己的生产入口**塞给
273
291
  kit 跑一遍 ⇒ 同一份 settings 语料在配置中心、worker、管理面、契约仓自身读出的 effective 逐字节一致、
274
- 告警多重集一致。
292
+ 告警多重集一致;写入席对同一份文档收不收也一致。**四条腿**(8.0.0):`domains`(各域原始文档 → effective)、
293
+ `strictWrite`(同一份文档 → 严写受理;契约仓 = `parseDomain`,任一份不受理 ⇒ `rejected{validate}`,读腿抢救处严写拒)、
294
+ `wire`、`localRoot`。
275
295
 
276
296
  ```ts
277
297
  import { runConformance } from "@sema-agent/settings-schema/conformance";
@@ -283,13 +303,15 @@ if (!report.ok) throw new Error(report.violations.join("\n"));
283
303
  (**生产代码**的入口 —— 在测试里把契约包函数本身当读腿,门证明的只是「包等于包」)。4.0.0 起这句
284
304
  **有牙**:入口落在**本包自己的 `dist/`**(拿自己比自己)⇒ `createContractAdapter` **当场拒**;落在
285
305
  **另一份**装好的契约包里(`historical` 模式的正当目标)⇒ 工厂不拒,由 `release` 门点名。
286
- - **两种模式**:`release`(版本三方对齐 + 生产入口核验 + 禁 `only`/归一器 + 至少登记一条腿 + **登记的腿
287
- 必须跑**;未登记的腿只记 `skipped: no adapter`,不算违例)与 `historical`(对旧版包跑,只记行为 diff)。
306
+ - **每条腿都要表态**(8.0.0):实现它,或 `{ declined: "no-production-path", why }`(你的生产代码结构上没有这种读法 / 写法)。
307
+ 缺表态 = release 违例;声明的腿逐格 `skipped`(带理由),报告 `adapter.declined` 列出。
308
+ - **两种模式**:`release`(版本三方对齐 + 每条腿有表态 + 生产入口核验 + 禁 `only`/归一器 + 至少实现一条腿 + **实现的腿
309
+ 必须跑**)与 `historical`(对旧版包跑,只记行为 diff)。
288
310
  - **四律**:对抗律 / 变异律 / 覆盖律 / 来源律 —— 缺一不算「门」。
289
311
  - **已知分歧账本** `knownDivergences`:消费方对金样的**合法**差异(临时欠账 / 成文语义)登记成机器可核的
290
312
  一行,命中记 `diff-ledgered` 不阻塞;**告警差与元数据差永不可入账**,而**从未命中的一条登记本身就是
291
313
  违例**(棘轮只降不升,逼着到期翻转)。契约仓自证不许挂账本。
292
- - 契约形、报告形、语料贡献形、以及**三条如实登记的边界**见 `docs/CONFORMANCE.md`。
314
+ - 契约形、报告形、语料贡献形、以及**如实登记的边界**见 `docs/CONFORMANCE.md`。
293
315
  - Node-only(语料在磁盘上,local-root 腿要落盘),不进主 barrel。
294
316
 
295
317
  ## Secret boundary
@@ -317,6 +339,7 @@ defaults. Each `config.d/<domain>.json` is exactly that domain's zod object.
317
339
  自 `1.0.0`(改名)起本包按**标准 semver** 发版;`0.x` 时代的「破坏性变更必 bump minor」规矩**已撤**
318
340
  (末条仅作历史注记)。判档看**消费方可见性**,不看 diff 大小:
319
341
 
342
+ - **CLI 报告形的契约边界(7.0.1 起)**:`migrate-workers` 的 stdout = 结论行 + JSON;其中 `outcome` / `written` / `refused` 三字段是机读契约(再改按 minor),其余键与行形是诊断面,patch 可变(test 认定口径 2026-10-03:CLI 报告形不在 C-R197 包导出面)。机读请取最后一行解析。
320
343
  - **破坏性变更必 bump major**:字段/导出改名、改形(retype)、删除、默认值变更、校验收紧或放宽
321
344
  (例:`.passthrough()` → 默认 strip、宽读 → 严判)、枚举**重排**——凡是能让一个按上一版契约写的
322
345
  消费方在**运行期**变红、或让同一份配置解析出不同结果的改动;编译期同判,**唯一裁定例外** = 下一条的
@@ -1 +1 @@
1
- 7.0.1
1
+ 8.0.0
@@ -98,6 +98,11 @@
98
98
  "updatedAt": "2026-09-16T00:00:00.000Z"
99
99
  }
100
100
  },
101
+ "strictWrite": {
102
+ "outcome": "rejected",
103
+ "stage": "validate",
104
+ "why": "拒:读腿逐条抢救 models(丢掉缺 id 的那一条 + entry-dropped);写入席不替运维删条目 —— 同一份文档里有一条坏条目,整次不受理。"
105
+ },
101
106
  "wire": {
102
107
  "derive": true
103
108
  },
@@ -89,6 +89,11 @@
89
89
  "updatedAt": "2026-09-16T00:00:00.000Z"
90
90
  }
91
91
  },
92
+ "strictWrite": {
93
+ "outcome": "rejected",
94
+ "stage": "validate",
95
+ "why": "拒:读腿对 hosts 先做存量夹紧(超长名剔除,hosts-grandfathered)再逐条抢救(entry-dropped);两样都只活在读路径,严写按现行 schema 判:名长 > 63 与名字形不合法都拒。"
96
+ },
92
97
  "wire": {
93
98
  "derive": true
94
99
  },
@@ -102,6 +102,47 @@
102
102
  "updatedAt": "2026-09-16T00:00:00.000Z"
103
103
  }
104
104
  },
105
+ "strictWrite": {
106
+ "outcome": "effective",
107
+ "effective": {
108
+ "models": {
109
+ "models": [
110
+ {
111
+ "api": "openai-completions",
112
+ "apiKeyEnv": "OPENAI_API_KEY",
113
+ "enabled": true,
114
+ "id": "m0-id",
115
+ "name": "m0",
116
+ "provider": "openai",
117
+ "quotaWeight": 1,
118
+ "reasoning": false,
119
+ "tier": "standard",
120
+ "vision": false
121
+ },
122
+ {
123
+ "api": "openai-completions",
124
+ "apiKeyEnv": "OPENAI_API_KEY",
125
+ "enabled": true,
126
+ "id": "m1-id",
127
+ "name": "m1",
128
+ "provider": "openai",
129
+ "quotaWeight": 1,
130
+ "reasoning": false,
131
+ "tier": "standard",
132
+ "vision": false
133
+ }
134
+ ],
135
+ "roles": {},
136
+ "tierGroups": []
137
+ }
138
+ },
139
+ "warnings": [],
140
+ "metadata": {
141
+ "version": 7,
142
+ "updatedAt": "2026-09-16T00:00:00.000Z"
143
+ },
144
+ "why": "受理:models 文档里字面叫 `models.1.k` 的键与条目里的未知键 `k` 都被 schema 剥掉(读腿报 unknown-keys-dropped);parseDomain 同样剥、不铸告警 —— 受理形逐字等于读腿 models 那一格,告警面为空。"
145
+ },
105
146
  "wire": {
106
147
  "derive": true
107
148
  },
@@ -67,6 +67,11 @@
67
67
  "updatedAt": "2026-09-16T00:00:00.000Z"
68
68
  }
69
69
  },
70
+ "strictWrite": {
71
+ "outcome": "rejected",
72
+ "stage": "validate",
73
+ "why": "拒:全坏集合,读腿救不出一条幸存者、整域降级成默认(domain-defaulted);写入席不会把坏文档换成空默认存下。"
74
+ },
70
75
  "wire": {
71
76
  "derive": true
72
77
  },
@@ -3,6 +3,11 @@
3
3
  "outcome": "rejected",
4
4
  "stage": "validate"
5
5
  },
6
+ "strictWrite": {
7
+ "outcome": "rejected",
8
+ "stage": "validate",
9
+ "why": "拒:rosters 是 gate 域,读腿对坏条目(name 是数字)本就整域拒;严写同一份文档同样过不了 rosters 的 schema。"
10
+ },
6
11
  "wire": {
7
12
  "outcome": "rejected",
8
13
  "stage": "validate",
@@ -89,6 +89,18 @@
89
89
  "updatedAt": "2026-09-16T00:00:00.000Z"
90
90
  }
91
91
  },
92
+ "strictWrite": {
93
+ "outcome": "effective",
94
+ "effective": {
95
+ "runtime": {}
96
+ },
97
+ "warnings": [],
98
+ "metadata": {
99
+ "version": 7,
100
+ "updatedAt": "2026-09-16T00:00:00.000Z"
101
+ },
102
+ "why": "🚧 S-1020 特征格(已裁 = 缺陷,源头修排下一版):受理,受理形 = { runtime: {} }。读腿从 runtime 旧文档抬升 governance 三闸(legacy-governance-lifted);写入席不抬升 —— 受理的只有提交的 runtime 一份,三闸旧键被 runtime schema 静默剥掉(不拒、不告警,读腿的剥键扫描本就豁免这三个键)。写入席若按受理形落库,读侧抬升此后找不到它们。S-1020 修掉(写侧拒 runtime 上的三闸旧键)时本格翻 rejected{validate}。"
103
+ },
92
104
  "wire": {
93
105
  "derive": true
94
106
  },
@@ -62,6 +62,11 @@
62
62
  "updatedAt": "2026-09-16T00:00:00.000Z"
63
63
  }
64
64
  },
65
+ "strictWrite": {
66
+ "outcome": "rejected",
67
+ "stage": "validate",
68
+ "why": "拒:`hooks` 不是域名(它是 settings.json 的键,不是配置域)—— 写入席没有这个域可受理;读腿各按各的处置(domains 腿按域名取文档故无声、wire 逐字承运、本地报 not-a-domain),严写不替运维把它丢掉。"
69
+ },
65
70
  "wire": {
66
71
  "derive": true
67
72
  },
@@ -326,6 +326,83 @@
326
326
  "updatedAt": "2026-09-16T00:00:00.000Z"
327
327
  }
328
328
  },
329
+ "strictWrite": {
330
+ "outcome": "effective",
331
+ "effective": {
332
+ "limits": {
333
+ "zzzUnknown000": 0,
334
+ "zzzUnknown001": 1,
335
+ "zzzUnknown002": 2,
336
+ "zzzUnknown003": 3,
337
+ "zzzUnknown004": 4,
338
+ "zzzUnknown005": 5,
339
+ "zzzUnknown006": 6,
340
+ "zzzUnknown007": 7,
341
+ "zzzUnknown008": 8,
342
+ "zzzUnknown009": 9,
343
+ "zzzUnknown010": 10,
344
+ "zzzUnknown011": 11,
345
+ "zzzUnknown012": 12,
346
+ "zzzUnknown013": 13,
347
+ "zzzUnknown014": 14,
348
+ "zzzUnknown015": 15,
349
+ "zzzUnknown016": 16,
350
+ "zzzUnknown017": 17,
351
+ "zzzUnknown018": 18,
352
+ "zzzUnknown019": 19,
353
+ "zzzUnknown020": 20,
354
+ "zzzUnknown021": 21,
355
+ "zzzUnknown022": 22,
356
+ "zzzUnknown023": 23,
357
+ "zzzUnknown024": 24,
358
+ "zzzUnknown025": 25,
359
+ "zzzUnknown026": 26,
360
+ "zzzUnknown027": 27,
361
+ "zzzUnknown028": 28,
362
+ "zzzUnknown029": 29,
363
+ "zzzUnknown030": 30,
364
+ "zzzUnknown031": 31,
365
+ "zzzUnknown032": 32,
366
+ "zzzUnknown033": 33,
367
+ "zzzUnknown034": 34,
368
+ "zzzUnknown035": 35,
369
+ "zzzUnknown036": 36,
370
+ "zzzUnknown037": 37,
371
+ "zzzUnknown038": 38,
372
+ "zzzUnknown039": 39,
373
+ "zzzUnknown040": 40,
374
+ "zzzUnknown041": 41,
375
+ "zzzUnknown042": 42,
376
+ "zzzUnknown043": 43,
377
+ "zzzUnknown044": 44,
378
+ "zzzUnknown045": 45,
379
+ "zzzUnknown046": 46,
380
+ "zzzUnknown047": 47,
381
+ "zzzUnknown048": 48,
382
+ "zzzUnknown049": 49,
383
+ "zzzUnknown050": 50,
384
+ "zzzUnknown051": 51,
385
+ "zzzUnknown052": 52,
386
+ "zzzUnknown053": 53,
387
+ "zzzUnknown054": 54,
388
+ "zzzUnknown055": 55,
389
+ "zzzUnknown056": 56,
390
+ "zzzUnknown057": 57,
391
+ "zzzUnknown058": 58,
392
+ "zzzUnknown059": 59,
393
+ "zzzUnknown060": 60,
394
+ "zzzUnknown061": 61,
395
+ "zzzUnknown062": 62,
396
+ "zzzUnknown063": 63
397
+ }
398
+ },
399
+ "warnings": [],
400
+ "metadata": {
401
+ "version": 7,
402
+ "updatedAt": "2026-09-16T00:00:00.000Z"
403
+ },
404
+ "why": "受理:limits 是 open-world,64 个未知键原样承运(读腿报 unknown-keys-carried);严写同样承运,受理形等于读腿 limits 那一格,告警面为空。"
405
+ },
329
406
  "wire": {
330
407
  "derive": true
331
408
  },
@@ -328,6 +328,84 @@
328
328
  "updatedAt": "2026-09-16T00:00:00.000Z"
329
329
  }
330
330
  },
331
+ "strictWrite": {
332
+ "outcome": "effective",
333
+ "effective": {
334
+ "limits": {
335
+ "zzzUnknown000": 0,
336
+ "zzzUnknown001": 1,
337
+ "zzzUnknown002": 2,
338
+ "zzzUnknown003": 3,
339
+ "zzzUnknown004": 4,
340
+ "zzzUnknown005": 5,
341
+ "zzzUnknown006": 6,
342
+ "zzzUnknown007": 7,
343
+ "zzzUnknown008": 8,
344
+ "zzzUnknown009": 9,
345
+ "zzzUnknown010": 10,
346
+ "zzzUnknown011": 11,
347
+ "zzzUnknown012": 12,
348
+ "zzzUnknown013": 13,
349
+ "zzzUnknown014": 14,
350
+ "zzzUnknown015": 15,
351
+ "zzzUnknown016": 16,
352
+ "zzzUnknown017": 17,
353
+ "zzzUnknown018": 18,
354
+ "zzzUnknown019": 19,
355
+ "zzzUnknown020": 20,
356
+ "zzzUnknown021": 21,
357
+ "zzzUnknown022": 22,
358
+ "zzzUnknown023": 23,
359
+ "zzzUnknown024": 24,
360
+ "zzzUnknown025": 25,
361
+ "zzzUnknown026": 26,
362
+ "zzzUnknown027": 27,
363
+ "zzzUnknown028": 28,
364
+ "zzzUnknown029": 29,
365
+ "zzzUnknown030": 30,
366
+ "zzzUnknown031": 31,
367
+ "zzzUnknown032": 32,
368
+ "zzzUnknown033": 33,
369
+ "zzzUnknown034": 34,
370
+ "zzzUnknown035": 35,
371
+ "zzzUnknown036": 36,
372
+ "zzzUnknown037": 37,
373
+ "zzzUnknown038": 38,
374
+ "zzzUnknown039": 39,
375
+ "zzzUnknown040": 40,
376
+ "zzzUnknown041": 41,
377
+ "zzzUnknown042": 42,
378
+ "zzzUnknown043": 43,
379
+ "zzzUnknown044": 44,
380
+ "zzzUnknown045": 45,
381
+ "zzzUnknown046": 46,
382
+ "zzzUnknown047": 47,
383
+ "zzzUnknown048": 48,
384
+ "zzzUnknown049": 49,
385
+ "zzzUnknown050": 50,
386
+ "zzzUnknown051": 51,
387
+ "zzzUnknown052": 52,
388
+ "zzzUnknown053": 53,
389
+ "zzzUnknown054": 54,
390
+ "zzzUnknown055": 55,
391
+ "zzzUnknown056": 56,
392
+ "zzzUnknown057": 57,
393
+ "zzzUnknown058": 58,
394
+ "zzzUnknown059": 59,
395
+ "zzzUnknown060": 60,
396
+ "zzzUnknown061": 61,
397
+ "zzzUnknown062": 62,
398
+ "zzzUnknown063": 63,
399
+ "zzzUnknown064": 64
400
+ }
401
+ },
402
+ "warnings": [],
403
+ "metadata": {
404
+ "version": 7,
405
+ "updatedAt": "2026-09-16T00:00:00.000Z"
406
+ },
407
+ "why": "受理:同上一条,65 个未知键(读腿告警截断到 64 条坐标 + truncatedFrom,那是告警面的事);严写逐字承运全部 65 个,告警面为空。"
408
+ },
331
409
  "wire": {
332
410
  "derive": true
333
411
  },
@@ -96,6 +96,11 @@
96
96
  "updatedAt": "2026-09-16T00:00:00.000Z"
97
97
  }
98
98
  },
99
+ "strictWrite": {
100
+ "outcome": "rejected",
101
+ "stage": "validate",
102
+ "why": "拒:models.roles 里有一个密钥形的坏键,读腿把它逐条丢掉(entry-dropped,坐标脱敏);严写不删条目 ⇒ 整次不受理。"
103
+ },
99
104
  "wire": {
100
105
  "derive": true
101
106
  },
@@ -78,6 +78,20 @@
78
78
  "updatedAt": "2026-09-16T00:00:00.000Z"
79
79
  }
80
80
  },
81
+ "strictWrite": {
82
+ "outcome": "effective",
83
+ "effective": {
84
+ "scenarios": {
85
+ "scenarios": []
86
+ }
87
+ },
88
+ "warnings": [],
89
+ "metadata": {
90
+ "version": 7,
91
+ "updatedAt": "2026-09-16T00:00:00.000Z"
92
+ },
93
+ "why": "受理:scenarios 里 CJK / emoji / 组合字符的未知键被 schema 剥掉(读腿报 unknown-keys-dropped);严写同样剥、不铸告警,受理形等于读腿 scenarios 那一格。"
94
+ },
81
95
  "wire": {
82
96
  "derive": true
83
97
  },
@@ -62,6 +62,11 @@
62
62
  "updatedAt": "2026-09-16T00:00:00.000Z"
63
63
  }
64
64
  },
65
+ "strictWrite": {
66
+ "outcome": "rejected",
67
+ "stage": "validate",
68
+ "why": "拒:`constructor` / `toString` / `hasOwnProperty` / `__proto__` 都不是域名 —— 原型链上的名字在严写这里同样只是「不是域的键」,写入席没有这些域可受理(读腿:domains 无声、wire 逐字承运、本地报 not-a-domain)。"
69
+ },
65
70
  "wire": {
66
71
  "derive": true
67
72
  },
@@ -67,6 +67,11 @@
67
67
  "updatedAt": "2026-09-16T00:00:00.000Z"
68
68
  }
69
69
  },
70
+ "strictWrite": {
71
+ "outcome": "rejected",
72
+ "stage": "validate",
73
+ "why": "拒:models 字段是 null,读腿整域降级成默认(domain-defaulted);写入席不把 null 读成空目录存下(与 c14 的合法空数组分开)。"
74
+ },
70
75
  "wire": {
71
76
  "derive": true
72
77
  },
@@ -62,6 +62,22 @@
62
62
  "updatedAt": "2026-09-16T00:00:00.000Z"
63
63
  }
64
64
  },
65
+ "strictWrite": {
66
+ "outcome": "effective",
67
+ "effective": {
68
+ "models": {
69
+ "models": [],
70
+ "roles": {},
71
+ "tierGroups": []
72
+ }
73
+ },
74
+ "warnings": [],
75
+ "metadata": {
76
+ "version": 7,
77
+ "updatedAt": "2026-09-16T00:00:00.000Z"
78
+ },
79
+ "why": "受理:空目录是合法输入,读腿零告警零降级;严写受理同一份文档,受理形等于读腿 models 那一格(与 c13 的 null 分开)。"
80
+ },
65
81
  "wire": {
66
82
  "derive": true
67
83
  },
@@ -67,6 +67,11 @@
67
67
  "updatedAt": "2026-09-16T00:00:00.000Z"
68
68
  }
69
69
  },
70
+ "strictWrite": {
71
+ "outcome": "rejected",
72
+ "stage": "validate",
73
+ "why": "拒:models 字段是数字,读腿整域降级成默认(domain-defaulted);写入席不会把坏文档换成空默认存下。"
74
+ },
70
75
  "wire": {
71
76
  "derive": true
72
77
  },
@@ -72,6 +72,20 @@
72
72
  "updatedAt": "2026-09-16T00:00:00.000Z"
73
73
  }
74
74
  },
75
+ "strictWrite": {
76
+ "outcome": "effective",
77
+ "effective": {
78
+ "scenarios": {
79
+ "scenarios": []
80
+ }
81
+ },
82
+ "warnings": [],
83
+ "metadata": {
84
+ "version": 7,
85
+ "updatedAt": "2026-09-16T00:00:00.000Z"
86
+ },
87
+ "why": "受理:205 层嵌套的未知值被 schema 剥掉(读腿报 unknown-keys-dropped);严写没有内容哈希那一层(本地腿在 hash 层的 200 层硬闸与它无关),受理剥后的 scenarios,告警面为空。"
88
+ },
75
89
  "wire": {
76
90
  "derive": true
77
91
  },