@telorun/kernel 0.2.5 → 0.2.7

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 (138) hide show
  1. package/dist/base-definition.d.ts +14 -0
  2. package/dist/base-definition.d.ts.map +1 -0
  3. package/dist/base-definition.js +17 -0
  4. package/dist/base-definition.js.map +1 -0
  5. package/dist/capabilities/component.d.ts +3 -0
  6. package/dist/capabilities/component.d.ts.map +1 -0
  7. package/dist/capabilities/component.js +4 -0
  8. package/dist/capabilities/component.js.map +1 -0
  9. package/dist/capabilities/executable.d.ts +3 -0
  10. package/dist/capabilities/executable.d.ts.map +1 -0
  11. package/dist/capabilities/executable.js +5 -0
  12. package/dist/capabilities/executable.js.map +1 -0
  13. package/dist/capabilities/handler.d.ts +3 -0
  14. package/dist/capabilities/handler.d.ts.map +1 -0
  15. package/dist/capabilities/handler.js +4 -0
  16. package/dist/capabilities/handler.js.map +1 -0
  17. package/dist/capabilities/invokable.d.ts +3 -0
  18. package/dist/capabilities/invokable.d.ts.map +1 -0
  19. package/dist/capabilities/invokable.js +5 -0
  20. package/dist/capabilities/invokable.js.map +1 -0
  21. package/dist/capabilities/listener.d.ts +3 -0
  22. package/dist/capabilities/listener.d.ts.map +1 -0
  23. package/dist/capabilities/listener.js +5 -0
  24. package/dist/capabilities/listener.js.map +1 -0
  25. package/dist/capabilities/mount.d.ts +3 -0
  26. package/dist/capabilities/mount.d.ts.map +1 -0
  27. package/dist/capabilities/mount.js +5 -0
  28. package/dist/capabilities/mount.js.map +1 -0
  29. package/dist/capabilities/provider.d.ts +3 -0
  30. package/dist/capabilities/provider.d.ts.map +1 -0
  31. package/dist/capabilities/provider.js +8 -0
  32. package/dist/capabilities/provider.js.map +1 -0
  33. package/dist/capabilities/runnable.d.ts +3 -0
  34. package/dist/capabilities/runnable.d.ts.map +1 -0
  35. package/dist/capabilities/runnable.js +5 -0
  36. package/dist/capabilities/runnable.js.map +1 -0
  37. package/dist/capabilities/service.d.ts +3 -0
  38. package/dist/capabilities/service.d.ts.map +1 -0
  39. package/dist/capabilities/service.js +5 -0
  40. package/dist/capabilities/service.js.map +1 -0
  41. package/dist/capabilities/template.d.ts +3 -0
  42. package/dist/capabilities/template.d.ts.map +1 -0
  43. package/dist/capabilities/template.js +5 -0
  44. package/dist/capabilities/template.js.map +1 -0
  45. package/dist/capabilities/type.d.ts +3 -0
  46. package/dist/capabilities/type.d.ts.map +1 -0
  47. package/dist/capabilities/type.js +5 -0
  48. package/dist/capabilities/type.js.map +1 -0
  49. package/dist/controller-loader.d.ts.map +1 -1
  50. package/dist/controller-loader.js +2 -1
  51. package/dist/controller-loader.js.map +1 -1
  52. package/dist/controller-registry.d.ts +0 -4
  53. package/dist/controller-registry.d.ts.map +1 -1
  54. package/dist/controller-registry.js +11 -22
  55. package/dist/controller-registry.js.map +1 -1
  56. package/dist/controllers/capability/capability-controller.d.ts +0 -5
  57. package/dist/controllers/capability/capability-controller.d.ts.map +1 -1
  58. package/dist/controllers/capability/capability-controller.js +1 -5
  59. package/dist/controllers/capability/capability-controller.js.map +1 -1
  60. package/dist/controllers/module/import-controller.d.ts +0 -3
  61. package/dist/controllers/module/import-controller.d.ts.map +1 -1
  62. package/dist/controllers/module/import-controller.js +21 -27
  63. package/dist/controllers/module/import-controller.js.map +1 -1
  64. package/dist/controllers/module/module-controller.d.ts +0 -59
  65. package/dist/controllers/module/module-controller.d.ts.map +1 -1
  66. package/dist/controllers/module/module-controller.js +0 -45
  67. package/dist/controllers/module/module-controller.js.map +1 -1
  68. package/dist/controllers/resource-definition/resource-definition-controller.d.ts +3 -4
  69. package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
  70. package/dist/controllers/resource-definition/resource-definition-controller.js +6 -8
  71. package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
  72. package/dist/controllers/resource-definition/resource-template-controller.d.ts +12 -0
  73. package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -0
  74. package/dist/controllers/resource-definition/resource-template-controller.js +112 -0
  75. package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -0
  76. package/dist/index.d.ts +2 -2
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +0 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/kernel.d.ts +38 -24
  81. package/dist/kernel.d.ts.map +1 -1
  82. package/dist/kernel.js +296 -201
  83. package/dist/kernel.js.map +1 -1
  84. package/dist/loader.d.ts +9 -8
  85. package/dist/loader.d.ts.map +1 -1
  86. package/dist/loader.js +50 -132
  87. package/dist/loader.js.map +1 -1
  88. package/dist/manifest-adapters/http-adapter.d.ts.map +1 -1
  89. package/dist/manifest-adapters/http-adapter.js +3 -1
  90. package/dist/manifest-adapters/http-adapter.js.map +1 -1
  91. package/dist/manifest-adapters/local-file-adapter.d.ts +7 -5
  92. package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
  93. package/dist/manifest-adapters/local-file-adapter.js +21 -26
  94. package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
  95. package/dist/manifest-adapters/manifest-adapter.d.ts +2 -0
  96. package/dist/manifest-adapters/manifest-adapter.d.ts.map +1 -1
  97. package/dist/manifest-adapters/registry-adapter.d.ts.map +1 -1
  98. package/dist/manifest-adapters/registry-adapter.js +3 -1
  99. package/dist/manifest-adapters/registry-adapter.js.map +1 -1
  100. package/dist/manifest-schemas.d.ts +61 -25
  101. package/dist/manifest-schemas.d.ts.map +1 -1
  102. package/dist/manifest-schemas.js +56 -16
  103. package/dist/manifest-schemas.js.map +1 -1
  104. package/dist/resource-context.d.ts +11 -10
  105. package/dist/resource-context.d.ts.map +1 -1
  106. package/dist/resource-context.js +70 -25
  107. package/dist/resource-context.js.map +1 -1
  108. package/dist/schema-valiator.d.ts +6 -1
  109. package/dist/schema-valiator.d.ts.map +1 -1
  110. package/dist/schema-valiator.js +76 -4
  111. package/dist/schema-valiator.js.map +1 -1
  112. package/package.json +6 -7
  113. package/src/controller-loader.ts +2 -1
  114. package/src/controller-registry.ts +11 -26
  115. package/src/controllers/module/import-controller.ts +30 -30
  116. package/src/controllers/module/module-controller.ts +0 -51
  117. package/src/controllers/resource-definition/resource-definition-controller.ts +14 -15
  118. package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
  119. package/src/index.ts +2 -2
  120. package/src/kernel.ts +364 -226
  121. package/src/manifest-adapters/local-file-adapter.ts +24 -31
  122. package/src/manifest-adapters/manifest-adapter.ts +2 -0
  123. package/src/manifest-schemas.ts +60 -24
  124. package/src/resource-context.ts +90 -35
  125. package/src/schema-valiator.ts +90 -13
  126. package/src/capabilities/component.yaml +0 -4
  127. package/src/capabilities/executable.yaml +0 -8
  128. package/src/capabilities/handler.yaml +0 -4
  129. package/src/capabilities/listener.yaml +0 -4
  130. package/src/capabilities/provider.yaml +0 -4
  131. package/src/capabilities/template.yaml +0 -4
  132. package/src/capabilities/type.yaml +0 -4
  133. package/src/controllers/capability/capability-controller.ts +0 -41
  134. package/src/controllers/module/module.json +0 -48
  135. package/src/controllers/module/module.yaml +0 -32
  136. package/src/loader.ts +0 -245
  137. package/src/manifest-adapters/http-adapter.ts +0 -35
  138. package/src/manifest-adapters/registry-adapter.ts +0 -56
