@sema-agent/settings-schema 11.0.0 → 11.2.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.
- package/CHANGELOG.md +148 -1
- package/README.md +17 -3
- package/bin/settings-schema.mjs +7 -20
- package/conformance/VERSION +1 -1
- package/conformance/cases/c06-legacy-governance-lift/expected.json +1 -1
- package/conformance/cases/c20-governance-absent-lifts/expected.json +1 -1
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/dist/atomic-write.d.ts +14 -0
- package/dist/atomic-write.js +90 -0
- package/dist/cli.d.ts +31 -6
- package/dist/cli.js +50 -124
- package/dist/config-dir-io.d.ts +2 -0
- package/dist/config-dir-io.js +13 -0
- package/dist/config-dir-migration.d.ts +64 -0
- package/dist/config-dir-migration.js +167 -0
- package/dist/file-edit.d.ts +3 -3
- package/dist/file-edit.js +6 -16
- package/dist/file-store.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local-load.d.ts +1 -1
- package/dist/local-load.js +4 -14
- package/dist/node.d.ts +2 -0
- package/dist/node.js +3 -0
- package/dist/one-time-migrations.d.ts +6 -0
- package/dist/one-time-migrations.js +12 -2
- package/dist/previous-web-admin-env.d.ts +15 -0
- package/dist/previous-web-admin-env.js +20 -0
- package/dist/remote-exec.js +3 -3
- package/dist/retired-envs.js +13 -2
- package/dist/retired-form.d.ts +7 -2
- package/dist/retired-form.js +11 -3
- package/dist/runtime-lift-targets.d.ts +7 -0
- package/dist/runtime-lift-targets.js +7 -0
- package/dist/scheduler-store-node.d.ts +4 -3
- package/dist/scheduler-store-node.js +7 -8
- package/dist/shell-quote.d.ts +1 -0
- package/dist/shell-quote.js +14 -0
- package/dist/types.d.ts +9 -1
- package/dist/types.js +71 -18
- package/dist/web-admin-credential.d.ts +1 -1
- package/dist/web-admin-credential.js +3 -3
- package/dist/web-admin-env.d.ts +2 -1
- package/dist/web-admin-env.js +2 -1
- package/dist/web-admin-session-url.d.ts +17 -0
- package/dist/web-admin-session-url.js +64 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,153 @@
|
|
|
6
6
|
> `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
|
|
7
7
|
> 包名是**历史记录**,如实保留不追改。
|
|
8
8
|
|
|
9
|
+
## 11.2.0 — 2026-10-10 — 六小件:SEMA_REGISTRY_* 八名加回退役墓碑(换名窗期与保留表交集写明)· 断言令牌 / 凭证文件键进保留表 · 会话网页链接模板函数与配对解析 · CLI 命令路径 shell 引用 · 原子写器保留符号链接 · node 侧目录迁移函数导出(minor:新导出与新墓碑行,零删零改名)
|
|
10
|
+
|
|
11
|
+
> 本节六件:上一代连接名 `SEMA_REGISTRY_*` 八名进墓碑表(硬破坏,读侧同代要求)· 断言令牌两键进保留 env 名表 · 会话网页链接模板函数与配对解析 · 命令行印出的命令路径加 shell 引用 ·
|
|
12
|
+
> 原子写器遇符号链接保留链接 · 目录形迁移函数导出。新导出:主桶 3 个(会话链接)、`./node` 6 个(目录迁移),其余零新导出;唯一的硬破坏是第一件。
|
|
13
|
+
|
|
14
|
+
### 硬破坏:上一代 web-admin 连接名 `SEMA_REGISTRY_*` 八名进墓碑表(读侧同代要求)
|
|
15
|
+
|
|
16
|
+
`RETIRED_ENVS` 加八行 `renamed` 墓碑:`SEMA_REGISTRY_URL` / `_TOKEN` / `_TOKEN_FILE` / `_WORKER` / `_LIVE_URL` / `_WEB_URL` / `_DRY_RUN` / `_ADMIN_TOKEN`,去向各是 `WEB_ADMIN_ENV` 同键的现名
|
|
17
|
+
(`SEMA_WEB_ADMIN_*`,一跳)。进表的判据没变 —— 没有任何受支持的引擎还读这个名:11.0.0 把现名换进 `WEB_ADMIN_ENV` 时,在跑的引擎还把这八个名当活旋钮读,所以当时不登记;
|
|
18
|
+
引擎换读现名之后的这一版加回。旧名在场即拒启(替代名设没设都一样,空串 = 未设),`WorkerSpec` 的 `env` / `secretEnv` 写闸同拒(拒句就是墓碑句 `retiredEnvRefusal`),
|
|
19
|
+
读侧 `readWorkersRows` 把带着它们的存量行逐行扣下,目录 wire 的 `retired[]` 多这八行(`buildRetiredEnvWire()` 同表派生,零代码改动)。上一代名字面只在包内私有的一处写一次,
|
|
20
|
+
墓碑行与保留表都从那里取,去向从 `WEB_ADMIN_ENV` 取、不手抄;包根不多导出任何名字。
|
|
21
|
+
|
|
22
|
+
- **读侧同代要求**:server ≥ 7.124.0 起才拒旧名;更老的 server 仍把它们当活旋钮读。给 worker 渲染 env 的一方(web-admin 渲染器)按**目标引擎的代**写名 ——
|
|
23
|
+
目标引擎 ≥ 7.124.0 写 `SEMA_WEB_ADMIN_*`,只有还读上一代名的引擎才写上一代名(混版本车队按目标版本渲)。
|
|
24
|
+
- **谁受影响**:部署里仍设着 `SEMA_REGISTRY_*` 的运维(server ≥ 7.124.0 拒启);在 worker 规格的 `env` / `secretEnv` 里自写其中任一名的(写闸拒;11.0.0 时这八名里只有 URL / WORKER / TOKEN 三名被拒,
|
|
25
|
+
其余五名照收,现在八名都拒);存量里带着它们的 worker 行(读侧扣下、在列表里标红)。
|
|
26
|
+
- **怎么改**:把这八个名字改成同键的现名(`SEMA_WEB_ADMIN_URL` 等);worker 规格里自写的删掉(渲染器会写)。
|
|
27
|
+
- **不改会怎样**:server ≥ 7.124.0 拒启,拒句点名旧名与现名;存量 worker 行读侧被扣下。
|
|
28
|
+
|
|
29
|
+
**两张表答两个问题,交集是窗期形,终态是零交集**:
|
|
30
|
+
- 保留表 `RESERVED_WORKER_ENV` =「规格作者可不可以自己设这个名」(不可以:渲染器 / 编排器写它,或某个结构字段产出它,或它与渲染器 / 编排器写的名互斥);
|
|
31
|
+
- 墓碑表 `RETIRED_ENVS` =「带着这张表的引擎还读不读这个名」(≥ 7.124.0 的引擎不读、见之拒启)。≤ 7.123.0 的引擎内带的是 10.1.1,看不见本版的表、仍读上一代名 ——
|
|
32
|
+
所以墓碑的进表判据读作「没有带着本表的引擎还读这个名」。
|
|
33
|
+
- **窗期**:渲染器对 ≤ 7.123.0 的目标引擎仍写上一代的 `URL` / `WORKER` / `TOKEN`(`TOKEN_FILE` 与 `TOKEN` 互斥,同组同入),这四名既是保留名也是墓碑,写闸交两句(墓碑句在前、保留句在后,
|
|
34
|
+
保留句与现名同一结构位);另外四名(`LIVE_URL` / `WEB_URL` / `DRY_RUN` / `ADMIN_TOKEN`)没有谁写、也不与谁互斥,只是墓碑。窗期交集是个写死的闭集,有钉盯着。
|
|
35
|
+
- **窗关即终态**:web-admin 渲染器不再对任何目标引擎写上一代名之后(此后没有引擎要读它们),这四名离开保留表,两表回到**零交集** —— 11.0.0 记的「老三名搬去墓碑表」就是这个终态,不是本版。
|
|
36
|
+
那一刻窗期交集那一钉翻回零交集,保留名增词账里记删词。
|
|
37
|
+
|
|
38
|
+
### 行为变:断言令牌(`wat_`)的两个声明键与主凭证的文件键进保留 env 名表
|
|
39
|
+
|
|
40
|
+
`RESERVED_WORKER_ENV` 加三名:`SEMA_WEB_ADMIN_ASSERTION_TOKEN`(值键,编排器注入)、`SEMA_WEB_ADMIN_ASSERTION_TOKEN_FILE` 与 `SEMA_WEB_ADMIN_TOKEN_FILE`(两把凭证的文件键,只给不经编排器的手接引擎、由运维直设)。
|
|
41
|
+
文件键与同一份凭证的值键互斥(`judgeWebAdminCredentialDeclaration(env, "assertion" | "access")`:一对里两个都设 ⇒ 拒启),规格里写了任一个就与渲染器 / 编排器注入的值键撞 —— 主凭证与断言令牌是同一条规则,所以同一个答案。名字取自 `WEB_ADMIN_ENV`。
|
|
42
|
+
换名窗里上一代的 `SEMA_REGISTRY_TOKEN_FILE` 与老三名同组同入(见上一节)。`WorkerSpec` 的 `env` / `secretEnv` 写闸同拒,拒句点名各自的出路:值键 ⇒ 「由编排器注入」,文件键 ⇒ 「由运维直设,不经 worker 规格」
|
|
43
|
+
(句尾仍是 `set it via <出路>`);读侧 `readWorkersRows` 把带着它们的存量行逐行扣下。
|
|
44
|
+
|
|
45
|
+
- **谁受影响**:在 worker 规格的 `env` / `secretEnv` 里自写这三个名字的(此前照收;引擎起服时与注入的凭证撞而拒启)。
|
|
46
|
+
- **怎么改**:从规格里删掉;值由编排器注入,不经编排器的手接引擎由运维在引擎进程环境里直设。
|
|
47
|
+
- **不改会怎样**:写闸拒;存量行读侧被扣下、在列表里标红。
|
|
48
|
+
|
|
49
|
+
### 新增:会话网页链接的一只模板函数与它的配对解析(`webAdminSessionUrl` / `parseWebAdminSessionUrl`)
|
|
50
|
+
|
|
51
|
+
web-admin 里一个会话的网页链接此前由各处按地址形手拼(把别仓的路由抄成自己的字符串)。路由属于 web-admin,现形是 `<webBaseUrl>/chat?agent=<worker>&session=<sessionId>`
|
|
52
|
+
(聊天页只读这两个查询参数;只带 `session` 的链接会落到页面自己挑的 worker 上,会话不在那一台就落空)。新导出(主桶,纯函数、浏览器安全,additive):
|
|
53
|
+
|
|
54
|
+
- `webAdminSessionUrl(webBaseUrl, { worker, sessionId })` —— 两位必填(空 / 缺位 / 非串 ⇒ 抛 `TypeError`),逐位 `encodeURIComponent`(含 `&` `#` `=` `?` 的 id 不串位;孤立代理项 ⇒ `URIError`,不静默替换),
|
|
55
|
+
基址须是干净的绝对 http(s) URL(无 userinfo / query / fragment,可带路径前缀,尾斜杠折掉;判的是 `NON_SECRET_URL` 同一只形判官),返回绝对 URL。
|
|
56
|
+
- `parseWebAdminSessionUrl(link)` —— 配对解析,收绝对 / 相对串、`URL`、`URLSearchParams`;宽读(旧链接可能缺 `agent`):缺的或空的那一位不出现在结果里,重复参数取第一个,路径与别的参数不看;串不是 URL ⇒ 抛 `TypeError`。
|
|
57
|
+
- 型 `WebAdminSessionTarget`(`{ worker, sessionId }`)。
|
|
58
|
+
|
|
59
|
+
往返钉:任意含特殊字符的 (worker, sessionId) 经模板再经解析逐位还原。链接没有 scope 位;日后加 scope 时作可选第三位、同一函数。
|
|
60
|
+
- **谁需要**:要给用户一个会话链接的壳 / 客户端 / 服务端文档;web-admin 聊天页的深链读写改读配对解析(路径与参数名只此一份)。
|
|
61
|
+
- **谁受伤**:无(纯增)。已手拼 `…/chat?session=<id>` 的调用方到货改调(只带 `session` 的形今天就会落空)。
|
|
62
|
+
|
|
63
|
+
### 行为变:命令行印出的命令,路径按 shell 单引号规则引用
|
|
64
|
+
|
|
65
|
+
`migrate` 命令的 stderr 指路句(上一层目录里 `config.d` 在场时,「`<目录>/config.d` exists — run: …」)此前把路径原样接在命令后面:配置根路径含空格(或引号、`$`)的运维照原样粘贴,
|
|
66
|
+
shell 把路径拆成多个参数 ⇒ 用法行 rc 2。现在印出的命令里路径过一只 `shellQuote`(POSIX 单引号规则;只有 ASCII 字母数字与 `_ @ + : , . / -` 组成的串原样不加引号,其余整串单引号包起来,
|
|
67
|
+
串里的 `'` 写成 `'\''`;空串写成 `''`),指路句可以直接粘贴跑通。用法行与迁移指路句里的 `<config.d directory>` 是元变量,不是参数,原样不引用(这两处的句子逐字不变)。
|
|
68
|
+
命令的拼写收在一处(带占位的模板 / 带真路径的函数),别处不再各自拼。无新导出(`shellQuote` 包内私有)。
|
|
69
|
+
- **谁需要**:配置根路径含空格 / 特殊字符的运维与脚本。
|
|
70
|
+
- **谁受伤**:按字面比对该指路句的脚本 —— 路径含特殊字符时句子里的路径多了引号;路径在安全字符集内的句子逐字不变。
|
|
71
|
+
- **补偿**:无需;要路径本身,去掉引号即可(引用遵循 POSIX shell,Windows cmd.exe 不认单引号)。
|
|
72
|
+
|
|
73
|
+
### 行为变:写盘处遇符号链接 —— 保留链接、更新目标(悬空链接响亮拒)
|
|
74
|
+
|
|
75
|
+
`config.d` 里的域文件若是符号链接(运维用链接把 `config.d` 指到别处管理的部署),写盘处此前在链接所在目录建临时文件再 `rename` 盖到链接路径上:链接本身被换成普通文件、链接目标不动 ——
|
|
76
|
+
写一次后链接断,以后两份漂移。现在那一只原子写器(`writeDomainFile` / `writeRemoteExec` / 调度店 / 命令行迁移写器都调它)先把目标解析到真文件(`realpath`),临时文件建在**真文件所在目录**、
|
|
77
|
+
rename 到真文件:链接(及链上每一环)原样保留,真文件内容更新,文件模式规则作用在真文件上。临时文件与真文件同目录 ⇒ rename 不会跨文件系统;真文件所在目录写不进、或 rename 因别的原因失败 ⇒
|
|
78
|
+
响亮抛,**绝不**退回成「在链接位置写一个普通文件」(那是静默换形)。路径本身是悬空链接 ⇒ 响亮抛(消息点名这条链接与它指向的位置),既不在链接位置建普通文件,也不替链接建它指向的文件;成环的链接抛 `ELOOP`。
|
|
79
|
+
目标不存在 ⇒ 新建(经符号链接的目录写新文件时,文件落在真目录里)。
|
|
80
|
+
- **谁需要**:用符号链接管理 `config.d` 域文件的部署(迁移命令、`writeDomainFile` 写一次后链接不再被换掉)。
|
|
81
|
+
- **谁受伤**:(1) 域文件是悬空链接的 —— 此前写成功(并把链接换成普通文件),现在抛;(2) 链接指向的真文件所在目录对进程不可写的 —— 此前写进链接所在目录成功(链接被换形),现在抛。
|
|
82
|
+
- **补偿**:修好或删掉悬空链接;让进程对真文件所在目录可写,或去掉链接改用普通文件。
|
|
83
|
+
|
|
84
|
+
### 新增:目录形迁移函数导出(`migrateConfigDirectory` / `nodeConfigDirectoryIo`,`./node`)
|
|
85
|
+
|
|
86
|
+
「读目录 → 迁 → 整域终判 → 原子写 → 删退役域文件」这条语义此前只活在命令行里,没有覆盖面开关、没导出 —— 引擎起服对本地 `config.d` 的迁移只能按同语义自写一份(读法 / 写法 / 模式规则各一小段)。
|
|
87
|
+
现在语义只此一处:`./node` 导出 `migrateConfigDirectory(dir, { documents, io, check? })`,命令行成了它的渲染器(退出码与报告行),引擎调同一只函数。新导出(additive,`./node` 子路径):
|
|
88
|
+
`migrateConfigDirectory`、真 I/O 绑定 `nodeConfigDirectoryIo`(`node:fs` + 本包唯一的原子写器与它的模式表)、型 `ConfigDirectoryIo` / `ConfigDirectoryMigration` / `ConfigDirectoryRefusal` / `MigrateConfigDirectoryOptions`。
|
|
89
|
+
|
|
90
|
+
- **覆盖面 = 调用方读的域名集**(`documents`):覆盖面外的文件不读、不判、不写、不删,它们是坏文件也挡不住覆盖面内的迁移;拼错的名字抛 `TypeError`。命令行的覆盖面是全部配置域加退役域。
|
|
91
|
+
**迁移不越过覆盖面去写**:退役的 `runtime` 域迁出时要写 `governance` 与 `limits`,覆盖面没把这两个域一并包进来(只含 `runtime`,或只含其中一个)时,目录算不上「一个 scope 的全集」,
|
|
92
|
+
`runtime` 的迁移以收据 `refused` 拒迁、一字不写 —— 不会把覆盖面外已有的 `governance.json` / `limits.json` 当作「从未设过」覆盖掉;
|
|
93
|
+
日后若别的迁移步新建覆盖面外的文档,函数响亮抛(守卫),不静默写。要迁 `runtime`,覆盖面里放齐 `runtime`、`governance`、`limits`。
|
|
94
|
+
- **结果是一个闭集**(调用方穷举):`unreadable`(读不了目录 / 文件,或某份文档是坏文件;一字不写)· `no-documents`(覆盖面内一份文档都没有;带回目录清单,调用方自己判「填错了目录」还是「真空」)·
|
|
95
|
+
`refused`(迁移步拒,或要写回的文档整域读不进;一字不写)· `noop` · `pending`(`check: true` 且有改动;一字不写)· `applied`(带写了哪些、删了哪些;先写后删,中途断掉再跑幂等)。
|
|
96
|
+
写盘阶段的 I/O 错误原样抛出。
|
|
97
|
+
- **命令行行为逐字不变**:既有命令行钉全绿(退出码、stdout 两行、stderr 句子);并有一道钉盯着「命令行没有自己的语义」—— 对仓内一致性语料的**每个 case 目录**,`migrate` 与 `migrate --check`
|
|
98
|
+
经命令行和直接调 `migrateConfigDirectory` 的退出码 / stdout / stderr / 落盘字节逐字同(函数这一边的期望由一只独立的小渲染器算出;语料的结局分布也被钉住,五形都有样本),
|
|
99
|
+
外加真 bin × 真 I/O 绑定的磁盘端到端对拍;唯一有意的差别是上面「命令行印出的命令」那一条的路径引用。
|
|
100
|
+
- **谁需要**:对本地 `config.d` 做一次性迁移的进程(引擎起服)。**谁受伤**:无(纯增)。已自写同语义一份的调用方到货改调、删掉自写段。
|
|
101
|
+
|
|
102
|
+
## 11.1.0 — 2026-10-10 — 11.0.0 持久面三件收口:URL fragment 拒句补迁移指路 + 全称律源码钉 · 迁移命令对零域文档目录响亮拒 · 写盘处收成一只原子写器与模式表(minor:一个新导出,零删零改名)
|
|
103
|
+
|
|
104
|
+
> 本节三件,都是对 11.0.0 持久面的收口:拒句补指路、迁移命令填错目录响亮拒、写盘处收成一只。一个新导出(`NON_SECRET_URL_NO_MIGRATION`,additive),零删零改名。
|
|
105
|
+
|
|
106
|
+
### URL 带 `#` 的拒句点名迁移 SS-M7;全称律进源码钉;登记表加 `oldForm` 格
|
|
107
|
+
|
|
108
|
+
11.0.0 的律是「读写两面遇旧形一律响亮拒,拒句点名迁移 id」,但 `NON_SECRET_URL` 座位上的 fragment 拒句(`must not carry a fragment (…)`)没带指路 ——
|
|
109
|
+
存量带 `#` 的 URL 被拒时,读到的句子不说有一条迁移能改写它。现在 fragment 拒句尾接迁移指路(`<规则句>; data stored before 11.0.0: run the one-time migration SS-M7 (…)`),
|
|
110
|
+
与钥匙双设的 SS-M5 拒句同形,对新写入同样生效;userinfo / 非 http(s) 的拒句没有迁移可指,保持判官的规则句原样。
|
|
111
|
+
|
|
112
|
+
- **谁需要**:持有带 `#` 的 `baseUrl` / MCP `url` / `engineUrl` / `gitApiBaseUrl` 存量的运维 —— 拒句直接告诉他跑哪一条迁移。
|
|
113
|
+
- **谁受伤**:按整句字面匹配 fragment 拒句的消费方(句子变长了:尾部多了 `; <指路>`);`issue.params.flaw`(`"fragment"`)不变,按它分流的不受影响。
|
|
114
|
+
- **补偿**:按 `params.flaw` 取规则词,别拿整句比对;要规则句本身用 `httpUrlRule("fragment")`。
|
|
115
|
+
- **指路只挂在 SS-M7 够得着的座位上**:`NON_SECRET_URL` 是域文档里 URL 座位的判官(SS-M7 按这只 schema 的身份找座位,所以「带指路」与「被 SS-M7 改写」是同一件事);不在域文档里的座位 ——
|
|
116
|
+
`remoteExec` 的端点(`apiUrl` / `s3Snapshot.endpoint`)、web-admin 凭据信封的 `issuer` —— 改用新导出 `NON_SECRET_URL_NO_MIGRATION`:同一判官、同样的规则句,只是 fragment 拒句不带迁移指路
|
|
117
|
+
(迁移命令改不到那些值,指过去只会让运维白跑一趟)。自己在宿主里拿 `NON_SECRET_URL` 校验非域文档 URL 的代码,按这个口径选一只。
|
|
118
|
+
- **全称律进源码钉**:登记表每行新增 `oldForm`(`"refused"` 旧形被拒、拒句点名本行 id / `"rewritten-on-read"` 读时改写不拒 / `"not-stored"` 没有存储形),
|
|
119
|
+
律的两个例外(调度店 SS-M3、wire 删码位 SS-W1)从散文变成表里的字段;一道源码钉(读 AST)要求每个 `refused` 行的 id 在 `src/` 至少有一处铸点
|
|
120
|
+
(`migrationSignpost("<id>"` 调用或 retired 形字面),非 `refused` 行零铸点 —— 加一行迁移而不加指路 ⇒ 门红。`OneTimeMigrationWindow` 因此多一个必填只读字段
|
|
121
|
+
(读登记表的代码不受影响;自己手造该型字面的代码要补这一格)。SS-M7 行的 `removal` 补「the fragment rule loses its SS-M7 clause」。
|
|
122
|
+
- **迁移指路句的目录占位统一为 `<config.d directory>`**(此前拒句里是 `<dir>`,命令行 USAGE 与文档是 `<config.d>` / `<config.d directory>` 三种拼法)。
|
|
123
|
+
|
|
124
|
+
### `migrate <目录>` 填错目录不再静默 noop
|
|
125
|
+
|
|
126
|
+
命令行扫完目录没找到任何域文档(`<域>.json`)时,此前对零个域文档照常跑迁移:零收据 ⇒ `outcome=noop` 退出 0,stdout 两行、stderr 空 —— 填成 `config.d` 的上一层、
|
|
127
|
+
只有 `remote-exec.json` 之类非域文件的目录,都报「无事可做」,运维读成「这个 scope 已迁完」。现在零域文档分两形:
|
|
128
|
+
|
|
129
|
+
- **目录里有条目却没有一个是域文档**(任何不以 `.` 开头的条目:子目录、说明文件、`remote-exec.json`、不认识的名;填成 `config.d` 的上一层是最常见的)⇒ **退出 2、stdout 空**、
|
|
130
|
+
stderr 一句点名这个目录(要的是 `config.d` 目录本身;`<目录>/config.d` 在场就直接点它,给出该跑的命令)。`--check` 同判。
|
|
131
|
+
- **目录真空**(零条目,或只有以 `.` 开头的条目)⇒ **noop 退出 0**,stdout 两行(`outcome=noop written=false` + JSON 报告)、stderr 零字节:新部署的空 `config.d` 是合法态,
|
|
132
|
+
「没有东西可迁」不是「指错了目录」。迁移把目录搬空(例如目录里只有一份空的或只剩被丢弃键的 `runtime.json`)之后再跑同一目录,也因此是 noop(幂等)。
|
|
133
|
+
|
|
134
|
+
有域文档、只是没有要改的,仍是 noop 退出 0;只有退役域(`runtime.json`)文件仍照旧迁。
|
|
135
|
+
|
|
136
|
+
- **谁需要**:本地 `config.d` 的运维、对一批 scope 目录循环跑迁移的脚本。
|
|
137
|
+
- **谁受伤**:循环里夹着「里面有东西却没有域文档」的目录的脚本,此前这些目录静默过关,现在退出 2(真空目录照旧过关)。
|
|
138
|
+
- **补偿**:脚本先判目录里有 `<域>.json` 再调;或接住退出 2 当「这个目录不是 config.d」。
|
|
139
|
+
|
|
140
|
+
### 写盘处收成一只原子写器,文件模式由一张按文档类的表定
|
|
141
|
+
|
|
142
|
+
此前包里三处各写各的:`writeDomainFile` 不带模式(umask 缺省,通常 0o644)、迁移命令的写器新建一律 0o600 且已有沿用、调度店 0o600 —— 同一个目录里「写出的域文档」是 644 还是 600
|
|
143
|
+
取决于哪条路写的;`writeRemoteExec` 又是第四份同样的写法;命令行入口的头注还说自己是包里唯一写文件的地方。现在 `writeDomainFile` / `writeRemoteExec` / 迁移命令的写器 /
|
|
144
|
+
`saveSchedulerStore` 全部调同一只(同目录隐藏临时文件 → rename;失败清残片),模式只在那一张表里:`config.d` 契约文档(域文档、`remote-exec.json`)新建 = 0o644(契约文件只带 env-NAME、
|
|
145
|
+
不带秘密),**已有的文件沿用它原来的模式**;调度店耐久记录 = 0o600,每次保存都回到 0o600。入口脚本只注入真 I/O,不再自己写盘、不再自己判模式。
|
|
146
|
+
|
|
147
|
+
- **谁需要**:想让同一目录里的契约文件模式一致的运维;读 `scheduled_tasks.json` 权限的审计。
|
|
148
|
+
- **谁受伤**:(1) 迁移命令**新建**的域文档(例如从 `runtime.json` 搬出来的 `governance.json` / `limits.json`)此前是 0o600,现在是 0o644(经 umask),与 `writeDomainFile` 一致;
|
|
149
|
+
(2) `writeDomainFile` / `writeRemoteExec` 改写**已有**文件时此前按 umask 缺省重置模式,现在沿用原模式(运维 chmod 过的文件保留)。调度店的行为不变(新建 0o600、每次保存收回 0o600)。
|
|
150
|
+
- **补偿**:要更严的权限,对 `config.d` 里的文件 `chmod`(现在写回不再冲掉它),或收紧进程 umask(新建文件仍经 umask)。
|
|
151
|
+
|
|
152
|
+
#### 未闭环
|
|
153
|
+
|
|
154
|
+
- `remote-exec.json` 里存量带 `#` 的端点:一次性迁移 SS-M7 只走**域文档**里的座位,迁移命令也只读 `<域>.json`,所以这类存量没有迁移可跑,只能手改(拒句是规则句,不指迁移)。
|
|
155
|
+
|
|
9
156
|
## 11.0.0 — 2026-10-10 — 依赖换 zod 4、三处死出口收回、持久面一次性迁移族、同一个问题一只判官、名与契约(major)
|
|
10
157
|
|
|
11
158
|
> 本节四件:以下先是平台一件(zod 4 + 三处出口收窄 + 上游锚抬钉),再是持久面一件(「持久面」一节起),再是判官一件(「判官」一节起),再是名与契约一件(「名与契约」一节起)。每条硬破坏写三句:谁受影响 / 怎么改 / 不改会怎样。
|
|
@@ -1525,7 +1672,7 @@ sema-server 7.108.0(S-931)给模型目录条目加了 `headers`(部署 ↔ 供
|
|
|
1525
1672
|
|
|
1526
1673
|
## 6.1.0 — 2026-09-25 — 四件 additive:`entry-dropped` 身份(S-712)· 中心凭证信封 + 三态判别器(S-698)· `RestartSignal` 一处定义(S-741)· 中心凭证两键名 + 二选一判官(S-742);`redactSecrets` 私钥 PEM 串内任意位置且判在替换前(6.0.0 起三面既有盲点顺带关)
|
|
1527
1674
|
|
|
1528
|
-
> 本节四件全 additive(新可选键 / 新导出 / 新文件)
|
|
1675
|
+
> 本节四件全 additive(新可选键 / 新导出 / 新文件),零删零改名;判档口径见下方「发版纪律」:
|
|
1529
1676
|
> 新键 / 新导出 = minor。
|
|
1530
1677
|
|
|
1531
1678
|
### S-712 · `entry-dropped` 告警带被丢条目的身份(`identity?`,additive)
|
package/README.md
CHANGED
|
@@ -70,8 +70,15 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
70
70
|
`docs` 里没改的文档是**同一引用**、被移除的(退役的 `runtime` 域)不在、迁出的新文档(`governance` / `limits`)在 —— 据此写回 / 删除。
|
|
71
71
|
`receipts` 逐条说「对哪个坐标做了什么」(`moved` / `rewritten` / `dropped-shadowed` / `dropped-inert` / `refused`;不带值)。
|
|
72
72
|
`refused` 的坐标原样留着,读侧照旧拒:要人改。再跑一次 = 零改动。
|
|
73
|
-
- **目录形**:`npx @sema-agent/settings-schema migrate <config.d> [--check]`(同一只函数;目录当一个 scope;有 refused 一字不写、退出 1;
|
|
74
|
-
`--check` 有改动退出 3)
|
|
73
|
+
- **目录形**:`npx @sema-agent/settings-schema migrate <config.d directory> [--check]`(同一只函数;目录当一个 scope;有 refused 一字不写、退出 1;
|
|
74
|
+
`--check` 有改动退出 3)。参数是 `config.d` 目录**本身**(里面直接放 `<域>.json`):目录里有条目却没有一份域文档 —— 填成了它的上一层、只有
|
|
75
|
+
`remote-exec.json` 之类非域文件 —— 退出 2、stdout 空,stderr 点名这个目录(`<目录>/config.d` 在场就直接点它);目录真空(零条目,或只有以 `.` 开头的条目)是合法态
|
|
76
|
+
(新部署的空 `config.d`),和「有域文档、只是没有要改的」一样是 noop、退出 0。
|
|
77
|
+
- **目录形的函数**(`./node`):`migrateConfigDirectory(dir, { documents, io, check? })` 是命令行背后的那只函数 —— 「读目录 → 迁 → 整域终判 → 原子写 → 删退役域文件」这条语义只此一处,
|
|
78
|
+
命令行只是它的渲染器(退出码 + 报告行),要对本地 `config.d` 做同一件事的进程(比如引擎起服)调它,不按同语义自写一份。`documents` = 调用方读的域名集(覆盖面:覆盖面外的文件不读、不判、不写、不删);
|
|
79
|
+
覆盖面要迁 `runtime` 就得把 `governance` 与 `limits` 一并放进来(迁出的旧值要写那两份,不能覆盖面外去写;缺了就按 `partial` 拒迁 `runtime`、一字不写)。
|
|
80
|
+
`io` 注入 `{ readDir, readFile, writeFileAtomic, removeFile }`,真绑定是 `nodeConfigDirectoryIo`(`node:fs` + 本包唯一的原子写器与它的模式表)。返回一个闭集
|
|
81
|
+
(`unreadable` / `no-documents` / `refused` / `noop` / `pending` / `applied`),调用方穷举;写盘阶段的 I/O 错误原样抛出(已写完的留在新位上,再跑一次幂等收尾)。
|
|
75
82
|
- **迁什么**(七条,`ONE_TIME_MIGRATIONS` 每行写着改写规则、删法、删后由谁接住):退役 `runtime` 域(治理三闸 → `governance`、五个限额键 → `limits`)·
|
|
76
83
|
hosts 超界 · 调度店 durable 键(读时改写,下一个写者落盘)· workers 6.x 行 · 模型钥匙双设 · `atModelAllowlist` → `atMention` · URL fragment。
|
|
77
84
|
- **什么时候删**:每条的窗 = 11.0.0 发布日 + 30 天,窗关之后的那个 major 整族删(删法与删后的响亮臂见登记表那一行);本包的到期门到点即红。
|
|
@@ -293,7 +300,7 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
293
300
|
`WebAdminCredentialEnvelope` (strict JSON: `kind: "user-session"` = `WEB_ADMIN_CREDENTIAL_ENVELOPE_KIND`, a JWT
|
|
294
301
|
`accessToken` and a `refreshToken` under the same token law as the bare line, RFC 3339
|
|
295
302
|
`expiresAt` with seconds and a `Z` / `±hh:mm` offset in range, `issuer` = the issuing web-admin's base URL judged
|
|
296
|
-
by the same
|
|
303
|
+
by the same judge the `remoteExec` endpoints use (`NON_SECRET_URL_NO_MIGRATION`) — same-origin is the reader's comparison).
|
|
297
304
|
`parseWebAdminCredentialFile(text)` is the ONE discrimination law — trimmed text starting with `{` is read ONLY
|
|
298
305
|
as an envelope (a failure is `unreadable`, never a fallback to a bare line); anything else is a bare line,
|
|
299
306
|
and a first line that is not a token is `unreadable` too (never sent as a bearer); empty is `unreadable`. The `reason` names envelope fields / zod issue codes / an unknown-key COUNT, never file
|
|
@@ -306,6 +313,13 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
306
313
|
no default) → `env` / `file` / `absent`, the one pick-exactly-one rule for every credential: an empty string is unset, BOTH set (non-empty) throws (the refusal
|
|
307
314
|
names the two keys, never a value), the file path comes back verbatim (resolving a relative one against a cwd
|
|
308
315
|
is the reader's job).
|
|
316
|
+
- **Session web link** (`web-admin-session-url.ts`, main barrel): `webAdminSessionUrl(webBaseUrl, { worker, sessionId })` — the ONE
|
|
317
|
+
template for a web-admin session link, `<webBaseUrl>/chat?agent=<worker>&session=<sessionId>`: both parts required (a link with only
|
|
318
|
+
the session id lands on whichever worker the page picks and may not find it), each part `encodeURIComponent`-escaped, the base a
|
|
319
|
+
clean absolute http(s) URL (trailing slashes folded; userinfo / query / fragment refused), the result absolute; bad input throws,
|
|
320
|
+
never a half link. `parseWebAdminSessionUrl(link)` is its paired reader (absolute / relative string, `URL` or `URLSearchParams`;
|
|
321
|
+
a missing or empty part is simply absent from the result) — the page and every link-minting caller share this one definition of the
|
|
322
|
+
route and the parameter names. No scope part today; a scope would be an optional third part of the same function.
|
|
309
323
|
- **Retired env names** (`retired-envs.ts`, main barrel): `RETIRED_ENVS` — the ONE tombstone table (literal tuples
|
|
310
324
|
`[old, to, "renamed"]` / `[old, to, "replaced", why]` / `[old, why, "removed"]`, a closed set of three families,
|
|
311
325
|
frozen). A tombstone is permanent: the old name present ⇒ refuse to start, whether or not the replacement is also
|
package/bin/settings-schema.mjs
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// `settings-schema` — this package's command line (
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
// `settings-schema` — this package's command line (`migrate <config.d directory> [--check]`, the one-time migrations).
|
|
3
|
+
// The real-I/O injection point: the logic is `runSettingsSchemaCli` (dist/cli.js), which never touches the filesystem itself — this
|
|
4
|
+
// entry hands it the package's one directory-migration I/O binding (dist/config-dir-io.js: directory / file reads and removal, and
|
|
5
|
+
// writes through the one atomic writer, dist/atomic-write.js, which alone decides how a file is written and with what mode) plus the
|
|
6
|
+
// two output streams.
|
|
6
7
|
import { runSettingsSchemaCli } from "../dist/cli.js";
|
|
8
|
+
import { nodeConfigDirectoryIo } from "../dist/config-dir-io.js";
|
|
7
9
|
|
|
8
10
|
const io = {
|
|
9
|
-
|
|
10
|
-
readFile: (path) => readFile(path, "utf8"),
|
|
11
|
-
// atomic: write a sibling temp file (same mode as the original when there is one), then rename over it — a half-written
|
|
12
|
-
// domain document is worse than the old one.
|
|
13
|
-
writeFileAtomic: async (path, text) => {
|
|
14
|
-
let mode = 0o600;
|
|
15
|
-
try {
|
|
16
|
-
mode = (await stat(path)).mode & 0o777;
|
|
17
|
-
} catch (e) {
|
|
18
|
-
if (e?.code !== "ENOENT") throw e;
|
|
19
|
-
}
|
|
20
|
-
const tmp = `${path}.migrate-${process.pid}.tmp`;
|
|
21
|
-
await writeFile(tmp, text, { encoding: "utf8", mode });
|
|
22
|
-
await rename(tmp, path);
|
|
23
|
-
},
|
|
24
|
-
removeFile: (path) => unlink(path),
|
|
11
|
+
...nodeConfigDirectoryIo,
|
|
25
12
|
stdout: (text) => process.stdout.write(text),
|
|
26
13
|
stderr: (text) => process.stderr.write(text),
|
|
27
14
|
};
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
11.
|
|
1
|
+
11.2.0
|
|
@@ -16,6 +16,6 @@
|
|
|
16
16
|
"localRoot": {
|
|
17
17
|
"outcome": "rejected",
|
|
18
18
|
"stage": "parse",
|
|
19
|
-
"why": "拒:config.d/runtime.json 带事实 ⇒ FileConfigStore 由同一只墓碑拒(本地目录用 `npx @sema-agent/settings-schema migrate <
|
|
19
|
+
"why": "拒:config.d/runtime.json 带事实 ⇒ FileConfigStore 由同一只墓碑拒(本地目录用 `npx @sema-agent/settings-schema migrate <config.d directory>` 迁)。"
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -16,6 +16,6 @@
|
|
|
16
16
|
"localRoot": {
|
|
17
17
|
"outcome": "rejected",
|
|
18
18
|
"stage": "parse",
|
|
19
|
-
"why": "拒:config.d/runtime.json 带事实 ⇒ FileConfigStore 由同一只墓碑拒(本地目录用 `npx @sema-agent/settings-schema migrate <
|
|
19
|
+
"why": "拒:config.d/runtime.json 带事实 ⇒ FileConfigStore 由同一只墓碑拒(本地目录用 `npx @sema-agent/settings-schema migrate <config.d directory>` 迁)。"
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"contributor": "contract",
|
|
3
|
-
"claim": "7.0.0 删除项读到即**响亮拒**(runtime.sessionBackend,哪怕是停用的 worker)—— 11.0.0 起由 WorkerSpec 里原位的墓碑字段拒,拒句点名一次性迁移 SS-M4(与 `npx @sema-agent/settings-schema migrate <
|
|
3
|
+
"claim": "7.0.0 删除项读到即**响亮拒**(runtime.sessionBackend,哪怕是停用的 worker)—— 11.0.0 起由 WorkerSpec 里原位的墓碑字段拒,拒句点名一次性迁移 SS-M4(与 `npx @sema-agent/settings-schema migrate <config.d directory>`);不被 zod 静默剥成「什么都没声明」。",
|
|
4
4
|
"firstRedOn": []
|
|
5
5
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** 按文档类的模式表(闭集)。 */
|
|
2
|
+
export declare const ATOMIC_FILE_CLASSES: {
|
|
3
|
+
readonly "config-document": {
|
|
4
|
+
readonly newMode: 420;
|
|
5
|
+
readonly existing: "keep";
|
|
6
|
+
};
|
|
7
|
+
readonly "scheduler-store": {
|
|
8
|
+
readonly newMode: 384;
|
|
9
|
+
readonly existing: "reset";
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export type AtomicFileClass = keyof typeof ATOMIC_FILE_CLASSES;
|
|
13
|
+
/** 原子写 `text`(UTF-8)到 `target`(符号链接则写到它指向的真文件,见文件头);模式按 `kind` 在 {@link ATOMIC_FILE_CLASSES} 里的行。父目录须已存在。 */
|
|
14
|
+
export declare function writeFileAtomic(target: string, text: string, kind: AtomicFileClass): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **本包写盘的唯一一只原子写器**(Node 专用,不进主桶):`writeDomainFile` / `writeRemoteExec`(`config.d` 下的契约文件)、调度店
|
|
3
|
+
* `saveSchedulerStore`、命令行迁移经 `bin/settings-schema.mjs` 注入的写器 —— 全部调它。写法只有一种:在目标同目录写一个唯一命名的隐藏
|
|
4
|
+
* 临时文件,再 `rename` 盖过目标(POSIX 上原子,并发读者永远看不到半截文件);失败时临时文件被清掉,不留残片。
|
|
5
|
+
*
|
|
6
|
+
* 同步实现(几 KB 的配置文件,一次写不到一毫秒):调度店的写口本来就是同步 API(`saveSchedulerStore(path, records): void`,
|
|
7
|
+
* `mutateSchedulerStore` 的提交点),异步调用方(`writeDomainFile` 等)直接调它 —— 一只写器,没有第二份同样的写法。
|
|
8
|
+
*
|
|
9
|
+
* ## 文件模式:一张按文档类的表({@link ATOMIC_FILE_CLASSES}),不在任何调用点另写
|
|
10
|
+
* · `config-document` —— `config.d` 下的契约 JSON(域文档、`remote-exec.json`):只带 env-NAME、不带秘密值(README 契约),新建 = 0o644;
|
|
11
|
+
* **已有的文件沿用它原来的模式**(运维 chmod 过的文件不被一次写回覆盖)。
|
|
12
|
+
* · `scheduler-store` —— 调度店耐久记录(可能带 principal / session 标识):新建 = 0o600,且**每次写都回到 0o600**(已有的更宽的文件被收回)。
|
|
13
|
+
* 新建的模式照常经进程 umask 遮一道(`writeFile` 的 `mode` 语义):比表更严的 umask 只会更严,不会被这里放宽;沿用的模式原样恢复(不再过 umask)。
|
|
14
|
+
*
|
|
15
|
+
* ## 目标是符号链接
|
|
16
|
+
* 域文件可以是符号链接 —— 运维用链接把 `config.d` 指到别处管理的部署,那是有意的布局,写器不替他改布局。规则一条:**先把目标解析到真文件(`realpath`),
|
|
17
|
+
* 临时文件建在真文件所在目录、rename 到真文件**:链接(及链上每一环)原样保留,真文件的内容更新,模式规则作用在真文件上。临时文件与目标同目录 ⇒ rename 不会跨文件系统;
|
|
18
|
+
* 真文件所在目录写不进、或 rename 因别的原因失败 ⇒ 响亮抛,**绝不**回退成「在链接位置写一个普通文件」(那是静默换形)。路径本身是悬空链接 ⇒ 响亮抛
|
|
19
|
+
* (不在链接位置建普通文件,也不替链接建它指向的文件);成环的链接由 `realpath` 抛 `ELOOP`。目标不存在 ⇒ 新建(没有链接可言;经符号链接的目录写新文件时,文件落在真目录里)。
|
|
20
|
+
*/
|
|
21
|
+
import { randomBytes } from "node:crypto";
|
|
22
|
+
import { chmodSync, lstatSync, readlinkSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
/** 按文档类的模式表(闭集)。 */
|
|
25
|
+
export const ATOMIC_FILE_CLASSES = {
|
|
26
|
+
"config-document": { newMode: 0o644, existing: "keep" },
|
|
27
|
+
"scheduler-store": { newMode: 0o600, existing: "reset" },
|
|
28
|
+
};
|
|
29
|
+
/** 写盘的真目标:`target` 是符号链接 ⇒ 解析到它指向的真文件(悬空 ⇒ 抛);不是链接或不存在 ⇒ 原样。 */
|
|
30
|
+
function resolveWriteTarget(target) {
|
|
31
|
+
let link;
|
|
32
|
+
try {
|
|
33
|
+
link = lstatSync(target).isSymbolicLink();
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
if (e.code === "ENOENT")
|
|
37
|
+
return target; // 不存在:新建
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
if (!link)
|
|
41
|
+
return target;
|
|
42
|
+
try {
|
|
43
|
+
return realpathSync(target);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
if (e.code === "ENOENT") {
|
|
47
|
+
throw new Error(`writeFileAtomic: ${target} is a dangling symbolic link (it points at ${readlinkSync(target)}) — refusing to replace the link or to create what it points at; repair or remove the link`, { cause: e });
|
|
48
|
+
}
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** 原子写 `text`(UTF-8)到 `target`(符号链接则写到它指向的真文件,见文件头);模式按 `kind` 在 {@link ATOMIC_FILE_CLASSES} 里的行。父目录须已存在。 */
|
|
53
|
+
export function writeFileAtomic(target, text, kind) {
|
|
54
|
+
const real = resolveWriteTarget(target);
|
|
55
|
+
const spec = ATOMIC_FILE_CLASSES[kind];
|
|
56
|
+
let kept; // the existing file's mode, when this class keeps it
|
|
57
|
+
switch (spec.existing) {
|
|
58
|
+
case "keep":
|
|
59
|
+
try {
|
|
60
|
+
kept = statSync(real).mode & 0o777;
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
if (e.code !== "ENOENT")
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
case "reset":
|
|
68
|
+
break;
|
|
69
|
+
default: {
|
|
70
|
+
const unhandled = spec.existing;
|
|
71
|
+
throw new Error(`writeFileAtomic: unhandled existing-file rule ${String(unhandled)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const tmp = path.join(path.dirname(real), `.${path.basename(real)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
75
|
+
try {
|
|
76
|
+
writeFileSync(tmp, text, { encoding: "utf8", mode: kept ?? spec.newMode });
|
|
77
|
+
if (kept !== undefined)
|
|
78
|
+
chmodSync(tmp, kept); // a kept mode is restored exactly (the creation mode above went through the umask)
|
|
79
|
+
renameSync(tmp, real);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
try {
|
|
83
|
+
unlinkSync(tmp);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* 临时文件没建成:没什么可清 */
|
|
87
|
+
}
|
|
88
|
+
throw e;
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `settings-schema` 命令行的核心(11.0.0)—— 子命令只有一个:`migrate <config.d 目录> [--check]`,即一次性迁移
|
|
3
|
+
* `migrateStoredDocuments`(`./one-time-migrations`)的目录形(本地 config.d / 离线文件用;web-admin 在它起服时直接调同一只函数)。
|
|
4
|
+
* 目录形的语义(读目录 → 迁 → 整域终判 → 原子写 → 删 retired 域文件)只有一处实现:`migrateConfigDirectory`(`./config-dir-migration`,`./node` 导出,引擎起服调同一只);
|
|
5
|
+
* 本模块是它的**渲染器**:把它的结果闭集渲染成退出码、报告行与 stderr 句子。命令行的覆盖面是全部配置域加 retired 域。
|
|
6
|
+
* 目录被当作**一个 scope 的全集**(`coverage: "scope"`):目录里没有的域文件就是从未设过。
|
|
7
|
+
*
|
|
8
|
+
* 本模块不碰 `node:fs`:读写文件由入口 `bin/settings-schema.mjs` 注入(真绑定是 `./config-dir-io` 的 `nodeConfigDirectoryIo`,写盘用的是 `./atomic-write` 那一只原子写器 —— 写法与文件模式在那里一处定),所以测试可以直接驱动它。
|
|
9
|
+
* 不进主桶 —— 它是 bin 的实现,不是契约面。
|
|
10
|
+
*
|
|
11
|
+
* 读哪些文件:目录里 `<名>.json`,名是配置域或 retired 域(`runtime`)—— 便携与否不论(离线的 `workers.json` 一样迁);别的文件
|
|
12
|
+
* (`remote-exec.json`、隐藏文件、不认识的名)不读不动。
|
|
13
|
+
*
|
|
14
|
+
* 零域文档分两形:**有条目却没有域文档**(任何非 `.` 开头的条目 —— 子目录、说明文件、不认识的名)⇒ 退出 2;**目录真空**(零条目,或只有以 `.` 开头的条目)⇒ 照常走判定,
|
|
15
|
+
* 零收据 = noop 退出 0 —— 新部署的空 `config.d` 是合法态,「没有东西可迁」不是「指错目录」(搬空之后再跑同一目录也因此幂等)。
|
|
16
|
+
*
|
|
17
|
+
* 退出码:0 = 已写回 / 无事可做(目录里有域文档只是没有要改的,或目录真空);1 = 有迁不了的坐标(收据 `refused`)**或**要写回的域文档整域读不进(整域终判,见 `migrateConfigDirectory`),**一字不写**;
|
|
18
|
+
* 2 = 用法 / 读目录 / 读文件错,或某个域文件是坏文件(空 / 纯空白 / 坏 JSON / 不是 JSON 对象 —— 与 `FileConfigStore` 同一只判、同一句),**或目录里有条目却没有一个是域文档**
|
|
19
|
+
* (多半是填错了目录:要的是 `config.d` 目录本身;`<目录>/config.d` 在场时句子直接点它)(此时 stdout 为空);3 = `--check` 且会有改动、整域判得过(不写)。`--check` 与真跑过同一只判定,只在
|
|
20
|
+
* 「有改动、且整域判得过」那一支上分开(pending vs applied)—— 整域会被拒的目录 `--check` 也报 refused / rc 1。
|
|
21
|
+
*
|
|
22
|
+
* ## 报告形(让「没写」在机读上不可能被读成「写了」)
|
|
23
|
+
* 退出码 0 / 1 / 3 时 stdout 恰两行:第 1 行 = **唯一结论行** `outcome=<refused|applied|noop|pending> written=<true|false>`;第 2 行 = JSON 报告
|
|
24
|
+
* (`event: "stored_documents_migrated"`,逐条收据只点名坐标与处置、不带值),同带 `outcome` / `written`。`written` 由 `outcome` 派生
|
|
25
|
+
* (只有 `applied` 写盘)。**键名带时态**:`written=true` 才有 `applied`;没写(refused / noop / pending)时是 `wouldApply`。
|
|
26
|
+
* 结论行在**写盘成功之后**才出(写盘抛错 ⇒ 没有 `outcome=applied`)。写盘次序:先写改过 / 新建的文件,最后删被移除的 retired 域文件 ——
|
|
27
|
+
* 中途断掉再跑一次,迁移照样恒等收尾(已搬过去的值在新位上,旧文件里同名键按「新位赢」丢弃)。
|
|
28
|
+
*/
|
|
29
|
+
import { type ConfigDirectoryIo } from "./config-dir-migration.js";
|
|
30
|
+
/** 入口注入的 I/O(核心不碰 node:fs):目录迁移的四个口加 stdout / stderr。入口注入的写盘是 `./atomic-write` 的那一只(经 `./config-dir-io`)。 */
|
|
31
|
+
export interface SettingsSchemaCliIo extends ConfigDirectoryIo {
|
|
7
32
|
stdout(text: string): void;
|
|
8
33
|
stderr(text: string): void;
|
|
9
34
|
}
|