@sema-agent/settings-schema 6.4.0 → 7.0.1

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 (95) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +53 -2
  3. package/bin/settings-schema.mjs +22 -0
  4. package/conformance/VERSION +1 -1
  5. package/conformance/cases/c50-s784-lane-host-legal/expected.json +102 -0
  6. package/conformance/cases/c50-s784-lane-host-legal/input.json +29 -0
  7. package/conformance/cases/c50-s784-lane-host-legal/meta.json +5 -0
  8. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +104 -0
  9. package/conformance/cases/c51-s784-lane-e2b-legal/input.json +32 -0
  10. package/conformance/cases/c51-s784-lane-e2b-legal/meta.json +5 -0
  11. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +106 -0
  12. package/conformance/cases/c52-s784-lane-k8s-legal/input.json +34 -0
  13. package/conformance/cases/c52-s784-lane-k8s-legal/meta.json +5 -0
  14. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +106 -0
  15. package/conformance/cases/c53-s784-lane-ssh-legal/input.json +34 -0
  16. package/conformance/cases/c53-s784-lane-ssh-legal/meta.json +5 -0
  17. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +102 -0
  18. package/conformance/cases/c54-s784-lane-adb-legal/input.json +29 -0
  19. package/conformance/cases/c54-s784-lane-adb-legal/meta.json +5 -0
  20. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +103 -0
  21. package/conformance/cases/c55-s784-lane-local-docker-legal/input.json +30 -0
  22. package/conformance/cases/c55-s784-lane-local-docker-legal/meta.json +5 -0
  23. package/conformance/cases/c56-s784-lane-device-legal/expected.json +109 -0
  24. package/conformance/cases/c56-s784-lane-device-legal/input.json +36 -0
  25. package/conformance/cases/c56-s784-lane-device-legal/meta.json +5 -0
  26. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +83 -0
  27. package/conformance/cases/c57-s784-g1-enabled-no-lane/input.json +23 -0
  28. package/conformance/cases/c57-s784-g1-enabled-no-lane/meta.json +5 -0
  29. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +83 -0
  30. package/conformance/cases/c58-s784-g2-multitenant-host/input.json +34 -0
  31. package/conformance/cases/c58-s784-g2-multitenant-host/meta.json +5 -0
  32. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +83 -0
  33. package/conformance/cases/c59-s784-g3-k8s-missing-image/input.json +26 -0
  34. package/conformance/cases/c59-s784-g3-k8s-missing-image/meta.json +5 -0
  35. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +83 -0
  36. package/conformance/cases/c60-s784-g3-e2b-missing-key/input.json +26 -0
  37. package/conformance/cases/c60-s784-g3-e2b-missing-key/meta.json +5 -0
  38. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +83 -0
  39. package/conformance/cases/c61-s784-g3-other-lane-key/input.json +30 -0
  40. package/conformance/cases/c61-s784-g3-other-lane-key/meta.json +5 -0
  41. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +83 -0
  42. package/conformance/cases/c62-s784-g7a-session-tidb/input.json +27 -0
  43. package/conformance/cases/c62-s784-g7a-session-tidb/meta.json +5 -0
  44. package/conformance/cases/c63-s784-host-two-replicas/expected.json +83 -0
  45. package/conformance/cases/c63-s784-host-two-replicas/input.json +27 -0
  46. package/conformance/cases/c63-s784-host-two-replicas/meta.json +5 -0
  47. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +83 -0
  48. package/conformance/cases/c64-s784-multitenant-local-store/input.json +30 -0
  49. package/conformance/cases/c64-s784-multitenant-local-store/meta.json +5 -0
  50. package/conformance/cases/c65-s784-pg-without-db/expected.json +83 -0
  51. package/conformance/cases/c65-s784-pg-without-db/input.json +27 -0
  52. package/conformance/cases/c65-s784-pg-without-db/meta.json +5 -0
  53. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +83 -0
  54. package/conformance/cases/c66-s784-memory-backend-mismatch/input.json +36 -0
  55. package/conformance/cases/c66-s784-memory-backend-mismatch/meta.json +5 -0
  56. package/conformance/cases/c67-s784-retired-field-loud/expected.json +83 -0
  57. package/conformance/cases/c67-s784-retired-field-loud/input.json +22 -0
  58. package/conformance/cases/c67-s784-retired-field-loud/meta.json +5 -0
  59. package/conformance/cases/c68-s784-device-local-store/expected.json +83 -0
  60. package/conformance/cases/c68-s784-device-local-store/input.json +26 -0
  61. package/conformance/cases/c68-s784-device-local-store/meta.json +5 -0
  62. package/conformance/cases/cli04-env-value-with-newline/expected.json +13 -2
  63. package/conformance/cases/cli04-env-value-with-newline/input.json +8 -1
  64. package/conformance/cases/cli04-env-value-with-newline/meta.json +1 -1
  65. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +49 -0
  66. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +26 -0
  67. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +4 -0
  68. package/conformance/migrate-workers/m02-g5-idempotent/expected.json +38 -0
  69. package/conformance/migrate-workers/m02-g5-idempotent/input.json +33 -0
  70. package/conformance/migrate-workers/m02-g5-idempotent/meta.json +5 -0
  71. package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +71 -0
  72. package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +42 -0
  73. package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +4 -0
  74. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +52 -0
  75. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +30 -0
  76. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +4 -0
  77. package/dist/cli.d.ts +8 -0
  78. package/dist/cli.js +110 -0
  79. package/dist/execution-lane-index.d.ts +38 -0
  80. package/dist/execution-lane-index.js +46 -0
  81. package/dist/execution-lanes.d.ts +64 -0
  82. package/dist/execution-lanes.js +152 -0
  83. package/dist/file-edit.js +26 -21
  84. package/dist/hooks.d.ts +4 -4
  85. package/dist/index.d.ts +2 -0
  86. package/dist/index.js +2 -0
  87. package/dist/migrate-workers.d.ts +63 -0
  88. package/dist/migrate-workers.js +327 -0
  89. package/dist/remote-exec.d.ts +23 -64
  90. package/dist/remote-exec.js +20 -71
  91. package/dist/secret-refs.d.ts +7 -1
  92. package/dist/secret-refs.js +9 -3
  93. package/dist/types.d.ts +1356 -455
  94. package/dist/types.js +267 -63
  95. package/package.json +9 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,215 @@
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 7.0.1 — 2026-10-03 — 7.0.0 验收三件(文案 / 报告形):拒因补迁移指路、CLI 报告「没写」机读不可误读、指路写 `npx` 全名形(patch)
12
+
13
+ > **判档 = patch**(README「发版纪律」:文案 / 报告形;**判据语义不变** —— 写闸的拒 / 放集合与每条 issue 的 `path` / `code` 同 7.0.0,`RESERVED_WORKER_ENV`
14
+ > 键集不变(90 个),`migrateWorkersDomain` 输出不变;变的只有**拒因文案**与 **CLI 报告形**。conformance 重加冕:`expected.json` / `baseline.json` 零 diff,只有
15
+ > `VERSION` 随版、c67 的 `meta.json` claim 文字改命令拼写)。来源 = test `d327-schema700` 验收的 ②③④(OBS-2 / OBS-3 与冲突 2)。
16
+ > 判据 G7b / G7c 的拆分见 design/327 v1.2(`sessionBackend:tidb` 带全坐标 ⇒ 迁 mysql;无坐标 / 无口令引用 ⇒ refused),**实现不变**。
17
+
18
+ ### 病(7.0.0)
19
+
20
+ - **② 车道旧形拒因不指路**:6.x 存量里最典型的一形 —— 车道只靠 `env.REMOTE_EXEC` / `K8S_*` 携带 —— 被保留名闸拒,拒因点名键与新字段,却**不提迁移命令**;
21
+ 只有被删结构键(`sessionBackend` 等)那几形的拒因指路。运维看到「env cannot set reserved key」,不知道有一条命令能整份搬过去。
22
+ - **③ CLI 报告歧义**:一个文件里有 worker 被 refused ⇒ rc 1、文件一字未写,但 stdout 的 JSON 把其余 worker 列在 `changed` 下;只解析 stdout 的运维脚本
23
+ 会以为它们已迁(真案:`evidence/l1/runs.json` g5-mixed,`fx-good-k8s` 列在 `changed`、盘上 sha256 与跑前相同)。
24
+ - **④ 指路写裸 bin 名**:拒因 / 文档里是 `settings-schema migrate-workers <file>`,没装本包的运维粘贴即 `command not found`(design/327 §3.4 的原句是 `npx` 全名形)。
25
+
26
+ ### 修
27
+
28
+ - **② 一条规则、一个谓词**:拒因带不带迁移指路只看**这个键「旧形 ⇒ 迁移能搬」**。「迁移能搬」= 迁移自己识别的键集 —— 车道键(车道表全部 env 名 +
29
+ `REMOTE_EXEC` / `REQUIRE_EXECUTION_LANE`)∪ 店键(`DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND`)—— 写闸读**同一只谓词**
30
+ (`isMigratableEnvKey`,包内私有,在 `execution-lane-index.ts`;迁移的 `laneKeysOf` / `storeKeysOf` 改读同一处),不是第二张手抄的表。命中的键,
31
+ `env` / `secretEnv` 两口的拒因都在键名后带指路;迁移搬不了的保留名(`PORT` / `MODEL_*` / `MYSQL_*` / `PG_*` / `SESSION_BACKEND` …)不带。句尾的
32
+ `— set it via <归属字段>` 一字不动(下游按它抽归属字段)。
33
+ - **④ 指路句是一个常量**:`MIGRATION_SIGNPOST`(内含 `MIGRATE_WORKERS_COMMAND` = `npx @sema-agent/settings-schema migrate-workers`),保留名闸与删除项预处理(七个被删旧键)
34
+ 引用同一个;迁移 / CLI 的 usage / 文档里的命令拼写同一处。全仓扫描零裸 bin 名残留(`CHANGELOG` 的 7.0.0 及更早段落是历史记录,原句不追改)。
35
+ - **③ 报告形**:CLI 对退出码 0 / 1 / 3 的 stdout 恰两行 —— 第 1 行**唯一结论行** `outcome=<refused|applied|noop|pending> written=<true|false>`,
36
+ 第 2 行 JSON(加 `outcome` / `written`)。`written` 由 `outcome` 派生(只有 `applied` 写盘,写盘成功之后才报;`pending` = `--check` 有改动、没写)。
37
+ 名单键名**带时态**:`changed` / `dropped` 仅在 `written=true` 时存在,没写时是 `wouldChange` / `wouldDrop`。退出码 0 / 1 / 2 / 3 与 stderr 文案不变。
38
+
39
+ ### 用户可见变化(逐条;对只解析旧字段的运维脚本是**行为变化**)
40
+
41
+ 1. **`changed` 在没写盘时不再出现**:7.0.0 的 stdout JSON 恒有 `changed`;7.0.1 起仅 `written=true`(`outcome=applied`)时有。refused / noop / pending 三态下它换名 `wouldChange`,
42
+ 只读 `changed` 的脚本读到 `undefined` —— 这正是要的结果(以前读到一份没落盘的「已迁」名单)。`dropped` 同理换名 `wouldDrop`。`applied` 的 `changed` / `dropped` 形与 7.0.0 相同(有正控格)。
43
+ 2. **stdout 从一行 JSON 变成两行**:第 1 行是结论行,JSON 在第 2 行(**取最后一行解析**);整段 `JSON.parse(stdout)` 的脚本要改成只解析最后一行。JSON 新增 `outcome` / `written`。
44
+ 3. **`--check` 有改动时 `outcome=pending written=false`**(退出 3,同前):结论词的第四个值,是 `--check` 专有的「有改动、没写」。
45
+ 4. **整域终判拒(迁出的文档整域读不进 7.0.0)现在也出报告**:7.0.0 此时 stdout 先打了含 `changed` 的 JSON、再在 stderr 说 nothing was written(同一个歧义);7.0.1 报 `outcome=refused written=false`,
46
+ `refused[]` 为空、原因仍在 stderr(那条文案不变)。
47
+ 5. **拒因文案变长**:车道键 / 店键的保留名拒因在键名后多一段指路;被删旧键的拒因把「run the one-time migration before reading this document: …」换成同一个常量的句子
48
+ (仍含 `migrateWorkersDomain` 与 `migrate-workers`)。读拒因文案做字符串匹配的下游:`set it via <字段>` 句尾不变。
49
+ 6. **已知(7.0.0 既有,披露、不在本 patch 改)**:`--check` 先于整域终判 —— 迁出的文档整域读不进 7.0.0(例:两只同名 worker)时,`--check` 报 `outcome=pending`(退出 3),
50
+ 真跑才报 `outcome=refused`(退出 1)。`pending` 的意思是「迁移产出了改动、`--check` 没写」,**不保证**真跑一定写盘;要改它会动 `--check` 的退出码,另案(`test/cli-report-7.0.1.test.ts` 有一格 🚧 特征钉,修掉时翻成 refused / rc 1)。
51
+
52
+ ### 红先 / 门
53
+
54
+ - 红先提交只含两个新测试文件 —— `test/migration-signpost-7.0.1.test.ts`(②④)、`test/cli-report-7.0.1.test.ts`(③)。修前读数:两文件 44 格 **29 红** / 15 绿(绿 = 判据语义不变的 P6 两格 +
55
+ 不带指路的保留名 8 格 + P3「两侧非空」+ 句尾不变 + 另 3 格正控:报告 `event` / `file`、用法错 rc 2 无 stdout、refused 的 stderr 文案);P3 对拍逐个保留名:迁移会动而拒因不带指路的 57 个名(车道 52 + `REMOTE_EXEC` / `REQUIRE_EXECUTION_LANE` + 店键 3,× `env` / `secretEnv` 两口)全红,反向(拒因带指路而迁移不动)0 个。
56
+ - **差分(车内一次性,脚本不入库)**:同一批 379 例(每个保留名 × `env` / `secretEnv` × enabled / 停用 = 360,七个删除项 × 2 = 14,非保留名 4 与合法新形 1 = 5)过 7.0.0 发布树与本树的
57
+ `WorkerSpec.safeParse`:`(success, issue.path, issue.code)` 零差异(374 例两边都拒、5 例两边都放);同批过 `migrateWorkersDomain`:输出逐字节相同。
58
+ - **codex 一轮**(`adversarial-review`,`gpt-6.1-sol`,`--base 1db8e3f --scope branch`):Verdict approve、No material findings;它复现的「`--check` 跳过整域终判」即上面「用户可见变化」第 6 条(基线既有)。
59
+ - 旧测试按意图改:`migrate-workers-7.0.0.test.ts` 一格(CLI 可迁:解析 stdout 最后一行,加结论行断言)+ describe 标题的命令拼写。
60
+
61
+ ## 7.0.0 — 2026-10-03 — M-R2 WorkerSpec 结构块:执行车道 `runtime.execution` + 存储 `runtime.store`,词表单正本 `EXECUTION_LANES` 等六个导出,一次性迁移 `migrateWorkersDomain`(BREAKING)
62
+
63
+ > **判档 = major**(README「发版纪律」:删除字段、校验收紧、保留键表加词让存量行读到即拒,都是破坏性)。设计稿 = server design/327 v1.1
64
+ > (板上写的「6.4.0」是版本标签,不是范围;6.4.0 已另发 `headers`)。台账 S-784;web-admin WA-49 / WA-50 的契约半场。
65
+
66
+ ### 病(6.4.0 及更早)
67
+
68
+ - **车道没有结构位**:编排器只能靠 `worker.env.REMOTE_EXEC`。漏写 ⇒ 执行体落进程内 stub ⇒ 手带工具一件不挂 ⇒ 任务照回 `completed`(WA-49)。
69
+ - **店的三处词表不齐**:`sessionBackend` 收 `tidb`(引擎见之拒启)、缺省 `auto` 且只有 MySQL 族坐标 `coords.tidb*`(PG 部署无坐标可写);
70
+ `memoryBackend` 渲成引擎从不读的死名 `MEMORY_BACKEND`(记忆持久面根本声明不了);`DB_BACKEND` 无位,引擎靠 SQL 信号猜(WA-50)。
71
+ - **词表四处手抄**:车道词(引擎能力表 / 部署面车道清单 / 中心表单 / 本包 `RemoteExecSpec`,后者还多一个引擎零读点的 `remote-docker`)、
72
+ 存储词、租户分离位 —— 任何一处改了别处不红。`RESERVED_WORKER_ENV` 与它的「归属字段」映射是两张要人记得同步的表。
73
+
74
+ ### 修(规则变少:一张表,三处读)
75
+
76
+ - **新导出(`src/execution-lanes.ts`,主桶,纯数据)**:`EXECUTION_LANE_WORDS` / `ExecutionLane`(`host | e2b | k8s | ssh | adb | local-docker | device`,
77
+ = 引擎 `REMOTE_EXEC` 收的词逐字同序;**不含** `in-process` 与 `remote-docker`)、`EXECUTION_LANES`(每行 `lane / tenantSeparation / requiredEnv /
78
+ requiredSecretEnv / optionalEnv / optionalSecretEnv`,深冻结)、`TENANT_SEPARATION_WORDS`、`STORE_BACKENDS` / `SESSION_BACKENDS` /
79
+ `MEMORY_ENGINE_BACKENDS`(各带同名单数类型)。行内容逐格抄自 server 7.112.0 树,坐标写在每行 JSDoc(必需键 = `REQUIRED_ENVS_BY_LANE`;
80
+ `tenantSeparation` = 能力表那一位;可选键 = `REMOTE_EXEC` 三元链该车道那一臂读的名,别的域也读的 `MINIO_*` 不收;按引擎目录值类型
81
+ `secret` / `opaque` 分进密钥栏)。
82
+ - **与稿的一处差异(主会话裁定 1:采)**:表行多一栏 `optionalSecretEnv`。稿的五栏里没有「可选的密钥」,而 k8s 的 `K8S_TOKEN` / `K8S_CA_CERT_B64`、
83
+ e2b 的 `E2B_SANDBOX_ENV`、local-docker 的 `DOCKER_SANDBOX_ENV` 正是这一类 —— 没有这一栏,它们要么写不进结构块(k8s 显式令牌部署声明不了),
84
+ 要么留在 `secretEnv` 逃生口(车道配置两把尺)。
85
+ - **`WorkerSpec.runtime.execution`**(可选;enabled 的 worker 必填):`lane` + `coords`(非密坐标,字面值)+ `secretRefs`(密钥,宿主 env-NAME)。
86
+ 键 ⊆ 表[lane] 的对应栏;必需键在场且非空串(引擎把空串读作未设);别车道的键拒并点名它属于哪条车道;密钥写进 coords / 坐标写进 secretRefs
87
+ 拒并指路另一栏;坐标值过与 `env` 同一条密钥形判据。
88
+ - **`WorkerSpec.runtime.store`**(可选;enabled 的 worker 必填):`backend`(必填,`STORE_BACKENDS`)/ `session`(缺省 `auto`)/
89
+ `memory {engine on|off 缺省 on, backend 缺省 file}` / `db {host, port, user, database}`(可选)+ `secretRefs.dbPasswordEnv`。
90
+ - **跨字段判据(WorkerSpec 层,每条一个 zod issue,`path` 指到要改的字段,拒因带出路)**:
91
+ ① enabled 无 `runtime.execution` ⇒ 拒(`runtime.execution.lane`,列全部车道词;没有「无手」词)/ 无 `runtime.store` ⇒ 拒(`runtime.store.backend`);
92
+ ② `requirePrincipal` ∧ 车道 `tenantSeparation === "single-user-only"` ⇒ 拒,出路 = 表里 `supported` 的车道**现算**(`e2b / k8s / local-docker / device`);
93
+ ③ `host` 车道 ∧ 最多副本数 > 1 ⇒ 拒 —— 副本数有两个旋钮,`replicas` 与 `autoscaling.maxReplicas` 同一条规则(稿只写了 `replicas`,补全这一类);
94
+ ④ SQL 坐标(`runtime.store.db` + `secretRefs.dbPasswordEnv`)在场 ⇔ `store.backend` ∈ {mysql, pg}(稿的①②两条是同一条规则的两面);
95
+ ⑤ `store.memory.backend` pg ⇒ 店 pg,tidb ⇒ 店 mysql;⑥ `requirePrincipal` ⇒ 店 ∈ {mysql, pg};
96
+ ⑦ `device` 车道 ⇒ 店 ∈ {mysql, pg} 且 `session` ≠ memory(= 引擎 `device-durable-store`:设备注册簿 / 绑定 / 审计都在 SQL 里,`SESSION_BACKEND=memory`
97
+ 一块坐标都不铸;稿没列这一条,gpt-6 腿 F2 逮到「写闸放行、引擎拒启」,与 ② 同族补上)。同族的 `device-durable-approval` 读 `DURABLE_APPROVAL`
98
+ (逃生口里的 env + 姿态缺省),不在结构字段上,写闸判不了 —— 只在起服拒(如实登记);
99
+ ⑧ `store.backend` memory ∧ `session` mysql ⇒ 拒(引擎 `plugins/session-store.ts:17`:durable 会话店要一只店,memory 后端一只都不建);
100
+ ⑨ 记忆引擎开 ∧ `memory.backend` ∈ {pg, tidb} ∧ `session` memory ⇒ 拒(引擎 `boot/stores.ts:232 / :300`:记忆面绑 SQL 池,session memory 时池不开)。
101
+ ⑧⑨ 是主会话裁定的核项:逐个读引擎(server 79de35f8)起服对 session × backend 各组合的处置,**起服拒的写闸同拒,起服放的不加** ——
102
+ local + session mysql(LocalBackend 自带会话店)、SQL + session memory(声明不用坐标、跑内存)、memory + auto / memory 都放。
103
+ 同样读出、但**写闸判不了**的一条:SQL 店还要对象存储(`MINIO_*` 三键,或 `SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true`)否则起服拒
104
+ (`plugins/store-backend.ts:476`)—— 那几个键在 `env` / `secretEnv` 逃生口里,不在结构字段上,只在起服拒(如实登记)。
105
+ 写闸 = `WorkerSpec.safeParse` / `DOMAIN_SCHEMAS.workers.safeParse`;**不另导出拒因函数**(schema 是唯一判据)。
106
+ - **`RESERVED_WORKER_ENV`**:改由「保留名 → 唯一写位」一张表派生(以前是两张手写表)。**加** `REMOTE_EXEC` / `REQUIRE_EXECUTION_LANE` /
107
+ `DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND` 与车道表里全部名(拒因指到 `runtime.execution.coords.K8S_SANDBOX_IMAGE` 这种形)——
108
+ 反转 0.1.30「执行基座键不保留」:当年没有结构位,如今有了,逃生口再留就是两把尺。**删** `MEMORY_BACKEND`(死名)与 `TIDB_*` 族(结构字段不再产出它们;
109
+ 引擎的墓碑表见之拒启)。`MYSQL_*` / `PG_*` 归 `runtime.store.db.*` 与 `secretRefs.dbPasswordEnv`。`MINIO_*` / `LOG_LEVEL` / `REMOTE_EXEC_HOME_DIR` 照旧不保留。
110
+ - **删除项读到即响亮拒**(不被 zod 静默剥成「什么都没声明」):`runtime.sessionBackend` / `runtime.memoryBackend` / `runtime.db` /
111
+ `coords.tidbHost|tidbPort|tidbUser` / `secretRefs.tidbPasswordEnv` 在场 ⇒ 每个一条 issue,拒因指路迁移命令(停用的 worker 也拒)。迁移窗口代码。
112
+ - **一次性迁移 `migrateWorkersDomain(doc) → { doc, changed, dropped, refused }`**(主桶;纯函数、幂等;读前抬升 —— 对**原始** workers 文档在
113
+ `parseDomain` 之前跑)。每个旧键恰好一种去处:搬进结构块 / 引擎本就不读 ⇒ `dropped`(通知)/ 表达不了 ⇒ `refused`(该 worker 不进 `doc`,
114
+ `field` 点名要人改的那一格,`original` = 输入里那一行逐字 —— **写回 `doc` 之前必须把 `refused[].original` 按 `index` 放回或另存**,
115
+ 否则运维的 spec 随写回消失;`doc` ∪ `refused[].original` 覆盖输入全部 worker,有钉)。enabled 且无 `env.REMOTE_EXEC` ⇒ refused(不替运维猜车道);`sessionBackend: "tidb"` ⇒ `store.session/backend: "mysql"`;
116
+ `memoryBackend` ⇒ dropped;`coords.tidbHost` 在 ⇒ mysql 店 + `store.db`(缺端口 ⇒ 3306),不在 ⇒ local 店;env 里的 `DB_BACKEND` /
117
+ `MEMORY_ENGINE*` 搬进 store。终判 = 7.0.0 `WorkerSpec`(迁移不复述写闸规则)。`dropped` / `refused` 的形比稿多 `index`(`refused` 再多
118
+ `original`),`field` 是 `ConfigPath`(本包 3.0.0 起坐标一律结构化)。
119
+ - **`WORKER_NAME`**(新导出):worker 名字的唯一判据。`WorkerSpec` 不再是普通对象 schema,`WorkerSpec.shape.name` 没了 —— 中心在租约 /
120
+ 用量 / discovery `?worker=` 上借上游名字判据的五处改读它(查漏轮读 web-admin `origin/main` 逮到)。
121
+ - **子路径 `./execution-lanes`**(照 `./secret-refs` 现形):只要词表的消费方(中心客户端组件)不必经主桶;导出与主桶**同一个模块实例**
122
+ (有钉:真走 Node 的 exports 解析,两处取到的 `EXECUTION_LANES` 是同一对象)。
123
+ - **CLI**:`settings-schema migrate-workers <workers.json> [--check]`(`package.json` 新增 `bin`;入口 `bin/settings-schema.mjs` 是本包唯一用
124
+ `node:fs` 写文件的地方,逻辑在 `src/cli.ts`,不碰 fs)。有 refused ⇒ 一字不写、退出 1;`--check` 有改动 ⇒ 退出 3;stdout 一行
125
+ `workers_domain_migrated` JSON 报告(只点名字段)。
126
+ - **`RemoteExecSpec` 删 `remote-docker` 变体**(零别名)—— 取证见下;远端 docker 守护进程 = `local-docker` 车道 + `DOCKER_HOST`。
127
+ `listRequiredEnvNames` 的 provider 分支改穷举 `switch`(新 provider 不表态 = 编译红)。
128
+ - **消费面跟上**:`workersReferencingSecret`(批量轮换)与 `collectEnvRefs`(密钥清单)看得见 `runtime.execution.secretRefs` 与 `dbPasswordEnv` ——
129
+ 否则迁移把 `E2B_API_KEY` 从 `secretEnv` 搬走之后,轮换 E2B 密钥不再重建引用它的 worker(静默,旧值留在跑着的 pod 里)。
130
+
131
+ ### remote-docker 取证(裁 = 删)
132
+
133
+ - core(server 树装的 7.38.3 dist):零命中。cli `src/`:零命中。web-admin `src/`:零命中(执行域标签表本就没有它)。
134
+ - server `src/`:有一只适配器工厂 `remoteDockerExecutionEnvFactory`(`plugins/remote-env-host.ts`),**生产代码零调用**(只有它自己的单测引用);
135
+ `AppConfig.remoteExec` 联合没有 `remote-docker` 臂(`config-types.ts` 注:配置面「remaining follow-up」);`REQUIRED_ENVS_BY_LANE` /
136
+ `REMOTE_EXEC_PROVIDER_WORDS` 没有它 ⇒ `REMOTE_EXEC=remote-docker` 起服即拒;`config.d/remote-exec.json` 写它 ⇒ 只告警「未接线」、env 车道照旧。
137
+ - 结论:没有任何读点能把这个词变成一条在跑的车道 ⇒ 删。远端守护进程的真形今天就是 `local-docker` + `DOCKER_HOST`(server `config.ts` 读点),
138
+ 本包表里 `DOCKER_HOST` 在 local-docker 的可选栏。
139
+
140
+ ### 用户可见行为变化(逐条;主会话裁定 5「采」)
141
+
142
+ 1. **6.x 没有 SQL 坐标的 worker,迁移后变成文件持久**:6.x 渲染器恒渲 `SESSION_BACKEND=auto`,引擎把它当 SQL 信号、`DB_BACKEND` 标 mysql
143
+ 却什么都不建 ⇒ 实际跑**内存**(重启即丢 run / 会话);迁移写 `store.backend: local`,渲染器改真后渲 `DB_BACKEND=local` ⇒ 跑**本机文件店**
144
+ (容器内持久,重建容器才丢)。与引擎裸 boot 的缺省同源(无 SQL 信号 ⇒ local)。
145
+ 2. **6.x 多租户(`requirePrincipal`)而没有 SQL 坐标的 worker,迁移 refused**:它今天跑内存(引擎对多租户裸 boot 落 memory + notice);
146
+ 7.0.0 多租户必须 mysql | pg 店(判据 ⑥)。出路 = 补 `runtime.store.db` + `secretRefs.dbPasswordEnv`,或关掉 `requirePrincipal`。
147
+ 3. **SQL 店的口令引用必填(主会话裁定 4:不放宽)**:无口令的 SQL 服务器(开发用 TiDB root 空口令)声明不了 —— 编排舰队是部署形,无口令 SQL
148
+ 不是合法声明;引擎侧接受空口令(`MYSQL_PASSWORD` 缺省 `""`),但写闸不收。6.x 行「有 `coords.tidbHost`、无 `tidbPasswordEnv`」迁移 refused,
149
+ 拒因点名 `secretRefs.dbPasswordEnv`(出路就在字段上)。
150
+ 4. **`DOCKER_HOST` 全局保留(主会话裁定 7)**:它是 local-docker 车道的坐标(远端守护进程 = `local-docker` + `coords.DOCKER_HOST`),保留集一直是
151
+ 全局闭集、不按车道豁免 ⇒ host 车道的 worker 想给 **agent 自己的 docker CLI** 经 `env.DOCKER_HOST` 指远端守护进程,这条路没了。
152
+ 5. **enabled 而没写车道的 worker 不再「无手照回 completed」**:写闸拒、迁移 refused(WA-49 病例本身)。
153
+ 6. **`remote-docker` 写进 `config.d/remote-exec.json` 的本机部署**:以前告警「未接线」、照用 env 车道;7.0.0 起读文件即拒(fail-closed)。
154
+
155
+ ### BREAKING 逐条(消费方 tsc 红 / 读到即拒即通知)
156
+
157
+ 1. `WorkerSpec.runtime.sessionBackend` / `.memoryBackend` / `.db`、`WorkerSpec.coords.tidbHost` / `.tidbPort` / `.tidbUser`、
158
+ `WorkerSpec.secretRefs.tidbPasswordEnv` 删除 —— 类型上消失(读它们 tsc 红),数据里出现即拒(指路迁移)。
159
+ 2. enabled 的 worker 必须声明 `runtime.execution` 与 `runtime.store`(6.x 的 `runtime: {}` 最小形读到即拒)。
160
+ 3. `RESERVED_WORKER_ENV` 加词:存量 `env.REMOTE_EXEC` / `K8S_*` / `E2B_*` / `SSH_*` / `ADB_*` / `DOCKER_*` / `DEVICE_*` / `DB_BACKEND` /
161
+ `MEMORY_ENGINE*` 透传行读到即拒(迁移搬进结构块);删 `MEMORY_BACKEND` / `TIDB_*`(不再被本表拒,由引擎墓碑表拒)。
162
+ 4. `RemoteExecSpec` 删 `remote-docker` 变体:`config.d/remote-exec.json` 写它 ⇒ `loadRemoteExec` / `validateRemoteExec` 拒(以前:读过、引擎告警未接线)。
163
+ 5. `WorkerSpec` 从 `ZodObject` 变成 `ZodEffects`(预处理 + 跨字段判据):`WorkerSpec.shape` / `.extend` / `.pick` 不再可用 —— web-admin
164
+ `origin/main` 有五处读 `WorkerSpec.shape.name`(`api/fleet/leases`、`api/fleet/usage`、`api/v1/scenarios/builtin`、`api/v1/scenarios/detail/[name]` ×2),
165
+ 改读新导出 `WORKER_NAME`(同一只判据);`z.input<typeof WorkerSpec>` 变成 `unknown`(普查零用法)。
166
+
167
+ ### 红先 / 门
168
+
169
+ - 红先提交 `e8f4520`(只有测试)。修前读数:A 组(新模块不存在)三个新文件收集即红,`remote-exec` 1 格红(remote-docker 曾合法的三形)、
170
+ `reserved-worker-env` 2 格红;B 组(只放词表数据、规则未写)`execution-lanes` + `worker-execution-store` 两文件 44 红 / 48 绿
171
+ (绿 = 表内容钉 33 + 正控 15)。消费面两格(`workersReferencingSecret` / `collectEnvRefs`)对 6.4.0 的 `secret-refs.ts` 2 红;`WORKER_NAME` 1 红;
172
+ gpt-6 腿两条 [high] 各自红先(F1 迁移 1 红、F2 写闸 2 红)再修。
173
+ - 新文件:`test/execution-lanes-7.0.0.test.ts`(表门:词序、逐行 = 引擎真码、栏不交、深冻结、`RemoteExecSpec.provider ⊆ EXECUTION_LANE_WORDS`
174
+ 运行期 + 编译期)、`test/worker-execution-store-7.0.0.test.ts`(写闸判据 + 绕法:别车道键 / 大小写 / 空串 / 密钥形 / 错栏)、
175
+ `test/migrate-workers-7.0.0.test.ts`(迁移规则逐条 + 幂等 + 纯 + CLI 内存 io 与真 bin)、`test/conformance-workers-7.0.0.test.ts`。
176
+ - 旧测试按意图改:`reserved-worker-env`(0.1.30 反向门按设计反转;`MINIO_*` / `LOG_LEVEL` 仍不保留那半留着)、`remote-exec`、`file-edit`
177
+ (R16 端口门挪到 `runtime.store.db.port`)、七个测试文件的 worker 夹具补两块(提货成本)、`deprecation-hygiene` 豁免表删一行陈旧条目、
178
+ `docs-hygiene` 外部版本白名单加 `7.112.0`(server 树版本,表行坐标出处)。
179
+ - conformance:新语料 `c50–c56`(七条车道各一份合法 spec,三腿零告警)、`c57–c68`(十二条拒因:G1 / G2 / G3 三形 / G7a / host∧两副本 /
180
+ 多租户∧local / pg 缺 db / 记忆后端不配 / 删除项响亮拒)+ `c68`(device ∧ local 店,gpt-6 腿 F2;`c56` 的 device 合法样随之用 pg 店),`firstRedOn` 留空(historical 阶梯证据真跑于入库之前,不手填,c41 同口径;对 6.4.0
181
+ 这 19 条都会红:合法 7 条两块被剥、拒因 12 条被放行)。`cli04` 的输入改成 7.0.0 形(显式声明车道与店;6.x 原输入是 enabled 无车道的 WA-49 形,
182
+ 迁移对它是 refused,本条要打的是 env 值承运,与车道无关)并重加冕;其余既有语料、`baseline.json` 全量重加冕零 diff。
183
+ 迁移金样 `conformance/migrate-workers/m01–m04`(G5 正臂 / 幂等 / refused / G7b),加冕脚本同一个(`npm run conformance:bless`)。
184
+
185
+ ### 消费方(@ 点名,迁移句)
186
+
187
+ - **@web-admin**(中心 / 编排器,10-17 窗):删除项 ⇒ 渲染器 `workerToServiceEnv` tsc 红即通知,按 `runtime.execution` / `runtime.store` 改真
188
+ (`REMOTE_EXEC` + `REQUIRE_EXECUTION_LANE=true` + coords / secretRefs 逐键;`DB_BACKEND` / `SESSION_BACKEND` / `MEMORY_ENGINE` /
189
+ `MEMORY_ENGINE_BACKEND` 恒渲;按 `backend` 选 `MYSQL_*` / `PG_*` 名族;`MEMORY_BACKEND` 不再产出)。workers 唯一读口在 `parseDomain` 之前跑
190
+ `migrateWorkersDomain`,写回 `doc`、日志 `workers_domain_migrated {from, changed, dropped}` 一行、UI 横幅;`refused` 的 worker 不加载、列表标红
191
+ 点名 `field`(它不在输出 `doc` 里 —— 写回时用 `index` 从原文档保住那一行,别把它删了)。写闸零新代码:`DOMAIN_SCHEMAS.workers.safeParse`
192
+ 已带全部判据。五处 `WorkerSpec.shape.name` 改 `WORKER_NAME`;编排器 `warnSharedDb` 读 `runtime.db` 改 `runtime.store.db.database`;向导建出的
193
+ `runtime: {}` 要补两块(否则写闸拒)。执行域(`allowedLanes`,开放词)的标签表:可声明车道取 `EXECUTION_LANE_WORDS`(今天手抄的那张缺 `device`);
194
+ `in-process` 是引擎「没有 remoteExec」的状态名、不在可声明表里 —— 那一行要留就单独写明来源。`RESERVED_WORKER_ENV` 派生的保留集自动跟上。
195
+ - **@server**(7.113.0 提货):`REMOTE_EXEC_PROVIDER_WORDS` / `REQUIRED_ENVS_BY_LANE` / `tenantSeparation` 改从 `EXECUTION_LANES` 派生
196
+ (运行期事实留在按导入联合穷举的 switch 里);`DB_BACKEND` / `SESSION_BACKEND` / `MEMORY_ENGINE_BACKEND` 三处 `enumEnv` 词表改读导出常量;
197
+ 加一格「运行期事实与表不矛盾」;对表:ssh / adb / local-docker / device 的可选栏是按 7.112.0 三元链各臂抄的,请对一遍。
198
+ `config.d/remote-exec.json` 写 `remote-docker` 的本机部署,提货后读文件即拒启(以前告警未接线)。
199
+ - **@cli**:不 import `WorkerSpec` / `RemoteExecSpec`(普查零用法),零改动;`engineEnvGroups.ts` 的 `REQUIRED_ENVS_BY_LANE` 镜像可改从
200
+ `EXECUTION_LANES` 派生(可选)。
201
+ - **@deploy**:`deploy/sema-up/lib/lanes.json` 的 `users` / `env` 列从 `EXECUTION_LANES` 生成(server 自决,design/327 已决)。
202
+
203
+ ### 车内对抗复审(gpt-6-astra 一轮)
204
+
205
+ 两条 [high],都亲读代码复现后红先修:**F1** 迁移在「运维已手填 `secretRefs.dbPasswordEnv`、旧 `tidbPasswordEnv` 还在、`runtime.store` 未写」时
206
+ 用旧引用静默覆盖新引用(两形并存检查只看了 `runtime.store`)⇒ 新形店字段 = `runtime.store` ∪ `secretRefs.dbPasswordEnv`,任一在场 ∧ 有旧店字段 ⇒ refused;
207
+ **F2** device 车道配非 SQL 店过闸而引擎拒启 ⇒ 判据 ⑦。
208
+
209
+ ### 主会话裁定(9 条候裁)
210
+
211
+ 1 第 6 栏 `optionalSecretEnv` 采 · 2 host 多副本同看 `autoscaling.maxReplicas` 采 · 3 device ⇒ SQL 店 采 · 4 `dbPasswordEnv` 必填不放宽(见「用户可见行为变化」3)·
212
+ 5 「无坐标 ⇒ local」与多租户无坐标 refused 采(同上 1、2)· 6 refused 行带原条目回传(`refused[].original`,本版改)· 7 `DOCKER_HOST` 全局保留 采(同上 4)·
213
+ 8 加子路径 `./execution-lanes`(本版改)· 9 `WorkerSpec` 成 ZodEffects 采。核项 session × backend ⇒ 判据 ⑧⑨。
214
+
215
+ ### 迁移代码删除窗
216
+
217
+ `migrateWorkersDomain`、`settings-schema migrate-workers`、删除项「读到即拒」的预处理 —— 在 web-admin 10-17 落地(中心读口接上迁移)后的
218
+ **下一版**删除(主会话开 DEBTS 票跟踪)。
219
+
11
220
  ## 6.4.0 — 2026-10-01 — `models.models[].headers` 解析透明键(S-935;与 `compat` 同一只变换)