package/dist/kernel.js CHANGED
@@ -1,9 +1,11 @@
1
- import { ModuleContext, RuntimeError, } from "@telorun/sdk";
1
+ import { AnalysisRegistry, Loader, StaticAnalyzer } from "@telorun/analyzer";
2
+ import { ModuleContext, RuntimeError, isCompiledValue, } from "@telorun/sdk";
2
3
  import * as path from "path";
4
+ import { parseArgs } from "util";
3
5
  import { ControllerRegistry } from "./controller-registry.js";
4
6
  import { EventStream } from "./event-stream.js";
5
7
  import { EventBus } from "./events.js";
6
- import { Loader } from "./loader.js";
8
+ import { LocalFileAdapter } from "./manifest-adapters/local-file-adapter.js";
7
9
  import { ResourceContextImpl } from "./resource-context.js";
8
10
  import { SchemaValidator } from "./schema-valiator.js";
9
11
  /**
@@ -11,40 +13,26 @@ import { SchemaValidator } from "./schema-valiator.js";
11
13
  * Handles resource loading, initialization, and execution through controllers
12
14
  */
13
15
  export class Kernel {
14
- constructor() {
16
+ constructor(options = {}) {
15
17
  this.loader = new Loader();
16
- // private manifests: ManifestRegistry = new ManifestRegistry();
18
+ this.analyzer = new StaticAnalyzer();
19
+ this.registry = new AnalysisRegistry();
17
20
  this.controllers = new ControllerRegistry();
18
21
  this.eventBus = new EventBus();
19
22
  this.eventStream = new EventStream();
20
- // private snapshotSerializer: SnapshotSerializer = new SnapshotSerializer();
21
- // private runtimeManifests: ResourceManifest[] | null = null;
22
- // private resourceInstances: Map<
23
- // string,
24
- // { resource: ResourceManifest; instance: ResourceInstance }
25
- // > = new Map();
26
- this.resourceEventBuses = new Map();
27
23
  this.holdCount = 0;
28
24
  this.idleResolvers = [];
29
25
  this._exitCode = 0;
30
- // private bootContextRegistry = new BootContextRegistry();
31
26
  this.sharedSchemaValidator = new SchemaValidator();
27
+ this.staticManifests = [];
28
+ this.stdin = options.stdin ?? process.stdin;
29
+ this.stdout = options.stdout ?? process.stdout;
30
+ this.stderr = options.stderr ?? process.stderr;
31
+ this.env = options.env ?? process.env;
32
+ this.argv = options.argv ?? [];
33
+ this.loader.register(new LocalFileAdapter());
32
34
  this.setupEventStreaming();
33
35
  }
34
- // async teardownResource(module: string, kind: string, name: string): Promise<void> {
35
- // const key = this.getResourceKey(module, kind, name);
36
- // const entry = this.resourceInstances.get(key);
37
- // if (!entry) return;
38
- // const { resource, instance } = entry;
39
- // if (instance.teardown) {
40
- // await instance.teardown();
41
- // }
42
- // await this.eventBus.emit(`${resource.kind}.${resource.metadata.name}.Teardown`, {
43
- // resource: { kind: resource.kind, name: resource.metadata.name },
44
- // });
45
- // // this.resourceInstances.delete(key);
46
- // this.resourceEventBuses.delete(key);
47
- // }
48
36
  async registerController(moduleName, kindName, controllerInstance) {
49
37
  this.controllers.registerController(`${moduleName}.${kindName}`, controllerInstance);
50
38
  await controllerInstance.register?.(this.createControllerContext(`${moduleName}.${kindName}`));
@@ -54,9 +42,7 @@ export class Kernel {
54
42
  */
55
43
  registerResourceDefinition(definition) {
56
44
  this.controllers.registerDefinition(definition);
57
- }
58
- registerCapability(name, schema) {
59
- this.controllers.registerCapability(name, schema);
45
+ this.registry.registerDefinition(definition);
60
46
  }
61
47
  getModuleContext(_moduleName) {
62
48
  return this.rootContext;
@@ -66,12 +52,13 @@ export class Kernel {
66
52
  }
67
53
  registerModuleImport(_declaringModule, alias, targetModule, kinds) {
68
54
  this.rootContext.registerImport(alias, targetModule, kinds);
55
+ this.registry.registerImport(alias, targetModule, kinds);
69
56
  }
70
- isCapabilityRegistered(name) {
71
- return this.controllers.isCapabilityRegistered(name);
72
- }
73
- getCapabilitySchema(name) {
74
- return this.controllers.getCapabilitySchema(name);
57
+ /** Returns the live analysis registry backed by this kernel's known definitions and aliases.
58
+ * Pass to StaticAnalyzer.analyze() for incremental validation of new manifests against
59
+ * already-registered types (e.g. front-end editor validating a manifest before submitting). */
60
+ getAnalysisRegistry() {
61
+ return this.registry;
75
62
  }
76
63
  /**
77
64
  * Load built-in Runtime definitions (e.g., Kernel.Module)
@@ -83,52 +70,57 @@ export class Kernel {
83
70
  // Declare built-in module namespaces upfront so getContext() can distinguish
84
71
  // "not yet populated" from a completely unknown module name.
85
72
  this.rootContext.registerImport("Kernel", "Kernel", []); // built-ins, unrestricted
86
- // this.moduleContextRegistry.declareModule("default"); // user resources with no module field
87
- this.controllers.registerDefinition({
88
- kind: "Kernel.Definition",
89
- metadata: { name: "Definition", module: "Kernel" },
90
- capabilities: ["template"],
91
- schema: { type: "object" },
92
- });
73
+ // Register built-in definitions with the controller registry.
74
+ // AnalysisRegistry's underlying DefinitionRegistry already seeds KERNEL_BUILTINS on construction.
75
+ for (const def of this.registry.builtinDefinitions())
76
+ this.controllers.registerDefinition(def);
93
77
  this.controllers.registerController("Kernel.Definition", await import("./controllers/resource-definition/resource-definition-controller.js"));
94
- this.controllers.registerDefinition({
95
- kind: "Kernel.Definition",
96
- metadata: { name: "Module", module: "Kernel" },
97
- capabilities: ["template"],
98
- schema: { type: "object" },
99
- });
100
78
  this.controllers.registerController("Kernel.Module", await import("./controllers/module/module-controller.js"));
101
- this.controllers.registerDefinition({
102
- kind: "Kernel.Definition",
103
- metadata: { name: "Import", module: "Kernel" },
104
- capabilities: ["template"],
105
- schema: { type: "object" },
106
- });
107
79
  this.controllers.registerController("Kernel.Import", await import("./controllers/module/import-controller.js"));
108
- this.controllers.registerDefinition({
109
- kind: "Kernel.Definition",
110
- metadata: { name: "Capability", module: "Kernel" },
111
- capabilities: ["template"],
112
- schema: { type: "object" },
113
- });
114
- this.controllers.registerController("Kernel.Capability", await import("./controllers/capability/capability-controller.js"));
115
80
  }
116
81
  /**
117
82
  * Load from runtime configuration file
118
83
  */
119
84
  async loadFromConfig(runtimeYamlPath) {
120
- this.rootContext = new ModuleContext(this.loader.resolvePath(`file://${process.cwd()}/`, runtimeYamlPath), {}, {}, {}, [], this._createInstance.bind(this), (event, payload) => this.eventBus.emit(event, payload));
85
+ const resolvedUrl = new URL(runtimeYamlPath, `file://${process.cwd()}/`).href;
86
+ const sourceUrl = await this.loader.resolveEntryPoint(resolvedUrl);
87
+ this.rootContext = new ModuleContext(sourceUrl, {}, {}, {}, [], this._createInstance.bind(this), (event, payload) => this.eventBus.emit(event, payload), this.env);
121
88
  // Initialize built-in Runtime definitions first
122
89
  await this.loadBuiltinDefinitions();
123
- // Load built-in capability manifests before user configuration so that
124
- // capability resources are available in the first initialization pass
125
- const { fileURLToPath } = await import("url");
126
- const capabilitiesDir = fileURLToPath(new URL("./capabilities/", import.meta.url));
127
- const capabilityManifests = await this.loader.loadDirectory(capabilitiesDir);
90
+ // Phase 5: attach injection hook fires between create() and init() for every resource
91
+ this.rootContext.preInitHook = (resource, getInstance) => this._injectDependencies(resource, getInstance);
92
+ // Static analysis pre-flight: validates schemas and invocation context compatibility.
93
+ // All errors are fatal — kernel does not start if analysis fails.
94
+ const staticManifests = await this.loader.loadManifests(sourceUrl);
95
+ this.staticManifests = staticManifests;
96
+ // Register module identities for x-telo-ref resolution (Phase 3 prerequisite).
97
+ // Kernel built-ins ("kernel" → "Kernel") are auto-registered when Kernel.Abstract
98
+ // definitions are registered in loadBuiltinDefinitions() above.
99
+ for (const m of staticManifests) {
100
+ if (m.kind === "Kernel.Module" && m.metadata?.name && m.metadata?.namespace) {
101
+ this.registry.registerModuleIdentity(m.metadata.namespace, m.metadata.name);
102
+ }
103
+ }
104
+ const errors = this.analyzer.analyzeErrors(staticManifests, {}, this.registry);
105
+ if (errors.length > 0) {
106
+ throw new RuntimeError("ERR_MANIFEST_VALIDATION_FAILED", "Manifest validation failed", errors.map((d) => ({
107
+ severity: "error",
108
+ message: d.message,
109
+ code: d.code !== undefined ? String(d.code) : undefined,
110
+ resource: d.data?.resource
111
+ ? `${d.data.resource.kind}.${d.data.resource.name}`
112
+ : undefined,
113
+ })));
114
+ }
128
115
  // Load runtime configuration — root module gets access to host env
129
- const userManifests = await this.loader.loadManifest(runtimeYamlPath, `file://${process.cwd()}/`, { env: process.env });
130
- const allManifests = [...capabilityManifests, ...userManifests];
131
- for (const manifest of allManifests) {
116
+ const allManifests = await this.loader.loadModule(sourceUrl, { compile: true });
117
+ // Phase 2: normalize inline resources — extract inline values from x-telo-ref slots
118
+ // into first-class named manifests and replace them in-place with {kind, name} references.
119
+ // Update staticManifests so Phase 3 (validateReferences) and Phase 4 (DAG) see
120
+ // the same normalized structure.
121
+ const normalizedManifests = this.analyzer.normalize(allManifests, this.registry);
122
+ this.staticManifests = normalizedManifests;
123
+ for (const manifest of normalizedManifests) {
132
124
  if (manifest.kind === "Kernel.Module") {
133
125
  this.rootContext.setSecrets(manifest.secrets ?? {});
134
126
  this.rootContext.setVariables(manifest.variables ?? {});
@@ -156,6 +148,26 @@ export class Kernel {
156
148
  await controller.register(this.createControllerContext(`controller:${kind}`));
157
149
  }
158
150
  }
151
+ // Phase 3+4: reference validation, cycle detection, and topo sort
152
+ const { diagnostics: refErrors, order, cycleError, } = this.analyzer.prepare(this.staticManifests, this.registry);
153
+ if (refErrors.length > 0) {
154
+ throw new RuntimeError("ERR_MANIFEST_VALIDATION_FAILED", "Manifest validation failed", refErrors.map((d) => ({
155
+ severity: "error",
156
+ message: d.message,
157
+ code: d.code !== undefined ? String(d.code) : undefined,
158
+ resource: d.data?.resource
159
+ ? `${d.data.resource.kind}.${d.data.resource.name}`
160
+ : undefined,
161
+ })));
162
+ }
163
+ if (cycleError) {
164
+ throw new RuntimeError("ERR_CIRCULAR_DEPENDENCY", cycleError);
165
+ }
166
+ // Phase 5: sort pending resources into topo order so injection always finds
167
+ // initialized dependencies, then run the init loop.
168
+ if (order) {
169
+ this.rootContext.setInitOrder(order);
170
+ }
159
171
  // Initialize resources
160
172
  try {
161
173
  await this.rootContext.initializeResources();
@@ -171,13 +183,6 @@ export class Kernel {
171
183
  await this.eventBus.emit("Kernel.Stopped", { exitCode: this._exitCode });
172
184
  }
173
185
  }
174
- // async runInstances(): Promise<void> {
175
- // for (const { instance } of this.resourceInstances.values()) {
176
- // if (instance.run) {
177
- // await instance.run();
178
- // }
179
- // }
180
- // }
181
186
  async emitRuntimeEvent(event, payload) {
182
187
  await this.eventBus.emit(event, payload);
183
188
  }
@@ -245,18 +250,49 @@ export class Kernel {
245
250
  void this.eventBus.emit(namespaced, payload);
246
251
  },
247
252
  acquireHold: (reason) => this.acquireHold(reason),
248
- expandValue: (value, context) => this.rootContext.merge(context).expand(value),
253
+ expandValue: (value, context) => this.rootContext.expandWith(value, context),
249
254
  requestExit: (code) => this.requestExit(code),
250
255
  };
251
256
  }
252
- createResourceContext(moduleContext, resource) {
253
- return new ResourceContextImpl(this, moduleContext, resource.metadata, this.sharedSchemaValidator);
257
+ createResourceContext(moduleContext, resource, args) {
258
+ return new ResourceContextImpl(this, moduleContext, resource.metadata, this.sharedSchemaValidator, this.stdin, this.stdout, this.stderr, args);
259
+ }
260
+ /**
261
+ * Parse kernel.argv using a controller's args spec (if present).
262
+ * If the controller exports no args spec, does a generic parse.
263
+ */
264
+ parseArgsForController(controller) {
265
+ if (this.argv.length === 0)
266
+ return { _: [] };
267
+ const argSpec = controller.args;
268
+ if (argSpec) {
269
+ const options = {};
270
+ for (const [name, def] of Object.entries(argSpec)) {
271
+ options[name] = { type: def.type ?? "string" };
272
+ if (def.alias)
273
+ options[name].short = def.alias;
274
+ }
275
+ const { values, positionals } = parseArgs({
276
+ args: this.argv,
277
+ options,
278
+ allowPositionals: true,
279
+ strict: false,
280
+ });
281
+ return { ...values, _: positionals };
282
+ }
283
+ // Generic parse: no spec, best-effort
284
+ const { values, positionals } = parseArgs({
285
+ args: this.argv,
286
+ allowPositionals: true,
287
+ strict: false,
288
+ });
289
+ return { ...values, _: positionals };
254
290
  }
255
291
  /**
256
- * Create a resource instance: resolves the controller, validates the schema,
257
- * calls create/init, registers context providers, stores the snapshot, and
258
- * mirrors the instance into the Kernel-level and module-level registries.
259
- * Returns null when the controller is not yet registered (retry signal).
292
+ * Create phase only: resolves the controller, validates the schema, and calls
293
+ * controller.create(). Returns { instance, ctx } so initializeResources can
294
+ * run init() separately in its second phase. Returns null when the controller
295
+ * is not yet registered (retry signal).
260
296
  */
261
297
  async _createInstance(evalContext, resource) {
262
298
  const kind = resource.kind;
@@ -274,134 +310,65 @@ export class Kernel {
274
310
  if (!controller.schema?.type) {
275
311
  throw new Error(`No schema defined for ${kind} controller`);
276
312
  }
313
+ // Resolve eval paths from x-telo-eval annotations in the parent and own schema
314
+ const definition = this.controllers.getDefinition(resolvedKind);
315
+ const parentDef = definition?.capability
316
+ ? this.controllers.getDefinition(definition.capability)
317
+ : undefined;
318
+ const parentEval = parentDef?.schema
319
+ ? buildEvalPaths(parentDef.schema)
320
+ : { compile: [], runtime: [] };
321
+ const ownEval = definition?.schema
322
+ ? buildEvalPaths(definition.schema)
323
+ : { compile: [], runtime: [] };
324
+ const compile = [...parentEval.compile, ...ownEval.compile];
325
+ const runtime = [...parentEval.runtime, ...ownEval.runtime];
326
+ // Schema validation runs before CEL evaluation so it sees the original manifest
327
+ // shape. CompiledValue wrappers (from load-time precompilation) are stripped,
328
+ // restoring the pre-CEL string view that the schema expects.
277
329
  try {
278
- this.sharedSchemaValidator.compile(controller.schema).validate(resource);
330
+ this.sharedSchemaValidator
331
+ .compile(controller.schema)
332
+ .validate(stripCompiledValues(resource, controller.schema));
279
333
  }
280
334
  catch (error) {
281
335
  throw new RuntimeError("ERR_RESOURCE_SCHEMA_VALIDATION_FAILED", `Resource does not match schema for kind ${kind}: ${error instanceof Error ? error.message : String(error)}`);
282
336
  }
283
- const ctx = this.createResourceContext(evalContext, resource);
284
- const instance = await controller.create(resource, ctx);
337
+ // Expand compile-time CEL fields before passing to the controller.
338
+ const processedResource = compile.length
339
+ ? evalContext.expandPaths(resource, compile, runtime)
340
+ : resource;
341
+ const parsedArgs = this.parseArgsForController(controller);
342
+ const ctx = this.createResourceContext(evalContext, processedResource, parsedArgs);
343
+ const instance = await controller.create(processedResource, ctx);
285
344
  if (!instance)
286
345
  return null;
287
- if (instance.init)
288
- await instance.init(ctx);
289
- // if (isContextProvider(instance)) {
290
- // this.bootContextRegistry.register(
291
- // resource.kind,
292
- // resource.metadata.name,
293
- // resource.metadata.module,
294
- // resource.grants as string[] | undefined,
295
- // instance.provideContext(),
296
- // );
297
- // }
298
- if (instance.snapshot) {
299
- const snap = await Promise.resolve(instance.snapshot()).catch(() => ({}));
300
- if (evalContext instanceof ModuleContext) {
301
- evalContext.setResource(resource.metadata.name, snap ?? {});
302
- }
303
- }
304
- return instance;
305
- }
306
- /**
307
- * Tear down all resource instances owned by a dynamically-spawned context,
308
- * cascading depth-first through its children. Removes entries from both
309
- * ctx.resourceInstances and the Kernel-level resourceInstances map, and
310
- * emits Teardown events (matching the behaviour of teardownResource()).
311
- */
312
- async teardownContext(ctx) {
313
- for (const child of [...ctx.children].reverse()) {
314
- await this.teardownContext(child);
315
- }
316
- const entries = [...ctx.resourceInstances.entries()].reverse();
317
- for (const [key, { resource, instance }] of entries) {
318
- if (instance.teardown)
319
- await instance.teardown();
320
- await this.eventBus.emit(`${resource.kind}.${resource.metadata.name}.Teardown`, {
321
- resource: { kind: resource.kind, name: resource.metadata.name },
322
- });
323
- // this.resourceInstances.delete(key);
324
- ctx.resourceInstances.delete(key);
325
- this.resourceEventBuses.delete(key);
326
- }
346
+ if (!runtime.length)
347
+ return { instance, ctx };
348
+ const wrapped = {
349
+ ...instance,
350
+ invoke: async (inputs) => {
351
+ const expanded = evalContext.expandPaths(inputs, runtime);
352
+ return instance.invoke(expanded);
353
+ },
354
+ };
355
+ return { instance: wrapped, ctx };
327
356
  }
328
- // getResourcesByKind(kind: string): RuntimeResource[] {
329
- // const resources: RuntimeResource[] = [];
330
- // for (const entry of this.resourceInstances.values()) {
331
- // if (entry.resource.kind === kind) {
332
- // resources.push(entry.instance as any);
333
- // }
334
- // }
335
- // return resources;
336
- // }
337
- // getResourceByName(declaringModule: string, alias: string, name: string): RuntimeResource | null {
338
- // const realModule = this.moduleContextRegistry.resolveAlias(declaringModule, alias) ?? alias;
339
- // for (const { resource, instance } of this.resourceInstances.values()) {
340
- // if (resource.metadata.module === realModule && resource.metadata.name === name) {
341
- // return instance as RuntimeResource;
342
- // }
343
- // }
344
- // return null;
345
- // }
346
- /**
347
- * Returns the unique set of local file paths from which resources were loaded.
348
- * HTTP/HTTPS sources are excluded — they cannot be watched on disk.
349
- */
350
- // getSourceFiles(): string[] {
351
- // const seen = new Set<string>();
352
- // for (const { resource } of this.resourceInstances.values()) {
353
- // const src = resource.metadata.source;
354
- // if (src && !src.startsWith("http://") && !src.startsWith("https://")) {
355
- // seen.add(src);
356
- // }
357
- // }
358
- // return Array.from(seen);
359
- // }
360
357
  /**
361
- * Reload all resources that were loaded from the given source file.
362
- * Safe order: parse first → if parse succeeds, tear down old → init new → run new only.
358
+ * Phase 5 Inject live instances into reference fields of a resource config.
359
+ *
360
+ * Called between create() and init() for every resource. Walks the definition's
361
+ * field map and replaces each {kind, name} reference value (outside scope visibility
362
+ * paths) with the live ResourceInstance returned by getInstance(name). Fields within
363
+ * scope paths are left as {kind, name} — the controller resolves them at runtime.
363
364
  */
364
- // async reloadSource(sourcePath: string): Promise<void> {
365
- // // Parse first bail before touching running resources if the file is invalid
366
- // const newManifests = await this.loader.loadManifest(sourcePath, `file://${process.cwd()}/`, {
367
- // env: process.env,
368
- // });
369
- // // Collect keys of resources loaded from this source (in insertion order)
370
- // const keysFromSource: string[] = [];
371
- // for (const [key, { resource }] of this.resourceInstances.entries()) {
372
- // if (resource.metadata.source === sourcePath) {
373
- // keysFromSource.push(key);
374
- // }
375
- // }
376
- // // Tear down in reverse order (children first via cascade)
377
- // for (const key of [...keysFromSource].reverse()) {
378
- // const entry = this.resourceInstances.get(key);
379
- // if (!entry) continue; // already removed by a cascade
380
- // await this.teardownResource(
381
- // entry.resource.metadata.module,
382
- // entry.resource.kind,
383
- // entry.resource.metadata.name,
384
- // );
385
- // }
386
- // for (const manifest of newManifests) {
387
- // this.rootContext.registerManifest(manifest);
388
- // }
389
- // const keysBefore = new Set(this.resourceInstances.keys());
390
- // await this.initializeResources();
391
- // // Run only newly created instances (not all instances — avoids double-run)
392
- // const newKeys = Array.from(this.resourceInstances.keys()).filter((k) => !keysBefore.has(k));
393
- // for (const key of newKeys) {
394
- // const entry = this.resourceInstances.get(key);
395
- // if (entry?.instance.run) {
396
- // await entry.instance.run();
397
- // }
398
- // }
399
- // }
400
- getResourceKey(module, kind, name) {
401
- if (!kind.includes(".")) {
402
- throw new Error(`Resource kind must include module prefix: ${kind}`);
403
- }
404
- return `${module}.${kind}.${name}`;
365
+ _injectDependencies(resource, getInstance) {
366
+ this.registry.iterateFieldEntries(resource, (fieldPath) => injectAtPath(resource, fieldPath, getInstance), (fieldPath) => {
367
+ const val = resource[fieldPath];
368
+ if (Array.isArray(val)) {
369
+ resource[fieldPath] = this.rootContext.createScopeHandle(val);
370
+ }
371
+ });
405
372
  }
406
373
  /**
407
374
  * Enable event streaming to a file (JSONL format)
@@ -434,4 +401,132 @@ export class Kernel {
434
401
  };
435
402
  }
436
403
  }
404
+ /** Returns a schema-appropriate placeholder value for a CompiledValue field. */
405
+ function placeholderForSchema(schema) {
406
+ if (schema.default !== undefined)
407
+ return schema.default;
408
+ switch (schema.type) {
409
+ case "integer":
410
+ case "number":
411
+ return schema.minimum ?? 0;
412
+ case "boolean":
413
+ return false;
414
+ case "array":
415
+ return [];
416
+ case "object":
417
+ return {};
418
+ default:
419
+ return "";
420
+ }
421
+ }
422
+ /** Replaces CompiledValue wrappers with schema-appropriate placeholders for schema validation.
423
+ * Template strings were compiled from YAML at load time; this restores a shape
424
+ * that AJV can validate without evaluating expressions. */
425
+ function stripCompiledValues(v, schema = {}) {
426
+ if (isCompiledValue(v))
427
+ return placeholderForSchema(schema);
428
+ if (Array.isArray(v)) {
429
+ const itemSchema = (schema.items ?? {});
430
+ return v.map((item) => stripCompiledValues(item, itemSchema));
431
+ }
432
+ if (v !== null && typeof v === "object") {
433
+ const props = (schema.properties ?? {});
434
+ const out = {};
435
+ for (const [k, val] of Object.entries(v)) {
436
+ out[k] = stripCompiledValues(val, props[k] ?? {});
437
+ }
438
+ return out;
439
+ }
440
+ return v;
441
+ }
442
+ /**
443
+ * Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
444
+ * For each leaf value that looks like a {kind, name} reference, calls getInstance(name)
445
+ * and replaces the value in-place with the returned live ResourceInstance.
446
+ * Values where getInstance returns undefined are left unchanged.
447
+ */
448
+ /**
449
+ * Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
450
+ * Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
451
+ * Property-level annotations produce the dot-notation path to that property.
452
+ */
453
+ function buildEvalPaths(schema) {
454
+ const compile = [];
455
+ const runtime = [];
456
+ if (schema["x-telo-eval"] === "compile")
457
+ compile.push("**");
458
+ else if (schema["x-telo-eval"] === "runtime")
459
+ runtime.push("**");
460
+ if (schema.properties) {
461
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
462
+ collectEvalPathsNode(propSchema, key, compile, runtime);
463
+ }
464
+ }
465
+ return { compile, runtime };
466
+ }
467
+ function collectEvalPathsNode(node, path, compile, runtime) {
468
+ if (node["x-telo-eval"] === "compile") {
469
+ compile.push(path);
470
+ return;
471
+ }
472
+ if (node["x-telo-eval"] === "runtime") {
473
+ runtime.push(path);
474
+ return;
475
+ }
476
+ if (node.properties) {
477
+ for (const [key, propSchema] of Object.entries(node.properties)) {
478
+ collectEvalPathsNode(propSchema, `${path}.${key}`, compile, runtime);
479
+ }
480
+ }
481
+ }
482
+ function injectAtPath(resource, fieldPath, getInstance) {
483
+ const parts = fieldPath.split(".");
484
+ function traverse(obj, partsLeft) {
485
+ if (!obj || typeof obj !== "object" || partsLeft.length === 0)
486
+ return;
487
+ const [head, ...rest] = partsLeft;
488
+ const isArr = head.endsWith("[]");
489
+ const key = isArr ? head.slice(0, -2) : head;
490
+ const container = obj;
491
+ const val = container[key];
492
+ if (val == null)
493
+ return;
494
+ if (isArr) {
495
+ if (!Array.isArray(val))
496
+ return;
497
+ for (let i = 0; i < val.length; i++) {
498
+ const elem = val[i];
499
+ if (!elem || typeof elem !== "object")
500
+ continue;
501
+ if (rest.length === 0) {
502
+ const ref = elem;
503
+ if (typeof ref.kind === "string" && typeof ref.name === "string") {
504
+ const instance = getInstance(ref.name);
505
+ if (instance)
506
+ val[i] = instance;
507
+ }
508
+ }
509
+ else {
510
+ traverse(elem, rest);
511
+ }
512
+ }
513
+ }
514
+ else {
515
+ if (rest.length === 0) {
516
+ if (val && typeof val === "object" && !Array.isArray(val)) {
517
+ const ref = val;
518
+ if (typeof ref.kind === "string" && typeof ref.name === "string") {
519
+ const instance = getInstance(ref.name);
520
+ if (instance)
521
+ container[key] = instance;
522
+ }
523
+ }
524
+ }
525
+ else {
526
+ traverse(val, rest);
527
+ }
528
+ }
529
+ }
530
+ traverse(resource, parts);
531
+ }
437
532
  //# sourceMappingURL=kernel.js.map