@sema-agent/settings-schema 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -1
- package/README.md +47 -5
- package/conformance/VERSION +1 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +5 -0
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +5 -0
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +41 -0
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +5 -0
- package/conformance/cases/c05-gate-domain-bad-entry-throws/expected.json +5 -0
- package/conformance/cases/c06-legacy-governance-lift/expected.json +12 -0
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +5 -0
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +77 -0
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +78 -0
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +5 -0
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +14 -0
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +5 -0
- package/conformance/cases/c13-models-field-null/expected.json +5 -0
- package/conformance/cases/c14-models-field-empty-array/expected.json +16 -0
- package/conformance/cases/c15-models-field-number/expected.json +5 -0
- package/conformance/cases/c16-deep-nesting-205/expected.json +14 -0
- package/conformance/cases/c17-empty-domain-file/expected.json +10 -0
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +10 -0
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +10 -0
- package/conformance/cases/c20-governance-absent-lifts/expected.json +12 -0
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +13 -0
- package/conformance/cases/c22-governance-null-throws/expected.json +5 -0
- package/conformance/cases/c23-mixed-validity-models/expected.json +5 -0
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +29 -0
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +33 -0
- package/conformance/cases/c26-hosts-array-1024/expected.json +7183 -0
- package/conformance/cases/c27-hosts-array-1025/expected.json +5 -0
- package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -0
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +5 -0
- package/conformance/cases/c30-version-triple-a-first/expected.json +29 -0
- package/conformance/cases/c31-version-triple-b-changed/expected.json +29 -0
- package/conformance/cases/c32-version-triple-a-restored/expected.json +29 -0
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +17 -0
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +23 -0
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +5 -0
- package/conformance/cases/c36-skills-entry-without-content/expected.json +23 -0
- package/conformance/cases/c37-skills-field-null/expected.json +5 -0
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +5 -0
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +5 -0
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +5 -0
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +5 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +33 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +5 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +5 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +5 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/expected.json +5 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/expected.json +5 -0
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +59 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +5 -0
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +44 -0
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +46 -0
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +48 -0
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +48 -0
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +44 -0
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +45 -0
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +51 -0
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +5 -0
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +5 -0
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +5 -0
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +5 -0
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +5 -0
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +5 -0
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +5 -0
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +5 -0
- package/conformance/cases/c65-s784-pg-without-db/expected.json +5 -0
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +5 -0
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +5 -0
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/conformance/cases/c68-s784-device-local-store/expected.json +5 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +54 -0
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +27 -0
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +33 -0
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +5 -0
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +5 -0
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +22 -0
- package/conformance/cases/cli04-env-value-with-newline/expected.json +46 -0
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +53 -0
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +5 -0
- package/conformance/cases/t02-entry-salvage-vs-whole-domain-refusal/expected.json +5 -0
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -0
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +5 -0
- package/conformance/cases/z01-every-domain-empty-object/expected.json +64 -0
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +66 -0
- package/dist/cli.js +97 -25
- package/dist/config-fns.js +7 -1
- package/dist/conformance/contract-adapter.d.ts +4 -2
- package/dist/conformance/contract-adapter.js +21 -7
- package/dist/conformance/corpus.js +12 -7
- package/dist/conformance/derive.d.ts +26 -0
- package/dist/conformance/derive.js +25 -2
- package/dist/conformance/index.d.ts +4 -3
- package/dist/conformance/index.js +4 -3
- package/dist/conformance/run.d.ts +3 -3
- package/dist/conformance/run.js +87 -17
- package/dist/conformance/types.d.ts +57 -17
- package/dist/conformance/types.js +4 -2
- package/dist/execution-lane-index.d.ts +20 -1
- package/dist/execution-lane-index.js +24 -1
- package/dist/hooks.d.ts +4 -4
- package/dist/migrate-workers.d.ts +1 -1
- package/dist/migrate-workers.js +3 -7
- package/dist/types.js +13 -4
- package/package.json +2 -1
|
@@ -99,6 +99,59 @@
|
|
|
99
99
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
|
+
"strictWrite": {
|
|
103
|
+
"outcome": "effective",
|
|
104
|
+
"effective": {
|
|
105
|
+
"models": {
|
|
106
|
+
"models": [
|
|
107
|
+
{
|
|
108
|
+
"api": "openai-completions",
|
|
109
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
110
|
+
"enabled": true,
|
|
111
|
+
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-id",
|
|
112
|
+
"name": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
113
|
+
"provider": "openai",
|
|
114
|
+
"quotaWeight": 1,
|
|
115
|
+
"reasoning": false,
|
|
116
|
+
"tier": "standard",
|
|
117
|
+
"vision": false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"api": "openai-completions",
|
|
121
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
122
|
+
"enabled": true,
|
|
123
|
+
"id": "prefix-shared-name-aaaa-id",
|
|
124
|
+
"name": "prefix-shared-name-aaaa",
|
|
125
|
+
"provider": "openai",
|
|
126
|
+
"quotaWeight": 1,
|
|
127
|
+
"reasoning": false,
|
|
128
|
+
"tier": "standard",
|
|
129
|
+
"vision": false
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"api": "openai-completions",
|
|
133
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
134
|
+
"enabled": true,
|
|
135
|
+
"id": "prefix-shared-name-aaab-id",
|
|
136
|
+
"name": "prefix-shared-name-aaab",
|
|
137
|
+
"provider": "openai",
|
|
138
|
+
"quotaWeight": 1,
|
|
139
|
+
"reasoning": false,
|
|
140
|
+
"tier": "standard",
|
|
141
|
+
"vision": false
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"roles": {},
|
|
145
|
+
"tierGroups": []
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"warnings": [],
|
|
149
|
+
"metadata": {
|
|
150
|
+
"version": 7,
|
|
151
|
+
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
152
|
+
},
|
|
153
|
+
"why": "受理:300 字节的模型名与一组同前缀名,读腿不截断不去重;严写同样逐字收下,受理形等于读腿 models 那一格。"
|
|
154
|
+
},
|
|
102
155
|
"wire": {
|
|
103
156
|
"derive": true
|
|
104
157
|
},
|
|
@@ -83,6 +83,11 @@
|
|
|
83
83
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
|
+
"strictWrite": {
|
|
87
|
+
"outcome": "rejected",
|
|
88
|
+
"stage": "validate",
|
|
89
|
+
"why": "拒:文档集里有不是域名的键 `web-admin-ui` —— 写入席没有这个域可受理(models / hosts 两份本身是好的,但写是一个动作,一份不受理就整次不受理)。"
|
|
90
|
+
},
|
|
86
91
|
"wire": {
|
|
87
92
|
"derive": true
|
|
88
93
|
},
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
"outcome": "rejected",
|
|
4
4
|
"stage": "validate"
|
|
5
5
|
},
|
|
6
|
+
"strictWrite": {
|
|
7
|
+
"outcome": "rejected",
|
|
8
|
+
"stage": "validate",
|
|
9
|
+
"why": "拒:同一份坏条目在 models(读腿逐条丢)与 rosters(gate 域,读腿整域拒)两处;读腿已整域拒,严写同样不受理 —— 两处任一都够拒。"
|
|
10
|
+
},
|
|
6
11
|
"wire": {
|
|
7
12
|
"outcome": "rejected",
|
|
8
13
|
"stage": "validate",
|
|
@@ -97,6 +97,35 @@
|
|
|
97
97
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
|
+
"strictWrite": {
|
|
101
|
+
"outcome": "effective",
|
|
102
|
+
"effective": {
|
|
103
|
+
"models": {
|
|
104
|
+
"models": [
|
|
105
|
+
{
|
|
106
|
+
"api": "openai-completions",
|
|
107
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
108
|
+
"enabled": true,
|
|
109
|
+
"id": "m0-id",
|
|
110
|
+
"name": "m0",
|
|
111
|
+
"provider": "openai",
|
|
112
|
+
"quotaWeight": 1,
|
|
113
|
+
"reasoning": false,
|
|
114
|
+
"tier": "standard",
|
|
115
|
+
"vision": false
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"roles": {},
|
|
119
|
+
"tierGroups": []
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"warnings": [],
|
|
123
|
+
"metadata": {
|
|
124
|
+
"version": 7,
|
|
125
|
+
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
126
|
+
},
|
|
127
|
+
"why": "受理:`__proto__` / `constructor` / `toString` 作条目内的未知键被 schema 剥掉(读腿报 unknown-keys-dropped);严写同样剥,受理形等于读腿 models 那一格,原型不被污染。"
|
|
128
|
+
},
|
|
100
129
|
"wire": {
|
|
101
130
|
"derive": true
|
|
102
131
|
},
|
|
@@ -99,6 +99,11 @@
|
|
|
99
99
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
|
+
"strictWrite": {
|
|
103
|
+
"outcome": "rejected",
|
|
104
|
+
"stage": "validate",
|
|
105
|
+
"why": "拒:models 第 2 条的 api 是一把密钥字面(不在闭集里),读腿逐条丢(entry-dropped)并剥未知键;严写 ⇒ 整次不受理。"
|
|
106
|
+
},
|
|
102
107
|
"wire": {
|
|
103
108
|
"derive": true
|
|
104
109
|
},
|
|
@@ -62,6 +62,70 @@
|
|
|
62
62
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
+
"strictWrite": {
|
|
66
|
+
"outcome": "effective",
|
|
67
|
+
"effective": {
|
|
68
|
+
"collab": {
|
|
69
|
+
"templates": []
|
|
70
|
+
},
|
|
71
|
+
"entitlement": {
|
|
72
|
+
"budgetGroups": [],
|
|
73
|
+
"groupBindings": [],
|
|
74
|
+
"subjects": [],
|
|
75
|
+
"tiers": []
|
|
76
|
+
},
|
|
77
|
+
"execution": {
|
|
78
|
+
"allowedLanes": [
|
|
79
|
+
"host"
|
|
80
|
+
],
|
|
81
|
+
"required": false
|
|
82
|
+
},
|
|
83
|
+
"governance": {},
|
|
84
|
+
"hosts": {
|
|
85
|
+
"hosts": []
|
|
86
|
+
},
|
|
87
|
+
"limits": {},
|
|
88
|
+
"mcp": {
|
|
89
|
+
"servers": []
|
|
90
|
+
},
|
|
91
|
+
"models": {
|
|
92
|
+
"models": [],
|
|
93
|
+
"roles": {},
|
|
94
|
+
"tierGroups": []
|
|
95
|
+
},
|
|
96
|
+
"plugins": {
|
|
97
|
+
"plugins": []
|
|
98
|
+
},
|
|
99
|
+
"projects": {
|
|
100
|
+
"projects": {}
|
|
101
|
+
},
|
|
102
|
+
"prompts": {
|
|
103
|
+
"bindings": []
|
|
104
|
+
},
|
|
105
|
+
"rosters": {
|
|
106
|
+
"rosters": []
|
|
107
|
+
},
|
|
108
|
+
"runtime": {},
|
|
109
|
+
"scenarios": {
|
|
110
|
+
"scenarios": []
|
|
111
|
+
},
|
|
112
|
+
"skills": {
|
|
113
|
+
"skills": []
|
|
114
|
+
},
|
|
115
|
+
"systems": {
|
|
116
|
+
"systems": []
|
|
117
|
+
},
|
|
118
|
+
"workers": {
|
|
119
|
+
"workers": []
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"warnings": [],
|
|
123
|
+
"metadata": {
|
|
124
|
+
"version": 7,
|
|
125
|
+
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
126
|
+
},
|
|
127
|
+
"why": "受理:十七个域各一份空对象,读腿零告警;严写逐份受理,受理形 = 十七个域的 schema 默认(只含提交的键 —— 这里恰好十七个都在)。"
|
|
128
|
+
},
|
|
65
129
|
"wire": {
|
|
66
130
|
"derive": true
|
|
67
131
|
},
|
|
@@ -218,6 +218,72 @@
|
|
|
218
218
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
219
219
|
}
|
|
220
220
|
},
|
|
221
|
+
"strictWrite": {
|
|
222
|
+
"outcome": "effective",
|
|
223
|
+
"effective": {
|
|
224
|
+
"collab": {
|
|
225
|
+
"templates": []
|
|
226
|
+
},
|
|
227
|
+
"entitlement": {
|
|
228
|
+
"budgetGroups": [],
|
|
229
|
+
"groupBindings": [],
|
|
230
|
+
"subjects": [],
|
|
231
|
+
"tiers": []
|
|
232
|
+
},
|
|
233
|
+
"execution": {
|
|
234
|
+
"allowedLanes": [
|
|
235
|
+
"host"
|
|
236
|
+
],
|
|
237
|
+
"required": false
|
|
238
|
+
},
|
|
239
|
+
"governance": {},
|
|
240
|
+
"hosts": {
|
|
241
|
+
"hosts": []
|
|
242
|
+
},
|
|
243
|
+
"limits": {
|
|
244
|
+
"zzzUnknown": 1
|
|
245
|
+
},
|
|
246
|
+
"mcp": {
|
|
247
|
+
"servers": []
|
|
248
|
+
},
|
|
249
|
+
"models": {
|
|
250
|
+
"models": [],
|
|
251
|
+
"roles": {},
|
|
252
|
+
"tierGroups": []
|
|
253
|
+
},
|
|
254
|
+
"plugins": {
|
|
255
|
+
"plugins": []
|
|
256
|
+
},
|
|
257
|
+
"projects": {
|
|
258
|
+
"projects": {}
|
|
259
|
+
},
|
|
260
|
+
"prompts": {
|
|
261
|
+
"bindings": []
|
|
262
|
+
},
|
|
263
|
+
"rosters": {
|
|
264
|
+
"rosters": []
|
|
265
|
+
},
|
|
266
|
+
"runtime": {},
|
|
267
|
+
"scenarios": {
|
|
268
|
+
"scenarios": []
|
|
269
|
+
},
|
|
270
|
+
"skills": {
|
|
271
|
+
"skills": []
|
|
272
|
+
},
|
|
273
|
+
"systems": {
|
|
274
|
+
"systems": []
|
|
275
|
+
},
|
|
276
|
+
"workers": {
|
|
277
|
+
"workers": []
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
"warnings": [],
|
|
281
|
+
"metadata": {
|
|
282
|
+
"version": 7,
|
|
283
|
+
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
284
|
+
},
|
|
285
|
+
"why": "受理:十七个域各带一个未知键 —— 十六个被 schema 剥掉(读腿报 unknown-keys-dropped)、limits 承运(unknown-keys-carried);严写同样剥 / 承运、不铸告警,受理形等于读腿十七格。"
|
|
286
|
+
},
|
|
221
287
|
"wire": {
|
|
222
288
|
"derive": true
|
|
223
289
|
},
|
package/dist/cli.js
CHANGED
|
@@ -6,17 +6,91 @@
|
|
|
6
6
|
* 不进主桶 —— 它是 bin 的实现,不是契约面。
|
|
7
7
|
*
|
|
8
8
|
* 退出码:0 = 已写回 / 无事可做;1 = 有 worker 迁不了(refused)或迁出的文档整域读不进 7.0.0,**一字不写**;2 = 用法 / 读文件 /
|
|
9
|
-
* JSON
|
|
9
|
+
* JSON 错(此时 stdout 为空);3 = `--check` 且会有改动(不写)。**8.0.0**:`--check` 与真跑过同一只判定({@link verdictOf}),
|
|
10
|
+
* 只在「有改动、且整域判得过」那一支上分开(pending vs applied)—— 整域会被拒的文档 `--check` 也报 refused / rc 1。
|
|
11
|
+
*
|
|
12
|
+
* ## 报告形(7.0.1:让「没写」在机读上不可能被读成「写了」)
|
|
13
|
+
* 退出码 0 / 1 / 3 时 stdout 恰两行:
|
|
14
|
+
* · 第 1 行 = **唯一结论行** `outcome=<refused|applied|noop|pending> written=<true|false>`;
|
|
15
|
+
* · 第 2 行 = JSON 报告(`event: "workers_domain_migrated"`,只点名字段、不带值),同带 `outcome` / `written`。
|
|
16
|
+
* `written` 不是独立字段:由 `outcome` 派生({@link writtenOf};只有 `applied` 写盘)。**键名带时态**:`written=true` 才有 `changed` /
|
|
17
|
+
* `dropped`;`written=false`(refused / noop / pending)时是 `wouldChange` / `wouldDrop` —— 读旧字段 `changed` 的脚本在没写的时候
|
|
18
|
+
* 读不到值,而不是读到一份「已迁」名单。结论行在**写盘成功之后**才出(写盘抛错 ⇒ 没有 `outcome=applied`)。
|
|
10
19
|
*/
|
|
11
20
|
import { formatConfigPath, sanitizeZodError } from "./config-fns.js";
|
|
21
|
+
import { MIGRATE_WORKERS_COMMAND } from "./execution-lane-index.js";
|
|
12
22
|
import { migrateWorkersDomain } from "./migrate-workers.js";
|
|
13
23
|
import { DOMAIN_SCHEMAS } from "./types.js";
|
|
14
24
|
const USAGE = [
|
|
15
|
-
|
|
25
|
+
`usage: ${MIGRATE_WORKERS_COMMAND} <workers.json> [--check]`,
|
|
16
26
|
" One-time 7.0.0 migration of a workers domain document ({ \"workers\": [...] }): env.REMOTE_EXEC + lane keys →",
|
|
17
27
|
" runtime.execution, sessionBackend / coords.tidb* / runtime.db / tidbPasswordEnv → runtime.store. Writes the file in",
|
|
18
28
|
" place only when every worker migrates; --check only reports (exit 3 if the file would change).",
|
|
29
|
+
" stdout (exit 0 / 1 / 3): line 1 = outcome=<refused|applied|noop|pending> written=<true|false>, line 2 = the JSON report",
|
|
30
|
+
" (changed / dropped only when written=true; otherwise wouldChange / wouldDrop).",
|
|
19
31
|
].join("\n");
|
|
32
|
+
/** 这个结论写没写盘 —— 只有 `applied` 写。`written` 从这里派生,不是报告里一个可以和 `outcome` 各说各话的独立字段。 */
|
|
33
|
+
function writtenOf(outcome) {
|
|
34
|
+
switch (outcome) {
|
|
35
|
+
case "applied":
|
|
36
|
+
return true;
|
|
37
|
+
case "refused":
|
|
38
|
+
case "noop":
|
|
39
|
+
case "pending":
|
|
40
|
+
return false;
|
|
41
|
+
default: {
|
|
42
|
+
const unhandled = outcome;
|
|
43
|
+
throw new Error(`settings-schema: unhandled outcome ${String(unhandled)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 迁移结果 → 结论。顺序:逐只 refused → 无改动 ⇒ noop → **整域终判**(迁出来的文档读不读得进 7.0.0:名字 / 路由唯一 …)→ 有改动。
|
|
49
|
+
* 7.0.1 把整域终判放在 `--check` 之后:同一份文档 `--check` 报 pending(rc 3)、真跑报 refused(rc 1),按 `--check` 做闸的脚本会放行
|
|
50
|
+
* 一次必被拒的真跑;而整域终判拒时 `refused[]` 为空、拒因只在 stderr。现在两种跑法过同一只判定,`refused[]` 逐条带出被拒项。
|
|
51
|
+
*/
|
|
52
|
+
function verdictOf(m) {
|
|
53
|
+
if (m.refused.length > 0) {
|
|
54
|
+
return {
|
|
55
|
+
kind: "refused",
|
|
56
|
+
refused: m.refused.map((r) => ({ name: r.name, field: r.field, reason: r.reason })),
|
|
57
|
+
why: `${m.refused.length} worker(s) cannot be migrated (see "refused") — fix them by hand and re-run`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (m.changed.length === 0)
|
|
61
|
+
return { kind: "noop" };
|
|
62
|
+
const whole = DOMAIN_SCHEMAS.workers.safeParse(m.doc);
|
|
63
|
+
if (whole.success)
|
|
64
|
+
return { kind: "changes" };
|
|
65
|
+
const issues = sanitizeZodError(whole.error).issues;
|
|
66
|
+
const workers = isRecord(m.doc) && Array.isArray(m.doc.workers) ? m.doc.workers : [];
|
|
67
|
+
return {
|
|
68
|
+
kind: "refused",
|
|
69
|
+
refused: issues.map((i) => {
|
|
70
|
+
const [head, index, ...rest] = i.path;
|
|
71
|
+
if (head !== "workers" || typeof index !== "number")
|
|
72
|
+
return { name: null, field: i.path, reason: i.message };
|
|
73
|
+
const entry = workers[index];
|
|
74
|
+
return { name: isRecord(entry) && typeof entry.name === "string" ? entry.name : null, field: rest, reason: i.message };
|
|
75
|
+
}),
|
|
76
|
+
why: `the migrated document does not read as a 7.0.0 workers domain — ${issues.map((i) => `${formatConfigPath(i.path)}: ${i.message}`).join("; ")}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
80
|
+
/** 一次发出报告:结论行 + JSON 行(一次 `stdout` 调用)。键名带时态:写了 ⇒ `changed` / `dropped`,没写 ⇒ `wouldChange` / `wouldDrop`。 */
|
|
81
|
+
function reportOf(outcome, file, m, refused) {
|
|
82
|
+
const written = writtenOf(outcome);
|
|
83
|
+
const dropped = m.dropped.map((d) => ({ name: d.name, field: formatConfigPath(d.field) }));
|
|
84
|
+
const json = JSON.stringify({
|
|
85
|
+
event: "workers_domain_migrated",
|
|
86
|
+
outcome,
|
|
87
|
+
written,
|
|
88
|
+
file,
|
|
89
|
+
...(written ? { changed: m.changed, dropped } : { wouldChange: m.changed, wouldDrop: dropped }),
|
|
90
|
+
refused: refused.map((r) => ({ name: r.name, field: formatConfigPath(r.field), reason: r.reason })),
|
|
91
|
+
});
|
|
92
|
+
return `outcome=${outcome} written=${written}\n${json}\n`;
|
|
93
|
+
}
|
|
20
94
|
export async function runSettingsSchemaCli(argv, io) {
|
|
21
95
|
const [command, ...rest] = argv;
|
|
22
96
|
const check = rest.includes("--check");
|
|
@@ -43,28 +117,26 @@ export async function runSettingsSchemaCli(argv, io) {
|
|
|
43
117
|
return 2;
|
|
44
118
|
}
|
|
45
119
|
const m = migrateWorkersDomain(doc);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
120
|
+
const verdict = verdictOf(m);
|
|
121
|
+
switch (verdict.kind) {
|
|
122
|
+
case "refused":
|
|
123
|
+
io.stdout(reportOf("refused", file, m, verdict.refused));
|
|
124
|
+
io.stderr(`settings-schema: ${verdict.why}; nothing was written\n`);
|
|
125
|
+
return 1;
|
|
126
|
+
case "noop":
|
|
127
|
+
io.stdout(reportOf("noop", file, m, []));
|
|
128
|
+
return 0;
|
|
129
|
+
case "changes":
|
|
130
|
+
if (check) {
|
|
131
|
+
io.stdout(reportOf("pending", file, m, []));
|
|
132
|
+
return 3;
|
|
133
|
+
}
|
|
134
|
+
await io.writeFileAtomic(file, `${JSON.stringify(m.doc, null, 2)}\n`);
|
|
135
|
+
io.stdout(reportOf("applied", file, m, [])); // 写盘成功之后才报 applied
|
|
136
|
+
return 0;
|
|
137
|
+
default: {
|
|
138
|
+
const unhandled = verdict;
|
|
139
|
+
throw new Error(`settings-schema: unhandled verdict ${JSON.stringify(unhandled)}`);
|
|
140
|
+
}
|
|
67
141
|
}
|
|
68
|
-
await io.writeFileAtomic(file, `${JSON.stringify(m.doc, null, 2)}\n`);
|
|
69
|
-
return 0;
|
|
70
142
|
}
|
package/dist/config-fns.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import { MODEL_ENTRY_REFUSAL_CODES, ModelRefusalsWire } from "./fleet.js";
|
|
20
20
|
import { carriesUserinfo, secretScanForms } from "./secret-scan.js"; // package-private display-side normalization + the userinfo judgement (not in the barrel)
|
|
21
|
-
import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, InfraCostRates, LimitsConfig, PRIVATE_KEY_PEM_HEADER, SkillsManifestSchema, containsSecretToken } from "./types.js";
|
|
21
|
+
import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, InfraCostRates, LimitsConfig, PRIVATE_KEY_PEM_HEADER, SkillsManifestSchema, containsSecretToken, isDomainName } from "./types.js";
|
|
22
22
|
/** Redact secret-shaped tokens from a validation MESSAGE — a zod error echoes the offending value (e.g.
|
|
23
23
|
* `received 'sk-live-…'`), so a secret pasted into the WRONG field would leak via the error string. Redacts
|
|
24
24
|
* space-spanning auth credentials (`Bearer <token>`) FIRST (the token pass can't see across the space), then
|
|
@@ -134,6 +134,12 @@ export function emptyEffective() {
|
|
|
134
134
|
}
|
|
135
135
|
/** Validate + fill defaults for a domain payload (or the schema's empty default when absent/invalid). */
|
|
136
136
|
export function parseDomain(domain, raw) {
|
|
137
|
+
// A name that is not a domain is refused for what it is — before, `DOMAIN_SCHEMAS[name]` was undefined and the
|
|
138
|
+
// TypeError surfaced through the guard below as "a value could not be read", which names the wrong failure. The
|
|
139
|
+
// strict-write face hands this function every key of a submitted document set, so the name is user text: it leaves
|
|
140
|
+
// through the same outgoing redaction as a warning coordinate segment.
|
|
141
|
+
if (!isDomainName(domain))
|
|
142
|
+
throw new Error(`"${outgoingSegment(String(domain))}" is not a config domain`);
|
|
137
143
|
// ONLY true absence (undefined) defaults; an explicit `null` (e.g. a `config.d/<d>.json` containing JSON
|
|
138
144
|
// `null`) is corrupt input → let zod reject it ("expected object, received null"), not silently default. R4.
|
|
139
145
|
let r;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ConformanceAdapter, ConformanceProvenance, LegEntry, LegName } from "./types.js";
|
|
2
|
-
/** 一份契约模块(新旧版同形 ——
|
|
2
|
+
/** 一份契约模块(新旧版同形 —— 四条腿的签名自 0.19.0 起没动过)。 */
|
|
3
3
|
export interface ContractModule {
|
|
4
4
|
buildEffective(version: number, getRaw: (d: string) => unknown, updatedAt: string, onWarning?: (w: unknown) => void): unknown;
|
|
5
|
+
/** 严写受理的生产入口:一个域名 + 一份原始文档 → 受理后的域文档,不受理就抛。 */
|
|
6
|
+
parseDomain(domain: string, raw: unknown): unknown;
|
|
5
7
|
readEffectiveWire(raw: unknown, onWarning?: (w: unknown) => void): unknown;
|
|
6
8
|
loadLocalConfig(root: string, opts?: {
|
|
7
9
|
onWarning?: (w: unknown) => void;
|
|
@@ -14,7 +16,7 @@ export interface ContractAdapterOptions {
|
|
|
14
16
|
name: string;
|
|
15
17
|
provenance: ConformanceProvenance;
|
|
16
18
|
mod: ContractModule;
|
|
17
|
-
/**
|
|
19
|
+
/** 四条腿各自的生产入口坐标(绝对路径)。 */
|
|
18
20
|
entries: Record<LegName, LegEntry>;
|
|
19
21
|
}
|
|
20
22
|
export declare function createContractAdapter(opts: ContractAdapterOptions): ConformanceAdapter;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 契约仓自己的四腿适配器 —— 也是 `historical` 模式拿旧版包跑时用的同一只工厂。
|
|
3
3
|
*
|
|
4
4
|
* **它不是给消费方抄的**:消费方登记的腿必须是**自己生产代码**的入口(来源律,§2d-2);把契约包的函数
|
|
5
|
-
*
|
|
6
|
-
* `buildEffective` / `readEffectiveWire` / `loadLocalConfig` **就是**它的生产入口);②historical 模式要对
|
|
7
|
-
* 一份旧版 dist
|
|
5
|
+
* 注册成腿,门证明的就只是「包等于包」。本工厂存在的理由只有两个:①契约仓自证四腿(对契约仓而言
|
|
6
|
+
* `buildEffective` / `parseDomain` / `readEffectiveWire` / `loadLocalConfig` **就是**它的生产入口);②historical 模式要对
|
|
7
|
+
* 一份旧版 dist 跑同样的四腿。
|
|
8
|
+
*
|
|
9
|
+
* `strictWrite`(严写受理)= 文档集里**每一个**键各自过 `parseDomain`(写入席收下的那份);任何一份不受理 ⇒ 整次
|
|
10
|
+
* `rejected{stage:"validate"}`(写是一个动作,不是逐份抢救);全部受理 ⇒ `effective` = 键 → 受理后的域文档(只含入参里的
|
|
11
|
+
* 键,不补缺席域)。`parseDomain` 不铸告警,所以这条腿的告警面恒空。
|
|
8
12
|
*
|
|
9
13
|
* 4.0.0 起这句自陈**有牙**:入口落在契约包自己身上 ⇒ 造适配器那一刻就**拒**(抛,不是告警 —— 一条告警
|
|
10
14
|
* 在 CI 日志里活不过第二天,而这道门一旦被同义反复接进去,它报的全绿是**假绿**)。判别式单铸在
|
|
@@ -13,7 +17,7 @@
|
|
|
13
17
|
*
|
|
14
18
|
* 拒绝分层的判据(一条,不是一串特例):**ZodError 形的错 = `validate`**(某个域没通过它的 schema);
|
|
15
19
|
* 其余按腿各自的默认层 —— wire 腿是 `envelope`(它在解析任何域之前先判信封),local-root 腿是 `parse`
|
|
16
|
-
* (文件层先判:空文件 / 非 JSON / 非对象),domains
|
|
20
|
+
* (文件层先判:空文件 / 非 JSON / 非对象),domains 与 strictWrite 两腿本来就只有校验这一层。
|
|
17
21
|
*/
|
|
18
22
|
import { materializeLocalRoot } from "./run.js";
|
|
19
23
|
import { contractOwnedEntry, contractOwnedEntryWhy } from "./entry-origin.js";
|
|
@@ -29,7 +33,7 @@ const reject = (e, fallback) => {
|
|
|
29
33
|
return { outcome: "rejected", stage, diagnostics };
|
|
30
34
|
};
|
|
31
35
|
/**
|
|
32
|
-
* 入口的**运行期拒**(fail-closed)
|
|
36
|
+
* 入口的**运行期拒**(fail-closed):四条腿的入口一条都不许落在**这份正在跑的**契约包自己的构建产物里,
|
|
33
37
|
* 缺席也不行。判别式与拒绝句来自 `./entry-origin.js`,这里只负责「拒」这个动作 —— 以及**按形分派**:
|
|
34
38
|
* 工厂看不见 `mode`,所以它只拒在任何模式下都错的那一形(`own-build-output` = 拿自己比自己);
|
|
35
39
|
* `another-installed-copy` 在 `historical` 模式下是正当的(那个模式就是拿旧版包比行为,而旧版包常常正好
|
|
@@ -40,7 +44,7 @@ function refuseContractOwnedEntries(entries) {
|
|
|
40
44
|
for (const leg of LEG_NAMES) {
|
|
41
45
|
const entry = entries[leg];
|
|
42
46
|
if (entry === undefined || typeof entry.module !== "string" || entry.module.length === 0) {
|
|
43
|
-
throw new Error(`conformance: leg \`${leg}\` 缺 entry.module ——
|
|
47
|
+
throw new Error(`conformance: leg \`${leg}\` 缺 entry.module —— 每条腿各自的**生产入口**坐标是必填的(来源律 §2d-2)`);
|
|
44
48
|
}
|
|
45
49
|
const owned = contractOwnedEntry(entry.module);
|
|
46
50
|
if (owned === undefined)
|
|
@@ -72,6 +76,15 @@ export function createContractAdapter(opts) {
|
|
|
72
76
|
catch (e) {
|
|
73
77
|
return reject(e, "validate");
|
|
74
78
|
}
|
|
79
|
+
case "strictWrite":
|
|
80
|
+
try {
|
|
81
|
+
// fromEntries(不是逐键赋值):文档集的键是用户写的,`__proto__` 作键时逐键赋值会走原型 setter。
|
|
82
|
+
const accepted = Object.fromEntries(Object.entries(input.docs).map(([d, raw]) => [d, mod.parseDomain(d, raw)]));
|
|
83
|
+
return { outcome: "effective", effective: accepted, warnings: [], version: input.version, updatedAt: input.updatedAt };
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
return reject(e, "validate");
|
|
87
|
+
}
|
|
75
88
|
case "wire":
|
|
76
89
|
try {
|
|
77
90
|
const effective = mod.readEffectiveWire(input.payload, onWarning);
|
|
@@ -104,6 +117,7 @@ export function createContractAdapter(opts) {
|
|
|
104
117
|
provenance: opts.provenance,
|
|
105
118
|
legs: {
|
|
106
119
|
domains: { entry: opts.entries.domains, run },
|
|
120
|
+
strictWrite: { entry: opts.entries.strictWrite, run },
|
|
107
121
|
wire: { entry: opts.entries.wire, run },
|
|
108
122
|
localRoot: { entry: opts.entries.localRoot, run },
|
|
109
123
|
},
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* · `input.json` —— **真源是 `domains`**(各域原始文档);`wire` / `local-root` 两腿的入参由
|
|
7
7
|
* `derive.ts` 的变换表派生。个别腿的入参派生不出来(坏 wire 信封、空文件)时,
|
|
8
8
|
* 用 `wirePayload` / `localFiles` 显式给。
|
|
9
|
-
* · `expected.json` ——
|
|
10
|
-
*
|
|
9
|
+
* · `expected.json` —— 每条腿一格(四条腿一条不少)。`domains` 与 `strictWrite` 两腿的期望恒是**显式 golden**
|
|
10
|
+
* (能不能派生只看 `derive.ts` 的 `LEG_EXPECTATION`);`wire` / `local-root` 默认写 `{"derive":true}`,
|
|
11
|
+
* 由变换表派生;派生解释不了的腿写显式期望并带 `why`(那是明示声明,不是暗门)。
|
|
11
12
|
* · `meta.json` —— `contributor`(**只写真交了的仓**)、`claim`(这条想打谁、为什么会读错)、
|
|
12
13
|
* `secrets`(语料里埋的秘密字面,告警面零命中门用)、`firstRedOn`(**由 historical
|
|
13
14
|
* 模式对旧版真跑填**,不许手写)。
|
|
@@ -17,7 +18,8 @@
|
|
|
17
18
|
import { readFile, readdir } from "node:fs/promises";
|
|
18
19
|
import { dirname, join } from "node:path";
|
|
19
20
|
import { fileURLToPath } from "node:url";
|
|
20
|
-
import { REJECT_STAGES } from "./types.js";
|
|
21
|
+
import { LEG_NAMES, REJECT_STAGES } from "./types.js";
|
|
22
|
+
import { LEG_EXPECTATION } from "./derive.js";
|
|
21
23
|
/** 本包的根目录 —— `src/conformance/*.ts` 与 `dist/conformance/*.js` 距根同深,两处求值同一个地方。 */
|
|
22
24
|
export const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
23
25
|
/** 语料目录(默认 = 本包的 `conformance/`)。 */
|
|
@@ -72,13 +74,16 @@ export async function loadCase(dir, id) {
|
|
|
72
74
|
if (!Array.isArray(meta.firstRedOn))
|
|
73
75
|
throw new Error(`conformance corpus: ${id}/meta.json#firstRedOn must be an array`);
|
|
74
76
|
const legs = {};
|
|
75
|
-
for (const leg of
|
|
77
|
+
for (const leg of LEG_NAMES) {
|
|
76
78
|
if (expected[leg] === undefined)
|
|
77
79
|
throw new Error(`conformance corpus: ${id}/expected.json has no \`${leg}\` — 每腿都要表态(派生 / 显式 / 不适用)`);
|
|
78
|
-
|
|
80
|
+
const spec = checkLegExpectation(expected[leg], `${id}/expected.json#${leg}`);
|
|
81
|
+
// 能不能派生只看一张表(derive.ts 的 LEG_EXPECTATION),不按腿名特判。
|
|
82
|
+
if ("derive" in spec && LEG_EXPECTATION[leg].source === "explicit") {
|
|
83
|
+
throw new Error(`conformance corpus: ${id}/expected.json#${leg} cannot be derived — ${LEG_EXPECTATION[leg].why}`);
|
|
84
|
+
}
|
|
85
|
+
legs[leg] = spec;
|
|
79
86
|
}
|
|
80
|
-
if ("derive" in legs.domains)
|
|
81
|
-
throw new Error(`conformance corpus: ${id}/expected.json#domains cannot be derived — domains 腿是真源`);
|
|
82
87
|
return { id, dir, input: input, meta: meta, expected: legs };
|
|
83
88
|
}
|
|
84
89
|
/** 整套语料。 */
|
|
@@ -9,6 +9,32 @@ export interface DerivationTransform {
|
|
|
9
9
|
}
|
|
10
10
|
/** 变换表(本文件头注的机器形;`docs/CONFORMANCE.md` 与 `test/conformance-coverage.test.ts` 都读它)。 */
|
|
11
11
|
export declare const DERIVATION_TRANSFORMS: readonly DerivationTransform[];
|
|
12
|
+
/**
|
|
13
|
+
* 每条腿的期望从哪来 —— 闭集表态(`Record<LegName, …>`:新开一条腿不在这里表态 ⇒ 编译红)。
|
|
14
|
+
*
|
|
15
|
+
* · `derivable`:语料可以写 `{"derive":true}`,由上面的变换表从 `domains` 期望派生(也可写显式期望并带 `why`)。
|
|
16
|
+
* · `explicit`:只收显式金样(`{"derive":true}` 加载即拒)。
|
|
17
|
+
*
|
|
18
|
+
* 语料加载器与驱动都读这张表(一条规则),不再各自特判某个腿名。
|
|
19
|
+
*/
|
|
20
|
+
export declare const LEG_EXPECTATION: {
|
|
21
|
+
readonly domains: {
|
|
22
|
+
readonly source: "explicit";
|
|
23
|
+
readonly why: "真源:domains 腿的期望就是金样本身";
|
|
24
|
+
};
|
|
25
|
+
readonly strictWrite: {
|
|
26
|
+
readonly source: "explicit";
|
|
27
|
+
readonly why: "入参与 domains 恒等,期望不派生:严写结局不是读结局的表示变换(读腿抢救处严写拒);从 domains 期望派生会让一份金样的错同时改写两条腿";
|
|
28
|
+
};
|
|
29
|
+
readonly wire: {
|
|
30
|
+
readonly source: "derivable";
|
|
31
|
+
readonly why: "T1 / T2 / T4";
|
|
32
|
+
};
|
|
33
|
+
readonly localRoot: {
|
|
34
|
+
readonly source: "derivable";
|
|
35
|
+
readonly why: "T3 / T4 / T5 / T6";
|
|
36
|
+
};
|
|
37
|
+
};
|
|
12
38
|
/** 非便携域 —— 由 {@link PORTABLE_DOMAINS} 派生,零手抄。 */
|
|
13
39
|
export declare const NON_PORTABLE_DOMAINS: readonly DomainName[];
|
|
14
40
|
/** 一份 `domains` 文档集里,**不是域名**的那些顶层键(T4 的触发面)。 */
|