@sema-agent/settings-schema 10.1.1 → 11.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 +414 -0
- package/README.md +141 -65
- package/bin/settings-schema.mjs +13 -6
- package/conformance/VERSION +1 -1
- package/conformance/baseline.json +3 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +3 -1
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +12 -5
- package/conformance/cases/c02-hosts-clamp-stacked/meta.json +1 -1
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +22 -2
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +3 -1
- package/conformance/cases/c06-legacy-governance-lift/expected.json +11 -101
- package/conformance/cases/c06-legacy-governance-lift/meta.json +1 -1
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +3 -1
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +203 -2
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +204 -2
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +3 -1
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +20 -2
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +3 -1
- package/conformance/cases/c13-models-field-null/expected.json +3 -1
- package/conformance/cases/c14-models-field-empty-array/expected.json +6 -1
- package/conformance/cases/c15-models-field-number/expected.json +3 -1
- package/conformance/cases/c16-deep-nesting-205/expected.json +14 -2
- package/conformance/cases/c17-empty-domain-file/expected.json +3 -1
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +3 -1
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +3 -1
- package/conformance/cases/c20-governance-absent-lifts/expected.json +11 -86
- package/conformance/cases/c20-governance-absent-lifts/meta.json +1 -1
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +6 -5
- package/conformance/cases/c21-governance-empty-object-wins/input.json +1 -4
- package/conformance/cases/c21-governance-empty-object-wins/meta.json +1 -1
- package/conformance/cases/c23-mixed-validity-models/expected.json +3 -1
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +22 -2
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +6 -1
- package/conformance/cases/c26-hosts-array-1024/expected.json +3 -1
- package/conformance/cases/c27-hosts-array-1025/expected.json +6 -7175
- package/conformance/cases/c27-hosts-array-1025/meta.json +1 -1
- package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -2
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +3 -1
- package/conformance/cases/c30-version-triple-a-first/expected.json +6 -1
- package/conformance/cases/c31-version-triple-b-changed/expected.json +6 -1
- package/conformance/cases/c32-version-triple-a-restored/expected.json +6 -1
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +18 -2
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +19 -3
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +3 -1
- package/conformance/cases/c36-skills-entry-without-content/expected.json +6 -2
- package/conformance/cases/c37-skills-field-null/expected.json +6 -2
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +3 -1
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +3 -1
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +11 -4
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +9 -3
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +39 -2
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +4 -1
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +3 -1
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +19 -10
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/meta.json +1 -1
- package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/expected.json +12 -5
- package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/meta.json +9 -0
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/expected.json +23 -3
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/input.json +6 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/meta.json +10 -0
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +6 -1
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +3 -1
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +3 -1
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +3 -1
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +3 -1
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +3 -1
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +3 -1
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +3 -1
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +3 -1
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +3 -1
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +3 -1
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +3 -1
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +3 -1
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +3 -1
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +3 -1
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +3 -1
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +3 -1
- package/conformance/cases/c65-s784-pg-without-db/expected.json +3 -1
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/conformance/cases/c68-s784-device-local-store/expected.json +3 -1
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +3 -1
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +3 -1
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +119 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/input.json +24 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/meta.json +10 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +111 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/input.json +26 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/meta.json +6 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +3 -1
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +3 -1
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +6 -1
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +11 -1
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +11 -1
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +16 -2
- package/conformance/cases/cli04-env-value-with-newline/expected.json +3 -1
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +6 -1
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +3 -1
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -2
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +3 -1
- package/conformance/cases/z01-every-domain-empty-object/expected.json +8 -4
- package/conformance/cases/z01-every-domain-empty-object/input.json +0 -1
- package/conformance/cases/z01-every-domain-empty-object/meta.json +1 -1
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +163 -23
- package/conformance/cases/z02-every-domain-unknown-key/input.json +0 -3
- package/conformance/cases/z02-every-domain-unknown-key/meta.json +1 -1
- package/dist/api/auth-bridge.d.ts +51 -181
- package/dist/api/auth-bridge.js +58 -33
- package/dist/api/auth.d.ts +37 -146
- package/dist/api/auth.js +16 -26
- package/dist/api/feedback.d.ts +87 -0
- package/dist/api/feedback.js +66 -0
- package/dist/api/pull-token.d.ts +32 -0
- package/dist/api/pull-token.js +88 -0
- package/dist/api/scopes.d.ts +82 -174
- package/dist/api/scopes.js +23 -18
- package/dist/bundle.d.ts +15 -7
- package/dist/bundle.js +28 -27
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +132 -81
- package/dist/config-d-file.d.ts +14 -0
- package/dist/config-d-file.js +67 -0
- package/dist/config-fns.d.ts +87 -186
- package/dist/config-fns.js +207 -262
- package/dist/conformance/contract-adapter.d.ts +9 -2
- package/dist/conformance/contract-adapter.js +15 -7
- package/dist/conformance/corpus.d.ts +3 -0
- package/dist/conformance/corpus.js +5 -0
- package/dist/conformance/derive.d.ts +3 -0
- package/dist/conformance/index.d.ts +2 -2
- package/dist/conformance/index.js +2 -2
- package/dist/conformance/run.js +67 -22
- package/dist/conformance/types.d.ts +31 -15
- package/dist/conformance/types.js +5 -0
- package/dist/conformance/warnings.d.ts +1 -1
- package/dist/conformance/warnings.js +1 -4
- package/dist/cross-domain.d.ts +66 -22
- package/dist/cross-domain.js +164 -93
- package/dist/cross-session.d.ts +29 -11
- package/dist/default-model.d.ts +43 -0
- package/dist/default-model.js +34 -0
- package/dist/execution-lane-index.d.ts +4 -8
- package/dist/execution-lane-index.js +4 -8
- package/dist/execution-lanes.d.ts +31 -8
- package/dist/execution-lanes.js +23 -5
- package/dist/file-edit.js +5 -2
- package/dist/file-store.d.ts +7 -6
- package/dist/file-store.js +37 -50
- package/dist/fleet.d.ts +364 -762
- package/dist/fleet.js +277 -224
- package/dist/hooks.d.ts +1151 -4737
- package/dist/hooks.js +59 -15
- package/dist/index.d.ts +10 -4
- package/dist/index.js +13 -7
- package/dist/integer.d.ts +11 -0
- package/dist/integer.js +11 -0
- package/dist/issue-path.d.ts +10 -0
- package/dist/issue-path.js +10 -0
- package/dist/loopback.d.ts +26 -0
- package/dist/loopback.js +54 -0
- package/dist/migration-receipt.d.ts +42 -0
- package/dist/migration-receipt.js +12 -0
- package/dist/one-time-migrations.d.ts +56 -0
- package/dist/one-time-migrations.js +819 -0
- package/dist/prompts-digest.d.ts +1 -1
- package/dist/prompts-digest.js +3 -2
- package/dist/reader.d.ts +2 -2
- package/dist/remote-exec.d.ts +45 -187
- package/dist/remote-exec.js +13 -7
- package/dist/resolve-roster.d.ts +18 -3
- package/dist/resolve-roster.js +28 -25
- package/dist/retired-envs.d.ts +24 -0
- package/dist/retired-envs.js +114 -0
- package/dist/retired-form.d.ts +31 -0
- package/dist/retired-form.js +28 -0
- package/dist/safety-merge-spec.d.ts +131 -191
- package/dist/safety-merge-spec.js +18 -14
- package/dist/scheduler-lifetime.d.ts +5 -0
- package/dist/scheduler-lifetime.js +7 -0
- package/dist/scheduler-store-migration.d.ts +14 -0
- package/dist/scheduler-store-migration.js +14 -0
- package/dist/scheduler-store-node.d.ts +24 -9
- package/dist/scheduler-store-node.js +25 -15
- package/dist/scheduler-store.d.ts +47 -14
- package/dist/scheduler-store.js +32 -61
- package/dist/secret-refs.d.ts +3 -3
- package/dist/secret-refs.js +1 -1
- package/dist/secret-scan.d.ts +14 -24
- package/dist/secret-scan.js +45 -23
- package/dist/sha256-core.d.ts +16 -0
- package/dist/sha256-core.js +138 -0
- package/dist/sha256.d.ts +6 -1
- package/dist/sha256.js +8 -98
- package/dist/skills-manifest.js +1 -1
- package/dist/types.d.ts +1501 -13536
- package/dist/types.js +494 -438
- package/dist/url-shape.d.ts +34 -0
- package/dist/url-shape.js +61 -0
- package/dist/web-admin-credential.d.ts +96 -0
- package/dist/{center-credential.js → web-admin-credential.js} +46 -43
- package/dist/web-admin-env.d.ts +33 -0
- package/dist/web-admin-env.js +33 -0
- package/dist/worker-row-verdict.d.ts +16 -0
- package/dist/worker-row-verdict.js +31 -0
- package/dist/workers-rows.d.ts +34 -0
- package/dist/workers-rows.js +42 -0
- package/dist/zod-unwrap.d.ts +11 -0
- package/dist/zod-unwrap.js +25 -0
- package/package.json +17 -9
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/meta.json +0 -6
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/meta.json +0 -6
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +0 -49
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +0 -26
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m02-g5-idempotent/expected.json +0 -38
- package/conformance/migrate-workers/m02-g5-idempotent/input.json +0 -33
- package/conformance/migrate-workers/m02-g5-idempotent/meta.json +0 -5
- package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +0 -71
- package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +0 -42
- package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +0 -4
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +0 -52
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +0 -30
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/expected.json +0 -82
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/input.json +0 -68
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/meta.json +0 -4
- package/dist/center-credential.d.ts +0 -116
- package/dist/issue-text.d.ts +0 -10
- package/dist/issue-text.js +0 -8
- package/dist/migrate-workers.d.ts +0 -71
- package/dist/migrate-workers.js +0 -351
- package/dist/migrate.d.ts +0 -255
- package/dist/migrate.js +0 -542
- /package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/input.json +0 -0
package/dist/execution-lanes.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* ## 为什么在这里
|
|
6
6
|
* 车道词、每条车道读哪些 env、哪条车道分租户、存储后端词 —— 这几张表以前在四处各抄一份(引擎的能力表、
|
|
7
|
-
*
|
|
8
|
-
* ·
|
|
9
|
-
* ·
|
|
7
|
+
* 部署面的车道清单、web-admin 表单、本包 `RemoteExecSpec`),任何一处改了别处都不红。从本版起**只有这一份**:
|
|
8
|
+
* · web-admin 写闸 = `WorkerSpec.safeParse`(本包 schema 读这张表判 `runtime.execution` / `runtime.store`);
|
|
9
|
+
* · web-admin 渲染器按这张表把结构块渲成 env(唯一写者);
|
|
10
10
|
* · 引擎提货时从这张表派生自己的车道词表 / 必需键表 / 租户分离位(引擎只留运行期事实,按导入联合类型穷举);
|
|
11
11
|
* · 部署面车道清单由这张表生成。
|
|
12
12
|
* 写闸与起服读同一张表 ⇒ 不会分叉。
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* · 车道词与次序:`src/execution-lane-caps.ts` `EXECUTION_LANE_WORDS`(:335)去掉 `in-process` =
|
|
16
16
|
* `REMOTE_EXEC_PROVIDER_WORDS`(:366)。次序照抄,于是引擎改派生之后,拒启句里的 `valid:` 列表与出路列表逐字不变。
|
|
17
17
|
* · `tenantSeparation`:同文件 `executionLaneCaps` 每臂的 `tenantSeparation` 位(:209 host / :230 e2b / :249 k8s /
|
|
18
|
-
* :269 ssh / :288 adb / :308 local-docker / :327 device)
|
|
18
|
+
* :269 ssh / :288 adb / :308 local-docker / :327 device)。`tenantSeparationBasis`(11.0.0):分租户的那几臂凭什么分 ——
|
|
19
|
+
* e2b / k8s / local-docker 每任务一只隔离目标(`isolation`),device 是发起者自己的设备(`principal-binding`)。
|
|
19
20
|
* · `requiredEnv ∪ requiredSecretEnv`:`src/config.ts` `REQUIRED_ENVS_BY_LANE`(:1054)逐字;按 `src/config-catalog.ts`
|
|
20
21
|
* 该键那一行的值类型分栏 —— 类型 `secret` / `opaque`(目录自己「绝不回显值」的两类)进 `*SecretEnv`,其余进 `*Env`。
|
|
21
22
|
* · `optionalEnv ∪ optionalSecretEnv`:`src/config.ts` `parseOrchestrationDomain` 的 `REMOTE_EXEC` 三元链里**该车道那一臂**
|
|
@@ -31,12 +32,24 @@
|
|
|
31
32
|
* 仍回 completed + 零工具,就是那个病换了个合法名字。要无手 worker 先另立设计、定用户面行为再给词。
|
|
32
33
|
* · `remote-docker`:引擎有一只没接线的适配器工厂,但没有任何读点能把这个词变成一条在跑的车道
|
|
33
34
|
* (`REMOTE_EXEC=remote-docker` 起服即拒;远端 docker 守护进程今天的真形 = `local-docker` + `DOCKER_HOST`)。
|
|
35
|
+
* 它在退役词墓碑 {@link RETIRED_LANE_WORDS} 里:读到即拒,拒句带出路。
|
|
34
36
|
*/
|
|
35
37
|
/** 可声明的执行车道闭集(= 引擎 `REMOTE_EXEC` 收的词,逐字同序)。 */
|
|
36
38
|
export const EXECUTION_LANE_WORDS = ["host", "e2b", "k8s", "ssh", "adb", "local-docker", "device"];
|
|
39
|
+
export const RETIRED_LANE_WORDS = Object.freeze([
|
|
40
|
+
Object.freeze(["remote-docker", "local-docker", "point it at the remote docker daemon with DOCKER_HOST (in a worker spec: runtime.execution.coords.DOCKER_HOST)"]),
|
|
41
|
+
]);
|
|
42
|
+
/** 一个车道值是不是退役词:是 ⇒ 带出路的整句;否(现行词、不认识的词、非串)⇒ `undefined`。纯函数。 */
|
|
43
|
+
export function retiredLaneWordRefusal(word) {
|
|
44
|
+
const row = RETIRED_LANE_WORDS.find(([old]) => old === word);
|
|
45
|
+
return row === undefined ? undefined : `execution lane ${row[0]} was retired — use ${row[1]}: ${row[2]}`; // no quotes: the sentence survives a JSON-rendered issue list verbatim
|
|
46
|
+
}
|
|
37
47
|
/** 车道分不分租户(引擎能力表的 `tenantSeparation` 位,两档,没有「不表态」)。
|
|
38
48
|
* `supported` = 每个租户的命令落在各自的目标上;`single-user-only` = 所有任务落同一个共享目标 ⇒ 只许单用户 worker。 */
|
|
39
49
|
export const TENANT_SEPARATION_WORDS = ["supported", "single-user-only"];
|
|
50
|
+
/** 分租户车道**凭什么**分租户(两档):`isolation` = 每个任务一只隔离的目标(microVM / pod / 容器);`principal-binding` = 目标绑在
|
|
51
|
+
* 发起者身上(设备 ↔ principal 绑定),目标本身不隔离。只有 `tenantSeparation: "supported"` 的车道有依据。 */
|
|
52
|
+
export const TENANT_SEPARATION_BASES = ["isolation", "principal-binding"];
|
|
40
53
|
/** 逐车道一行。`satisfies Record<ExecutionLane, …>` = 词表加词而这里不加行 ⇒ 编译红。 */
|
|
41
54
|
const LANE_ROWS = {
|
|
42
55
|
/** server `config.ts` :4014 臂(无必需键);`execution-lane-caps.ts` :209 `single-user-only`(所有任务落 worker 宿主)。 */
|
|
@@ -51,6 +64,7 @@ const LANE_ROWS = {
|
|
|
51
64
|
* `execution-lane-caps.ts` :230 `supported`(每任务一只 microVM)。 */
|
|
52
65
|
e2b: {
|
|
53
66
|
tenantSeparation: "supported",
|
|
67
|
+
tenantSeparationBasis: "isolation",
|
|
54
68
|
requiredEnv: [],
|
|
55
69
|
requiredSecretEnv: ["E2B_API_KEY"],
|
|
56
70
|
optionalEnv: ["E2B_TEMPLATE", "E2B_TIMEOUT_MS", "E2B_LIVENESS_MS", "E2B_ALLOW_NET", "E2B_MOUNT_PATH", "E2B_ORPHAN_RECLAIM"],
|
|
@@ -60,6 +74,7 @@ const LANE_ROWS = {
|
|
|
60
74
|
* `execution-lane-caps.ts` :249 `supported`(每任务一只 pod)。`MINIO_*` 刻意不收(见文件头)。 */
|
|
61
75
|
k8s: {
|
|
62
76
|
tenantSeparation: "supported",
|
|
77
|
+
tenantSeparationBasis: "isolation",
|
|
63
78
|
requiredEnv: ["K8S_SANDBOX_IMAGE"],
|
|
64
79
|
requiredSecretEnv: [],
|
|
65
80
|
optionalEnv: [
|
|
@@ -98,6 +113,7 @@ const LANE_ROWS = {
|
|
|
98
113
|
* (每任务一只容器)。`DOCKER_HOST` 在本行:远端 docker 守护进程 = 本车道 + `coords.DOCKER_HOST`。 */
|
|
99
114
|
"local-docker": {
|
|
100
115
|
tenantSeparation: "supported",
|
|
116
|
+
tenantSeparationBasis: "isolation",
|
|
101
117
|
requiredEnv: ["DOCKER_IMAGE"],
|
|
102
118
|
requiredSecretEnv: [],
|
|
103
119
|
optionalEnv: [
|
|
@@ -117,6 +133,7 @@ const LANE_ROWS = {
|
|
|
117
133
|
* 连进来的);`execution-lane-caps.ts` :327 `supported`(发起者自己的设备)。 */
|
|
118
134
|
device: {
|
|
119
135
|
tenantSeparation: "supported",
|
|
136
|
+
tenantSeparationBasis: "principal-binding",
|
|
120
137
|
requiredEnv: [],
|
|
121
138
|
requiredSecretEnv: [],
|
|
122
139
|
optionalEnv: [
|
|
@@ -134,9 +151,10 @@ const LANE_ROWS = {
|
|
|
134
151
|
/** 车道表(次序 = {@link EXECUTION_LANE_WORDS})。整张表深冻结 —— 它是唯一正本,消费方拿到的不许被改。 */
|
|
135
152
|
export const EXECUTION_LANES = Object.freeze(EXECUTION_LANE_WORDS.map((lane) => {
|
|
136
153
|
const row = LANE_ROWS[lane];
|
|
154
|
+
const tenancy = row.tenantSeparation === "supported" ? { tenantSeparation: row.tenantSeparation, tenantSeparationBasis: row.tenantSeparationBasis } : { tenantSeparation: row.tenantSeparation };
|
|
137
155
|
return Object.freeze({
|
|
138
156
|
lane,
|
|
139
|
-
|
|
157
|
+
...tenancy,
|
|
140
158
|
requiredEnv: Object.freeze([...row.requiredEnv]),
|
|
141
159
|
requiredSecretEnv: Object.freeze([...row.requiredSecretEnv]),
|
|
142
160
|
optionalEnv: Object.freeze([...row.optionalEnv]),
|
package/dist/file-edit.js
CHANGED
|
@@ -9,13 +9,16 @@ import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { collectEnvRefs } from "./secret-refs.js";
|
|
11
11
|
import { RemoteExecSpec } from "./remote-exec.js";
|
|
12
|
-
import { parseDomain, sanitizeZodError } from "./config-fns.js";
|
|
13
|
-
import { DOMAIN_SCHEMAS, isDomainName } from "./types.js";
|
|
12
|
+
import { RetiredDomainError, parseDomain, sanitizeZodError } from "./config-fns.js";
|
|
13
|
+
import { DOMAIN_SCHEMAS, isDomainName, isRetiredDomainName } from "./types.js";
|
|
14
14
|
/** Throw a CLEAR error for a non-domain key instead of the cryptic `undefined.parse(...)` you'd get from
|
|
15
15
|
* `DOMAIN_SCHEMAS[badKey]`. The classic trip-wire: a consumer scans `config.d/*.json` and feeds every file
|
|
16
16
|
* (incl. the non-domain `remote-exec.json`) into writeBundle/writeDomainFile. `remote-exec` is the exec
|
|
17
17
|
* backend, NOT a domain — write it via writeRemoteExec. Filter arbitrary keys with `isDomainName` first. */
|
|
18
18
|
function assertDomainName(domain) {
|
|
19
|
+
// A retired domain is refused by its tombstone (the sentence names where its keys went), like every other reader / writer.
|
|
20
|
+
if (isRetiredDomainName(domain))
|
|
21
|
+
throw new RetiredDomainError(domain);
|
|
19
22
|
if (!isDomainName(domain)) {
|
|
20
23
|
const hint = domain === "remote-exec" ? " — the exec backend is NOT a domain; use writeRemoteExec (config.d/remote-exec.json)" : "";
|
|
21
24
|
throw new Error(`"${domain}" is not a config domain (expected one of: ${DOMAINS_LIST})${hint}`);
|
package/dist/file-store.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type EffectiveReadWarning } from "./config-fns.js";
|
|
2
2
|
import type { ConfigReader } from "./reader.js";
|
|
3
|
-
import { type DomainConfig, type DomainName, type EffectiveConfig } from "./types.js";
|
|
3
|
+
import { type DomainConfig, type DomainName, type EffectiveConfig, type RetiredDomainName } from "./types.js";
|
|
4
4
|
/** The domains a single-machine TOC carries: what `FileConfigStore` reads out of `config.d/`, and the
|
|
5
5
|
* **default**域集 of the bundle helpers (`exportBundle`/`readBundle`). 每个被排除的域必须在
|
|
6
6
|
* {@link NON_PORTABLE_DOMAIN_REASONS} 里有一条成文理由(禁裸排除,#282 件5 附带裁定)。
|
|
@@ -15,7 +15,7 @@ import { type DomainConfig, type DomainName, type EffectiveConfig } from "./type
|
|
|
15
15
|
* 同形**。当年 `limits` 域就是这样在单机腿静默丢了一整片限额发布(远端腿同批可用,所以更难看出来)。
|
|
16
16
|
* 今日的兜底:漏登记的文件会被 {@link FileConfigStore} 的 `"unread-config-file"` 警告点名(fail-loud,
|
|
17
17
|
* 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
|
|
18
|
-
export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "
|
|
18
|
+
export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits"];
|
|
19
19
|
export type PortableDomain = (typeof PORTABLE_DOMAINS)[number];
|
|
20
20
|
/** `config.d/` 里**合法存在但不是域文档**的文件名(不进 `"unread-config-file"` 告警)。`remote-exec`
|
|
21
21
|
* 不是 `DOMAIN_SCHEMAS` 成员(resolver 不读它),按 service 表决住在这个目录,由 loadRemoteExec 读
|
|
@@ -35,9 +35,9 @@ export declare const NON_DOMAIN_CONFIG_FILES: ReadonlySet<string>;
|
|
|
35
35
|
export declare const NON_PORTABLE_DOMAIN_REASONS: {
|
|
36
36
|
readonly workers: "FLEET-only:单机没有 reconciler,`{workers:[]}` 的 schema 默认就是正确语义(缺席不需要 stub)。";
|
|
37
37
|
readonly hosts: "FLEET-only:同 workers —— 单机无机器注册表/自动排布,空注册表即正确语义。";
|
|
38
|
-
readonly systems: "FLEET-only:系统接入/凭据下发的元数据(nonce+路由)属于
|
|
38
|
+
readonly systems: "FLEET-only:系统接入/凭据下发的元数据(nonce+路由)属于 web-admin 侧生命周期,单机 lane 无对应执法点。";
|
|
39
39
|
readonly projects: "TENANT IDENTITY DATA,不是可搬运的配置:登记簿随包复制进另一个部署 = 凭空多出的身份登记(design/142 §1.4 威胁模型①「carried-in foreign projectId」换了身 import 的衣服)。要带走走 142 §5 的导出面(service 半场)。";
|
|
40
|
-
readonly entitlement: "per-PRINCIPAL 商务/治理数据(tier·预算上限·kill-switch·allowlist),与 projects 同威胁模型(#282 件5 附带裁定,此前是唯一裸排除项):(a) 搬走 = 把一个租户的商务档复制进另一部署;(b) 反向也不成立 —— 单机 TOC 无 principal 轴(无鉴权语境、无 `/effective?principal=` 解析面),本地 entitlement.json 只会造出一份**永不被执法**的假管控面(真执法点在 service/
|
|
40
|
+
readonly entitlement: "per-PRINCIPAL 商务/治理数据(tier·预算上限·kill-switch·allowlist),与 projects 同威胁模型(#282 件5 附带裁定,此前是唯一裸排除项):(a) 搬走 = 把一个租户的商务档复制进另一部署;(b) 反向也不成立 —— 单机 TOC 无 principal 轴(无鉴权语境、无 `/effective?principal=` 解析面),本地 entitlement.json 只会造出一份**永不被执法**的假管控面(真执法点在 service / web-admin 边界)。缺席语义如实:本地 buildEffective 给它 schema 默认值(空表),这**不等于**把远端 kill-switch/预算上限搬到了本地 —— 单机 lane 本就没有那个执法点。";
|
|
41
41
|
};
|
|
42
42
|
/** One unreadable/corrupt domain FILE surfaced by `getEffective({tolerant:true})`(0.10.12,[897]②)。
|
|
43
43
|
* `error` 是 #readDomainRaw 的 sanitized 文案(带文件路径语境、绝不回显文件内容)。 */
|
|
@@ -62,8 +62,9 @@ export declare class FileConfigStore implements ConfigReader {
|
|
|
62
62
|
onWarning?: (w: EffectiveReadWarning) => void;
|
|
63
63
|
});
|
|
64
64
|
/** Absolute path to a domain's JSON file. Guards the name (defence in depth — a non-domain string like
|
|
65
|
-
* `../../secret` must never be joined into a filesystem path, even if a caller bypasses the type).
|
|
66
|
-
|
|
65
|
+
* `../../secret` must never be joined into a filesystem path, even if a caller bypasses the type). A retired domain's
|
|
66
|
+
* name is accepted: its file is read for the tombstone. */
|
|
67
|
+
domainPath(domain: DomainName | RetiredDomainName): string;
|
|
67
68
|
/** Read+parse one domain (validated, defaults filled), or `undefined` if the domain is ABSENT — matching
|
|
68
69
|
* the {@link ConfigReader} contract (reader.ts: absent domain → undefined) and the server stores'
|
|
69
70
|
* `has(domain) ? parse : undefined`. A present-but-empty file (`{}` / whitespace) IS a set domain → parsed
|
package/dist/file-store.js
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* "absent/defaults" the file must be missing, not empty. A valid `{}` object IS a set domain (→ defaults).
|
|
17
17
|
* - Non-portable domains are OMITTED on a single machine and resolve to their schema defaults (`{workers:[]}`
|
|
18
18
|
* etc.) — no stubbing needed. 逐域为什么被排除见 {@link NON_PORTABLE_DOMAIN_REASONS}(禁裸排除)。
|
|
19
|
+
* - A RETIRED domain's file (`config.d/runtime.json`, see `RETIRED_DOMAINS`) is read only for its tombstone: carrying facts ⇒
|
|
20
|
+
* every read entry (`getDomain`, `getEffective` — and so `exportBundle`) throws `RetiredDomainError` (`refuseRetiredDomainFiles`, shared with `readBundle`) (the sentence names the one-time migration — `npx @sema-agent/settings-schema migrate
|
|
21
|
+
* <dir>` rewrites the directory); `{}` carries nothing and is left alone. It is never an "unread file".
|
|
19
22
|
* - The JSON files only ever carry env-NAMEs (the ENV_NAME boundary holds locally too); `.env` holds values.
|
|
20
23
|
* - `getVersion()` = a stable content hash of all portable files → a u53 number, so a consumer's
|
|
21
24
|
* "skip re-apply if version unchanged" works locally. `getEffective().updatedAt` = the max file mtime,
|
|
@@ -24,8 +27,11 @@
|
|
|
24
27
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
25
28
|
import path from "node:path";
|
|
26
29
|
import { buildEffective, DOMAIN_READ_FALLBACK, parseDomainLoud } from "./config-fns.js";
|
|
30
|
+
import { configDPath, readConfigDFile, refuseRetiredDomainFiles } from "./config-d-file.js";
|
|
27
31
|
import { stableHash } from "./hash.js";
|
|
28
|
-
import { isDomainName } from "./types.js";
|
|
32
|
+
import { RETIRED_DOMAINS, isDomainName, isRetiredDomainName } from "./types.js";
|
|
33
|
+
/** The retired domain names (`RETIRED_DOMAINS` keys) — read from `config.d/` for their tombstone only. */
|
|
34
|
+
const RETIRED_DOMAIN_NAMES = Object.keys(RETIRED_DOMAINS);
|
|
29
35
|
/** The domains a single-machine TOC carries: what `FileConfigStore` reads out of `config.d/`, and the
|
|
30
36
|
* **default**域集 of the bundle helpers (`exportBundle`/`readBundle`). 每个被排除的域必须在
|
|
31
37
|
* {@link NON_PORTABLE_DOMAIN_REASONS} 里有一条成文理由(禁裸排除,#282 件5 附带裁定)。
|
|
@@ -40,7 +46,7 @@ import { isDomainName } from "./types.js";
|
|
|
40
46
|
* 同形**。当年 `limits` 域就是这样在单机腿静默丢了一整片限额发布(远端腿同批可用,所以更难看出来)。
|
|
41
47
|
* 今日的兜底:漏登记的文件会被 {@link FileConfigStore} 的 `"unread-config-file"` 警告点名(fail-loud,
|
|
42
48
|
* 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
|
|
43
|
-
export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "
|
|
49
|
+
export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits"];
|
|
44
50
|
/** `config.d/` 里**合法存在但不是域文档**的文件名(不进 `"unread-config-file"` 告警)。`remote-exec`
|
|
45
51
|
* 不是 `DOMAIN_SCHEMAS` 成员(resolver 不读它),按 service 表决住在这个目录,由 loadRemoteExec 读
|
|
46
52
|
* ——**它有自己的读取与校验路径**,所以既不是「未知文件」也不是「域文件」。
|
|
@@ -59,9 +65,9 @@ export const NON_DOMAIN_CONFIG_FILES = new Set(["remote-exec.json"]);
|
|
|
59
65
|
export const NON_PORTABLE_DOMAIN_REASONS = {
|
|
60
66
|
workers: "FLEET-only:单机没有 reconciler,`{workers:[]}` 的 schema 默认就是正确语义(缺席不需要 stub)。",
|
|
61
67
|
hosts: "FLEET-only:同 workers —— 单机无机器注册表/自动排布,空注册表即正确语义。",
|
|
62
|
-
systems: "FLEET-only:系统接入/凭据下发的元数据(nonce+路由)属于
|
|
68
|
+
systems: "FLEET-only:系统接入/凭据下发的元数据(nonce+路由)属于 web-admin 侧生命周期,单机 lane 无对应执法点。",
|
|
63
69
|
projects: "TENANT IDENTITY DATA,不是可搬运的配置:登记簿随包复制进另一个部署 = 凭空多出的身份登记(design/142 §1.4 威胁模型①「carried-in foreign projectId」换了身 import 的衣服)。要带走走 142 §5 的导出面(service 半场)。",
|
|
64
|
-
entitlement: "per-PRINCIPAL 商务/治理数据(tier·预算上限·kill-switch·allowlist),与 projects 同威胁模型(#282 件5 附带裁定,此前是唯一裸排除项):(a) 搬走 = 把一个租户的商务档复制进另一部署;(b) 反向也不成立 —— 单机 TOC 无 principal 轴(无鉴权语境、无 `/effective?principal=` 解析面),本地 entitlement.json 只会造出一份**永不被执法**的假管控面(真执法点在 service/
|
|
70
|
+
entitlement: "per-PRINCIPAL 商务/治理数据(tier·预算上限·kill-switch·allowlist),与 projects 同威胁模型(#282 件5 附带裁定,此前是唯一裸排除项):(a) 搬走 = 把一个租户的商务档复制进另一部署;(b) 反向也不成立 —— 单机 TOC 无 principal 轴(无鉴权语境、无 `/effective?principal=` 解析面),本地 entitlement.json 只会造出一份**永不被执法**的假管控面(真执法点在 service / web-admin 边界)。缺席语义如实:本地 buildEffective 给它 schema 默认值(空表),这**不等于**把远端 kill-switch/预算上限搬到了本地 —— 单机 lane 本就没有那个执法点。",
|
|
65
71
|
};
|
|
66
72
|
export class FileConfigStore {
|
|
67
73
|
root;
|
|
@@ -74,50 +80,20 @@ export class FileConfigStore {
|
|
|
74
80
|
this.opts = opts;
|
|
75
81
|
}
|
|
76
82
|
/** Absolute path to a domain's JSON file. Guards the name (defence in depth — a non-domain string like
|
|
77
|
-
* `../../secret` must never be joined into a filesystem path, even if a caller bypasses the type).
|
|
83
|
+
* `../../secret` must never be joined into a filesystem path, even if a caller bypasses the type). A retired domain's
|
|
84
|
+
* name is accepted: its file is read for the tombstone. */
|
|
78
85
|
domainPath(domain) {
|
|
79
|
-
|
|
80
|
-
throw new Error(`"${domain}" is not a config domain — refusing to build a config.d path (no traversal)`);
|
|
81
|
-
return path.join(this.root, "config.d", `${domain}.json`);
|
|
86
|
+
return configDPath(this.root, domain); // the one name guard (no traversal) lives there
|
|
82
87
|
}
|
|
83
|
-
/** Read + JSON.parse one domain file
|
|
84
|
-
* way to mean "use defaults")
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* `null` to the SAME bytes, so an empty/null file that resolved to undefined would hash IDENTICALLY to
|
|
91
|
-
* "domain absent" and a version-gated poller would skip a change getEffective/getDomain actually FAIL on.
|
|
92
|
-
* Every domain schema is a z.object, so a non-object/empty file is never valid — reject all those shapes
|
|
93
|
-
* here, once. R17 + R(0.1.27 整体复审). */
|
|
88
|
+
/** Read + JSON.parse one domain file —— the ONE `config.d/` file reading (`./config-d-file`, shared with `readBundle`): a MISSING
|
|
89
|
+
* file (ENOENT) → undefined = the domain is ABSENT (the ONLY way to mean "use defaults"); a present file that is EMPTY/whitespace-only,
|
|
90
|
+
* or parses to anything but a JSON OBJECT, is CORRUPT → throw, FAIL CLOSED (an empty present file is a half-write/truncation, NOT
|
|
91
|
+
* "absent": treating it as absent would silently DROP a published domain — e.g. a truncated governance.json drops its
|
|
92
|
+
* commandPolicy/autonomy guardrails). This also keeps getVersion in lock-step with getEffective/getDomain: stableStringify collapses
|
|
93
|
+
* `undefined` (absent) and `null` to the SAME bytes, so an empty/null file that resolved to undefined would hash IDENTICALLY to
|
|
94
|
+
* "domain absent" and a version-gated poller would skip a change getEffective/getDomain actually FAIL on. R17 + R(0.1.27 整体复审). */
|
|
94
95
|
async #readDomainRaw(domain) {
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
text = await readFile(this.domainPath(domain), "utf8");
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
if (e.code === "ENOENT")
|
|
101
|
-
return undefined;
|
|
102
|
-
throw e;
|
|
103
|
-
}
|
|
104
|
-
const trimmed = text.trim();
|
|
105
|
-
// FAIL CLOSED on an empty/whitespace present file (half-write/truncation), same as loadRemoteExec/file: —
|
|
106
|
-
// to mean "absent → defaults" the file must be MISSING (ENOENT), never present-but-empty.
|
|
107
|
-
if (!trimmed)
|
|
108
|
-
throw new Error(`config.d/${domain}.json is empty/whitespace — delete it for the domain default, or write a valid object`);
|
|
109
|
-
let parsed;
|
|
110
|
-
try {
|
|
111
|
-
parsed = JSON.parse(trimmed);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
throw new Error(`invalid JSON in config.d/${domain}.json`); // sanitized: never echo the raw file text into the error
|
|
115
|
-
}
|
|
116
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
117
|
-
const kind = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
118
|
-
throw new Error(`config.d/${domain}.json must be a JSON object (got ${kind}) — delete the file for the domain default, or write a valid object`);
|
|
119
|
-
}
|
|
120
|
-
return parsed;
|
|
96
|
+
return readConfigDFile(this.root, domain); // the shared reader guards the name (configDPath)
|
|
121
97
|
}
|
|
122
98
|
/**
|
|
123
99
|
* FAIL-LOUD 扫描(0.19.0):`config.d/` 里存在、但本地读取集**永远不会读**的 `*.json` 文件,逐个经
|
|
@@ -152,15 +128,19 @@ export class FileConfigStore {
|
|
|
152
128
|
const base = name.slice(0, -".json".length);
|
|
153
129
|
if (PORTABLE_DOMAINS.includes(base))
|
|
154
130
|
continue; // 真被读的域
|
|
131
|
+
if (isRetiredDomainName(base))
|
|
132
|
+
continue; // retired 域:由墓碑判(带事实 ⇒ getEffective 抛),不是「没人读的文件」
|
|
155
133
|
onWarning(isDomainName(base)
|
|
156
134
|
? { kind: "unread-config-file", file: name, domain: base, why: "non-portable-domain" }
|
|
157
135
|
: { kind: "unread-config-file", file: name, why: "not-a-domain" });
|
|
158
136
|
}
|
|
159
137
|
}
|
|
160
|
-
/** Raw (unparsed) values for every PORTABLE domain
|
|
138
|
+
/** Raw (unparsed) values for every PORTABLE domain and every retired domain (the latter for the tombstone), keyed by
|
|
139
|
+
* name (absent → undefined). Both feed the version hash: a retired file appearing / changing flips `getEffective`
|
|
140
|
+
* between a refusal and a read, so a version-gated poller must see it. */
|
|
161
141
|
async #readAllRaw() {
|
|
162
142
|
const out = {};
|
|
163
|
-
await Promise.all(PORTABLE_DOMAINS.map(async (d) => { out[d] = await this.#readDomainRaw(d); }));
|
|
143
|
+
await Promise.all([...PORTABLE_DOMAINS, ...RETIRED_DOMAIN_NAMES].map(async (d) => { out[d] = await this.#readDomainRaw(d); }));
|
|
164
144
|
return out;
|
|
165
145
|
}
|
|
166
146
|
/** The max mtime (ISO) across the present portable files, or the epoch when none exist — so `updatedAt`
|
|
@@ -186,6 +166,7 @@ export class FileConfigStore {
|
|
|
186
166
|
* with defaults; a missing file is absence → undefined. (getEffective still fills defaults for absent
|
|
187
167
|
* domains; getDomain is the per-domain "was this ever set?" probe.) */
|
|
188
168
|
async getDomain(domain) {
|
|
169
|
+
await refuseRetiredDomainFiles(this.root); // every read entry asks the tombstone first (exportBundle reads through here)
|
|
189
170
|
const raw = await this.#readDomainRaw(domain);
|
|
190
171
|
if (raw === undefined)
|
|
191
172
|
return undefined; // missing/empty file → domain absent (not schema defaults)
|
|
@@ -199,14 +180,20 @@ export class FileConfigStore {
|
|
|
199
180
|
return parseInt(stableHash(raws).slice(0, 13), 16); // 13 hex digits ≤ 2^52, safely within u53
|
|
200
181
|
}
|
|
201
182
|
async getEffective(opts) {
|
|
183
|
+
// Every read entry asks the tombstone first (the same check getDomain runs); what it read (absent / `{}`) joins the version hash.
|
|
202
184
|
if (opts?.tolerant !== true) {
|
|
203
|
-
const [
|
|
185
|
+
const [retired] = await Promise.all([refuseRetiredDomainFiles(this.root), this.#warnUnreadFiles()]); // 两条读法同一门(0.19.0)
|
|
186
|
+
const raws = { ...retired };
|
|
187
|
+
await Promise.all(PORTABLE_DOMAINS.map(async (d) => { raws[d] = await this.#readDomainRaw(d); }));
|
|
204
188
|
const updatedAt = await this.#maxMtimeIso();
|
|
205
189
|
return buildEffective(this.#versionOf(raws), (d) => raws[d], updatedAt, this.opts.onWarning);
|
|
206
190
|
}
|
|
207
191
|
await this.#warnUnreadFiles();
|
|
208
|
-
|
|
209
|
-
|
|
192
|
+
// A retired domain's file is read strictly (no tolerance): it exists only to be refused, and an unreadable one is still
|
|
193
|
+
// an unmigrated document sitting in the directory.
|
|
194
|
+
const retired = await refuseRetiredDomainFiles(this.root);
|
|
195
|
+
const raws = { ...retired };
|
|
196
|
+
const hashRaws = { ...retired };
|
|
210
197
|
const domainErrors = [];
|
|
211
198
|
await Promise.all(PORTABLE_DOMAINS.map(async (d) => {
|
|
212
199
|
try {
|