@sema-agent/settings-schema 15.2.0 → 17.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +71 -46
  3. package/conformance/VERSION +1 -1
  4. package/conformance/cases/c16-deep-nesting-205/expected.json +2 -2
  5. package/conformance/cases/c16-deep-nesting-205/meta.json +1 -1
  6. package/conformance/cases/c50-s784-lane-host-legal/expected.json +6 -2
  7. package/conformance/cases/c50-s784-lane-host-legal/input.json +3 -1
  8. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +6 -2
  9. package/conformance/cases/c51-s784-lane-e2b-legal/input.json +3 -1
  10. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +6 -2
  11. package/conformance/cases/c52-s784-lane-k8s-legal/input.json +3 -1
  12. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +6 -2
  13. package/conformance/cases/c53-s784-lane-ssh-legal/input.json +3 -1
  14. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +6 -2
  15. package/conformance/cases/c54-s784-lane-adb-legal/input.json +3 -1
  16. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +6 -2
  17. package/conformance/cases/c55-s784-lane-local-docker-legal/input.json +3 -1
  18. package/conformance/cases/c56-s784-lane-device-legal/expected.json +8 -2
  19. package/conformance/cases/c56-s784-lane-device-legal/input.json +5 -2
  20. package/conformance/cases/c57-s784-g1-enabled-no-lane/meta.json +1 -1
  21. package/conformance/cases/c58-s784-g2-multitenant-host/input.json +3 -1
  22. package/conformance/cases/c59-s784-g3-k8s-missing-image/input.json +3 -1
  23. package/conformance/cases/c60-s784-g3-e2b-missing-key/input.json +3 -1
  24. package/conformance/cases/c61-s784-g3-other-lane-key/input.json +3 -1
  25. package/conformance/cases/c62-s784-g7a-session-tidb/input.json +3 -1
  26. package/conformance/cases/c63-s784-host-two-replicas/input.json +3 -1
  27. package/conformance/cases/c64-s784-multitenant-local-store/input.json +3 -1
  28. package/conformance/cases/c65-s784-pg-without-db/input.json +3 -1
  29. package/conformance/cases/c66-s784-memory-backend-mismatch/input.json +3 -1
  30. package/conformance/cases/c66-s784-memory-backend-mismatch/meta.json +1 -1
  31. package/conformance/cases/c68-s784-device-local-store/input.json +6 -1
  32. package/conformance/cases/cli04-env-value-with-newline/expected.json +6 -2
  33. package/conformance/cases/cli04-env-value-with-newline/input.json +3 -1
  34. package/dist/atomic-write.d.ts +4 -0
  35. package/dist/atomic-write.js +20 -0
  36. package/dist/bundle.d.ts +0 -18
  37. package/dist/bundle.js +1 -54
  38. package/dist/cli.d.ts +1 -1
  39. package/dist/config-d-file.d.ts +13 -12
  40. package/dist/config-d-file.js +72 -27
  41. package/dist/config-document-refusal.d.ts +9 -7
  42. package/dist/config-document-refusal.js +2 -2
  43. package/dist/device-relay.d.ts +21 -0
  44. package/dist/device-relay.js +23 -0
  45. package/dist/env-model-table.d.ts +2 -9
  46. package/dist/env-model-table.js +6 -14
  47. package/dist/execution-lane-index.d.ts +19 -7
  48. package/dist/execution-lane-index.js +26 -8
  49. package/dist/execution-lanes.d.ts +9 -4
  50. package/dist/execution-lanes.js +18 -6
  51. package/dist/file-store.d.ts +31 -4
  52. package/dist/file-store.js +78 -11
  53. package/dist/fleet.d.ts +6 -8
  54. package/dist/fleet.js +5 -7
  55. package/dist/hooks.d.ts +1 -1
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.js +2 -1
  58. package/dist/local-load.d.ts +12 -4
  59. package/dist/local-load.js +20 -31
  60. package/dist/migration-receipt.d.ts +1 -1
  61. package/dist/migration-receipt.js +1 -1
  62. package/dist/one-time-migrations.d.ts +1 -1
  63. package/dist/one-time-migrations.js +166 -45
  64. package/dist/remote-exec.js +11 -4
  65. package/dist/types.d.ts +15 -11
  66. package/dist/types.js +99 -48
  67. package/dist/url-shape.d.ts +18 -0
  68. package/dist/url-shape.js +22 -0
  69. package/docs/API-SURFACE.md +12 -8
  70. package/docs/CONFORMANCE.md +1 -7
  71. package/docs/CONSUMER-INTEGRATION.md +3 -3
  72. package/llms-full.txt +25 -29
  73. package/llms.txt +9 -8
  74. package/package.json +1 -1
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "local-docker",
16
+ "lanes": [
17
+ "local-docker"
18
+ ],
17
19
  "coords": {
18
20
  "DOCKER_IMAGE": "registry.internal/sandbox:1",
19
21
  "DOCKER_HOST": "tcp://docker.internal:2376"
@@ -63,9 +63,12 @@
63
63
  "runtime": {
64
64
  "execution": {
65
65
  "coords": {
66
+ "DEVICE_RELAY_URL": "none",
66
67
  "DEVICE_SUPERSEDE": "deny"
67
68
  },
68
- "lane": "device",
69
+ "lanes": [
70
+ "device"
71
+ ],
69
72
  "secretRefs": {}
70
73
  },
71
74
  "rateLimitRpm": 0,
@@ -113,9 +116,12 @@
113
116
  "runtime": {
114
117
  "execution": {
115
118
  "coords": {
119
+ "DEVICE_RELAY_URL": "none",
116
120
  "DEVICE_SUPERSEDE": "deny"
117
121
  },
118
- "lane": "device",
122
+ "lanes": [
123
+ "device"
124
+ ],
119
125
  "secretRefs": {}
120
126
  },
121
127
  "rateLimitRpm": 0,
@@ -14,9 +14,12 @@
14
14
  },
15
15
  "runtime": {
16
16
  "execution": {
17
- "lane": "device",
17
+ "lanes": [
18
+ "device"
19
+ ],
18
20
  "coords": {
19
- "DEVICE_SUPERSEDE": "deny"
21
+ "DEVICE_SUPERSEDE": "deny",
22
+ "DEVICE_RELAY_URL": "none"
20
23
  }
21
24
  },
22
25
  "store": {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "contributor": "contract",
3
- "claim": "G1:enabled 的 worker 没有 runtime.execution ⇒ 拒(拒因点名 runtime.execution.lane)。打「声明不了车道也能过闸、起服落进程内 stub、无手照回 completed」那条静默路(WA-49)。",
3
+ "claim": "G1:enabled 的 worker 没有 runtime.execution ⇒ 拒(拒因点名 runtime.execution.lanes —— 16.0.0 起车道集)。打「声明不了车道也能过闸、起服落进程内 stub、无手照回 completed」那条静默路(WA-49)。",
4
4
  "firstRedOn": []
5
5
  }
@@ -14,7 +14,9 @@
14
14
  },
15
15
  "runtime": {
16
16
  "execution": {
17
- "lane": "host"
17
+ "lanes": [
18
+ "host"
19
+ ]
18
20
  },
19
21
  "store": {
20
22
  "backend": "mysql",
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "k8s",
16
+ "lanes": [
17
+ "k8s"
18
+ ],
17
19
  "coords": {
18
20
  "K8S_NAMESPACE": "sandboxes"
19
21
  }
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "e2b"
16
+ "lanes": [
17
+ "e2b"
18
+ ]
17
19
  },
18
20
  "store": {
19
21
  "backend": "local"
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "k8s",
16
+ "lanes": [
17
+ "k8s"
18
+ ],
17
19
  "coords": {
18
20
  "K8S_SANDBOX_IMAGE": "img",
19
21
  "K8S_NAMESPACE": "sandboxes",
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "host"
16
+ "lanes": [
17
+ "host"
18
+ ]
17
19
  },
18
20
  "store": {
19
21
  "backend": "local",
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "host"
16
+ "lanes": [
17
+ "host"
18
+ ]
17
19
  },
18
20
  "store": {
19
21
  "backend": "local"
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "k8s",
16
+ "lanes": [
17
+ "k8s"
18
+ ],
17
19
  "coords": {
18
20
  "K8S_SANDBOX_IMAGE": "img",
19
21
  "K8S_NAMESPACE": "sandboxes"
@@ -14,7 +14,9 @@
14
14
  },
15
15
  "runtime": {
16
16
  "execution": {
17
- "lane": "host"
17
+ "lanes": [
18
+ "host"
19
+ ]
18
20
  },
19
21
  "store": {
20
22
  "backend": "pg"
@@ -14,7 +14,9 @@
14
14
  },
15
15
  "runtime": {
16
16
  "execution": {
17
- "lane": "host"
17
+ "lanes": [
18
+ "host"
19
+ ]
18
20
  },
19
21
  "store": {
20
22
  "backend": "mysql",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "contributor": "contract",
3
- "claim": "store.memory.backend pg 而 store.backend mysql ⇒ 拒(pg 记忆面要 pg 店;tidb 要 mysql)。",
3
+ "claim": "store.memory.backend pg 而 store.backend mysql ⇒ 拒(pg 记忆面要 pg 店;mysql 记忆面要 mysql 店)。",
4
4
  "firstRedOn": []
5
5
  }
@@ -13,7 +13,12 @@
13
13
  },
14
14
  "runtime": {
15
15
  "execution": {
16
- "lane": "device"
16
+ "lanes": [
17
+ "device"
18
+ ],
19
+ "coords": {
20
+ "DEVICE_RELAY_URL": "none"
21
+ }
17
22
  },
18
23
  "store": {
19
24
  "backend": "local"
@@ -67,7 +67,9 @@
67
67
  "runtime": {
68
68
  "execution": {
69
69
  "coords": {},
70
- "lane": "host",
70
+ "lanes": [
71
+ "host"
72
+ ],
71
73
  "secretRefs": {}
72
74
  },
73
75
  "rateLimitRpm": 0,
@@ -112,7 +114,9 @@
112
114
  "runtime": {
113
115
  "execution": {
114
116
  "coords": {},
115
- "lane": "host",
117
+ "lanes": [
118
+ "host"
119
+ ],
116
120
  "secretRefs": {}
117
121
  },
118
122
  "rateLimitRpm": 0,
@@ -10,7 +10,9 @@
10
10
  "name": "w1",
11
11
  "runtime": {
12
12
  "execution": {
13
- "lane": "host"
13
+ "lanes": [
14
+ "host"
15
+ ]
14
16
  },
15
17
  "store": {
16
18
  "backend": "local"
@@ -16,6 +16,10 @@ export declare function danglingSymlinkSentence(link: string, pointsAt: string):
16
16
  /** 读写的真目标 —— **一只判法**(写器与目录迁移的读口共用):`target` 是符号链接 ⇒ 解析到它指向的真文件(悬空 ⇒ 抛 {@link danglingSymlinkSentence});
17
17
  * 不是链接或不存在 ⇒ 原样(不存在的由调用方的读 / 写照常报)。 */
18
18
  export declare function resolveSymlinkTarget(target: string): string;
19
+ /** 读一份**可以缺席**的 UTF-8 文本 —— `config.d` 文档与 `.env` 的缺席律在这里:读得到 ⇒ 文本;`ENOENT` ⇒ 先在缺席臂里对 `file` 自己与 `dirSeats`(这份文件所在、包把它当布局座的目录,如 `config.d`)各问一次
20
+ * {@link resolveSymlinkTarget}(与目录迁移的读口同一只判官):任一座是悬空链接 ⇒ 抛 {@link danglingSymlinkSentence} 那一句(在场却读不出,不是缺席);都不是 ⇒ `undefined`(路径上真的没有条目)。
21
+ * 别的读不出(`EACCES` / `EISDIR` / `ELOOP` …)原样抛。配置根自己是悬空链接(祖先座)不在问的范围内:`dirSeats` 只列文件所在的那一级布局座。 */
22
+ export declare function readTextIfPresent(file: string, dirSeats?: readonly string[]): Promise<string | undefined>;
19
23
  /** 原子写 `text`(UTF-8)到 `target`(符号链接则写到它指向的真文件,见文件头);模式按 `kind` 在 {@link ATOMIC_FILE_CLASSES} 里的行。父目录须已存在。 */
20
24
  export declare function writeFileAtomic(target: string, text: string, kind: AtomicFileClass): void;
21
25
  /**
@@ -20,6 +20,10 @@
20
20
  * 「链接 ⇒ 真文件、悬空 ⇒ 响亮抛」这一只判法是 {@link resolveSymlinkTarget}:目录迁移的读口(`./config-dir-io`)读目录 / 读文件前也问它,
21
21
  * 悬空链接在读阶段与写阶段是**同一句**({@link danglingSymlinkSentence})。
22
22
  *
23
+ * ## `config.d` 文档与 `.env` 的缺席只有一种读法({@link readTextIfPresent})
24
+ * **缺席 = 路径上没有任何条目。** `readFile` 跟着符号链接走,链接在而目标不在时内核同样答 `ENOENT`:那是在场却读不出,不是缺席。读一份可以缺席的文本(`config.d` 的文档、配置根下的 `.env`)
25
+ * 因此都过同一只读者 —— 读不出且是 `ENOENT` ⇒ 在**缺席臂里**问一次链接判法再答「缺席」,悬空 ⇒ 抛那一句(同一句,不带 errno 码)。判在缺席臂而不在读前:读得到的路径零变化(不多一次系统调用);读之后目标恰好出现仍答缺席 —— 读的那一刻它确实不在,与旧读法同一边界,不是放宽。
26
+ *
23
27
  * ## 不存在才建({@link createFileExclusive})
24
28
  * 另一种写法只有一处不同:最后一步不是 `rename` 盖过目标,而是把(已 fsync 的)临时文件 `link` 成目标名 —— 目标名已在(任何东西:文件、目录、哪怕是悬空的符号链接)⇒
25
29
  * 内核拒(`EEXIST`),答 `present`、一字不动。读到「缺席」与写入之间别人写下的东西因此不会被吞(一次性迁移铸配置根的决定文档时用它)。
@@ -38,6 +42,7 @@
38
42
  * 写器在建临时文件那一步读了经落点递给铸点(`EPERM` ∧ 可写 ⇒ 真因是不可变标志,首选出路是清标志;`EACCES` ∧ 可写 ⇒ 真因是 ACL / 安全模块;不可写 ⇒ 先让目录可写);`chmod` / `rename` 那两步不读 —— 临时文件刚在同目录建成,可写是已证明的事实,铸点自己给。
39
43
  */
40
44
  import { randomBytes } from "node:crypto";
45
+ import { readFile } from "node:fs/promises";
41
46
  import { chmodSync, closeSync, existsSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readlinkSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
42
47
  import path from "node:path";
43
48
  import { FsFailure } from "./fs-failure.js";
@@ -75,6 +80,21 @@ export function resolveSymlinkTarget(target) {
75
80
  throw e;
76
81
  }
77
82
  }
83
+ /** 读一份**可以缺席**的 UTF-8 文本 —— `config.d` 文档与 `.env` 的缺席律在这里:读得到 ⇒ 文本;`ENOENT` ⇒ 先在缺席臂里对 `file` 自己与 `dirSeats`(这份文件所在、包把它当布局座的目录,如 `config.d`)各问一次
84
+ * {@link resolveSymlinkTarget}(与目录迁移的读口同一只判官):任一座是悬空链接 ⇒ 抛 {@link danglingSymlinkSentence} 那一句(在场却读不出,不是缺席);都不是 ⇒ `undefined`(路径上真的没有条目)。
85
+ * 别的读不出(`EACCES` / `EISDIR` / `ELOOP` …)原样抛。配置根自己是悬空链接(祖先座)不在问的范围内:`dirSeats` 只列文件所在的那一级布局座。 */
86
+ export async function readTextIfPresent(file, dirSeats = []) {
87
+ try {
88
+ return await readFile(file, "utf8");
89
+ }
90
+ catch (e) {
91
+ if (e.code !== "ENOENT")
92
+ throw e;
93
+ }
94
+ for (const seat of [file, ...dirSeats])
95
+ resolveSymlinkTarget(seat);
96
+ return undefined;
97
+ }
78
98
  /** 原子写 `text`(UTF-8)到 `target`(符号链接则写到它指向的真文件,见文件头);模式按 `kind` 在 {@link ATOMIC_FILE_CLASSES} 里的行。父目录须已存在。 */
79
99
  export function writeFileAtomic(target, text, kind) {
80
100
  const real = resolveSymlinkTarget(target);
package/dist/bundle.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { type PortableDomain } from "./portable-domains.js";
2
2
  import type { ConfigReader } from "./reader.js";
3
- import type { ConfigDocumentRefusal } from "./config-document-refusal.js";
4
3
  /** A bundle carries PORTABLE domains only — the non-portable ones (`NON_PORTABLE_DOMAIN_REASONS`: workers / hosts / systems / projects /
5
4
  * entitlement) never travel, and the three bundle functions are that table's other face (types narrow to {@link PortableDomain}; a
6
5
  * JS / `as` caller is refused at run time). */
@@ -20,20 +19,3 @@ export declare function writeBundle(root: string, bundle: ConfigBundle): Promise
20
19
  * `FileConfigStore`: carrying facts ⇒ `RetiredDomainError` (a bundle assembled from the remaining files would silently drop those
21
20
  * facts); absent / `{}` ⇒ left alone. */
22
21
  export declare function readBundle(root: string, domains?: readonly PortableDomain[]): Promise<ConfigBundle>;
23
- /** {@link inspectBundle} 的答案:`refusals` = 文件层拒了的每一份文档(空 ⇒ 这棵目录在文件层干净);`version` = 这一次读到的整棵目录的内容版本。 */
24
- export interface BundleInspection {
25
- /** 被拒的文档,逐份一条,次序确定:retired 域文档在前(墓碑表的声明次序),便携域按 `PORTABLE_DOMAINS` 的次序。 */
26
- readonly refusals: readonly ConfigDocumentRefusal[];
27
- /** 这一次读到的整棵目录(便携域 + retired 域的文档)的内容版本(稳定的 u53 整数):文档都干净时 = `FileConfigStore.getVersion()`;被拒的文档以「坏文档的指纹」参与
28
- * (与 `getEffective({ tolerant: true })` 对坏文件的版本哨兵同一只),所以同样的坏内容同一个数、坏内容变了换数(指纹取 trim 之后的文本:只差首尾空白的两份坏文件同数)、修好后回到内容版本,绝不与「文件缺席」同数。 */
29
- readonly version: number;
30
- }
31
- /**
32
- * 一次读完 `<root>/config.d/` 里契约读的每一份文档(全部便携域 + retired 域的墓碑文档),**不抛**文件层的拒,把它们交成类型化的拒单({@link BundleInspection})。
33
- * 与 {@link readBundle} / `FileConfigStore` 的严读读的是同一只文件读法(`./config-d-file` 的读数)—— 同一种坏形同一个词、同一句话;区别只在渲染:严读遇到第一份坏文档就抛,
34
- * 本函数把整个目录读完、逐份交出(retired 域文档带事实 ⇒ 一条 `retired_domain` 墓碑拒,带迁移编号;retired 域文档自己读不出 ⇒ 归到那一份的文件层拒)。
35
- *
36
- * 只覆盖**文件层与墓碑**:文档读得出、却过不了域的 schema(门控域判形失败)、内容哈希深度闸这类拒,不在这里 —— 它们属于严读的后半段(`getEffective` 抛的)。
37
- * 目录读口自己的错(路径不是合法的域名这类调用错)照旧抛。每份文档只读一次,答案出自这一次读到的字节(没有「先抛、再重探」的第二次读)。
38
- */
39
- export declare function inspectBundle(root: string): Promise<BundleInspection>;
package/dist/bundle.js CHANGED
@@ -10,9 +10,7 @@
10
10
  */
11
11
  import { writeDomainFile } from "./file-edit.js";
12
12
  import { PORTABLE_DOMAINS } from "./portable-domains.js";
13
- import { RETIRED_DOMAIN_NAMES, configDPath, configDVersionOf, corruptDocumentRaw, readConfigDFile, readConfigDFileReading, refuseRetiredDomainFiles } from "./config-d-file.js";
14
- import { RetiredDomainError, refuseRetiredDomain } from "./config-fns.js";
15
- import { isRetiredDomainName } from "./types.js";
13
+ import { configDPath, readConfigDFile, refuseRetiredDomainFiles } from "./config-d-file.js";
16
14
  /** The runtime guard of the same rule (a name that is not even a domain is refused first — no path traversal). */
17
15
  function assertPortable(d) {
18
16
  if (!PORTABLE_DOMAINS.includes(d)) {
@@ -68,54 +66,3 @@ export async function readBundle(root, domains = PORTABLE_DOMAINS) {
68
66
  }));
69
67
  return out;
70
68
  }
71
- /**
72
- * 一次读完 `<root>/config.d/` 里契约读的每一份文档(全部便携域 + retired 域的墓碑文档),**不抛**文件层的拒,把它们交成类型化的拒单({@link BundleInspection})。
73
- * 与 {@link readBundle} / `FileConfigStore` 的严读读的是同一只文件读法(`./config-d-file` 的读数)—— 同一种坏形同一个词、同一句话;区别只在渲染:严读遇到第一份坏文档就抛,
74
- * 本函数把整个目录读完、逐份交出(retired 域文档带事实 ⇒ 一条 `retired_domain` 墓碑拒,带迁移编号;retired 域文档自己读不出 ⇒ 归到那一份的文件层拒)。
75
- *
76
- * 只覆盖**文件层与墓碑**:文档读得出、却过不了域的 schema(门控域判形失败)、内容哈希深度闸这类拒,不在这里 —— 它们属于严读的后半段(`getEffective` 抛的)。
77
- * 目录读口自己的错(路径不是合法的域名这类调用错)照旧抛。每份文档只读一次,答案出自这一次读到的字节(没有「先抛、再重探」的第二次读)。
78
- */
79
- export async function inspectBundle(root) {
80
- const documents = [...RETIRED_DOMAIN_NAMES, ...PORTABLE_DOMAINS];
81
- const readings = await Promise.all(documents.map((d) => readConfigDFileReading(root, d)));
82
- const raws = {};
83
- const refusals = [];
84
- documents.forEach((document, i) => {
85
- const reading = readings[i];
86
- switch (reading.status) {
87
- case "absent":
88
- raws[document] = undefined;
89
- return;
90
- case "refused":
91
- raws[document] = corruptDocumentRaw(reading);
92
- refusals.push({ document, reason: reading.reason, error: reading.error });
93
- return;
94
- case "present":
95
- raws[document] = reading.value;
96
- if (isRetiredDomainName(document)) {
97
- const tombstone = tombstoneOf(document, reading.value);
98
- if (tombstone !== undefined)
99
- refusals.push(tombstone);
100
- }
101
- return;
102
- default: {
103
- const unhandled = reading;
104
- throw new Error(`inspectBundle: unhandled reading ${String(unhandled)}`);
105
- }
106
- }
107
- });
108
- return { refusals, version: configDVersionOf(raws) };
109
- }
110
- /** 墓碑判一次(与严读同一只判官 `refuseRetiredDomain`、同一句):retired 域文档带事实 ⇒ 一条 `retired_domain` 拒;缺席 / `{}` ⇒ `undefined`。 */
111
- function tombstoneOf(domain, raw) {
112
- try {
113
- refuseRetiredDomain(domain, raw);
114
- return undefined;
115
- }
116
- catch (e) {
117
- if (e instanceof RetiredDomainError)
118
- return { document: e.domain, reason: "retired_domain", error: e.message, migration: e.migration };
119
- throw e;
120
- }
121
- }
package/dist/cli.d.ts CHANGED
@@ -35,5 +35,5 @@ export interface SettingsSchemaCliIo extends ConfigDirectoryIo {
35
35
  stderr(text: string): void;
36
36
  }
37
37
  /** 命令行的覆盖面:全部配置域、retired 域(便携与否不论 —— 离线的 `workers.json` 一样迁)与 `remote-exec.json`。 */
38
- export declare const COVERED_DOCUMENTS: ("models" | "rosters" | "scenarios" | "skills" | "execution" | "plugins" | "systems" | "limits" | "runtime" | "workers" | "hosts" | "projects" | "mcp" | "collab" | "governance" | "entitlement" | "prompts" | "remote-exec")[];
38
+ export declare const COVERED_DOCUMENTS: ("remote-exec" | "models" | "rosters" | "scenarios" | "skills" | "execution" | "plugins" | "systems" | "limits" | "runtime" | "workers" | "hosts" | "projects" | "mcp" | "collab" | "governance" | "entitlement" | "prompts")[];
39
39
  export declare function runSettingsSchemaCli(argv: readonly string[], io: SettingsSchemaCliIo): Promise<number>;
@@ -7,11 +7,11 @@ export declare function configDDocumentText(value: unknown): string;
7
7
  /** 判一份 remote-exec 文档的值(`RemoteExecSpec`),错误**脱敏**(zod 会回显出错的值 —— 粘错位置的密钥不许随抛出的错走);反射访问抛(恶意 getter /
8
8
  * Proxy)⇒ 定句。`loadRemoteExec` / `writeRemoteExec` 与目录迁移的整文档终判调同一只。 */
9
9
  export declare function parseRemoteExecDocument(raw: unknown): RemoteExecSpec;
10
- /** `<root>/config.d/<名>.json` 的路径 —— 名字的判也在这里(**一处**):不是配置域也不是 retired 域 ⇒ 拒(不拿用户给的名拼路径,防 `../` 穿越)。 */
11
- export declare function configDPath(root: string, name: string): string;
12
10
  /** `config.d` 里能放的文档名:配置域、retired 域、`remote-exec`(`config.d/remote-exec.json`,不是配置域)。 */
13
11
  export type ConfigDDocumentName = DomainName | RetiredDomainName | typeof REMOTE_EXEC_DOCUMENT;
14
- /** 文件层的拒因(闭集词里除墓碑外的四个)。 */
12
+ /** `<root>/config.d/<名>.json` 的路径 —— 名字的判也在这里(**一处**):不是 `config.d` 里能放的文档名(配置域、retired 域、`remote-exec`)⇒ 拒(不拿用户给的名拼路径,防 `../` 穿越)。 */
13
+ export declare function configDPath(root: string, name: string): string;
14
+ /** 文件层的拒因(闭集词里除墓碑外的五个)。 */
15
15
  export type ConfigDFileReason = Exclude<ConfigDocumentRefusalReason, "retired_domain">;
16
16
  /** 一份在场文档被拒的读数:`reason` 闭集词 · `error` 整句 · `thrown` 旧抛出口抛的那个对象(渲染成抛时原样抛它)· `fingerprint` 这份坏文档的指纹(版本哈希里的坏文件哨兵用它:
17
17
  * 文本路径取 trim 后原文的哈希 —— 不同坏内容不同数;读不出文本的 `io` 取错误文案的哈希)。 */
@@ -30,24 +30,25 @@ export type ConfigDReading = {
30
30
  readonly value: Record<string, unknown>;
31
31
  } | ConfigDRefused;
32
32
  /** 一个 `config.d/<名>.json` 在场时的**文本** → 读数(纯判,零 I/O;见文件头的律):一个 JSON 对象 ⇒ 在场;空 / 纯空白、坏 JSON、
33
- * 不是对象 ⇒ 被拒,每种一个词一句话(点名 `file` —— 调用方读的那份文件的真实路径 —— 与形,句尾 {@link documentWayOut})。 */
33
+ * 不是对象、嵌得过深({@link MAX_CONFIG_D_DOCUMENT_DEPTH})⇒ 被拒,每种一个词一句话(点名 `file` —— 调用方读的那份文件的真实路径 —— 与形,句尾 {@link documentWayOut})。 */
34
34
  export declare function judgeConfigDTextReading(name: ConfigDDocumentName, text: string, file: string): Exclude<ConfigDReading, {
35
35
  readonly status: "absent";
36
36
  }>;
37
- /** 抛出口:一个 `config.d/<名>.json` 在场时的**文本** → 它的值:读数是被拒 ⇒ 抛它的 `thrown`(空 / 坏 JSON / 不是对象各一句)。读盘的入口(`readConfigDFile`、`loadRemoteExec`)
37
+ /** 抛出口:一个 `config.d/<名>.json` 在场时的**文本** → 它的值:读数是被拒 ⇒ 抛它的 `thrown`(空 / 坏 JSON / 不是对象各一句)。读盘的入口(`readConfigDFile`)
38
38
  * 与自己读文本的命令行(`migrate`)都过它 —— 一种坏形一句话,不论是哪份文档。 */
39
39
  export declare function judgeConfigDText(name: ConfigDDocumentName, text: string, file: string): Record<string, unknown>;
40
- /** 一份**已解析的**存量文档在场时的值(同步口 `migrateConfigDocuments` 收的是各层的文档原文,不是文件文本)→ 它自己:一个 JSON 对象 ⇒ 它;`null` / 数组 / 数 / 串 / 布尔 /
41
- * 非朴素对象 ⇒ 抛。{@link judgeConfigDText} 的最后一道判就是这只(文本解析出来之后过它),所以「不是对象」一种坏形一句话,不论文档是从文件文本来的还是调用方直接给的;
40
+ /** 一份**已解析的**存量文档在场时的值(同步口 `migrateConfigDocuments` 收的是各层的文档原文,不是文件文本)→ 它自己:一个嵌得不过深的 JSON 对象 ⇒ 它;`null` / 数组 / 数 / 串 / 布尔 /
41
+ * 非朴素对象、嵌得过深 ⇒ 抛。{@link judgeConfigDText} 的最后一道判就是这只(文本解析出来之后过它),所以「不是对象」「过深」各一种坏形一句话,不论文档是从文件文本来的还是调用方直接给的;
42
42
  * `label` = 句首的称呼(文本路径给文件的真实路径,值路径给 `the <名> document`)。 */
43
43
  export declare function judgeConfigDValue(name: ConfigDDocumentName, value: unknown, label: string): Record<string, unknown>;
44
- /** 读一个 `config.d/<名>.json` → 读数:不存在(ENOENT)⇒ 缺席;在场 ⇒ {@link judgeConfigDTextReading};在场却读不出(非 ENOENT 的 fs 错误)⇒ 被拒,词 `io`。 */
45
- export declare function readConfigDFileReading(root: string, name: DomainName | RetiredDomainName): Promise<ConfigDReading>;
46
- /** 抛出口:读一个 `config.d/<名>.json`:不存在(ENOENT)⇒ `undefined`;在场 ⇒ 它的值;被拒 ⇒ 抛读数的 `thrown`(见 {@link readConfigDFileReading})。 */
47
- export declare function readConfigDFile(root: string, name: DomainName | RetiredDomainName): Promise<Record<string, unknown> | undefined>;
44
+ /** 读一个 `config.d/<名>.json` → 读数:路径上没有任何条目 ⇒ 缺席;在场 ⇒ {@link judgeConfigDTextReading};在场却读不出 ⇒ 被拒,词 `io` —— 非 ENOENT 的 fs 错误,以及悬空的符号链接
45
+ * (文档本身或 `config.d` 目录:内核跟链接读到 ENOENT,但条目在场;句子是 `danglingSymlinkSentence`(`./atomic-write`)那一句,与目录迁移的读口同一句)。缺席与悬空的分辨在 `readTextIfPresent`。 */
46
+ export declare function readConfigDFileReading(root: string, name: ConfigDDocumentName): Promise<ConfigDReading>;
47
+ /** 抛出口:读一个 `config.d/<名>.json`:路径上没有任何条目 ⇒ `undefined`;在场 ⇒ 它的值;被拒 ⇒ 抛读数的 `thrown`(见 {@link readConfigDFileReading})。 */
48
+ export declare function readConfigDFile(root: string, name: ConfigDDocumentName): Promise<Record<string, unknown> | undefined>;
48
49
  /** 一棵 `config.d` 的原文快照:便携域与 retired 域的文档原文(缺席 ⇒ `undefined`),版本哈希的入参。 */
49
50
  export type ConfigDRaws = Partial<Record<DomainName | RetiredDomainName, unknown>>;
50
- /** 一棵 `config.d` 原文快照的内容版本(稳定的 u53 整数):同样的文档 ⇒ 同一个数。`FileConfigStore.getVersion()` 与 `inspectBundle` 读同一只。 */
51
+ /** 一棵 `config.d` 原文快照的内容版本(稳定的 u53 整数):同样的文档 ⇒ 同一个数。`FileConfigStore` 的 `getVersion()` / `getEffective()` / `read()` 读同一只。 */
51
52
  export declare function configDVersionOf(raws: ConfigDRaws): number;
52
53
  /** 一份被拒文档在版本哈希里的哨兵:`{ __corruptDomainFile: <坏文档的指纹> }` —— 绝不与「文件缺席」同字节(`stableStringify` 把 `undefined` 与 `null` 折成同样的字节,
53
54
  * 一份空 / 坏文件若读成缺席,会与「域缺席」同哈希,按版本轮询的消费方就漏掉一次变化),而且不同的坏内容换数:坏→修好、好→坏、坏内容 A→坏内容 B 都换版本。 */