@telorun/kernel 0.12.0 → 0.13.2

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 (58) hide show
  1. package/LICENSE +2 -2
  2. package/dist/application-env.d.ts +24 -0
  3. package/dist/application-env.d.ts.map +1 -0
  4. package/dist/application-env.js +156 -0
  5. package/dist/application-env.js.map +1 -0
  6. package/dist/controller-loaders/npm-loader.d.ts +32 -8
  7. package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
  8. package/dist/controller-loaders/npm-loader.js +74 -101
  9. package/dist/controller-loaders/npm-loader.js.map +1 -1
  10. package/dist/controllers/module/import-controller.d.ts +3 -2
  11. package/dist/controllers/module/import-controller.d.ts.map +1 -1
  12. package/dist/controllers/module/import-controller.js +23 -25
  13. package/dist/controllers/module/import-controller.js.map +1 -1
  14. package/dist/controllers/resource-definition/resource-definition-controller.d.ts +1 -0
  15. package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
  16. package/dist/controllers/resource-definition/resource-definition-controller.js +3 -0
  17. package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
  18. package/dist/controllers/resource-definition/resource-template-controller.d.ts +5 -0
  19. package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -1
  20. package/dist/controllers/resource-definition/resource-template-controller.js +67 -6
  21. package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -1
  22. package/dist/internal-context.d.ts +25 -0
  23. package/dist/internal-context.d.ts.map +1 -0
  24. package/dist/internal-context.js +2 -0
  25. package/dist/internal-context.js.map +1 -0
  26. package/dist/kernel.d.ts +21 -1
  27. package/dist/kernel.d.ts.map +1 -1
  28. package/dist/kernel.js +109 -5
  29. package/dist/kernel.js.map +1 -1
  30. package/dist/manifest-schemas.d.ts +7 -23
  31. package/dist/manifest-schemas.d.ts.map +1 -1
  32. package/dist/manifest-schemas.js +18 -8
  33. package/dist/manifest-schemas.js.map +1 -1
  34. package/dist/manifest-sources/analysis-stamp.d.ts +25 -0
  35. package/dist/manifest-sources/analysis-stamp.d.ts.map +1 -0
  36. package/dist/manifest-sources/analysis-stamp.js +151 -0
  37. package/dist/manifest-sources/analysis-stamp.js.map +1 -0
  38. package/dist/resource-context.d.ts +2 -0
  39. package/dist/resource-context.d.ts.map +1 -1
  40. package/dist/resource-context.js +28 -0
  41. package/dist/resource-context.js.map +1 -1
  42. package/dist/schema-validator.d.ts +28 -0
  43. package/dist/schema-validator.d.ts.map +1 -1
  44. package/dist/schema-validator.js +161 -1
  45. package/dist/schema-validator.js.map +1 -1
  46. package/package.json +9 -6
  47. package/src/application-env.ts +216 -0
  48. package/src/controller-loaders/npm-loader.ts +78 -103
  49. package/src/controllers/module/import-controller.ts +33 -36
  50. package/src/controllers/resource-definition/resource-definition-controller.ts +6 -0
  51. package/src/controllers/resource-definition/resource-template-controller.ts +95 -7
  52. package/src/internal-context.ts +25 -0
  53. package/src/kernel.ts +130 -5
  54. package/src/manifest-schemas.ts +31 -11
  55. package/src/manifest-sources/analysis-stamp.ts +169 -0
  56. package/src/resource-context.ts +34 -0
  57. package/src/schema-validator.ts +178 -2
  58. package/dist/generated/runtime-deps.json +0 -6
@@ -1,18 +1,99 @@
1
1
  import { evaluate } from "@marcbachmann/cel-js";
2
2
  import { RuntimeError } from "@telorun/sdk";
3
3
  import AjvModule from "ajv";
4
+ import standaloneCodeMod from "ajv/dist/standalone/index.js";
4
5
  import addFormats from "ajv-formats";
6
+ import { createHash } from "node:crypto";
7
+ import * as fs from "node:fs";
8
+ import { createRequire } from "node:module";
9
+ import * as path from "node:path";
5
10
  import { formatAjvErrors } from "./manifest-schemas.js";
11
+ import { ManifestRootSchema } from "@telorun/templating";
6
12
  const Ajv = AjvModule.default ?? AjvModule;
