@sema-agent/settings-schema 7.0.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.
- package/CHANGELOG.md +50 -0
- package/README.md +19 -0
- package/conformance/VERSION +1 -1
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/dist/cli.js +51 -11
- package/dist/execution-lane-index.d.ts +20 -1
- package/dist/execution-lane-index.js +24 -1
- package/dist/migrate-workers.d.ts +1 -1
- package/dist/migrate-workers.js +3 -7
- package/dist/types.js +13 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,56 @@
|
|
|
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
|
+
|
|
11
61
|
## 7.0.0 — 2026-10-03 — M-R2 WorkerSpec 结构块:执行车道 `runtime.execution` + 存储 `runtime.store`,词表单正本 `EXECUTION_LANES` 等六个导出,一次性迁移 `migrateWorkersDomain`(BREAKING)
|
|
12
62
|
|
|
13
63
|
> **判档 = major**(README「发版纪律」:删除字段、校验收紧、保留键表加词让存量行读到即拒,都是破坏性)。设计稿 = server design/327 v1.1
|
package/README.md
CHANGED
|
@@ -98,6 +98,25 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
98
98
|
`env.REMOTE_EXEC` — the migration never guesses a lane) come back in `refused` with the field to fix and the
|
|
99
99
|
`original` row verbatim — they are NOT in the migrated `doc`, so a caller that writes `doc` back must put each
|
|
100
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.
|
|
101
120
|
- **Safety merge-spec** (`safety-merge-spec.ts`, subpath `./safety-merge-spec`): `SAFETY_MERGE_SPEC`, the ONE
|
|
102
121
|
per-category merge/enforcement table every settings resolver reads (`mergeShape` / `minTrust` / `tighten` /
|
|
103
122
|
`enforcement` / `fail`) — no consumer re-implements the merge law. 1.1.0 adds **member-level admission**:
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.0.
|
|
1
|
+
7.0.1
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"contributor": "contract",
|
|
3
|
-
"claim": "7.0.0 删除项读到即**响亮拒**(runtime.sessionBackend,哪怕是停用的 worker),拒因指路 migrateWorkersDomain / settings-schema migrate-workers —— 不被 zod 静默剥成「什么都没声明」。",
|
|
3
|
+
"claim": "7.0.0 删除项读到即**响亮拒**(runtime.sessionBackend,哪怕是停用的 worker),拒因指路 migrateWorkersDomain / npx @sema-agent/settings-schema migrate-workers —— 不被 zod 静默剥成「什么都没声明」。",
|
|
4
4
|
"firstRedOn": []
|
|
5
5
|
}
|
package/dist/cli.js
CHANGED
|
@@ -6,17 +6,57 @@
|
|
|
6
6
|
* 不进主桶 —— 它是 bin 的实现,不是契约面。
|
|
7
7
|
*
|
|
8
8
|
* 退出码:0 = 已写回 / 无事可做;1 = 有 worker 迁不了(refused)或迁出的文档整域读不进 7.0.0,**一字不写**;2 = 用法 / 读文件 /
|
|
9
|
-
* JSON
|
|
9
|
+
* JSON 错(此时 stdout 为空);3 = `--check` 且会有改动(不写)。
|
|
10
|
+
*
|
|
11
|
+
* ## 报告形(7.0.1:让「没写」在机读上不可能被读成「写了」)
|
|
12
|
+
* 退出码 0 / 1 / 3 时 stdout 恰两行:
|
|
13
|
+
* · 第 1 行 = **唯一结论行** `outcome=<refused|applied|noop|pending> written=<true|false>`;
|
|
14
|
+
* · 第 2 行 = JSON 报告(`event: "workers_domain_migrated"`,只点名字段、不带值),同带 `outcome` / `written`。
|
|
15
|
+
* `written` 不是独立字段:由 `outcome` 派生({@link writtenOf};只有 `applied` 写盘)。**键名带时态**:`written=true` 才有 `changed` /
|
|
16
|
+
* `dropped`;`written=false`(refused / noop / pending)时是 `wouldChange` / `wouldDrop` —— 读旧字段 `changed` 的脚本在没写的时候
|
|
17
|
+
* 读不到值,而不是读到一份「已迁」名单。结论行在**写盘成功之后**才出(写盘抛错 ⇒ 没有 `outcome=applied`)。
|
|
10
18
|
*/
|
|
11
19
|
import { formatConfigPath, sanitizeZodError } from "./config-fns.js";
|
|
20
|
+
import { MIGRATE_WORKERS_COMMAND } from "./execution-lane-index.js";
|
|
12
21
|
import { migrateWorkersDomain } from "./migrate-workers.js";
|
|
13
22
|
import { DOMAIN_SCHEMAS } from "./types.js";
|
|
14
23
|
const USAGE = [
|
|
15
|
-
|
|
24
|
+
`usage: ${MIGRATE_WORKERS_COMMAND} <workers.json> [--check]`,
|
|
16
25
|
" One-time 7.0.0 migration of a workers domain document ({ \"workers\": [...] }): env.REMOTE_EXEC + lane keys →",
|
|
17
26
|
" runtime.execution, sessionBackend / coords.tidb* / runtime.db / tidbPasswordEnv → runtime.store. Writes the file in",
|
|
18
27
|
" place only when every worker migrates; --check only reports (exit 3 if the file would change).",
|
|
28
|
+
" stdout (exit 0 / 1 / 3): line 1 = outcome=<refused|applied|noop|pending> written=<true|false>, line 2 = the JSON report",
|
|
29
|
+
" (changed / dropped only when written=true; otherwise wouldChange / wouldDrop).",
|
|
19
30
|
].join("\n");
|
|
31
|
+
/** 这个结论写没写盘 —— 只有 `applied` 写。`written` 从这里派生,不是报告里一个可以和 `outcome` 各说各话的独立字段。 */
|
|
32
|
+
function writtenOf(outcome) {
|
|
33
|
+
switch (outcome) {
|
|
34
|
+
case "applied":
|
|
35
|
+
return true;
|
|
36
|
+
case "refused":
|
|
37
|
+
case "noop":
|
|
38
|
+
case "pending":
|
|
39
|
+
return false;
|
|
40
|
+
default: {
|
|
41
|
+
const unhandled = outcome;
|
|
42
|
+
throw new Error(`settings-schema: unhandled outcome ${String(unhandled)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** 一次发出报告:结论行 + JSON 行(一次 `stdout` 调用)。键名带时态:写了 ⇒ `changed` / `dropped`,没写 ⇒ `wouldChange` / `wouldDrop`。 */
|
|
47
|
+
function reportOf(outcome, file, m) {
|
|
48
|
+
const written = writtenOf(outcome);
|
|
49
|
+
const dropped = m.dropped.map((d) => ({ name: d.name, field: formatConfigPath(d.field) }));
|
|
50
|
+
const json = JSON.stringify({
|
|
51
|
+
event: "workers_domain_migrated",
|
|
52
|
+
outcome,
|
|
53
|
+
written,
|
|
54
|
+
file,
|
|
55
|
+
...(written ? { changed: m.changed, dropped } : { wouldChange: m.changed, wouldDrop: dropped }),
|
|
56
|
+
refused: m.refused.map((r) => ({ name: r.name, field: formatConfigPath(r.field), reason: r.reason })),
|
|
57
|
+
});
|
|
58
|
+
return `outcome=${outcome} written=${written}\n${json}\n`;
|
|
59
|
+
}
|
|
20
60
|
export async function runSettingsSchemaCli(argv, io) {
|
|
21
61
|
const [command, ...rest] = argv;
|
|
22
62
|
const check = rest.includes("--check");
|
|
@@ -43,28 +83,28 @@ export async function runSettingsSchemaCli(argv, io) {
|
|
|
43
83
|
return 2;
|
|
44
84
|
}
|
|
45
85
|
const m = migrateWorkersDomain(doc);
|
|
46
|
-
io.stdout(`${JSON.stringify({
|
|
47
|
-
event: "workers_domain_migrated",
|
|
48
|
-
file,
|
|
49
|
-
changed: m.changed,
|
|
50
|
-
dropped: m.dropped.map((d) => ({ name: d.name, field: formatConfigPath(d.field) })),
|
|
51
|
-
refused: m.refused.map((r) => ({ name: r.name, field: formatConfigPath(r.field), reason: r.reason })),
|
|
52
|
-
})}\n`);
|
|
53
86
|
if (m.refused.length > 0) {
|
|
87
|
+
io.stdout(reportOf("refused", file, m));
|
|
54
88
|
io.stderr(`settings-schema: ${m.refused.length} worker(s) cannot be migrated (see "refused") — fix them by hand and re-run; nothing was written\n`);
|
|
55
89
|
return 1;
|
|
56
90
|
}
|
|
57
|
-
if (m.changed.length === 0)
|
|
91
|
+
if (m.changed.length === 0) {
|
|
92
|
+
io.stdout(reportOf("noop", file, m));
|
|
58
93
|
return 0;
|
|
59
|
-
|
|
94
|
+
}
|
|
95
|
+
if (check) {
|
|
96
|
+
io.stdout(reportOf("pending", file, m));
|
|
60
97
|
return 3;
|
|
98
|
+
}
|
|
61
99
|
// 整域再判一次(域层规则:名字 / 路由唯一 …)—— 读不进 7.0.0 的文档不落盘。
|
|
62
100
|
const whole = DOMAIN_SCHEMAS.workers.safeParse(m.doc);
|
|
63
101
|
if (!whole.success) {
|
|
64
102
|
const issues = sanitizeZodError(whole.error).issues.map((i) => `${formatConfigPath(i.path)}: ${i.message}`);
|
|
103
|
+
io.stdout(reportOf("refused", file, m));
|
|
65
104
|
io.stderr(`settings-schema: the migrated document does not read as a 7.0.0 workers domain — ${issues.join("; ")}; nothing was written\n`);
|
|
66
105
|
return 1;
|
|
67
106
|
}
|
|
68
107
|
await io.writeFileAtomic(file, `${JSON.stringify(m.doc, null, 2)}\n`);
|
|
108
|
+
io.stdout(reportOf("applied", file, m)); // 写盘成功之后才报 applied
|
|
69
109
|
return 0;
|
|
70
110
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 包内私有:车道表({@link EXECUTION_LANES})
|
|
2
|
+
* 包内私有:车道表({@link EXECUTION_LANES})的两只**派生索引** + 「迁移能搬哪些键」的**一份**键集与指路句,
|
|
3
|
+
* 供 `types.ts`(写闸)与 `migrate-workers.ts`(一次性迁移)同读。
|
|
3
4
|
* 不进主桶、不进 `package.json` 的 `exports`(与 `secret-scan.ts` 同一姿势)—— 消费方只拿表本身,索引是本包的实现细节。
|
|
4
5
|
* 索引只从表派生,不另存任何词:表改了,这里跟着变。
|
|
6
|
+
*
|
|
7
|
+
* ## 迁移指路(7.0.1)
|
|
8
|
+
* 拒因要不要带迁移指路只有一条规则:**这个键「旧形 ⇒ 迁移能搬」就带,搬不了的保留名(`PORT` / `MODEL_*` …)不带**。
|
|
9
|
+
* 「迁移能搬」= 迁移自己识别的键集(车道键 {@link isLaneEnvKey} ∪ 店键 {@link STORE_ENV_KEYS}),写闸读的是**同一只谓词**
|
|
10
|
+
* ({@link isMigratableEnvKey}),不是第二张手抄的表 —— 迁移多认一个键而写闸没跟,或反过来,不会静默分叉
|
|
11
|
+
* (`test/migration-signpost-7.0.1.test.ts` 用真跑迁移的读数逐个保留名对拍)。
|
|
5
12
|
*/
|
|
6
13
|
import { type ExecutionLane, type ExecutionLaneRow } from "./execution-lanes.js";
|
|
7
14
|
/** 一条车道的表行。词表与表行由 `execution-lanes.ts` 的 `satisfies Record<ExecutionLane, …>` 一一对应 ——
|
|
@@ -17,3 +24,15 @@ export interface LaneEnvOwner {
|
|
|
17
24
|
export declare const LANE_ENV_OWNER: ReadonlyMap<string, LaneEnvOwner>;
|
|
18
25
|
/** 一个车道 env 名在 WorkerSpec 里的**唯一写位**(保留键的拒因指路用它)。 */
|
|
19
26
|
export declare function laneEnvSeat(name: string, owner: LaneEnvOwner): string;
|
|
27
|
+
/** 车道块的两个特殊键(不在车道表里,但归车道块:迁移把 `REMOTE_EXEC` 搬成 `runtime.execution.lane`、`REQUIRE_EXECUTION_LANE` 丢弃)。 */
|
|
28
|
+
export declare const LANE_WORD_KEYS: readonly ["REMOTE_EXEC", "REQUIRE_EXECUTION_LANE"];
|
|
29
|
+
/** 渲染器以前不渲、运维只能写在 env 逃生口里的店键(7.0.0 起归 `runtime.store`;迁移搬进 `store`)。 */
|
|
30
|
+
export declare const STORE_ENV_KEYS: readonly ["DB_BACKEND", "MEMORY_ENGINE", "MEMORY_ENGINE_BACKEND"];
|
|
31
|
+
/** 一个名字是不是车道块的键:车道词两键,或车道表里的任一车道 env 名。 */
|
|
32
|
+
export declare function isLaneEnvKey(name: string): boolean;
|
|
33
|
+
/** 一个名字是不是迁移会处理的 6.x env 键(搬进结构块 / 丢弃 / 点名它拒):车道块的键 ∪ 店键。写闸拒因带不带迁移指路只看它。 */
|
|
34
|
+
export declare function isMigratableEnvKey(name: string): boolean;
|
|
35
|
+
/** 迁移命令的唯一拼写(design/327 §3.4 原句):`npx` 全名形 —— 没装本包的运维粘贴即可跑;裸 bin 名 `settings-schema …` 在没装本包的机器上是 `command not found`。 */
|
|
36
|
+
export declare const MIGRATE_WORKERS_COMMAND: "npx @sema-agent/settings-schema migrate-workers";
|
|
37
|
+
/** 拒因里的迁移指路 —— **一句话、一个常量**:保留名闸(`env` / `secretEnv` 里的 {@link isMigratableEnvKey} 键)与删除项预处理(七个被删旧键)引用同一个。 */
|
|
38
|
+
export declare const MIGRATION_SIGNPOST: string;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 包内私有:车道表({@link EXECUTION_LANES})
|
|
2
|
+
* 包内私有:车道表({@link EXECUTION_LANES})的两只**派生索引** + 「迁移能搬哪些键」的**一份**键集与指路句,
|
|
3
|
+
* 供 `types.ts`(写闸)与 `migrate-workers.ts`(一次性迁移)同读。
|
|
3
4
|
* 不进主桶、不进 `package.json` 的 `exports`(与 `secret-scan.ts` 同一姿势)—— 消费方只拿表本身,索引是本包的实现细节。
|
|
4
5
|
* 索引只从表派生,不另存任何词:表改了,这里跟着变。
|
|
6
|
+
*
|
|
7
|
+
* ## 迁移指路(7.0.1)
|
|
8
|
+
* 拒因要不要带迁移指路只有一条规则:**这个键「旧形 ⇒ 迁移能搬」就带,搬不了的保留名(`PORT` / `MODEL_*` …)不带**。
|
|
9
|
+
* 「迁移能搬」= 迁移自己识别的键集(车道键 {@link isLaneEnvKey} ∪ 店键 {@link STORE_ENV_KEYS}),写闸读的是**同一只谓词**
|
|
10
|
+
* ({@link isMigratableEnvKey}),不是第二张手抄的表 —— 迁移多认一个键而写闸没跟,或反过来,不会静默分叉
|
|
11
|
+
* (`test/migration-signpost-7.0.1.test.ts` 用真跑迁移的读数逐个保留名对拍)。
|
|
5
12
|
*/
|
|
6
13
|
import { EXECUTION_LANES } from "./execution-lanes.js";
|
|
7
14
|
/** 一条车道的表行。词表与表行由 `execution-lanes.ts` 的 `satisfies Record<ExecutionLane, …>` 一一对应 ——
|
|
@@ -21,3 +28,19 @@ export const LANE_ENV_OWNER = new Map(EXECUTION_LANES.flatMap((r) => {
|
|
|
21
28
|
export function laneEnvSeat(name, owner) {
|
|
22
29
|
return `runtime.execution.${owner.secret ? "secretRefs" : "coords"}.${name}`;
|
|
23
30
|
}
|
|
31
|
+
/** 车道块的两个特殊键(不在车道表里,但归车道块:迁移把 `REMOTE_EXEC` 搬成 `runtime.execution.lane`、`REQUIRE_EXECUTION_LANE` 丢弃)。 */
|
|
32
|
+
export const LANE_WORD_KEYS = ["REMOTE_EXEC", "REQUIRE_EXECUTION_LANE"];
|
|
33
|
+
/** 渲染器以前不渲、运维只能写在 env 逃生口里的店键(7.0.0 起归 `runtime.store`;迁移搬进 `store`)。 */
|
|
34
|
+
export const STORE_ENV_KEYS = ["DB_BACKEND", "MEMORY_ENGINE", "MEMORY_ENGINE_BACKEND"];
|
|
35
|
+
/** 一个名字是不是车道块的键:车道词两键,或车道表里的任一车道 env 名。 */
|
|
36
|
+
export function isLaneEnvKey(name) {
|
|
37
|
+
return LANE_WORD_KEYS.some((k) => k === name) || LANE_ENV_OWNER.has(name);
|
|
38
|
+
}
|
|
39
|
+
/** 一个名字是不是迁移会处理的 6.x env 键(搬进结构块 / 丢弃 / 点名它拒):车道块的键 ∪ 店键。写闸拒因带不带迁移指路只看它。 */
|
|
40
|
+
export function isMigratableEnvKey(name) {
|
|
41
|
+
return isLaneEnvKey(name) || STORE_ENV_KEYS.some((k) => k === name);
|
|
42
|
+
}
|
|
43
|
+
/** 迁移命令的唯一拼写(design/327 §3.4 原句):`npx` 全名形 —— 没装本包的运维粘贴即可跑;裸 bin 名 `settings-schema …` 在没装本包的机器上是 `command not found`。 */
|
|
44
|
+
export const MIGRATE_WORKERS_COMMAND = "npx @sema-agent/settings-schema migrate-workers";
|
|
45
|
+
/** 拒因里的迁移指路 —— **一句话、一个常量**:保留名闸(`env` / `secretEnv` 里的 {@link isMigratableEnvKey} 键)与删除项预处理(七个被删旧键)引用同一个。 */
|
|
46
|
+
export const MIGRATION_SIGNPOST = `lift a stored 6.x spec once with migrateWorkersDomain(doc) (on the raw document, ahead of parseDomain) or \`${MIGRATE_WORKERS_COMMAND} <file>\``;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* workers 域的**一次性迁移**(7.0.0,M-R2 WorkerSpec 结构块)—— 持久面唯一允许的过渡,窗口关了就整文件删除
|
|
3
3
|
* (连同 `types.ts` 里「删除项读到即拒」那一只预处理)。纯函数、无 I/O、不改入参;文件形的入口是
|
|
4
|
-
* `settings-schema migrate-workers <file>`(`bin/settings-schema.mjs
|
|
4
|
+
* `npx @sema-agent/settings-schema migrate-workers <file>`(`bin/settings-schema.mjs`,同一只函数;命令拼写的唯一正本 = `MIGRATE_WORKERS_COMMAND`)。
|
|
5
5
|
*
|
|
6
6
|
* ## 谁跑、在哪
|
|
7
7
|
* 读前抬升:在 `parseDomain("workers", raw)` / `DOMAIN_SCHEMAS.workers.safeParse` **之前**对**原始 JSON** 跑 —— workers 是
|
package/dist/migrate-workers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* workers 域的**一次性迁移**(7.0.0,M-R2 WorkerSpec 结构块)—— 持久面唯一允许的过渡,窗口关了就整文件删除
|
|
3
3
|
* (连同 `types.ts` 里「删除项读到即拒」那一只预处理)。纯函数、无 I/O、不改入参;文件形的入口是
|
|
4
|
-
* `settings-schema migrate-workers <file>`(`bin/settings-schema.mjs
|
|
4
|
+
* `npx @sema-agent/settings-schema migrate-workers <file>`(`bin/settings-schema.mjs`,同一只函数;命令拼写的唯一正本 = `MIGRATE_WORKERS_COMMAND`)。
|
|
5
5
|
*
|
|
6
6
|
* ## 谁跑、在哪
|
|
7
7
|
* 读前抬升:在 `parseDomain("workers", raw)` / `DOMAIN_SCHEMAS.workers.safeParse` **之前**对**原始 JSON** 跑 —— workers 是
|
|
@@ -32,16 +32,12 @@
|
|
|
32
32
|
* 已迁形(没有任何旧键、该有的块都在)⇒ 原样(同一引用)返回、`changed = []`。所以再跑一次 ⇒ 零变化、`doc` 字节相等。
|
|
33
33
|
*/
|
|
34
34
|
import { formatConfigPath, redactSecrets } from "./config-fns.js";
|
|
35
|
-
import { LANE_ENV_OWNER } from "./execution-lane-index.js";
|
|
35
|
+
import { LANE_ENV_OWNER, STORE_ENV_KEYS, isLaneEnvKey } from "./execution-lane-index.js";
|
|
36
36
|
import { EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, STORE_BACKENDS } from "./execution-lanes.js";
|
|
37
37
|
import { WorkerSpec } from "./types.js";
|
|
38
38
|
function isRec(v) {
|
|
39
39
|
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
40
40
|
}
|
|
41
|
-
/** 车道块的两个特殊键(不在车道表里,但归车道块)。 */
|
|
42
|
-
const LANE_WORD_KEYS = ["REMOTE_EXEC", "REQUIRE_EXECUTION_LANE"];
|
|
43
|
-
/** 渲染器以前不渲、运维只能写在 env 逃生口里的店键(7.0.0 起归 `runtime.store`)。 */
|
|
44
|
-
const STORE_ENV_KEYS = ["DB_BACKEND", "MEMORY_ENGINE", "MEMORY_ENGINE_BACKEND"];
|
|
45
41
|
/** 6.x 删除项:`[容器, 键]`。 */
|
|
46
42
|
const SIX_X_STORE_FIELDS = [
|
|
47
43
|
["runtime", "sessionBackend"],
|
|
@@ -78,7 +74,7 @@ function migrateEntry(raw, index) {
|
|
|
78
74
|
const refuse = (field, reason) => ({ kind: "refused", refusal: { name, index, field, reason: redactSecrets(reason), original: raw } });
|
|
79
75
|
const enabled = raw.enabled !== false;
|
|
80
76
|
const has = (container, key) => isRec(container) && Object.hasOwn(container, key);
|
|
81
|
-
const laneKeysOf = (rec) => isRec(rec) ? Object.keys(rec).filter(
|
|
77
|
+
const laneKeysOf = (rec) => (isRec(rec) ? Object.keys(rec).filter(isLaneEnvKey) : []);
|
|
82
78
|
const storeKeysOf = (rec) => (isRec(rec) ? STORE_ENV_KEYS.filter((k) => Object.hasOwn(rec, k)) : []);
|
|
83
79
|
const envLaneKeys = laneKeysOf(raw.env);
|
|
84
80
|
const secretLaneKeys = laneKeysOf(raw.secretEnv);
|
package/dist/types.js
CHANGED
|
@@ -20,7 +20,7 @@ import { MAX_EXTRA_BODY_DEPTH } from "./hash.js"; // reconcile the extraBody dep
|
|
|
20
20
|
import { DispatchPolicy } from "./fleet.js"; // WorkerSpec.fleet.dispatch (scheduling framework D3)
|
|
21
21
|
import { carriesUserinfo, percentLayers } from "./secret-scan.js"; // package-private normalization + the userinfo judgement (not in the barrel)
|
|
22
22
|
import { EXECUTION_LANES, EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "./execution-lanes.js"; // 7.0.0 M-R2: the lane / store vocabulary (one exported table)
|
|
23
|
-
import { LANE_ENV_OWNER, laneEnvSeat, laneRowOf } from "./execution-lane-index.js"; // package-private indexes derived from that table (not in the barrel)
|
|
23
|
+
import { LANE_ENV_OWNER, MIGRATION_SIGNPOST, isMigratableEnvKey, laneEnvSeat, laneRowOf } from "./execution-lane-index.js"; // package-private indexes derived from that table (not in the barrel)
|
|
24
24
|
// COARSE, best-effort literal-secret SHAPE detector (NOT exhaustive — the real boundary is the env-NAME schema
|
|
25
25
|
// on structured fields). Rejected in free-form value fields (worker `env`, model `extraBody`), URL components,
|
|
26
26
|
// AND as an env-NAME value (an AWS key id is a valid env-NAME charset yet is a secret VALUE, not a NAME). Case-
|
|
@@ -1575,6 +1575,15 @@ const RESERVED_ENV_FIELD = Object.freeze({
|
|
|
1575
1575
|
/** 结构化 WorkerSpec 字段已经产出的 env 名 —— 通用 `env` / `secretEnv` 逃生口**不许**设它们(用专属字段)。由
|
|
1576
1576
|
* `RESERVED_ENV_FIELD`(名 → 唯一写位)的键派生;增删理由见那张表的头注。 */
|
|
1577
1577
|
export const RESERVED_WORKER_ENV = new Set(Object.keys(RESERVED_ENV_FIELD));
|
|
1578
|
+
/**
|
|
1579
|
+
* 保留名被 `env` / `secretEnv` 逃生口设置时的拒因:点名键 + 它的唯一写位。句尾恒是「set it via <字段>」(下游按它抽归属字段,不动)。
|
|
1580
|
+
* **迁移指路只有一条规则(7.0.1)**:这个键「旧形 ⇒ 迁移能搬」({@link isMigratableEnvKey},与迁移自己识别的键集同一只谓词)就在键名后
|
|
1581
|
+
* 带 {@link MIGRATION_SIGNPOST};搬不了的保留名(`PORT` / `MODEL_*` / `MYSQL_*` …)不带 —— 指一条跑了也没用的命令比不指更糟。
|
|
1582
|
+
*/
|
|
1583
|
+
function reservedKeyMessage(slot, key) {
|
|
1584
|
+
const signpost = isMigratableEnvKey(key) ? ` (${MIGRATION_SIGNPOST})` : "";
|
|
1585
|
+
return `${slot} cannot set reserved key "${key}"${signpost} — set it via ${RESERVED_ENV_FIELD[key]}`;
|
|
1586
|
+
}
|
|
1578
1587
|
// ── 7.0.0 M-R2:`runtime.execution` / `runtime.store` 两个结构块 ─────────────────────────────────────────────
|
|
1579
1588
|
// 词表只有一个正本(`execution-lanes.ts` 的导出表):本包写闸(`WorkerSpec.safeParse`)、中心渲染器、引擎起服三处读同一张表。
|
|
1580
1589
|
// 拒因都在 zod issue 里(`path` 指到要改的字段、`message` 带出路)—— schema 本身是唯一判据,不另导出拒因函数。
|
|
@@ -1692,7 +1701,7 @@ function retiredWorkerFieldIssues(raw, ctx) {
|
|
|
1692
1701
|
ctx.addIssue({
|
|
1693
1702
|
code: z.ZodIssueCode.custom,
|
|
1694
1703
|
path: [parent, key],
|
|
1695
|
-
message: `${parent}.${key} was removed in 7.0.0 (now ${now}) —
|
|
1704
|
+
message: `${parent}.${key} was removed in 7.0.0 (now ${now}) — ${MIGRATION_SIGNPOST}`,
|
|
1696
1705
|
});
|
|
1697
1706
|
}
|
|
1698
1707
|
}
|
|
@@ -1798,7 +1807,7 @@ const WorkerSpecObject = z.object({
|
|
|
1798
1807
|
.superRefine((rec, ctx) => {
|
|
1799
1808
|
for (const [k, v] of Object.entries(rec)) {
|
|
1800
1809
|
if (RESERVED_WORKER_ENV.has(k))
|
|
1801
|
-
ctx.addIssue({ code: "custom", message:
|
|
1810
|
+
ctx.addIssue({ code: "custom", message: reservedKeyMessage("env", k) });
|
|
1802
1811
|
// 0.1.32: NO name exemption — a PUBLIC key is allowed because SECRET_LITERAL_RE now only flags PRIVATE keys
|
|
1803
1812
|
// (root fix), so a literal private key / token in env (ANY name, incl `*_PUBKEY`) is still rejected.
|
|
1804
1813
|
if (containsSecretToken(v))
|
|
@@ -1817,7 +1826,7 @@ const WorkerSpecObject = z.object({
|
|
|
1817
1826
|
.superRefine((rec, ctx) => {
|
|
1818
1827
|
for (const k of Object.keys(rec))
|
|
1819
1828
|
if (RESERVED_WORKER_ENV.has(k))
|
|
1820
|
-
ctx.addIssue({ code: "custom", message:
|
|
1829
|
+
ctx.addIssue({ code: "custom", message: reservedKeyMessage("secretEnv", k) });
|
|
1821
1830
|
})
|
|
1822
1831
|
.optional(),
|
|
1823
1832
|
/** Opaque recreate trigger (hash-INCLUDED): bump it to force the reconciler to recreate the worker — e.g. to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/settings-schema",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.1",
|
|
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",
|