@telorun/kernel 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-definition.d.ts +14 -0
- package/dist/base-definition.d.ts.map +1 -0
- package/dist/base-definition.js +17 -0
- package/dist/base-definition.js.map +1 -0
- package/dist/boot-context-registry.d.ts.map +1 -1
- package/dist/boot-context-registry.js +6 -6
- package/dist/boot-context-registry.js.map +1 -1
- package/dist/capabilities/capabilities/component.yaml +2 -1
- package/dist/capabilities/capabilities/executable.yaml +2 -1
- package/dist/capabilities/capabilities/handler.yaml +2 -1
- package/dist/capabilities/capabilities/listener.yaml +2 -1
- package/dist/capabilities/capabilities/provider.yaml +2 -1
- package/dist/capabilities/capabilities/template.yaml +2 -1
- package/dist/capabilities/capabilities/type.yaml +2 -1
- package/dist/capabilities/component.d.ts +3 -0
- package/dist/capabilities/component.d.ts.map +1 -0
- package/dist/capabilities/component.js +4 -0
- package/dist/capabilities/component.js.map +1 -0
- package/dist/capabilities/component.yaml +1 -1
- package/dist/capabilities/executable.d.ts +3 -0
- package/dist/capabilities/executable.d.ts.map +1 -0
- package/dist/capabilities/executable.js +5 -0
- package/dist/capabilities/executable.js.map +1 -0
- package/dist/capabilities/executable.yaml +1 -1
- package/dist/capabilities/handler.d.ts +3 -0
- package/dist/capabilities/handler.d.ts.map +1 -0
- package/dist/capabilities/handler.js +4 -0
- package/dist/capabilities/handler.js.map +1 -0
- package/dist/capabilities/handler.yaml +1 -1
- package/dist/capabilities/invokable.d.ts +3 -0
- package/dist/capabilities/invokable.d.ts.map +1 -0
- package/dist/capabilities/invokable.js +5 -0
- package/dist/capabilities/invokable.js.map +1 -0
- package/dist/capabilities/listener.d.ts +3 -0
- package/dist/capabilities/listener.d.ts.map +1 -0
- package/dist/capabilities/listener.js +5 -0
- package/dist/capabilities/listener.js.map +1 -0
- package/dist/capabilities/listener.yaml +1 -1
- package/dist/capabilities/mount.d.ts +3 -0
- package/dist/capabilities/mount.d.ts.map +1 -0
- package/dist/capabilities/mount.js +5 -0
- package/dist/capabilities/mount.js.map +1 -0
- package/dist/capabilities/provider.d.ts +3 -0
- package/dist/capabilities/provider.d.ts.map +1 -0
- package/dist/capabilities/provider.js +8 -0
- package/dist/capabilities/provider.js.map +1 -0
- package/dist/capabilities/provider.yaml +1 -1
- package/dist/capabilities/runnable.d.ts +3 -0
- package/dist/capabilities/runnable.d.ts.map +1 -0
- package/dist/capabilities/runnable.js +5 -0
- package/dist/capabilities/runnable.js.map +1 -0
- package/dist/capabilities/service.d.ts +3 -0
- package/dist/capabilities/service.d.ts.map +1 -0
- package/dist/capabilities/service.js +5 -0
- package/dist/capabilities/service.js.map +1 -0
- package/dist/capabilities/template.d.ts +3 -0
- package/dist/capabilities/template.d.ts.map +1 -0
- package/dist/capabilities/template.js +5 -0
- package/dist/capabilities/template.js.map +1 -0
- package/dist/capabilities/template.yaml +1 -1
- package/dist/capabilities/type.d.ts +3 -0
- package/dist/capabilities/type.d.ts.map +1 -0
- package/dist/capabilities/type.js +5 -0
- package/dist/capabilities/type.js.map +1 -0
- package/dist/capabilities/type.yaml +1 -1
- package/dist/controller-loader.d.ts +1 -1
- package/dist/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +6 -3
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +1 -6
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js +11 -22
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/capability/capability-controller.d.ts +0 -5
- package/dist/controllers/capability/capability-controller.d.ts.map +1 -1
- package/dist/controllers/capability/capability-controller.js +1 -5
- package/dist/controllers/capability/capability-controller.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts +35 -0
- package/dist/controllers/module/import-controller.d.ts.map +1 -0
- package/dist/controllers/module/import-controller.js +113 -0
- package/dist/controllers/module/import-controller.js.map +1 -0
- package/dist/controllers/module/module-controller.d.ts +2 -15
- package/dist/controllers/module/module-controller.d.ts.map +1 -1
- package/dist/controllers/module/module-controller.js +9 -90
- package/dist/controllers/module/module-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +3 -4
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +17 -11
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-template-controller.d.ts +12 -0
- package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -0
- package/dist/controllers/resource-definition/resource-template-controller.js +112 -0
- package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -0
- package/dist/evaluation-context.d.ts +91 -0
- package/dist/evaluation-context.d.ts.map +1 -0
- package/dist/evaluation-context.js +220 -0
- package/dist/evaluation-context.js.map +1 -0
- package/dist/execution-context.d.ts +13 -0
- package/dist/execution-context.d.ts.map +1 -0
- package/dist/execution-context.js +14 -0
- package/dist/execution-context.js.map +1 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +31 -42
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +315 -371
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +11 -10
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +50 -112
- package/dist/loader.js.map +1 -1
- package/dist/manifest-adapters/http-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/http-adapter.js +3 -1
- package/dist/manifest-adapters/http-adapter.js.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.d.ts +7 -5
- package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.js +28 -24
- package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
- package/dist/manifest-adapters/manifest-adapter.d.ts +2 -0
- package/dist/manifest-adapters/manifest-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/registry-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/registry-adapter.js +3 -1
- package/dist/manifest-adapters/registry-adapter.js.map +1 -1
- package/dist/manifest-schemas.d.ts +61 -49
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +58 -37
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/module-context-registry.d.ts +48 -0
- package/dist/module-context-registry.d.ts.map +1 -0
- package/dist/module-context-registry.js +91 -0
- package/dist/module-context-registry.js.map +1 -0
- package/dist/module-context.d.ts +31 -0
- package/dist/module-context.d.ts.map +1 -0
- package/dist/module-context.js +67 -0
- package/dist/module-context.js.map +1 -0
- package/dist/registry.d.ts +1 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +1 -1
- package/dist/resource-context.d.ts +27 -10
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +100 -44
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +16 -3
- package/dist/schema-valiator.js.map +1 -1
- package/dist/snapshot-serializer.d.ts +1 -2
- package/dist/snapshot-serializer.d.ts.map +1 -1
- package/dist/snapshot-serializer.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +11 -9
- package/src/boot-context-registry.ts +169 -0
- package/src/controller-loader.ts +299 -0
- package/src/controller-registry.ts +191 -0
- package/src/controllers/module/import-controller.ts +143 -0
- package/src/controllers/module/module-controller.ts +16 -0
- package/src/controllers/resource-definition/resource-definition-controller.ts +86 -0
- package/src/controllers/resource-definition/resource-definition.json +18 -0
- package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
- package/src/event-stream.ts +121 -0
- package/src/events.ts +99 -0
- package/src/index.ts +7 -0
- package/src/kernel.ts +647 -0
- package/src/loader.ts +134 -0
- package/src/manifest-adapters/local-file-adapter.ts +62 -0
- package/src/manifest-adapters/manifest-adapter.ts +35 -0
- package/src/manifest-schemas.ts +85 -0
- package/src/registry.ts +137 -0
- package/src/resource-context.ts +267 -0
- package/src/resource-uri.ts +200 -0
- package/src/schema-valiator.ts +68 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -109
- package/dist/cli.js.map +0 -1
- package/dist/expressions.d.ts +0 -20
- package/dist/expressions.d.ts.map +0 -1
- package/dist/expressions.js +0 -253
- package/dist/expressions.js.map +0 -1
- package/dist/src/controllers/module/module.yaml +0 -32
- package/dist/template-definition.d.ts +0 -38
- package/dist/template-definition.d.ts.map +0 -1
- package/dist/template-definition.js +0 -26
- package/dist/template-definition.js.map +0 -1
- package/dist/template-expander.d.ts +0 -19
- package/dist/template-expander.d.ts.map +0 -1
- package/dist/template-expander.js +0 -425
- package/dist/template-expander.js.map +0 -1
package/dist/kernel.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnalysisRegistry, StaticAnalyzer } from "@telorun/analyzer";
|
|
2
|
+
import { ModuleContext, RuntimeError, isCompiledValue, } from "@telorun/sdk";
|
|
2
3
|
import * as path from "path";
|
|
3
|
-
import { BootContextRegistry } from "./boot-context-registry.js";
|
|
4
4
|
import { ControllerRegistry } from "./controller-registry.js";
|
|
5
5
|
import { EventStream } from "./event-stream.js";
|
|
6
6
|
import { EventBus } from "./events.js";
|
|
7
|
-
import { evaluateCel, expandValue, resolveManifestWithContext } from "./expressions.js";
|
|
8
7
|
import { Loader } from "./loader.js";
|
|
9
8
|
import { ResourceContextImpl } from "./resource-context.js";
|
|
10
9
|
import { SchemaValidator } from "./schema-valiator.js";
|
|
11
|
-
import { RuntimeError, } from "./types.js";
|
|
12
10
|
/**
|
|
13
11
|
* Kernel: Central orchestrator managing lifecycle and message bus
|
|
14
12
|
* Handles resource loading, initialization, and execution through controllers
|
|
@@ -16,65 +14,26 @@ import { RuntimeError, } from "./types.js";
|
|
|
16
14
|
export class Kernel {
|
|
17
15
|
constructor() {
|
|
18
16
|
this.loader = new Loader();
|
|
17
|
+
this.analyzer = new StaticAnalyzer();
|
|
18
|
+
this.registry = new AnalysisRegistry();
|
|
19
19
|
// private manifests: ManifestRegistry = new ManifestRegistry();
|
|
20
|
-
this.initializationQueue = [];
|
|
21
20
|
this.controllers = new ControllerRegistry();
|
|
22
21
|
this.eventBus = new EventBus();
|
|
23
22
|
this.eventStream = new EventStream();
|
|
24
23
|
// private snapshotSerializer: SnapshotSerializer = new SnapshotSerializer();
|
|
25
24
|
// private runtimeManifests: ResourceManifest[] | null = null;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
// private resourceInstances: Map<
|
|
26
|
+
// string,
|
|
27
|
+
// { resource: ResourceManifest; instance: ResourceInstance }
|
|
28
|
+
// > = new Map();
|
|
29
29
|
this.holdCount = 0;
|
|
30
30
|
this.idleResolvers = [];
|
|
31
31
|
this._exitCode = 0;
|
|
32
|
-
|
|
32
|
+
// private bootContextRegistry = new BootContextRegistry();
|
|
33
33
|
this.sharedSchemaValidator = new SchemaValidator();
|
|
34
|
+
this.staticManifests = [];
|
|
34
35
|
this.setupEventStreaming();
|
|
35
36
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Register a resource dynamically during initialization
|
|
38
|
-
*/
|
|
39
|
-
registerManifest(resource) {
|
|
40
|
-
this.initializationQueue.push(resource);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Register a child resource and track the parent-child relationship for cascade teardown
|
|
44
|
-
*/
|
|
45
|
-
registerChildManifest(parentKey, resource) {
|
|
46
|
-
this.initializationQueue.push(resource);
|
|
47
|
-
const childKey = this.getResourceKey(resource.metadata.module, resource.kind, resource.metadata.name);
|
|
48
|
-
const children = this.resourceChildren.get(parentKey) ?? [];
|
|
49
|
-
children.push(childKey);
|
|
50
|
-
this.resourceChildren.set(parentKey, children);
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Tear down a single resource and cascade to its children first
|
|
54
|
-
*/
|
|
55
|
-
async teardownResource(module, kind, name) {
|
|
56
|
-
const key = this.getResourceKey(module, kind, name);
|
|
57
|
-
// Cascade: tear down children in reverse registration order first
|
|
58
|
-
const childKeys = this.resourceChildren.get(key) ?? [];
|
|
59
|
-
for (const childKey of [...childKeys].reverse()) {
|
|
60
|
-
const childEntry = this.resourceInstances.get(childKey);
|
|
61
|
-
if (childEntry) {
|
|
62
|
-
await this.teardownResource(childEntry.resource.metadata.module, childEntry.resource.kind, childEntry.resource.metadata.name);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
this.resourceChildren.delete(key);
|
|
66
|
-
// Tear down self
|
|
67
|
-
const entry = this.resourceInstances.get(key);
|
|
68
|
-
if (!entry)
|
|
69
|
-
return;
|
|
70
|
-
const { resource, instance } = entry;
|
|
71
|
-
if (instance.teardown) {
|
|
72
|
-
await instance.teardown();
|
|
73
|
-
}
|
|
74
|
-
await this.eventBus.emit(`${resource.metadata.module}.${resource.kind}.${resource.metadata.name}.Teardown`, { resource: { kind: resource.kind, name: resource.metadata.name } });
|
|
75
|
-
this.resourceInstances.delete(key);
|
|
76
|
-
this.resourceEventBuses.delete(key);
|
|
77
|
-
}
|
|
78
37
|
async registerController(moduleName, kindName, controllerInstance) {
|
|
79
38
|
this.controllers.registerController(`${moduleName}.${kindName}`, controllerInstance);
|
|
80
39
|
await controllerInstance.register?.(this.createControllerContext(`${moduleName}.${kindName}`));
|
|
@@ -84,59 +43,93 @@ export class Kernel {
|
|
|
84
43
|
*/
|
|
85
44
|
registerResourceDefinition(definition) {
|
|
86
45
|
this.controllers.registerDefinition(definition);
|
|
46
|
+
this.registry.registerDefinition(definition);
|
|
87
47
|
}
|
|
88
|
-
|
|
89
|
-
this.
|
|
48
|
+
getModuleContext(_moduleName) {
|
|
49
|
+
return this.rootContext;
|
|
90
50
|
}
|
|
91
|
-
|
|
92
|
-
return this.
|
|
51
|
+
resolveModuleAlias(_declaringModule, alias) {
|
|
52
|
+
return this.rootContext.importAliases.get(alias);
|
|
93
53
|
}
|
|
94
|
-
|
|
95
|
-
|
|
54
|
+
registerModuleImport(_declaringModule, alias, targetModule, kinds) {
|
|
55
|
+
this.rootContext.registerImport(alias, targetModule, kinds);
|
|
56
|
+
this.registry.registerImport(alias, targetModule, kinds);
|
|
57
|
+
}
|
|
58
|
+
/** Returns the live analysis registry backed by this kernel's known definitions and aliases.
|
|
59
|
+
* Pass to StaticAnalyzer.analyze() for incremental validation of new manifests against
|
|
60
|
+
* already-registered types (e.g. front-end editor validating a manifest before submitting). */
|
|
61
|
+
getAnalysisRegistry() {
|
|
62
|
+
return this.registry;
|
|
96
63
|
}
|
|
97
64
|
/**
|
|
98
|
-
* Load built-in Runtime definitions (e.g.,
|
|
65
|
+
* Load built-in Runtime definitions (e.g., Kernel.Module)
|
|
66
|
+
* Also declares all known module namespaces upfront so that resources can be
|
|
67
|
+
* registered to them. User-defined modules are declared explicitly by Kernel.Module
|
|
68
|
+
* resources during the initialization phase.
|
|
99
69
|
*/
|
|
100
70
|
async loadBuiltinDefinitions() {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this.controllers.registerDefinition({
|
|
112
|
-
kind: "Runtime.Definition",
|
|
113
|
-
metadata: { name: "Module", module: "Runtime" },
|
|
114
|
-
capabilities: ["template"],
|
|
115
|
-
schema: moduleSchema,
|
|
116
|
-
});
|
|
117
|
-
this.controllers.registerController("Runtime.Module", await import("./controllers/module/module-controller.js"));
|
|
118
|
-
this.controllers.registerDefinition({
|
|
119
|
-
kind: "Runtime.Definition",
|
|
120
|
-
metadata: { name: "Capability", module: "Runtime" },
|
|
121
|
-
capabilities: ["template"],
|
|
122
|
-
schema: { type: "object" },
|
|
123
|
-
});
|
|
124
|
-
this.controllers.registerController("Runtime.Capability", await import("./controllers/capability/capability-controller.js"));
|
|
71
|
+
// Declare built-in module namespaces upfront so getContext() can distinguish
|
|
72
|
+
// "not yet populated" from a completely unknown module name.
|
|
73
|
+
this.rootContext.registerImport("Kernel", "Kernel", []); // built-ins, unrestricted
|
|
74
|
+
// Register built-in definitions with the controller registry.
|
|
75
|
+
// AnalysisRegistry's underlying DefinitionRegistry already seeds KERNEL_BUILTINS on construction.
|
|
76
|
+
for (const def of this.registry.builtinDefinitions())
|
|
77
|
+
this.controllers.registerDefinition(def);
|
|
78
|
+
this.controllers.registerController("Kernel.Definition", await import("./controllers/resource-definition/resource-definition-controller.js"));
|
|
79
|
+
this.controllers.registerController("Kernel.Module", await import("./controllers/module/module-controller.js"));
|
|
80
|
+
this.controllers.registerController("Kernel.Import", await import("./controllers/module/import-controller.js"));
|
|
125
81
|
}
|
|
126
82
|
/**
|
|
127
83
|
* Load from runtime configuration file
|
|
128
84
|
*/
|
|
129
85
|
async loadFromConfig(runtimeYamlPath) {
|
|
86
|
+
// Resolve directory paths to their module.yaml so that relative imports
|
|
87
|
+
// (e.g. ../../modules/foo) use the correct base directory.
|
|
88
|
+
const sourceUrl = await this.loader.resolveEntryPoint(new URL(runtimeYamlPath, `file://${process.cwd()}/`).href);
|
|
89
|
+
this.rootContext = new ModuleContext(sourceUrl, {}, {}, {}, [], this._createInstance.bind(this), (event, payload) => this.eventBus.emit(event, payload), process.env);
|
|
130
90
|
// Initialize built-in Runtime definitions first
|
|
131
91
|
await this.loadBuiltinDefinitions();
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
92
|
+
// Phase 5: attach injection hook — fires between create() and init() for every resource
|
|
93
|
+
this.rootContext.preInitHook = (resource, getInstance) => this._injectDependencies(resource, getInstance);
|
|
94
|
+
// Static analysis pre-flight: validates schemas and invocation context compatibility.
|
|
95
|
+
// All errors are fatal — kernel does not start if analysis fails.
|
|
96
|
+
const staticManifests = await this.loader.loadManifests(sourceUrl);
|
|
97
|
+
this.staticManifests = staticManifests;
|
|
98
|
+
// Register module identities for x-telo-ref resolution (Phase 3 prerequisite).
|
|
99
|
+
// Kernel built-ins ("kernel" → "Kernel") are auto-registered when Kernel.Abstract
|
|
100
|
+
// definitions are registered in loadBuiltinDefinitions() above.
|
|
101
|
+
for (const m of staticManifests) {
|
|
102
|
+
if (m.kind === "Kernel.Module" && m.metadata?.name && m.metadata?.namespace) {
|
|
103
|
+
this.registry.registerModuleIdentity(m.metadata.namespace, m.metadata.name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const errors = this.analyzer.analyzeErrors(staticManifests, {}, this.registry);
|
|
107
|
+
if (errors.length > 0) {
|
|
108
|
+
throw new RuntimeError("ERR_MANIFEST_VALIDATION_FAILED", "Manifest validation failed", errors.map((d) => ({
|
|
109
|
+
severity: "error",
|
|
110
|
+
message: d.message,
|
|
111
|
+
code: d.code !== undefined ? String(d.code) : undefined,
|
|
112
|
+
resource: d.data?.resource
|
|
113
|
+
? `${d.data.resource.kind}.${d.data.resource.name}`
|
|
114
|
+
: undefined,
|
|
115
|
+
})));
|
|
116
|
+
}
|
|
117
|
+
// Load runtime configuration — root module gets access to host env
|
|
118
|
+
const allManifests = await this.loader.loadModule(sourceUrl, { compile: true });
|
|
119
|
+
// Phase 2: normalize inline resources — extract inline values from x-telo-ref slots
|
|
120
|
+
// into first-class named manifests and replace them in-place with {kind, name} references.
|
|
121
|
+
// Update staticManifests so Phase 3 (validateReferences) and Phase 4 (DAG) see
|
|
122
|
+
// the same normalized structure.
|
|
123
|
+
const normalizedManifests = this.analyzer.normalize(allManifests, this.registry);
|
|
124
|
+
this.staticManifests = normalizedManifests;
|
|
125
|
+
for (const manifest of normalizedManifests) {
|
|
126
|
+
if (manifest.kind === "Kernel.Module") {
|
|
127
|
+
this.rootContext.setSecrets(manifest.secrets ?? {});
|
|
128
|
+
this.rootContext.setVariables(manifest.variables ?? {});
|
|
129
|
+
this.rootContext.setTargets(manifest.targets ?? []);
|
|
130
|
+
}
|
|
131
|
+
this.rootContext.registerManifest(manifest);
|
|
132
|
+
}
|
|
140
133
|
}
|
|
141
134
|
/**
|
|
142
135
|
* Phase 1: Load - Ingest files from directory and load runtime config
|
|
@@ -152,32 +145,44 @@ export class Kernel {
|
|
|
152
145
|
async start() {
|
|
153
146
|
// Call controller register hooks first (before any initialization)
|
|
154
147
|
for (const kind of this.controllers.getKinds()) {
|
|
155
|
-
const controller =
|
|
148
|
+
const controller = this.controllers.getController(kind);
|
|
156
149
|
if (controller?.register) {
|
|
157
150
|
await controller.register(this.createControllerContext(`controller:${kind}`));
|
|
158
151
|
}
|
|
159
152
|
}
|
|
153
|
+
// Phase 3+4: reference validation, cycle detection, and topo sort
|
|
154
|
+
const { diagnostics: refErrors, order, cycleError, } = this.analyzer.prepare(this.staticManifests, this.registry);
|
|
155
|
+
if (refErrors.length > 0) {
|
|
156
|
+
throw new RuntimeError("ERR_MANIFEST_VALIDATION_FAILED", "Manifest validation failed", refErrors.map((d) => ({
|
|
157
|
+
severity: "error",
|
|
158
|
+
message: d.message,
|
|
159
|
+
code: d.code !== undefined ? String(d.code) : undefined,
|
|
160
|
+
resource: d.data?.resource
|
|
161
|
+
? `${d.data.resource.kind}.${d.data.resource.name}`
|
|
162
|
+
: undefined,
|
|
163
|
+
})));
|
|
164
|
+
}
|
|
165
|
+
if (cycleError) {
|
|
166
|
+
throw new RuntimeError("ERR_CIRCULAR_DEPENDENCY", cycleError);
|
|
167
|
+
}
|
|
168
|
+
// Phase 5: sort pending resources into topo order so injection always finds
|
|
169
|
+
// initialized dependencies, then run the init loop.
|
|
170
|
+
if (order) {
|
|
171
|
+
this.rootContext.setInitOrder(order);
|
|
172
|
+
}
|
|
160
173
|
// Initialize resources
|
|
161
174
|
try {
|
|
162
|
-
await this.initializeResources();
|
|
163
|
-
await this.eventBus.emit("
|
|
164
|
-
await this.eventBus.emit("
|
|
165
|
-
await this.
|
|
166
|
-
await this.eventBus.emit("
|
|
175
|
+
await this.rootContext.initializeResources();
|
|
176
|
+
await this.eventBus.emit("Kernel.Initialized", {});
|
|
177
|
+
await this.eventBus.emit("Kernel.Starting", {});
|
|
178
|
+
await this.rootContext.runTargets();
|
|
179
|
+
await this.eventBus.emit("Kernel.Started", {});
|
|
167
180
|
await this.waitForIdle();
|
|
168
181
|
}
|
|
169
182
|
finally {
|
|
170
|
-
await this.eventBus.emit("
|
|
171
|
-
await this.teardownResources();
|
|
172
|
-
await this.eventBus.emit("
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async runInstances() {
|
|
176
|
-
for (const entry of this.resourceInstances.values()) {
|
|
177
|
-
const { resource, instance } = entry;
|
|
178
|
-
if (instance.run) {
|
|
179
|
-
await instance.run();
|
|
180
|
-
}
|
|
183
|
+
await this.eventBus.emit("Kernel.Stopping", {});
|
|
184
|
+
await this.rootContext.teardownResources();
|
|
185
|
+
await this.eventBus.emit("Kernel.Stopped", { exitCode: this._exitCode });
|
|
181
186
|
}
|
|
182
187
|
}
|
|
183
188
|
async emitRuntimeEvent(event, payload) {
|
|
@@ -192,7 +197,7 @@ export class Kernel {
|
|
|
192
197
|
acquireHold(reason) {
|
|
193
198
|
this.holdCount += 1;
|
|
194
199
|
if (this.holdCount === 1) {
|
|
195
|
-
void this.eventBus.emit("
|
|
200
|
+
void this.eventBus.emit("Kernel.Blocked", {
|
|
196
201
|
reason,
|
|
197
202
|
count: this.holdCount,
|
|
198
203
|
});
|
|
@@ -209,7 +214,7 @@ export class Kernel {
|
|
|
209
214
|
for (const resolve of resolvers) {
|
|
210
215
|
resolve();
|
|
211
216
|
}
|
|
212
|
-
void this.eventBus.emit("
|
|
217
|
+
void this.eventBus.emit("Kernel.Unblocked", { count: this.holdCount });
|
|
213
218
|
}
|
|
214
219
|
};
|
|
215
220
|
}
|
|
@@ -234,18 +239,6 @@ export class Kernel {
|
|
|
234
239
|
hasEventHandlers(event) {
|
|
235
240
|
return this.eventBus.hasHandlers(event);
|
|
236
241
|
}
|
|
237
|
-
hasResourceInstances() {
|
|
238
|
-
return this.resourceInstances.size > 0;
|
|
239
|
-
}
|
|
240
|
-
async teardownResources() {
|
|
241
|
-
const keys = Array.from(this.resourceInstances.keys()).reverse();
|
|
242
|
-
for (const key of keys) {
|
|
243
|
-
const entry = this.resourceInstances.get(key);
|
|
244
|
-
if (!entry)
|
|
245
|
-
continue; // already removed by a cascade
|
|
246
|
-
await this.teardownResource(entry.resource.metadata.module, entry.resource.kind, entry.resource.metadata.name);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
242
|
on(event, handler) {
|
|
250
243
|
this.eventBus.on(event, handler);
|
|
251
244
|
}
|
|
@@ -259,261 +252,94 @@ export class Kernel {
|
|
|
259
252
|
void this.eventBus.emit(namespaced, payload);
|
|
260
253
|
},
|
|
261
254
|
acquireHold: (reason) => this.acquireHold(reason),
|
|
262
|
-
|
|
263
|
-
expandValue: (value, context) => expandValue(value, context),
|
|
255
|
+
expandValue: (value, context) => this.rootContext.expandWith(value, context),
|
|
264
256
|
requestExit: (code) => this.requestExit(code),
|
|
265
257
|
};
|
|
266
258
|
}
|
|
267
|
-
createResourceContext(resource) {
|
|
268
|
-
|
|
269
|
-
return new ResourceContextImpl(this, resource.metadata, this.sharedSchemaValidator, key);
|
|
270
|
-
}
|
|
271
|
-
getResourcesByKind(kind) {
|
|
272
|
-
const resources = [];
|
|
273
|
-
for (const entry of this.resourceInstances.values()) {
|
|
274
|
-
if (entry.resource.kind === kind) {
|
|
275
|
-
resources.push(entry.instance);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return resources;
|
|
279
|
-
}
|
|
280
|
-
getResourceByName(module, kind, name) {
|
|
281
|
-
// const [declarationModule, knd] = kind.includes('.')
|
|
282
|
-
// ? kind.split('.', 2)
|
|
283
|
-
// : ['Runtime', kind];
|
|
284
|
-
const key = this.getResourceKey(module, kind, name);
|
|
285
|
-
const entry = this.resourceInstances.get(key);
|
|
286
|
-
if (entry) {
|
|
287
|
-
return entry.instance;
|
|
288
|
-
}
|
|
289
|
-
return null;
|
|
259
|
+
createResourceContext(moduleContext, resource) {
|
|
260
|
+
return new ResourceContextImpl(this, moduleContext, resource.metadata, this.sharedSchemaValidator);
|
|
290
261
|
}
|
|
291
262
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
263
|
+
* Create phase only: resolves the controller, validates the schema, and calls
|
|
264
|
+
* controller.create(). Returns { instance, ctx } so initializeResources can
|
|
265
|
+
* run init() separately in its second phase. Returns null when the controller
|
|
266
|
+
* is not yet registered (retry signal).
|
|
294
267
|
*/
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
268
|
+
async _createInstance(evalContext, resource) {
|
|
269
|
+
const kind = resource.kind;
|
|
270
|
+
// Resolve the alias-prefixed kind to its real fully-qualified kind.
|
|
271
|
+
// resolveKind() throws with a clear message if the alias or kind is not found.
|
|
272
|
+
const resolvedKind = this.rootContext.resolveKind(kind);
|
|
273
|
+
const controller = this.controllers.getControllerOrUndefined(resolvedKind);
|
|
274
|
+
if (!controller) {
|
|
275
|
+
const kindInfo = resolvedKind !== kind ? `'${kind}' (resolved to '${resolvedKind}')` : `'${kind}'`;
|
|
276
|
+
throw new Error(`No controller registered for kind ${kindInfo}, known controllers are: ${this.controllers.getKinds().join(", ")}`);
|
|
302
277
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Reload all resources that were loaded from the given source file.
|
|
307
|
-
* Safe order: parse first → if parse succeeds, tear down old → init new → run new only.
|
|
308
|
-
*/
|
|
309
|
-
async reloadSource(sourcePath) {
|
|
310
|
-
// Parse first — bail before touching running resources if the file is invalid
|
|
311
|
-
const newManifests = await this.loader.loadManifest(sourcePath);
|
|
312
|
-
// Collect keys of resources loaded from this source (in insertion order)
|
|
313
|
-
const keysFromSource = [];
|
|
314
|
-
for (const [key, { resource }] of this.resourceInstances.entries()) {
|
|
315
|
-
if (resource.metadata.source === sourcePath) {
|
|
316
|
-
keysFromSource.push(key);
|
|
317
|
-
}
|
|
278
|
+
if (!controller.create) {
|
|
279
|
+
throw new RuntimeError("ERR_CONTROLLER_INVALID", `Controller for ${kind} does not implement create method`);
|
|
318
280
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const entry = this.resourceInstances.get(key);
|
|
322
|
-
if (!entry)
|
|
323
|
-
continue; // already removed by a cascade
|
|
324
|
-
await this.teardownResource(entry.resource.metadata.module, entry.resource.kind, entry.resource.metadata.name);
|
|
281
|
+
if (!controller.schema?.type) {
|
|
282
|
+
throw new Error(`No schema defined for ${kind} controller`);
|
|
325
283
|
}
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
284
|
+
// Resolve eval paths from x-telo-eval annotations in the parent and own schema
|
|
285
|
+
const definition = this.controllers.getDefinition(resolvedKind);
|
|
286
|
+
const parentDef = definition?.capability
|
|
287
|
+
? this.controllers.getDefinition(definition.capability)
|
|
288
|
+
: undefined;
|
|
289
|
+
const parentEval = parentDef?.schema
|
|
290
|
+
? buildEvalPaths(parentDef.schema)
|
|
291
|
+
: { compile: [], runtime: [] };
|
|
292
|
+
const ownEval = definition?.schema
|
|
293
|
+
? buildEvalPaths(definition.schema)
|
|
294
|
+
: { compile: [], runtime: [] };
|
|
295
|
+
const compile = [...parentEval.compile, ...ownEval.compile];
|
|
296
|
+
const runtime = [...parentEval.runtime, ...ownEval.runtime];
|
|
297
|
+
// Schema validation runs before CEL evaluation so it sees the original manifest
|
|
298
|
+
// shape. CompiledValue wrappers (from load-time precompilation) are stripped,
|
|
299
|
+
// restoring the pre-CEL string view that the schema expects.
|
|
300
|
+
try {
|
|
301
|
+
this.sharedSchemaValidator
|
|
302
|
+
.compile(controller.schema)
|
|
303
|
+
.validate(stripCompiledValues(resource, controller.schema));
|
|
329
304
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
// Run only newly created instances (not all instances — avoids double-run)
|
|
333
|
-
const newKeys = Array.from(this.resourceInstances.keys()).filter((k) => !keysBefore.has(k));
|
|
334
|
-
for (const key of newKeys) {
|
|
335
|
-
const entry = this.resourceInstances.get(key);
|
|
336
|
-
if (entry?.instance.run) {
|
|
337
|
-
await entry.instance.run();
|
|
338
|
-
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
throw new RuntimeError("ERR_RESOURCE_SCHEMA_VALIDATION_FAILED", `Resource does not match schema for kind ${kind}: ${error instanceof Error ? error.message : String(error)}`);
|
|
339
307
|
}
|
|
308
|
+
// Expand compile-time CEL fields before passing to the controller.
|
|
309
|
+
const processedResource = compile.length
|
|
310
|
+
? evalContext.expandPaths(resource, compile, runtime)
|
|
311
|
+
: resource;
|
|
312
|
+
const ctx = this.createResourceContext(evalContext, processedResource);
|
|
313
|
+
const instance = await controller.create(processedResource, ctx);
|
|
314
|
+
if (!instance)
|
|
315
|
+
return null;
|
|
316
|
+
if (!runtime.length)
|
|
317
|
+
return { instance, ctx };
|
|
318
|
+
const wrapped = {
|
|
319
|
+
...instance,
|
|
320
|
+
invoke: async (inputs) => {
|
|
321
|
+
const expanded = evalContext.expandPaths(inputs, runtime);
|
|
322
|
+
return instance.invoke(expanded);
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
return { instance: wrapped, ctx };
|
|
340
326
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// Multi-pass loop
|
|
355
|
-
do {
|
|
356
|
-
handledThisPass = [];
|
|
357
|
-
for (const resource of this.initializationQueue) {
|
|
358
|
-
const kind = resource.kind;
|
|
359
|
-
const key = this.getResourceKey(resource.metadata.module, kind, resource.metadata.name);
|
|
360
|
-
const resourceId = `${kind}:${resource.metadata.name}`;
|
|
361
|
-
// Skip if already created
|
|
362
|
-
if (this.resourceInstances.has(key)) {
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
const controller = this.controllers.getControllerOrUndefined(kind);
|
|
366
|
-
if (!controller) {
|
|
367
|
-
// No controller and no definition - track error and skip for now
|
|
368
|
-
resourceErrors.set(resourceId, `No controller registered for kind: ${kind}`);
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
if (!controller.create) {
|
|
372
|
-
// Controller exists but has no create method, skip
|
|
373
|
-
throw new RuntimeError("ERR_CONTROLLER_INVALID", `Controller for ${kind} does not implement create method`);
|
|
374
|
-
}
|
|
375
|
-
try {
|
|
376
|
-
if (!controller.schema || !controller.schema.type) {
|
|
377
|
-
throw new Error(`No schema defined for ${kind} controller`);
|
|
378
|
-
}
|
|
379
|
-
// AOT: resolve static boot-context expressions before schema validation
|
|
380
|
-
// and controller creation. This is a no-op until at least one provider
|
|
381
|
-
// has registered its context.
|
|
382
|
-
let resolvedResource = resource;
|
|
383
|
-
if (this.bootContextRegistry.hasProviders()) {
|
|
384
|
-
const bootContext = this.bootContextRegistry.buildContext(resource.kind, resource.metadata.name, resource);
|
|
385
|
-
resolvedResource = resolveManifestWithContext(resource, bootContext);
|
|
386
|
-
}
|
|
387
|
-
this.sharedSchemaValidator.compile(controller.schema).validate(resolvedResource);
|
|
388
|
-
// Create resource instance using the resolved manifest
|
|
389
|
-
const instance = await controller.create(resolvedResource, this.createResourceContext(resolvedResource));
|
|
390
|
-
if (instance) {
|
|
391
|
-
if (instance.init) {
|
|
392
|
-
await instance.init(this.createResourceContext(resolvedResource));
|
|
393
|
-
}
|
|
394
|
-
// Register ContextProvider after init() — init may populate context state
|
|
395
|
-
if (isContextProvider(instance)) {
|
|
396
|
-
this.bootContextRegistry.register(resource.kind, resource.metadata.name, resource.metadata.module, resource.grants, instance.provideContext());
|
|
397
|
-
}
|
|
398
|
-
this.resourceInstances.set(key, { resource: resolvedResource, instance });
|
|
399
|
-
createdResources.push({ kind, resource: resolvedResource, instance });
|
|
400
|
-
handledThisPass.push({ kind, resource });
|
|
401
|
-
resourceErrors.delete(resourceId);
|
|
402
|
-
}
|
|
403
|
-
this.initializationQueue = this.initializationQueue.filter((r) => r.metadata.module !== resource.metadata.module ||
|
|
404
|
-
r.kind !== resource.kind ||
|
|
405
|
-
r.metadata.name !== resource.metadata.name);
|
|
406
|
-
}
|
|
407
|
-
catch (error) {
|
|
408
|
-
// Security violations are fatal — never retry
|
|
409
|
-
if (error instanceof RuntimeError && error.code === "ERR_VISIBILITY_DENIED") {
|
|
410
|
-
throw error;
|
|
411
|
-
}
|
|
412
|
-
// Creation failed - track latest error and retry later
|
|
413
|
-
resourceErrors.set(resourceId, error instanceof Error ? (error.stack ?? error.message) : String(error));
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
passNumber++;
|
|
417
|
-
} while (passNumber <= MAX_PASSES && handledThisPass.length > 0);
|
|
418
|
-
const unhandledResources = new Map();
|
|
419
|
-
// After loop, collect any resources that were never handled
|
|
420
|
-
for (const { kind, metadata } of this.initializationQueue) {
|
|
421
|
-
const key = this.getResourceKey(metadata.module, kind, metadata.name);
|
|
422
|
-
if (!this.resourceInstances.has(key)) {
|
|
423
|
-
const resourceId = `${kind}:${metadata.name}`;
|
|
424
|
-
const errorMessage = resourceErrors.get(resourceId) || "Unknown error";
|
|
425
|
-
unhandledResources.set(resourceId, errorMessage);
|
|
327
|
+
/**
|
|
328
|
+
* Phase 5 — Inject live instances into reference fields of a resource config.
|
|
329
|
+
*
|
|
330
|
+
* Called between create() and init() for every resource. Walks the definition's
|
|
331
|
+
* field map and replaces each {kind, name} reference value (outside scope visibility
|
|
332
|
+
* paths) with the live ResourceInstance returned by getInstance(name). Fields within
|
|
333
|
+
* scope paths are left as {kind, name} — the controller resolves them at runtime.
|
|
334
|
+
*/
|
|
335
|
+
_injectDependencies(resource, getInstance) {
|
|
336
|
+
this.registry.iterateFieldEntries(resource, (fieldPath) => injectAtPath(resource, fieldPath, getInstance), (fieldPath) => {
|
|
337
|
+
const val = resource[fieldPath];
|
|
338
|
+
if (Array.isArray(val)) {
|
|
339
|
+
resource[fieldPath] = this.rootContext.createScopeHandle(val);
|
|
426
340
|
}
|
|
427
|
-
}
|
|
428
|
-
// After all passes complete, check for unhandled resources
|
|
429
|
-
if (unhandledResources.size > 0) {
|
|
430
|
-
const unhandledList = Array.from(unhandledResources.entries())
|
|
431
|
-
.reverse() // Most relevant errors (root causes) are last in the list, so reverse to show them first
|
|
432
|
-
.map(([resource, error]) => `- ${resource}: ${error}`)
|
|
433
|
-
.join("\n");
|
|
434
|
-
throw new RuntimeError("ERR_CONTROLLER_NOT_FOUND", `Unable to process resources:\n\n${unhandledList}`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
// /**
|
|
438
|
-
// * Execute - Dispatch execution request to appropriate controller
|
|
439
|
-
// */
|
|
440
|
-
// async execute(urn: string, input: any, ctx?: any): Promise<any> {
|
|
441
|
-
// const [kind, name] = this.parseUrn(urn);
|
|
442
|
-
// // Lookup resource
|
|
443
|
-
// const resource = this.manifests.get(kind, name);
|
|
444
|
-
// if (!resource) {
|
|
445
|
-
// throw new RuntimeError("ERR_RESOURCE_NOT_FOUND", `Resource not found: ${urn}`);
|
|
446
|
-
// }
|
|
447
|
-
// // Find controller for this Kind
|
|
448
|
-
// const controller = await this.controllers.getController(kind);
|
|
449
|
-
// if (!controller) {
|
|
450
|
-
// throw new RuntimeError(
|
|
451
|
-
// "ERR_CONTROLLER_NOT_FOUND",
|
|
452
|
-
// `No controller registered for Kind: ${kind}`,
|
|
453
|
-
// );
|
|
454
|
-
// }
|
|
455
|
-
// // Create execution context with recursive execute capability
|
|
456
|
-
// const execContext: ExecContext = {
|
|
457
|
-
// execute: (nestedUrn: string, nestedInput: any) => this.execute(nestedUrn, nestedInput, ctx),
|
|
458
|
-
// ...ctx,
|
|
459
|
-
// };
|
|
460
|
-
// try {
|
|
461
|
-
// await this.eventBus.emit(`${name}.ExecutionStarted`, { urn });
|
|
462
|
-
// const result = await controller.execute?.(name, input, {
|
|
463
|
-
// ...execContext,
|
|
464
|
-
// resource,
|
|
465
|
-
// });
|
|
466
|
-
// await this.eventBus.emit(`${name}.ExecutionCompleted`, { urn });
|
|
467
|
-
// return result;
|
|
468
|
-
// } catch (error) {
|
|
469
|
-
// await this.eventBus.emit(`${name}.ExecutionFailed`, {
|
|
470
|
-
// urn,
|
|
471
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
472
|
-
// });
|
|
473
|
-
// throw new RuntimeError(
|
|
474
|
-
// "ERR_EXECUTION_FAILED",
|
|
475
|
-
// `Execution failed for ${urn}: ${error instanceof Error ? error.message : String(error)}`,
|
|
476
|
-
// );
|
|
477
|
-
// }
|
|
478
|
-
// }
|
|
479
|
-
// private parseUrn(urn: string): [string, string] {
|
|
480
|
-
// const separator = urn.lastIndexOf(".");
|
|
481
|
-
// if (separator <= 0 || separator === urn.length - 1) {
|
|
482
|
-
// throw new Error(
|
|
483
|
-
// `Invalid URN format: ${urn}. Expected "Kind.Name" where Kind can include dots`,
|
|
484
|
-
// );
|
|
485
|
-
// }
|
|
486
|
-
// const kind = urn.slice(0, separator);
|
|
487
|
-
// const name = urn.slice(separator + 1);
|
|
488
|
-
// return [kind, name];
|
|
489
|
-
// }
|
|
490
|
-
async invoke(module, kind, name, ...args) {
|
|
491
|
-
const instance = this.getResourceByName(module, kind, name);
|
|
492
|
-
if (!instance) {
|
|
493
|
-
throw new RuntimeError("ERR_RESOURCE_NOT_FOUND", `Resource not found for invocation: ${module}.${kind}.${name}`);
|
|
494
|
-
}
|
|
495
|
-
if (typeof instance !== "object" || typeof instance["invoke"] !== "function") {
|
|
496
|
-
throw new RuntimeError("ERR_RESOURCE_NOT_INVOKABLE", `Resource ${kind}.${name} does not have an invoke method`);
|
|
497
|
-
}
|
|
498
|
-
const outputs = await instance["invoke"](...args);
|
|
499
|
-
this.emitRuntimeEvent(`${module}.${kind}.${name}.Invoked`, {
|
|
500
|
-
outputs,
|
|
501
341
|
});
|
|
502
|
-
return outputs;
|
|
503
342
|
}
|
|
504
|
-
getResourceKey(module, kind, name) {
|
|
505
|
-
if (!kind.includes(".")) {
|
|
506
|
-
throw new Error(`Resource kind must include module prefix: ${kind}`);
|
|
507
|
-
}
|
|
508
|
-
return `${module}.${kind}.${name}`;
|
|
509
|
-
}
|
|
510
|
-
// private assertResourceEventAllowed(event: string): void {
|
|
511
|
-
// const parts = event.split(".");
|
|
512
|
-
// const leaf = parts[parts.length - 1];
|
|
513
|
-
// if (leaf === "Initialized" || leaf === "Teardown") {
|
|
514
|
-
// throw new Error(`Resource events cannot use reserved lifecycle event: ${leaf}`);
|
|
515
|
-
// }
|
|
516
|
-
// }
|
|
517
343
|
/**
|
|
518
344
|
* Enable event streaming to a file (JSONL format)
|
|
519
345
|
*/
|
|
@@ -532,16 +358,6 @@ export class Kernel {
|
|
|
532
358
|
getEventStream() {
|
|
533
359
|
return this.eventStream;
|
|
534
360
|
}
|
|
535
|
-
/**
|
|
536
|
-
* Take a snapshot of current runtime state
|
|
537
|
-
*/
|
|
538
|
-
// async takeSnapshot(filePath?: string) {
|
|
539
|
-
// return this.snapshotSerializer.takeSnapshot(
|
|
540
|
-
// this.manifests.getAll(),
|
|
541
|
-
// this.resourceInstances,
|
|
542
|
-
// filePath,
|
|
543
|
-
// );
|
|
544
|
-
// }
|
|
545
361
|
/**
|
|
546
362
|
* Setup event streaming hook to capture all events
|
|
547
363
|
*/
|
|
@@ -555,4 +371,132 @@ export class Kernel {
|
|
|
555
371
|
};
|
|
556
372
|
}
|
|
557
373
|
}
|
|
374
|
+
/** Returns a schema-appropriate placeholder value for a CompiledValue field. */
|
|
375
|
+
function placeholderForSchema(schema) {
|
|
376
|
+
if (schema.default !== undefined)
|
|
377
|
+
return schema.default;
|
|
378
|
+
switch (schema.type) {
|
|
379
|
+
case "integer":
|
|
380
|
+
case "number":
|
|
381
|
+
return schema.minimum ?? 0;
|
|
382
|
+
case "boolean":
|
|
383
|
+
return false;
|
|
384
|
+
case "array":
|
|
385
|
+
return [];
|
|
386
|
+
case "object":
|
|
387
|
+
return {};
|
|
388
|
+
default:
|
|
389
|
+
return "";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** Replaces CompiledValue wrappers with schema-appropriate placeholders for schema validation.
|
|
393
|
+
* Template strings were compiled from YAML at load time; this restores a shape
|
|
394
|
+
* that AJV can validate without evaluating expressions. */
|
|
395
|
+
function stripCompiledValues(v, schema = {}) {
|
|
396
|
+
if (isCompiledValue(v))
|
|
397
|
+
return placeholderForSchema(schema);
|
|
398
|
+
if (Array.isArray(v)) {
|
|
399
|
+
const itemSchema = (schema.items ?? {});
|
|
400
|
+
return v.map((item) => stripCompiledValues(item, itemSchema));
|
|
401
|
+
}
|
|
402
|
+
if (v !== null && typeof v === "object") {
|
|
403
|
+
const props = (schema.properties ?? {});
|
|
404
|
+
const out = {};
|
|
405
|
+
for (const [k, val] of Object.entries(v)) {
|
|
406
|
+
out[k] = stripCompiledValues(val, props[k] ?? {});
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
return v;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
|
|
414
|
+
* For each leaf value that looks like a {kind, name} reference, calls getInstance(name)
|
|
415
|
+
* and replaces the value in-place with the returned live ResourceInstance.
|
|
416
|
+
* Values where getInstance returns undefined are left unchanged.
|
|
417
|
+
*/
|
|
418
|
+
/**
|
|
419
|
+
* Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
|
|
420
|
+
* Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
|
|
421
|
+
* Property-level annotations produce the dot-notation path to that property.
|
|
422
|
+
*/
|
|
423
|
+
function buildEvalPaths(schema) {
|
|
424
|
+
const compile = [];
|
|
425
|
+
const runtime = [];
|
|
426
|
+
if (schema["x-telo-eval"] === "compile")
|
|
427
|
+
compile.push("**");
|
|
428
|
+
else if (schema["x-telo-eval"] === "runtime")
|
|
429
|
+
runtime.push("**");
|
|
430
|
+
if (schema.properties) {
|
|
431
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
432
|
+
collectEvalPathsNode(propSchema, key, compile, runtime);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return { compile, runtime };
|
|
436
|
+
}
|
|
437
|
+
function collectEvalPathsNode(node, path, compile, runtime) {
|
|
438
|
+
if (node["x-telo-eval"] === "compile") {
|
|
439
|
+
compile.push(path);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (node["x-telo-eval"] === "runtime") {
|
|
443
|
+
runtime.push(path);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (node.properties) {
|
|
447
|
+
for (const [key, propSchema] of Object.entries(node.properties)) {
|
|
448
|
+
collectEvalPathsNode(propSchema, `${path}.${key}`, compile, runtime);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function injectAtPath(resource, fieldPath, getInstance) {
|
|
453
|
+
const parts = fieldPath.split(".");
|
|
454
|
+
function traverse(obj, partsLeft) {
|
|
455
|
+
if (!obj || typeof obj !== "object" || partsLeft.length === 0)
|
|
456
|
+
return;
|
|
457
|
+
const [head, ...rest] = partsLeft;
|
|
458
|
+
const isArr = head.endsWith("[]");
|
|
459
|
+
const key = isArr ? head.slice(0, -2) : head;
|
|
460
|
+
const container = obj;
|
|
461
|
+
const val = container[key];
|
|
462
|
+
if (val == null)
|
|
463
|
+
return;
|
|
464
|
+
if (isArr) {
|
|
465
|
+
if (!Array.isArray(val))
|
|
466
|
+
return;
|
|
467
|
+
for (let i = 0; i < val.length; i++) {
|
|
468
|
+
const elem = val[i];
|
|
469
|
+
if (!elem || typeof elem !== "object")
|
|
470
|
+
continue;
|
|
471
|
+
if (rest.length === 0) {
|
|
472
|
+
const ref = elem;
|
|
473
|
+
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
474
|
+
const instance = getInstance(ref.name);
|
|
475
|
+
if (instance)
|
|
476
|
+
val[i] = instance;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
traverse(elem, rest);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
if (rest.length === 0) {
|
|
486
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
487
|
+
const ref = val;
|
|
488
|
+
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
489
|
+
const instance = getInstance(ref.name);
|
|
490
|
+
if (instance)
|
|
491
|
+
container[key] = instance;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
traverse(val, rest);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
traverse(resource, parts);
|
|
501
|
+
}
|
|
558
502
|
//# sourceMappingURL=kernel.js.map
|