12
221
 
13
222
  > **判档 = minor**(新键 = additive,README「发版纪律」)。缺席 = 键不铸 = 6.3.0 行为,零迁移;不带 `headers` 的文档读出来逐字节不变
package/README.md CHANGED
@@ -68,8 +68,55 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
68
68
  - **Secret inventory** (`secret-refs.ts`): `collectEnvRefs` — every referenced env-NAME.
69
69
  - **The narrow reader** (`reader.ts`): `ConfigReader` (3 methods) — the config-center 产品's fat `ConfigStore`
70
70
  `extends` it; a local file source / test fixture implements it without stubbing ~32 methods.
71
- - **`remoteExec` contract** (`remote-exec.ts`): the execution-substrate schema mirroring the service's
72
- actual providers (`e2b` / `k8s` / `ssh` / `adb`), discriminated on `provider`; every secret an env-NAME ref.
71
+ - **`remoteExec` contract** (`remote-exec.ts`): the per-machine `config.d/remote-exec.json` execution-substrate
72
+ schema (`host` / `local-docker` / `e2b` / `k8s` / `ssh` / `adb`), discriminated on `provider`; every secret an
73
+ env-NAME ref. Every `provider` word is an execution-lane word (`EXECUTION_LANE_WORDS`, pinned at runtime and at
74
+ compile time). **7.0.0** deleted the `remote-docker` variant: no engine read point ever ran it; a remote docker
75
+ daemon is the `local-docker` lane pointed at it (`DOCKER_HOST`).
76
+ - **WorkerSpec execution lane + store (7.0.0, M-R2)** (`execution-lanes.ts`, also subpath `./execution-lanes`, + `types.ts`): the lane / store
77
+ vocabulary has ONE source — the exported data `EXECUTION_LANE_WORDS`, `EXECUTION_LANES` (per lane:
78
+ `tenantSeparation` + the env names it reads, in four columns `requiredEnv` / `requiredSecretEnv` / `optionalEnv` /
79
+ `optionalSecretEnv`, copied from the engine with per-row coordinates), `STORE_BACKENDS`, `SESSION_BACKENDS`,
80
+ `MEMORY_ENGINE_BACKENDS`. The center's write gate (this package's `WorkerSpec.safeParse`), the center's env
81
+ renderer and the engine's boot checks all read that one table, so they cannot drift. A worker declares
82
+ `runtime.execution = { lane, coords, secretRefs }` (non-secret coordinates as literals, secrets as host env-NAMEs,
83
+ keys ⊆ that lane's columns, required ones present and non-empty) and `runtime.store = { backend, session, memory,
84
+ db? }` (+ `secretRefs.dbPasswordEnv`); an enabled worker must declare both. Cross-field refusals live in the
85
+ schema as zod issues whose `path` names the field to change and whose message carries the way out: a
86
+ multi-tenant worker (`requirePrincipal`) on a lane that does not separate tenants (outlets computed from the
87
+ table), the `host` lane with more than one replica (`replicas` or `autoscaling.maxReplicas`), the `device` lane
88
+ without a SQL store that actually builds its coordinates (the engine's `device-durable-store`), SQL coordinates
89
+ without a SQL store (or the reverse), a memory backend that does not match the store, a multi-tenant worker
90
+ without a SQL store, and the two session × store combinations the engine refuses at boot (`session: mysql` on a
91
+ `memory` store; a pg / tidb memory engine with `session: memory`). `WorkerSpec` is no longer a plain object schema — the worker-name rule is exported on its
92
+ own as `WORKER_NAME`. `RESERVED_WORKER_ENV` (derived from a name → owning-field table) now reserves every
93
+ env name those blocks produce, so the `env` / `secretEnv` escape hatches can no longer set a lane or a store.
94
+ Stored 6.x rows are lifted ONCE by `migrateWorkersDomain(doc)` (pure, idempotent; run it on the RAW workers
95
+ document before `parseDomain` — the 6.x fields are refused loudly by the 7.0.0 schema, not silently stripped)
96
+ or its file form `npx @sema-agent/settings-schema migrate-workers <workers.json> [--check]` (the package's
97
+ `bin`; writes only when every worker migrates). Workers it cannot lift (an enabled worker with no
98
+ `env.REMOTE_EXEC` — the migration never guesses a lane) come back in `refused` with the field to fix and the
99
+ `original` row verbatim — they are NOT in the migrated `doc`, so a caller that writes `doc` back must put each
100
+ `refused[].original` back at its `index` (or store it aside), or that worker's spec disappears.
101
+ - **`migrate-workers` CLI: report shape + rejection signposts (7.0.1)** (`bin/settings-schema.mjs`, core in `cli.ts`):
102
+ `npx @sema-agent/settings-schema migrate-workers <workers.json> [--check]` prints exactly two lines on stdout (exit 0 / 1 / 3): line 1 is the
103
+ ONE conclusion line `outcome=<refused|applied|noop|pending> written=<true|false>`, line 2 is the JSON report (`event: "workers_domain_migrated"`,
104
+ `outcome`, `written`, `file`, the worker lists, `refused[]`). Only `applied` writes the file (`written` is derived from `outcome`; `pending` =
105
+ `--check` with changes). The list keys carry tense — `changed` / `dropped` exist **only when `written=true`**; when nothing was written
106
+ (refused / noop / pending) the same lists are `wouldChange` / `wouldDrop`, so a script that reads only stdout can never take a worker that was
107
+ not migrated for migrated. Exit codes are unchanged (0 applied or noop · 1 refused or the migrated document does not read as a 7.0.0 workers
108
+ domain, nothing written · 2 usage / unreadable / not JSON, empty stdout · 3 `--check` with changes).
109
+
110
+ | 7.0.0 stdout | 7.0.1 stdout |
111
+ |---|---|
112
+ | one JSON line | the conclusion line, then the JSON line (parse the **last** line) |
113
+ | `changed` (always) | `changed` when `written=true`, else `wouldChange` |
114
+ | `dropped` (always) | `dropped` when `written=true`, else `wouldDrop` |
115
+ | — | `outcome`, `written` |
116
+
117
+ Rejections that a migration can lift carry ONE signpost sentence (a constant, `MIGRATION_SIGNPOST`): the seven deleted 6.x fields, and an
118
+ `env` / `secretEnv` key the migration moves or drops (the execution-lane keys incl. `REMOTE_EXEC` / `REQUIRE_EXECUTION_LANE`, and the store keys
119
+ `DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND`). A reserved name the migration cannot move (`PORT`, `MODEL_*`, `MYSQL_*` …) carries none.
73
120
  - **Safety merge-spec** (`safety-merge-spec.ts`, subpath `./safety-merge-spec`): `SAFETY_MERGE_SPEC`, the ONE
74
121
  per-category merge/enforcement table every settings resolver reads (`mergeShape` / `minTrust` / `tighten` /
75
122
  `enforcement` / `fail`) — no consumer re-implements the merge law. 1.1.0 adds **member-level admission**:
@@ -283,6 +330,10 @@ defaults. Each `config.d/<domain>.json` is exactly that domain's zod object.
283
330
  「修 bug」若改变了合法输入的解析结果,按上两档判,不按 patch。
284
331
  - 契约破坏面动之前先盘 center / server / CLI 三方消费点;commit message 的 `feat!`/`BREAKING` 标记照旧,
285
332
  但标记不豁免版号义务。每版 CHANGELOG 一节,git tag `v<version>` 同名。
333
+ - **持久数据面的破坏性变更(例:7.0.0)**:类型 / API 面直接删改、零别名(消费方 tsc 红即通知);**存下来的数据**不会自己红,
334
+ 所以同版配三件:读到旧形即**响亮拒**(拒因指路迁移命令,不是被 zod 静默剥掉)、一只**一次性迁移**(纯函数、幂等、
335
+ 表达不了就 refused 点名,不替运维猜)、以及一个**写明的删除窗**(7.0.0 的 `migrateWorkersDomain` 在中心接上迁移后的下一版删除)——
336
+ 绝不长成长命双读。
286
337
  - **历史注记(0.x 时代)**:0.x 下 `^0.7.0` 这类 range 只自动收 patch,故当年规矩是「破坏性必 bump
287
338
  minor,patch 只装 additive/修 bug/文档」。0.7.1(2026-07-10)曾携带两件预协调 BREAKING(撤 governance
288
339
  effective 双写镜像、撤 QuotaLease 0.5 `tokens` 别名 shim),名义上违反该条;当时下游(center/service
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // `settings-schema` — this package's command line (7.0.0: `migrate-workers <workers.json> [--check]`).
3
+ // The ONLY place the package writes files with node:fs; the logic is `runSettingsSchemaCli` (dist/cli.js), which never
4
+ // touches the filesystem itself — this entry injects real file I/O into it.
5
+ import { readFile, rename, stat, writeFile } from "node:fs/promises";
6
+ import { runSettingsSchemaCli } from "../dist/cli.js";
7
+
8
+ const io = {
9
+ readFile: (path) => readFile(path, "utf8"),
10
+ // atomic: write a sibling temp file (same mode as the original), then rename over it — a half-written workers
11
+ // document is worse than the old one.
12
+ writeFileAtomic: async (path, text) => {
13
+ const { mode } = await stat(path);
14
+ const tmp = `${path}.migrate-${process.pid}.tmp`;
15
+ await writeFile(tmp, text, { encoding: "utf8", mode: mode & 0o777 });
16
+ await rename(tmp, path);
17
+ },
18
+ stdout: (text) => process.stdout.write(text),
19
+ stderr: (text) => process.stderr.write(text),
20
+ };
21
+
22
+ process.exitCode = await runSettingsSchemaCli(process.argv.slice(2), io);
@@ -1 +1 @@
1
- 6.4.0
1
+ 7.0.1
@@ -0,0 +1,102 @@
1
+ {
2
+ "domains": {
3
+ "outcome": "effective",
4
+ "effective": {
5
+ "collab": {
6
+ "templates": []
7
+ },
8
+ "entitlement": {
9
+ "budgetGroups": [],
10
+ "groupBindings": [],
11
+ "subjects": [],
12
+ "tiers": []
13
+ },
14
+ "execution": {
15
+ "allowedLanes": [
16
+ "host"
17
+ ],
18
+ "required": false
19
+ },
20
+ "governance": {},
21
+ "hosts": {
22
+ "hosts": []
23
+ },
24
+ "limits": {},
25
+ "mcp": {
26
+ "servers": []
27
+ },
28
+ "models": {
29
+ "models": [],
30
+ "roles": {},
31
+ "tierGroups": []
32
+ },
33
+ "plugins": {
34
+ "plugins": []
35
+ },
36
+ "projects": {
37
+ "projects": {}
38
+ },
39
+ "prompts": {
40
+ "bindings": []
41
+ },
42
+ "rosters": {
43
+ "rosters": []
44
+ },
45
+ "runtime": {},
46
+ "scenarios": {
47
+ "scenarios": []
48
+ },
49
+ "skills": {
50
+ "skills": []
51
+ },
52
+ "systems": {
53
+ "systems": []
54
+ },
55
+ "workers": {
56
+ "workers": [
57
+ {
58
+ "coords": {},
59
+ "enabled": true,
60
+ "host": "default",
61
+ "image": "sema-server:latest",
62
+ "name": "w-host",
63
+ "runtime": {
64
+ "execution": {
65
+ "coords": {
66
+ "HOST_WORKSPACE_BASE": "/work"
67
+ },
68
+ "lane": "host",
69
+ "secretRefs": {}
70
+ },
71
+ "rateLimitRpm": 0,
72
+ "requirePrincipal": false,
73
+ "store": {
74
+ "backend": "local",
75
+ "memory": {
76
+ "backend": "file",
77
+ "engine": "on"
78
+ },
79
+ "session": "auto"
80
+ }
81
+ },
82
+ "secretRefs": {
83
+ "apiKeyEnv": "MODEL_KEY"
84
+ }
85
+ }
86
+ ]
87
+ }
88
+ },
89
+ "warnings": [],
90
+ "metadata": {
91
+ "version": 7,
92
+ "updatedAt": "2026-10-03T00:00:00.000Z"
93
+ }
94
+ },
95
+ "wire": {
96
+ "derive": true
97
+ },
98
+ "localRoot": {
99
+ "derive": true,
100
+ "localVersion": 2617555914511015
101
+ }
102
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "meta": {
3
+ "version": 7,
4
+ "updatedAt": "2026-10-03T00:00:00.000Z"
5
+ },
6
+ "domains": {
7
+ "workers": {
8
+ "workers": [
9
+ {
10
+ "name": "w-host",
11
+ "secretRefs": {
12
+ "apiKeyEnv": "MODEL_KEY"
13
+ },
14
+ "runtime": {
15
+ "execution": {
16
+ "lane": "host",
17
+ "coords": {
18
+ "HOST_WORKSPACE_BASE": "/work"
19
+ }
20
+ },
21
+ "store": {
22
+ "backend": "local"
23
+ }
24
+ }
25
+ }
26
+ ]
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "contributor": "contract",
3
+ "claim": "7.0.0 M-R2:`host` 车道的一份合法 WorkerSpec(必需坐标 / 密钥齐、可选键在本车道栏内)三腿读出、零告警。打的是「写闸把合法车道块判成坏的」或「读腿把 runtime.execution / runtime.store 剥掉」两种读法分歧(6.4.0 两块都会被静默剥)。",
4
+ "firstRedOn": []
5
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "domains": {
3
+ "outcome": "effective",
4
+ "effective": {
5
+ "collab": {
6
+ "templates": []
7
+ },
8
+ "entitlement": {
9
+ "budgetGroups": [],
10
+ "groupBindings": [],
11
+ "subjects": [],
12
+ "tiers": []
13
+ },
14
+ "execution": {
15
+ "allowedLanes": [
16
+ "host"
17
+ ],
18
+ "required": false
19
+ },
20
+ "governance": {},
21
+ "hosts": {
22
+ "hosts": []
23
+ },
24
+ "limits": {},
25
+ "mcp": {
26
+ "servers": []
27
+ },
28
+ "models": {
29
+ "models": [],
30
+ "roles": {},
31
+ "tierGroups": []
32
+ },
33
+ "plugins": {
34
+ "plugins": []
35
+ },
36
+ "projects": {
37
+ "projects": {}
38
+ },
39
+ "prompts": {
40
+ "bindings": []
41
+ },
42
+ "rosters": {
43
+ "rosters": []
44
+ },
45
+ "runtime": {},
46
+ "scenarios": {
47
+ "scenarios": []
48
+ },
49
+ "skills": {
50
+ "skills": []
51
+ },
52
+ "systems": {
53
+ "systems": []
54
+ },
55
+ "workers": {
56
+ "workers": [
57
+ {
58
+ "coords": {},
59
+ "enabled": true,
60
+ "host": "default",
61
+ "image": "sema-server:latest",
62
+ "name": "w-e2b",
63
+ "runtime": {
64
+ "execution": {
65
+ "coords": {
66
+ "E2B_TEMPLATE": "base"
67
+ },
68
+ "lane": "e2b",
69
+ "secretRefs": {
70
+ "E2B_API_KEY": "HOST_E2B_KEY"
71
+ }
72
+ },
73
+ "rateLimitRpm": 0,
74
+ "requirePrincipal": false,
75
+ "store": {
76
+ "backend": "local",
77
+ "memory": {
78
+ "backend": "file",
79
+ "engine": "on"
80
+ },
81
+ "session": "auto"
82
+ }
83
+ },
84
+ "secretRefs": {
85
+ "apiKeyEnv": "MODEL_KEY"
86
+ }
87
+ }
88
+ ]
89
+ }
90
+ },
91
+ "warnings": [],
92
+ "metadata": {
93
+ "version": 7,
94
+ "updatedAt": "2026-10-03T00:00:00.000Z"
95
+ }
96
+ },
97
+ "wire": {
98
+ "derive": true
99
+ },
100
+ "localRoot": {
101
+ "derive": true,
102
+ "localVersion": 2617555914511015
103
+ }
104
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "meta": {
3
+ "version": 7,
4
+ "updatedAt": "2026-10-03T00:00:00.000Z"
5
+ },
6
+ "domains": {
7
+ "workers": {
8
+ "workers": [
9
+ {
10
+ "name": "w-e2b",
11
+ "secretRefs": {
12
+ "apiKeyEnv": "MODEL_KEY"
13
+ },
14
+ "runtime": {
15
+ "execution": {
16
+ "lane": "e2b",
17
+ "coords": {
18
+ "E2B_TEMPLATE": "base"
19
+ },
20
+ "secretRefs": {
21
+ "E2B_API_KEY": "HOST_E2B_KEY"
22
+ }
23
+ },
24
+ "store": {
25
+ "backend": "local"
26
+ }
27
+ }
28
+ }
29
+ ]
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "contributor": "contract",
3
+ "claim": "7.0.0 M-R2:`e2b` 车道的一份合法 WorkerSpec(必需坐标 / 密钥齐、可选键在本车道栏内)三腿读出、零告警。打的是「写闸把合法车道块判成坏的」或「读腿把 runtime.execution / runtime.store 剥掉」两种读法分歧(6.4.0 两块都会被静默剥)。",
4
+ "firstRedOn": []
5
+ }