13
+ // AJV's standalone subpath is CJS — the default export shows up as either
14
+ // the function itself or `.default` depending on how the bundler/loader
15
+ // rewrites it. Normalise once.
16
+ const standaloneCode = standaloneCodeMod.default ?? standaloneCodeMod;
17
+ /** `require` resolved from this file's URL — used to satisfy `ajv/dist/...`
18
+ * / `ajv-formats/...` imports embedded in standalone-compiled validators
19
+ * loaded back off disk. Anchored here so it always resolves through the
20
+ * kernel package's node_modules, regardless of where the cache file
21
+ * lives on disk. */
22
+ const kernelRequire = createRequire(import.meta.url);
23
+ /** Resolved AJV + ajv-formats versions, baked into every cache key so a
24
+ * pnpm/npm install that upgrades either package invalidates all stale
25
+ * `<hash>.cjs` files automatically. Standalone-compiled validators
26
+ * embed `require("ajv/dist/runtime/...")` — running a validator built
27
+ * against an older AJV against the current runtime is undefined
28
+ * behaviour, so the version pin must be part of the hash, not a manual
29
+ * bump. Falls back to walking up from the package's main entry when
30
+ * the dependency restricts subpath access via `exports`. */
31
+ function readDepVersion(spec) {
32
+ try {
33
+ const pkg = kernelRequire(`${spec}/package.json`);
34
+ if (typeof pkg.version === "string")
35
+ return pkg.version;
36
+ }
37
+ catch {
38
+ // restricted exports — try the filesystem walk below
39
+ }
40
+ try {
41
+ const entry = kernelRequire.resolve(spec);
42
+ let dir = path.dirname(entry);
43
+ while (dir !== path.dirname(dir)) {
44
+ const candidate = path.join(dir, "package.json");
45
+ try {
46
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
47
+ const expectedName = spec.split("/").slice(0, spec.startsWith("@") ? 2 : 1).join("/");
48
+ if (typeof pkg.name === "string" && pkg.name === expectedName) {
49
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
50
+ }
51
+ }
52
+ catch {
53
+ // keep walking — not at the package root yet
54
+ }
55
+ dir = path.dirname(dir);
56
+ }
57
+ }
58
+ catch {
59
+ // package not installed
60
+ }
61
+ return "unknown";
62
+ }
63
+ const AJV_VERSION = readDepVersion("ajv");
64
+ const AJV_FORMATS_VERSION = readDepVersion("ajv-formats");
65
+ const VALIDATOR_RUNTIME_TAG = `ajv@${AJV_VERSION}+ajv-formats@${AJV_FORMATS_VERSION}`;
66
+ const SHA256_HEADER_PATTERN = /^\/\/ sha256:([0-9a-f]{64})\n/;
67
+ /** Verify a cached validator file's SHA-256 integrity header and return
68
+ * the body when the digest matches. Returns `null` on any mismatch /
69
+ * malformed header — the caller treats that as a cache miss and
70
+ * recompiles + overwrites the file. */
71
+ function verifyAndExtractBody(text) {
72
+ const match = text.match(SHA256_HEADER_PATTERN);
73
+ if (!match)
74
+ return null;
75
+ const body = text.slice(match[0].length);
76
+ const actual = createHash("sha256").update(body).digest("hex");
77
+ return actual === match[1] ? body : null;
78
+ }
7
79
  export class SchemaValidator {
8
80
  constructor() {
9
81
  this.typeRules = new Map();
10
82
  this.rawSchemas = new Map();
11
83
  this.compiledValidators = new WeakMap();
84
+ /** Tracks (schema-hash → in-memory compiled validator) so two distinct
85
+ * but content-equal schema objects share one compile across the kernel
86
+ * process — `compiledValidators` is keyed by object identity and would
87
+ * miss those cases. */
88
+ this.hashCache = new Map();
12
89
  this.ajv = new Ajv({
13
90
  strict: false,
14
91
  removeAdditional: false,
15
92
  useDefaults: true,
93
+ // Required for `standaloneCode` extraction — tells AJV to keep the
94
+ // generated validator's source available rather than wrapping it
95
+ // through `new Function`. The cost at compile time is negligible.
96
+ code: { source: true },
16
97
  });
17
98
  addFormats.default(this.ajv);
18
99
  for (const kw of [
@@ -29,6 +110,10 @@ export class SchemaValidator {
29
110
  ]) {
30
111
  this.ajv.addKeyword(kw);
31
112
  }
113
+ // Register the shared manifest root so module schemas can
114
+ // `$ref: "telo://manifest#/$defs/ResourceRef"` without each manifest
115
+ // bundling its own copy. Mirrors the analyzer's createAjv().
116
+ this.ajv.addSchema(ManifestRootSchema);
32
117
  }
33
118
  addSchema(name, schema) {
34
119
  if (!this.ajv.getSchema(name)) {
@@ -45,6 +130,18 @@ export class SchemaValidator {
45
130
  getTypeRules(name) {
46
131
  return this.typeRules.get(name);
47
132
  }
133
+ /** Enable the on-disk validator cache rooted at `dir`. Compiled AJV
134
+ * validators are written as standalone CJS modules keyed by content
135
+ * hash, so subsequent process invocations skip the ≈2–10 ms AJV
136
+ * codegen for each unseen schema. Safe to call before or after
137
+ * `compile()` — already-compiled in-memory entries are unaffected.
138
+ * The caller is responsible for choosing a writable directory; the
139
+ * kernel anchors this under `<entry-dir>/.telo/manifests/__validators/`
140
+ * so it lives next to the manifest cache and rides along in
141
+ * `COPY --from=build /srv /srv` Docker images. */
142
+ setCacheDir(dir) {
143
+ this.cacheDir = dir;
144
+ }
48
145
  compile(schema) {
49
146
  if (schema && typeof schema === "object") {
50
147
  const cached = this.compiledValidators.get(schema);
@@ -74,7 +171,18 @@ export class SchemaValidator {
74
171
  },
75
172
  }
76
173
  : normalized;
77
- const validate = this.ajv.compile(injected);
174
+ const hash = createHash("sha256")
175
+ .update(JSON.stringify({ runtime: VALIDATOR_RUNTIME_TAG, schema: injected }))
176
+ .digest("hex")
177
+ .slice(0, 32);
178
+ const cachedByHash = this.hashCache.get(hash);
179
+ if (cachedByHash) {
180
+ if (schema && typeof schema === "object") {
181
+ this.compiledValidators.set(schema, cachedByHash);
182
+ }
183
+ return cachedByHash;
184
+ }
185
+ const validate = this.compileAjvOrLoadCached(injected, hash);
78
186
  const validator = {
79
187
  validate: (data) => {
80
188
  const isValid = validate(data);
@@ -86,11 +194,63 @@ export class SchemaValidator {
86
194
  return validate(data);
87
195
  },
88
196
  };
197
+ this.hashCache.set(hash, validator);
89
198
  if (schema && typeof schema === "object") {
90
199
  this.compiledValidators.set(schema, validator);
91
200
  }
92
201
  return validator;
93
202
  }
203
+ /** Load `<cacheDir>/<hash>.cjs` if present, else compile via AJV and
204
+ * persist as standalone CJS. Cached files start with a
205
+ * `// sha256:<hex>\n` header covering the rest of the file; a
206
+ * mismatch (truncated write, FS corruption, tampering inside a baked
207
+ * Docker image) is treated as a cache miss and the validator is
208
+ * recompiled — and overwritten — so the cache self-heals. The cached
209
+ * body is wrapped so its embedded `require("ajv/...")` /
210
+ * `require("ajv-formats/...")` calls resolve against the kernel
211
+ * package; the cache file lives outside any `node_modules` tree, so a
212
+ * bare `require()` from its own path would fail. Read/write failures
213
+ * surface to stderr but never abort compilation. */
214
+ compileAjvOrLoadCached(schema, hash) {
215
+ const cacheDir = this.cacheDir;
216
+ if (cacheDir) {
217
+ const cachePath = path.join(cacheDir, `${hash}.cjs`);
218
+ try {
219
+ const text = fs.readFileSync(cachePath, "utf-8");
220
+ const body = verifyAndExtractBody(text);
221
+ if (body !== null) {
222
+ const factory = new Function("require", "module", "exports", `${body}\nreturn module.exports;`);
223
+ const mod = { exports: {} };
224
+ const loaded = factory(kernelRequire, mod, mod.exports);
225
+ if (typeof loaded === "function") {
226
+ return loaded;
227
+ }
228
+ }
229
+ // Header missing / mismatched / non-function export — fall
230
+ // through and recompile. The write step below overwrites the
231
+ // stale file with a fresh hash header.
232
+ }
233
+ catch (err) {
234
+ if (err?.code !== "ENOENT") {
235
+ process.stderr.write(`[telo:kernel] validator cache load failed (${hash}): ${err instanceof Error ? err.message : String(err)}\n`);
236
+ }
237
+ }
238
+ }
239
+ const validate = this.ajv.compile(schema);
240
+ if (cacheDir) {
241
+ try {
242
+ const body = standaloneCode(this.ajv, validate);
243
+ const integrity = createHash("sha256").update(body).digest("hex");
244
+ const payload = `// sha256:${integrity}\n${body}`;
245
+ fs.mkdirSync(cacheDir, { recursive: true });
246
+ fs.writeFileSync(path.join(cacheDir, `${hash}.cjs`), payload, "utf-8");
247
+ }
248
+ catch (err) {
249
+ process.stderr.write(`[telo:kernel] validator cache write failed (${hash}): ${err instanceof Error ? err.message : String(err)}\n`);
250
+ }
251
+ }
252
+ return validate;
253
+ }
94
254
  composeWithRules(base, typeName, rules) {
95
255
  return {
96
256
  validate: (data) => {
@@ -1 +1 @@
1
- {"version":3,"file":"schema-validator.js","sourceRoot":"","sources":["../src/schema-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAiB,YAAY,EAAY,MAAM,cAAc,CAAC;AACrE,OAAO,SAAS,MAAM,KAAK,CAAC;AAC5B,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC;AAE3C,MAAM,OAAO,eAAe;IAM1B;QAJQ,cAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC1C,eAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,uBAAkB,GAAG,IAAI,OAAO,EAAyB,CAAC;QAGhE,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC;YACjB,MAAM,EAAE,KAAK;YACb,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,KAAK,MAAM,EAAE,IAAI;YACf,YAAY;YACZ,aAAa;YACb,cAAc;YACd,gBAAgB;YAChB,qBAAqB;YACrB,yBAAyB;YACzB,oBAAoB;YACpB,sBAAsB;YACtB,qBAAqB;YACrB,eAAe;SAChB,EAAE,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,MAAc;QACpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,YAAY,CAAC,IAAY,EAAE,KAAiB;QAC1C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,CAAC,MAAW;QACjB,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAgB,CAAC,CAAC;YAC7D,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;QAC5B,CAAC;QAED,MAAM,YAAY,GAChB,CAAC,MAAM,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC;YACrD,OAAO,IAAI,MAAM;YACjB,OAAO,IAAI,MAAM;YACjB,OAAO,IAAI,MAAM;YACjB,MAAM,IAAI,MAAM,CAAC;QACnB,MAAM,UAAU,GAAG,YAAY;YAC7B,CAAC,CAAC,MAAM;YACR,CAAC,CAAC;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,MAAM;gBAClB,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC7B,oBAAoB,EAAE,KAAK;aAC5B,CAAC;QACN,MAAM,QAAQ,GACZ,UAAU,CAAC,oBAAoB,KAAK,KAAK;YACvC,CAAC,CAAC;gBACE,GAAG,UAAU;gBACb,UAAU,EAAE;oBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACxB,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBAC5B,GAAG,UAAU,CAAC,UAAU;iBACzB;aACF;YACH,CAAC,CAAC,UAAU,CAAC;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE5C,MAAM,SAAS,GAAG;YAChB,QAAQ,EAAE,CAAC,IAAS,EAAE,EAAE;gBACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,YAAY,CACpB,uCAAuC,EACvC,yBAAyB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAC5F,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,EAAE,CAAC,IAAS,EAAE,EAAE;gBACrB,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC;SACF,CAAC;QAEF,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAgB,EAAE,SAAS,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,gBAAgB,CAAC,IAAmB,EAAE,QAAgB,EAAE,KAAiB;QACvE,OAAO;YACL,QAAQ,EAAE,CAAC,IAAS,EAAE,EAAE;gBACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,MAAe,CAAC;oBACpB,IAAI,CAAC;wBACH,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;oBACpD,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,IAAI,YAAY,CACpB,4BAA4B,EAC5B,SAAS,QAAQ,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACjG,CAAC;oBACJ,CAAC;oBACD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;wBACpB,MAAM,IAAI,YAAY,CACpB,IAAI,CAAC,IAAI,IAAI,4BAA4B,EACzC,IAAI,CAAC,OAAO,IAAI,SAAS,QAAQ,8BAA8B,IAAI,CAAC,IAAI,iBAAiB,CAC1F,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO,EAAE,CAAC,IAAS,EAAE,EAAE;gBACrB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,CAAC;wBACH,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,IAAI;4BAAE,OAAO,KAAK,CAAC;oBACtE,CAAC;oBAAC,MAAM,CAAC;wBACP,OAAO,KAAK,CAAC;oBACf,CAAC;gBACH,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
1
+ {"version":3,"file":"schema-validator.js","sourceRoot":"","sources":["../src/schema-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAiB,YAAY,EAAY,MAAM,cAAc,CAAC;AACrE,OAAO,SAAoC,MAAM,KAAK,CAAC;AACvD,OAAO,iBAAiB,MAAM,8BAA8B,CAAC;AAC7D,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC;AAC3C,0EAA0E;AAC1E,wEAAwE;AACxE,+BAA+B;AAC/B,MAAM,cAAc,GACjB,iBAAyB,CAAC,OAAO,IAAK,iBAAyB,CAAC;AAEnE;;;;qBAIqB;AACrB,MAAM,aAAa,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAErD;;;;;;;6DAO6D;AAC7D,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,IAAI,eAAe,CAAC,CAAC;QAClD,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;IACvD,CAAC;IACD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YACjD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;gBAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACtF,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBAC9D,OAAO,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;gBACnE,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,6CAA6C;YAC/C,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AACD,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;AAC1C,MAAM,mBAAmB,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;AAC1D,MAAM,qBAAqB,GAAG,OAAO,WAAW,gBAAgB,mBAAmB,EAAE,CAAC;AAEtF,MAAM,qBAAqB,GAAG,+BAA+B,CAAC;AAE9D;;;wCAGwC;AACxC,SAAS,oBAAoB,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/D,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED,MAAM,OAAO,eAAe;IAY1B;QAVQ,cAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC1C,eAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,uBAAkB,GAAG,IAAI,OAAO,EAAyB,CAAC;QAElE;;;gCAGwB;QAChB,cAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;QAGnD,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC;YACjB,MAAM,EAAE,KAAK;YACb,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,IAAI;YACjB,mEAAmE;YACnE,iEAAiE;YACjE,kEAAkE;YAClE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;SACvB,CAAC,CAAC;QACH,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,KAAK,MAAM,EAAE,IAAI;YACf,YAAY;YACZ,aAAa;YACb,cAAc;YACd,gBAAgB;YAChB,qBAAqB;YACrB,yBAAyB;YACzB,oBAAoB;YACpB,sBAAsB;YACtB,qBAAqB;YACrB,eAAe;SAChB,EAAE,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;QACD,0DAA0D;QAC1D,qEAAqE;QACrE,6DAA6D;QAC7D,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IACzC,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,MAAc;QACpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,YAAY,CAAC,IAAY,EAAE,KAAiB;QAC1C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;;;uDAQmD;IACnD,WAAW,CAAC,GAAuB;QACjC,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;IACtB,CAAC;IAED,OAAO,CAAC,MAAW;QACjB,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAgB,CAAC,CAAC;YAC7D,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;QAC5B,CAAC;QAED,MAAM,YAAY,GAChB,CAAC,MAAM,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC;YACrD,OAAO,IAAI,MAAM;YACjB,OAAO,IAAI,MAAM;YACjB,OAAO,IAAI,MAAM;YACjB,MAAM,IAAI,MAAM,CAAC;QACnB,MAAM,UAAU,GAAG,YAAY;YAC7B,CAAC,CAAC,MAAM;YACR,CAAC,CAAC;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,MAAM;gBAClB,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC7B,oBAAoB,EAAE,KAAK;aAC5B,CAAC;QACN,MAAM,QAAQ,GACZ,UAAU,CAAC,oBAAoB,KAAK,KAAK;YACvC,CAAC,CAAC;gBACE,GAAG,UAAU;gBACb,UAAU,EAAE;oBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACxB,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBAC5B,GAAG,UAAU,CAAC,UAAU;iBACzB;aACF;YACH,CAAC,CAAC,UAAU,CAAC;QAEjB,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC;aAC9B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;aAC5E,MAAM,CAAC,KAAK,CAAC;aACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChB,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,YAAY,EAAE,CAAC;YACjB,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACzC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAgB,EAAE,YAAY,CAAC,CAAC;YAC9D,CAAC;YACD,OAAO,YAAY,CAAC;QACtB,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAE7D,MAAM,SAAS,GAAG;YAChB,QAAQ,EAAE,CAAC,IAAS,EAAE,EAAE;gBACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,YAAY,CACpB,uCAAuC,EACvC,yBAAyB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAC5F,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,EAAE,CAAC,IAAS,EAAE,EAAE;gBACrB,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC;SACF,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACpC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAgB,EAAE,SAAS,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;;;;;yDAUqD;IAC7C,sBAAsB,CAC5B,MAAW,EACX,IAAY;QAEZ,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;YACrD,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBACjD,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;gBACxC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAClB,MAAM,OAAO,GAAG,IAAI,QAAQ,CAC1B,SAAS,EACT,QAAQ,EACR,SAAS,EACT,GAAG,IAAI,0BAA0B,CAClC,CAAC;oBACF,MAAM,GAAG,GAAqB,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;oBAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;oBACxD,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;wBACjC,OAAO,MAA0B,CAAC;oBACpC,CAAC;gBACH,CAAC;gBACD,2DAA2D;gBAC3D,6DAA6D;gBAC7D,uCAAuC;YACzC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAK,GAA6B,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8CAA8C,IAAI,MAAM,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC7G,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAqB,CAAC;QAC9D,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBAChD,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAClE,MAAM,OAAO,GAAG,aAAa,SAAS,KAAK,IAAI,EAAE,CAAC;gBAClD,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC5C,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YACzE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+CAA+C,IAAI,MAAM,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC9G,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,gBAAgB,CAAC,IAAmB,EAAE,QAAgB,EAAE,KAAiB;QACvE,OAAO;YACL,QAAQ,EAAE,CAAC,IAAS,EAAE,EAAE;gBACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,MAAe,CAAC;oBACpB,IAAI,CAAC;wBACH,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;oBACpD,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,IAAI,YAAY,CACpB,4BAA4B,EAC5B,SAAS,QAAQ,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACjG,CAAC;oBACJ,CAAC;oBACD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;wBACpB,MAAM,IAAI,YAAY,CACpB,IAAI,CAAC,IAAI,IAAI,4BAA4B,EACzC,IAAI,CAAC,OAAO,IAAI,SAAS,QAAQ,8BAA8B,IAAI,CAAC,IAAI,iBAAiB,CAC1F,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO,EAAE,CAAC,IAAS,EAAE,EAAE;gBACrB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,CAAC;wBACH,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,IAAI;4BAAE,OAAO,KAAK,CAAC;oBACtE,CAAC;oBAAC,MAAM,CAAC;wBACP,OAAO,KAAK,CAAC;oBACf,CAAC;gBACH,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@telorun/kernel",
3
- "version": "0.12.0",
3
+ "version": "0.13.2",
4
4
  "description": "Telo Runtime - A lightweight, polyglot execution host.",
5
5
  "keywords": [
6
- "digly",
6
+ "telo",
7
7
  "runtime",
8
8
  "execution",
9
9
  "manifest"
@@ -47,8 +47,8 @@
47
47
  "dependencies": {
48
48
  "@marcbachmann/cel-js": "^7.5.3",
49
49
  "@sinclair/typebox": "^0.34.48",
50
- "@telorun/analyzer": "0.11.0",
51
- "@telorun/sdk": "0.11.1",
50
+ "@telorun/analyzer": "0.12.1",
51
+ "@telorun/templating": "0.3.0",
52
52
  "ajv": "^8.17.1",
53
53
  "ajv-formats": "^3.0.1",
54
54
  "minimatch": "^10.2.5",
@@ -60,11 +60,14 @@
60
60
  "typescript": "^5.0.0",
61
61
  "vitest": "^2.1.8"
62
62
  },
63
+ "peerDependencies": {
64
+ "@telorun/sdk": "0.12.0"
65
+ },
63
66
  "overrides": {
64
67
  "@marcbachmann/cel-js": "$@marcbachmann/cel-js",
65
68
  "@sinclair/typebox": "$@sinclair/typebox",
66
69
  "@telorun/analyzer": "$@telorun/analyzer",
67
- "@telorun/sdk": "$@telorun/sdk",
70
+ "@telorun/templating": "$@telorun/templating",
68
71
  "ajv": "$ajv",
69
72
  "ajv-formats": "$ajv-formats",
70
73
  "minimatch": "$minimatch",
@@ -72,7 +75,7 @@
72
75
  "yaml": "$yaml"
73
76
  },
74
77
  "scripts": {
75
- "build": "tsc && node ../../scripts/generate-runtime-deps.mjs .",
78
+ "build": "tsc",
76
79
  "dev": "tsc --watch",
77
80
  "test": "vitest run",
78
81
  "test:watch": "vitest"
@@ -0,0 +1,216 @@
1
+ import { residualEntrySchema } from "@telorun/analyzer";
2
+ import { RuntimeError } from "@telorun/sdk";
3
+ import { SchemaValidator } from "./schema-validator.js";
4
+
5
+ type EntryType = "string" | "integer" | "number" | "boolean" | "object" | "array";
6
+
7
+ interface EnvEntry {
8
+ env: string;
9
+ type: EntryType;
10
+ default?: unknown;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ export interface EnvResolutionResult {
15
+ variables: Record<string, unknown>;
16
+ secrets: Record<string, unknown>;
17
+ }
18
+
19
+ /**
20
+ * Populate the root Application's `variables` / `secrets` namespaces from
21
+ * host environment variables, per the per-field `env:` mapping declared on
22
+ * each entry.
23
+ *
24
+ * Implements the polyglot env-resolution spec from
25
+ * kernel/nodejs/plans/application-env-variables.md: read the env var, coerce
26
+ * per `entry.type`, validate the coerced value (or the declared default, when
27
+ * the env var is unset) against the entry's residual schema, and aggregate
28
+ * every failure into a single `ERR_MANIFEST_VALIDATION_FAILED` error so all
29
+ * problems surface before any controller initializes.
30
+ *
31
+ * This must run BEFORE any Telo.Import controller initializes — imports may
32
+ * pass `${{ variables.X }}` as their `variables:` inputs, so the root scope
33
+ * has to be populated by the time the import controller evaluates those
34
+ * expressions.
35
+ */
36
+ export function resolveApplicationEnv(
37
+ manifest: Record<string, any>,
38
+ env: Record<string, string | undefined>,
39
+ validator: SchemaValidator,
40
+ ): EnvResolutionResult {
41
+ const errors: string[] = [];
42
+ const variables = resolveBlock(
43
+ manifest.variables ?? {},
44
+ env,
45
+ validator,
46
+ errors,
47
+ false,
48
+ );
49
+ const secrets = resolveBlock(
50
+ manifest.secrets ?? {},
51
+ env,
52
+ validator,
53
+ errors,
54
+ true,
55
+ );
56
+ if (errors.length > 0) {
57
+ throw new RuntimeError(
58
+ "ERR_MANIFEST_VALIDATION_FAILED",
59
+ `Application environment validation failed:\n` +
60
+ errors.map((e) => ` - ${e}`).join("\n"),
61
+ );
62
+ }
63
+ return { variables, secrets };
64
+ }
65
+
66
+ function resolveBlock(
67
+ block: Record<string, EnvEntry> | unknown,
68
+ env: Record<string, string | undefined>,
69
+ validator: SchemaValidator,
70
+ errors: string[],
71
+ isSecret: boolean,
72
+ ): Record<string, unknown> {
73
+ const out: Record<string, unknown> = {};
74
+ if (!block || typeof block !== "object" || Array.isArray(block)) {
75
+ return out;
76
+ }
77
+ for (const [name, entry] of Object.entries(block as Record<string, EnvEntry>)) {
78
+ if (!entry || typeof entry !== "object") continue;
79
+ const envKey = entry.env;
80
+ const raw = env[envKey];
81
+ const residual = residualEntrySchema(entry as Record<string, unknown>);
82
+
83
+ if (raw === undefined || raw === null) {
84
+ if (entry.default !== undefined) {
85
+ const validation = validateResidual(entry.default, residual, validator);
86
+ if (validation) {
87
+ errors.push(`${name}: ${validation}`);
88
+ } else {
89
+ out[name] = entry.default;
90
+ }
91
+ continue;
92
+ }
93
+ errors.push(`${name}: environment variable ${envKey} is not set (no default)`);
94
+ continue;
95
+ }
96
+
97
+ let coerced: unknown;
98
+ try {
99
+ coerced = coerce(raw, entry.type, envKey, isSecret);
100
+ } catch (e) {
101
+ errors.push(`${name}: ${(e as Error).message}`);
102
+ continue;
103
+ }
104
+
105
+ const validation = validateResidual(coerced, residual, validator);
106
+ if (validation) {
107
+ errors.push(`${name}: ${validation}`);
108
+ continue;
109
+ }
110
+
111
+ out[name] = coerced;
112
+ }
113
+ return out;
114
+ }
115
+
116
+ /** Render a raw env value for inclusion in an error message. Secret values
117
+ * are masked so coercion / schema diagnostics don't leak secret material
118
+ * into logs (the env-var name and the failure reason still surface). */
119
+ function renderRawForError(raw: string, isSecret: boolean): string {
120
+ return isSecret ? "<redacted>" : `"${raw}"`;
121
+ }
122
+
123
+ function coerce(
124
+ raw: string,
125
+ type: EntryType,
126
+ envKey: string,
127
+ isSecret: boolean,
128
+ ): unknown {
129
+ switch (type) {
130
+ case "string":
131
+ return raw;
132
+ case "integer": {
133
+ const trimmed = raw.trim();
134
+ if (!/^-?\d+$/.test(trimmed)) {
135
+ throw new Error(
136
+ `environment variable ${envKey}: value ${renderRawForError(raw, isSecret)} is not a valid integer`,
137
+ );
138
+ }
139
+ return parseInt(trimmed, 10);
140
+ }
141
+ case "number": {
142
+ const n = parseFloat(raw);
143
+ if (Number.isNaN(n)) {
144
+ throw new Error(
145
+ `environment variable ${envKey}: value ${renderRawForError(raw, isSecret)} is not a valid number`,
146
+ );
147
+ }
148
+ return n;
149
+ }
150
+ case "boolean":
151
+ if (raw === "true") return true;
152
+ if (raw === "false") return false;
153
+ throw new Error(
154
+ `environment variable ${envKey}: value ${renderRawForError(raw, isSecret)} is not a valid boolean (expected "true" or "false")`,
155
+ );
156
+ case "object": {
157
+ const parsed = parseJson(raw, envKey, isSecret);
158
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
159
+ throw new Error(
160
+ `environment variable ${envKey}: expected JSON object, got ${describeJsonType(parsed)}`,
161
+ );
162
+ }
163
+ return parsed;
164
+ }
165
+ case "array": {
166
+ const parsed = parseJson(raw, envKey, isSecret);
167
+ if (!Array.isArray(parsed)) {
168
+ throw new Error(
169
+ `environment variable ${envKey}: expected JSON array, got ${describeJsonType(parsed)}`,
170
+ );
171
+ }
172
+ return parsed;
173
+ }
174
+ }
175
+ }
176
+
177
+ function parseJson(raw: string, envKey: string, isSecret: boolean): unknown {
178
+ try {
179
+ return JSON.parse(raw);
180
+ } catch (e) {
181
+ // Node's JSON.parse error embeds the offending character / position; for
182
+ // secrets, swallow the parser detail and surface only the env var name.
183
+ const detail = isSecret ? "value is not valid JSON" : (e as Error).message;
184
+ throw new Error(`environment variable ${envKey}: ${isSecret ? detail : `value is not valid JSON: ${detail}`}`);
185
+ }
186
+ }
187
+
188
+ function describeJsonType(value: unknown): string {
189
+ if (value === null) return "null";
190
+ if (Array.isArray(value)) return "array";
191
+ return typeof value;
192
+ }
193
+
194
+ function validateResidual(
195
+ value: unknown,
196
+ residual: Record<string, unknown>,
197
+ validator: SchemaValidator,
198
+ ): string | null {
199
+ try {
200
+ validator.compile(residual as any).validate(value);
201
+ return null;
202
+ } catch (e) {
203
+ const msg = e instanceof Error ? e.message : String(e);
204
+ // Strip SchemaValidator's "Invalid value passed: <JSON>. Error: " prefix
205
+ // so the JSON-stringified value (which can be secret material for entries
206
+ // under `secrets:`) never reaches the caller. The split is anchored on
207
+ // the literal ". Error: " delimiter — a `[^.]*` regex would have leaked
208
+ // any value containing a dot (URLs, versions, paths).
209
+ const sentinel = ". Error: ";
210
+ const idx = msg.indexOf(sentinel);
211
+ if (msg.startsWith("Invalid value passed:") && idx !== -1) {
212
+ return msg.slice(idx + sentinel.length);
213
+ }
214
+ return msg;
215
+ }
216
+ }