@sema-ai/registry-core 0.10.3

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 (103) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +84 -0
  3. package/dist/api/auth-bridge.d.ts +332 -0
  4. package/dist/api/auth-bridge.d.ts.map +1 -0
  5. package/dist/api/auth-bridge.js +211 -0
  6. package/dist/api/auth-bridge.js.map +1 -0
  7. package/dist/api/auth.d.ts +258 -0
  8. package/dist/api/auth.d.ts.map +1 -0
  9. package/dist/api/auth.js +204 -0
  10. package/dist/api/auth.js.map +1 -0
  11. package/dist/api/scopes.d.ts +353 -0
  12. package/dist/api/scopes.d.ts.map +1 -0
  13. package/dist/api/scopes.js +229 -0
  14. package/dist/api/scopes.js.map +1 -0
  15. package/dist/bundle.d.ts +14 -0
  16. package/dist/bundle.d.ts.map +1 -0
  17. package/dist/bundle.js +68 -0
  18. package/dist/bundle.js.map +1 -0
  19. package/dist/config-fns.d.ts +236 -0
  20. package/dist/config-fns.d.ts.map +1 -0
  21. package/dist/config-fns.js +272 -0
  22. package/dist/config-fns.js.map +1 -0
  23. package/dist/cross-domain.d.ts +35 -0
  24. package/dist/cross-domain.d.ts.map +1 -0
  25. package/dist/cross-domain.js +119 -0
  26. package/dist/cross-domain.js.map +1 -0
  27. package/dist/file-edit.d.ts +37 -0
  28. package/dist/file-edit.d.ts.map +1 -0
  29. package/dist/file-edit.js +126 -0
  30. package/dist/file-edit.js.map +1 -0
  31. package/dist/file-store.d.ts +39 -0
  32. package/dist/file-store.d.ts.map +1 -0
  33. package/dist/file-store.js +142 -0
  34. package/dist/file-store.js.map +1 -0
  35. package/dist/fleet.d.ts +479 -0
  36. package/dist/fleet.d.ts.map +1 -0
  37. package/dist/fleet.js +303 -0
  38. package/dist/fleet.js.map +1 -0
  39. package/dist/hash.d.ts +33 -0
  40. package/dist/hash.d.ts.map +1 -0
  41. package/dist/hash.js +60 -0
  42. package/dist/hash.js.map +1 -0
  43. package/dist/hooks.d.ts +5478 -0
  44. package/dist/hooks.d.ts.map +1 -0
  45. package/dist/hooks.js +628 -0
  46. package/dist/hooks.js.map +1 -0
  47. package/dist/index.d.ts +23 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +27 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/local-load.d.ts +33 -0
  52. package/dist/local-load.d.ts.map +1 -0
  53. package/dist/local-load.js +164 -0
  54. package/dist/local-load.js.map +1 -0
  55. package/dist/migrate.d.ts +245 -0
  56. package/dist/migrate.d.ts.map +1 -0
  57. package/dist/migrate.js +543 -0
  58. package/dist/migrate.js.map +1 -0
  59. package/dist/node.d.ts +12 -0
  60. package/dist/node.d.ts.map +1 -0
  61. package/dist/node.js +12 -0
  62. package/dist/node.js.map +1 -0
  63. package/dist/reader.d.ts +19 -0
  64. package/dist/reader.d.ts.map +1 -0
  65. package/dist/reader.js +2 -0
  66. package/dist/reader.js.map +1 -0
  67. package/dist/remote-exec.d.ts +275 -0
  68. package/dist/remote-exec.d.ts.map +1 -0
  69. package/dist/remote-exec.js +183 -0
  70. package/dist/remote-exec.js.map +1 -0
  71. package/dist/resolve-roster.d.ts +29 -0
  72. package/dist/resolve-roster.d.ts.map +1 -0
  73. package/dist/resolve-roster.js +105 -0
  74. package/dist/resolve-roster.js.map +1 -0
  75. package/dist/safety-merge-spec.d.ts +328 -0
  76. package/dist/safety-merge-spec.d.ts.map +1 -0
  77. package/dist/safety-merge-spec.js +65 -0
  78. package/dist/safety-merge-spec.js.map +1 -0
  79. package/dist/scheduler-store-node.d.ts +15 -0
  80. package/dist/scheduler-store-node.d.ts.map +1 -0
  81. package/dist/scheduler-store-node.js +43 -0
  82. package/dist/scheduler-store-node.js.map +1 -0
  83. package/dist/scheduler-store.d.ts +65 -0
  84. package/dist/scheduler-store.d.ts.map +1 -0
  85. package/dist/scheduler-store.js +59 -0
  86. package/dist/scheduler-store.js.map +1 -0
  87. package/dist/secret-refs.d.ts +34 -0
  88. package/dist/secret-refs.d.ts.map +1 -0
  89. package/dist/secret-refs.js +49 -0
  90. package/dist/secret-refs.js.map +1 -0
  91. package/dist/sha256.d.ts +17 -0
  92. package/dist/sha256.d.ts.map +1 -0
  93. package/dist/sha256.js +115 -0
  94. package/dist/sha256.js.map +1 -0
  95. package/dist/skills-manifest.d.ts +13 -0
  96. package/dist/skills-manifest.d.ts.map +1 -0
  97. package/dist/skills-manifest.js +55 -0
  98. package/dist/skills-manifest.js.map +1 -0
  99. package/dist/types.d.ts +9911 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +1727 -0
  102. package/dist/types.js.map +1 -0
  103. package/package.json +130 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * TOC editor helpers — the read/write/VALIDATE surface the desktop/CLI builds its OWN config UX on top of
3
+ * (Clay's decision: only the LIB is shared, the TOC builds its own UI). These expose write/validate, not just
4
+ * read, so a single-machine consumer never needs the server's routes.
5
+ *
6
+ * Validation uses the SAME zod schemas as the server's parseDomain → identical rules on both sides.
7
+ */
8
+ import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { collectEnvRefs } from "./secret-refs.js";
11
+ import { RemoteExecSpec } from "./remote-exec.js";
12
+ import { parseDomain, sanitizeZodError } from "./config-fns.js";
13
+ import { DOMAIN_SCHEMAS, isDomainName } from "./types.js";
14
+ /** Throw a CLEAR error for a non-domain key instead of the cryptic `undefined.parse(...)` you'd get from
15
+ * `DOMAIN_SCHEMAS[badKey]`. The classic trip-wire: a consumer scans `config.d/*.json` and feeds every file
16
+ * (incl. the non-domain `remote-exec.json`) into writeBundle/writeDomainFile. `remote-exec` is the exec
17
+ * backend, NOT a domain — write it via writeRemoteExec. Filter arbitrary keys with `isDomainName` first. */
18
+ function assertDomainName(domain) {
19
+ if (!isDomainName(domain)) {
20
+ const hint = domain === "remote-exec" ? " — the exec backend is NOT a domain; use writeRemoteExec (config.d/remote-exec.json)" : "";
21
+ throw new Error(`"${domain}" is not a config domain (expected one of: ${DOMAINS_LIST})${hint}`);
22
+ }
23
+ }
24
+ const DOMAINS_LIST = Object.keys(DOMAIN_SCHEMAS).join(", ");
25
+ // Monotonic suffix so concurrent same-domain atomic writes in one process+ms can't collide on the tmp name.
26
+ let tmpSeq = 0;
27
+ /** VALIDATE a domain payload against its zod schema WITHOUT writing — the editor's "is this form valid?" check.
28
+ * Returns the parsed+defaulted value or the zod issues. Identical rules to the server's parseDomain. Throws
29
+ * (clear message) if `domain` is not a real domain name (a misuse, not invalid user data). */
30
+ export function validateDomain(domain, raw) {
31
+ assertDomainName(domain);
32
+ let r;
33
+ try {
34
+ r = DOMAIN_SCHEMAS[domain].safeParse(raw === undefined ? {} : raw); // explicit null is corrupt, not defaults (R4)
35
+ }
36
+ catch {
37
+ // a hostile getter/Proxy trap threw during traversal — never surface its (secret-bearing) message. R13 (codex).
38
+ return { ok: false, issues: [{ code: "custom", message: `invalid "${domain}" config (a value could not be read during validation)`, path: [] }] };
39
+ }
40
+ if (r.success)
41
+ return { ok: true, value: r.data };
42
+ return { ok: false, issues: sanitizeZodError(r.error).issues }; // recursive redaction (R13/R14) — no secret echo
43
+ }
44
+ /** Same, for the remote-exec contract (a standalone contract, not a DOMAIN_SCHEMAS member). */
45
+ export function validateRemoteExec(raw) {
46
+ let r;
47
+ try {
48
+ r = RemoteExecSpec.safeParse(raw);
49
+ }
50
+ catch {
51
+ return { ok: false, issues: [{ code: "custom", message: "invalid remoteExec (a value could not be read during validation)", path: [] }] };
52
+ }
53
+ return r.success ? { ok: true, value: r.data } : { ok: false, issues: sanitizeZodError(r.error).issues }; // no secret echo (R14)
54
+ }
55
+ /** Absolute path to a domain's JSON file under a config root (mirrors FileConfigStore.domainPath). Guards the
56
+ * name (no `../../secret` traversal even if a caller bypasses the DomainName type). */
57
+ export function domainFilePath(root, domain) {
58
+ assertDomainName(domain);
59
+ return path.join(root, "config.d", `${domain}.json`);
60
+ }
61
+ /** WRITE a domain file ATOMICALLY (write a tmp in the same dir → rename) so a crash never leaves a half-written
62
+ * config.d/<domain>.json. VALIDATES first (throws on invalid — never persists garbage); persists the parsed +
63
+ * defaulted value so the file is canonical. */
64
+ export async function writeDomainFile(root, domain, value) {
65
+ assertDomainName(domain); // a stray non-domain key (e.g. "remote-exec" from a config.d scan) fails LOUD + clear
66
+ const parsed = parseDomain(domain, value); // routes through the SANITIZED parser (explicit-null reject R4 + no secret echo R14)
67
+ const dir = path.join(root, "config.d");
68
+ await mkdir(dir, { recursive: true });
69
+ const dest = path.join(dir, `${domain}.json`);
70
+ const tmp = path.join(dir, `.${domain}.json.${process.pid}.${Date.now()}.${tmpSeq++}.tmp`);
71
+ try {
72
+ await writeFile(tmp, JSON.stringify(parsed, null, 2) + "\n", "utf8");
73
+ await rename(tmp, dest);
74
+ }
75
+ catch (e) {
76
+ await unlink(tmp).catch(() => { }); // never leave a half-written tmp behind on a write/rename failure
77
+ throw e;
78
+ }
79
+ return parsed;
80
+ }
81
+ /** LIST every env-NAME the current local config requires (across models/workers/mcp + the
82
+ * remote-exec backend), marking which are MISSING from the resolved env — the TOC "you still need to set
83
+ * these secrets" panel. Reuses collectEnvRefs for the domain side, then appends the remoteExec secret refs. */
84
+ export function listRequiredEnvNames(eff, remoteExec, resolvedEnv) {
85
+ const map = new Map();
86
+ const add = (envName, domain, detail) => {
87
+ if (!envName)
88
+ return;
89
+ const list = map.get(envName) ?? [];
90
+ list.push({ domain, detail });
91
+ map.set(envName, list);
92
+ };
93
+ for (const r of collectEnvRefs(eff))
94
+ for (const use of r.refs)
95
+ add(r.envName, use.domain, use.detail);
96
+ if (remoteExec) {
97
+ const re = remoteExec;
98
+ if (re.provider === "e2b") {
99
+ add(re.apiKeyEnv, "remoteExec", "e2b.apiKeyEnv");
100
+ for (const e of re.sandboxEnv ?? [])
101
+ add(e, "remoteExec", "e2b.sandboxEnv[]");
102
+ }
103
+ else if (re.provider === "k8s") {
104
+ add(re.tokenEnv, "remoteExec", "k8s.tokenEnv");
105
+ add(re.caCertEnv, "remoteExec", "k8s.caCertEnv");
106
+ if (re.s3Snapshot) {
107
+ add(re.s3Snapshot.accessKeyEnv, "remoteExec", "k8s.s3Snapshot.accessKeyEnv");
108
+ add(re.s3Snapshot.secretKeyEnv, "remoteExec", "k8s.s3Snapshot.secretKeyEnv");
109
+ }
110
+ }
111
+ else if (re.provider === "ssh") {
112
+ add(re.privateKeyEnv, "remoteExec", "ssh.privateKeyEnv");
113
+ }
114
+ else if (re.provider === "remote-docker") {
115
+ add(re.tlsCertEnv, "remoteExec", "remote-docker.tlsCertEnv");
116
+ add(re.tlsKeyEnv, "remoteExec", "remote-docker.tlsKeyEnv");
117
+ add(re.tlsCaEnv, "remoteExec", "remote-docker.tlsCaEnv");
118
+ add(re.sshKeyEnv, "remoteExec", "remote-docker.sshKeyEnv");
119
+ }
120
+ // adb / host / local-docker carry no secret env-NAME refs.
121
+ }
122
+ return [...map.entries()]
123
+ .map(([envName, refs]) => ({ envName, refs, present: resolvedEnv[envName] !== undefined }))
124
+ .sort((a, b) => a.envName.localeCompare(b.envName));
125
+ }
126
+ //# sourceMappingURL=file-edit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-edit.js","sourceRoot":"","sources":["../src/file-edit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { type EffectiveReadWarning } from "./config-fns.js";
2
+ import type { ConfigReader } from "./reader.js";
3
+ import { type DomainConfig, type DomainName, type EffectiveConfig } from "./types.js";
4
+ /** The domains a single-machine TOC carries (fleet-only workers/hosts/systems omitted — they default empty).
5
+ * `projects` (0.10.0) is ALSO deliberately excluded: the ledger is TENANT IDENTITY DATA, not portable
6
+ * config — bundling it into another deployment would copy identity registrations out of thin air (the
7
+ * design/142 §1.4 threat-model-① "carried-in foreign projectId" in import clothing). Export/带走 goes
8
+ * through the 142 §5 export face (service half), never the config bundle. */
9
+ export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "runtime", "governance", "execution"];
10
+ export type PortableDomain = (typeof PORTABLE_DOMAINS)[number];
11
+ export declare class FileConfigStore implements ConfigReader {
12
+ #private;
13
+ private readonly root;
14
+ private readonly opts;
15
+ /** `opts.onWarning` (0.8.1): receives buildEffective's read-degradation warnings (grandfathered hosts /
16
+ * domain defaulted — see {@link EffectiveReadWarning}) so a TOC shell can surface them; omit = resilient
17
+ * but silent. Additive — existing `new FileConfigStore(root)` callers are unchanged. */
18
+ constructor(root: string, opts?: {
19
+ onWarning?: (w: EffectiveReadWarning) => void;
20
+ });
21
+ /** Absolute path to a domain's JSON file. Guards the name (defence in depth — a non-domain string like
22
+ * `../../secret` must never be joined into a filesystem path, even if a caller bypasses the type). */
23
+ domainPath(domain: DomainName): string;
24
+ /** Read+parse one domain (validated, defaults filled), or `undefined` if the domain is ABSENT — matching
25
+ * the {@link ConfigReader} contract (reader.ts: absent domain → undefined) and the server stores'
26
+ * `has(domain) ? parse : undefined`. A present-but-empty file (`{}` / whitespace) IS a set domain → parsed
27
+ * with defaults; a missing file is absence → undefined. (getEffective still fills defaults for absent
28
+ * domains; getDomain is the per-domain "was this ever set?" probe.) */
29
+ getDomain<K extends DomainName>(domain: K): Promise<DomainConfig[K] | undefined>;
30
+ /** All portable domains → EffectiveConfig. The PAYLOAD and its `version` come from ONE raws snapshot (no
31
+ * re-read between them — else a concurrent atomic rename could pair payload A with version B and a consumer
32
+ * would skip the change). `updatedAt` = max file mtime (informational; the content-hash version is the
33
+ * authoritative change-detector). */
34
+ getEffective(): Promise<EffectiveConfig>;
35
+ /** Stable version number for the current files: same files ⇒ same number ⇒ a consumer's "skip re-apply if
36
+ * version unchanged" works locally too. */
37
+ getVersion(): Promise<number>;
38
+ }
39
+ //# sourceMappingURL=file-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAuBA,OAAO,EAA+B,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEzF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpG;;;;8EAI8E;AAC9E,eAAO,MAAM,gBAAgB,yHAA0J,CAAC;AACxL,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,qBAAa,eAAgB,YAAW,YAAY;;IAKhD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALvB;;6FAEyF;gBAEtE,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAA;KAAO;IAG/E;2GACuG;IACvG,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM;IAiEtC;;;;4EAIwE;IAClE,SAAS,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAWtF;;;0CAGsC;IAChC,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC;IAM9C;gDAC4C;IACtC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;CAGpC"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * FileConfigStore — a local, single-machine config source for the TOC desktop/CLI. Reads the PORTABLE domains
3
+ * from `<root>/config.d/<domain>.json` (one file per domain, each = exactly that domain's zod object) and
4
+ * resolves them into an {@link EffectiveConfig} via the SAME pure `buildEffective`/`parseDomain` the server
5
+ * stores use. Implements ONLY the narrow {@link ConfigReader} (3 methods) — no publish/RBAC/worker-status.
6
+ *
7
+ * LOCAL FILE LAYOUT (the TOC owns the root, e.g. `~/.ai-agent/` or a project-local `.ai-agent/`):
8
+ * <root>/config.d/{models,rosters,skills,mcp,plugins,scenarios,collab,runtime,governance,execution}.json
9
+ * <root>/.env secret VALUES (KEY=value) — the ONLY file holding secrets; gitignored, never read here.
10
+ *
11
+ * Rules:
12
+ * - A MISSING file (ENOENT) → the domain is absent → schema defaults fill in (parseDomain tolerates undefined).
13
+ * A present-but-EMPTY/whitespace file is a half-write → THROW (fail-closed, like remote-exec); to mean
14
+ * "absent/defaults" the file must be missing, not empty. A valid `{}` object IS a set domain (→ defaults).
15
+ * - FLEET-only domains (`workers`/`hosts`/`systems`) are OMITTED on a single machine (no reconciler); they
16
+ * resolve to their schema defaults (`{workers:[]}` etc.) — no stubbing needed.
17
+ * - The JSON files only ever carry env-NAMEs (the ENV_NAME boundary holds locally too); `.env` holds values.
18
+ * - `getVersion()` = a stable content hash of all portable files → a u53 number, so a consumer's
19
+ * "skip re-apply if version unchanged" works locally. `getEffective().updatedAt` = the max file mtime,
20
+ * passed EXPLICITLY into buildEffective (the determinism refactor) so identical files ⇒ identical bytes.
21
+ */
22
+ import { readFile, stat } from "node:fs/promises";
23
+ import path from "node:path";
24
+ import { buildEffective, parseDomain } from "./config-fns.js";
25
+ import { stableHash } from "./hash.js";
26
+ import { isDomainName } from "./types.js";
27
+ /** The domains a single-machine TOC carries (fleet-only workers/hosts/systems omitted — they default empty).
28
+ * `projects` (0.10.0) is ALSO deliberately excluded: the ledger is TENANT IDENTITY DATA, not portable
29
+ * config — bundling it into another deployment would copy identity registrations out of thin air (the
30
+ * design/142 §1.4 threat-model-① "carried-in foreign projectId" in import clothing). Export/带走 goes
31
+ * through the 142 §5 export face (service half), never the config bundle. */
32
+ export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "runtime", "governance", "execution"];
33
+ export class FileConfigStore {
34
+ root;
35
+ opts;
36
+ /** `opts.onWarning` (0.8.1): receives buildEffective's read-degradation warnings (grandfathered hosts /
37
+ * domain defaulted — see {@link EffectiveReadWarning}) so a TOC shell can surface them; omit = resilient
38
+ * but silent. Additive — existing `new FileConfigStore(root)` callers are unchanged. */
39
+ constructor(root, opts = {}) {
40
+ this.root = root;
41
+ this.opts = opts;
42
+ }
43
+ /** Absolute path to a domain's JSON file. Guards the name (defence in depth — a non-domain string like
44
+ * `../../secret` must never be joined into a filesystem path, even if a caller bypasses the type). */
45
+ domainPath(domain) {
46
+ if (!isDomainName(domain))
47
+ throw new Error(`"${domain}" is not a config domain — refusing to build a config.d path (no traversal)`);
48
+ return path.join(this.root, "config.d", `${domain}.json`);
49
+ }
50
+ /** Read + JSON.parse one domain file. A MISSING file (ENOENT) → undefined = the domain is ABSENT (the ONLY
51
+ * way to mean "use defaults"). A present file that is EMPTY/whitespace-only, or parses to anything but a JSON
52
+ * OBJECT (literal `null`, an array, a number/string/bool), is CORRUPT → throw, FAIL CLOSED — identical
53
+ * discipline to loadRemoteExec + the `file:` ref (an empty present file is a half-write/truncation, NOT
54
+ * "absent": treating it as absent would silently DROP a published domain — e.g. a truncated runtime.json
55
+ * drops its commandPolicy/autonomy guardrails and the consumer falls back to env). This also keeps
56
+ * getVersion in lock-step with getEffective/getDomain: stableStringify collapses `undefined` (absent) and
57
+ * `null` to the SAME bytes, so an empty/null file that resolved to undefined would hash IDENTICALLY to
58
+ * "domain absent" and a version-gated poller would skip a change getEffective/getDomain actually FAIL on.
59
+ * Every domain schema is a z.object, so a non-object/empty file is never valid — reject all those shapes
60
+ * here, once. R17 + R(codex 整体复审 0.1.27). */
61
+ async #readDomainRaw(domain) {
62
+ let text;
63
+ try {
64
+ text = await readFile(this.domainPath(domain), "utf8");
65
+ }
66
+ catch (e) {
67
+ if (e.code === "ENOENT")
68
+ return undefined;
69
+ throw e;
70
+ }
71
+ const trimmed = text.trim();
72
+ // FAIL CLOSED on an empty/whitespace present file (half-write/truncation), same as loadRemoteExec/file: —
73
+ // to mean "absent → defaults" the file must be MISSING (ENOENT), never present-but-empty.
74
+ if (!trimmed)
75
+ throw new Error(`config.d/${domain}.json is empty/whitespace — delete it for the domain default, or write a valid object`);
76
+ let parsed;
77
+ try {
78
+ parsed = JSON.parse(trimmed);
79
+ }
80
+ catch {
81
+ throw new Error(`invalid JSON in config.d/${domain}.json`); // sanitized: never echo the raw file text into the error
82
+ }
83
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
84
+ const kind = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
85
+ 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`);
86
+ }
87
+ return parsed;
88
+ }
89
+ /** Raw (unparsed) values for every PORTABLE domain, keyed by domain name (absent → undefined). */
90
+ async #readAllRaw() {
91
+ const out = {};
92
+ await Promise.all(PORTABLE_DOMAINS.map(async (d) => { out[d] = await this.#readDomainRaw(d); }));
93
+ return out;
94
+ }
95
+ /** The max mtime (ISO) across the present portable files, or the epoch when none exist — so `updatedAt`
96
+ * is deterministic for a given set of files (not the wall clock). */
97
+ async #maxMtimeIso() {
98
+ let maxMs = 0;
99
+ await Promise.all(PORTABLE_DOMAINS.map(async (d) => {
100
+ try {
101
+ const s = await stat(this.domainPath(d));
102
+ if (s.mtimeMs > maxMs)
103
+ maxMs = s.mtimeMs;
104
+ }
105
+ catch (e) {
106
+ if (e.code !== "ENOENT")
107
+ throw e;
108
+ }
109
+ }));
110
+ return new Date(maxMs).toISOString();
111
+ }
112
+ /** Read+parse one domain (validated, defaults filled), or `undefined` if the domain is ABSENT — matching
113
+ * the {@link ConfigReader} contract (reader.ts: absent domain → undefined) and the server stores'
114
+ * `has(domain) ? parse : undefined`. A present-but-empty file (`{}` / whitespace) IS a set domain → parsed
115
+ * with defaults; a missing file is absence → undefined. (getEffective still fills defaults for absent
116
+ * domains; getDomain is the per-domain "was this ever set?" probe.) */
117
+ async getDomain(domain) {
118
+ const raw = await this.#readDomainRaw(domain);
119
+ if (raw === undefined)
120
+ return undefined; // missing/empty file → domain absent (not schema defaults)
121
+ return parseDomain(domain, raw);
122
+ }
123
+ /** Content hash of a raws snapshot → a stable u53-ish version number (≤ Number.MAX_SAFE_INTEGER). */
124
+ #versionOf(raws) {
125
+ return parseInt(stableHash(raws).slice(0, 13), 16); // 13 hex digits ≤ 2^52, safely within u53
126
+ }
127
+ /** All portable domains → EffectiveConfig. The PAYLOAD and its `version` come from ONE raws snapshot (no
128
+ * re-read between them — else a concurrent atomic rename could pair payload A with version B and a consumer
129
+ * would skip the change). `updatedAt` = max file mtime (informational; the content-hash version is the
130
+ * authoritative change-detector). */
131
+ async getEffective() {
132
+ const raws = await this.#readAllRaw();
133
+ const updatedAt = await this.#maxMtimeIso();
134
+ return buildEffective(this.#versionOf(raws), (d) => raws[d], updatedAt, this.opts.onWarning);
135
+ }
136
+ /** Stable version number for the current files: same files ⇒ same number ⇒ a consumer's "skip re-apply if
137
+ * version unchanged" works locally too. */
138
+ async getVersion() {
139
+ return this.#versionOf(await this.#readAllRaw());
140
+ }
141
+ }
142
+ //# sourceMappingURL=file-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.js","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}