@telorun/kernel 0.2.5 → 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/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/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/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/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/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/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/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/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +2 -1
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +0 -4
- 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 +0 -3
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +21 -27
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/controllers/module/module-controller.d.ts +0 -59
- package/dist/controllers/module/module-controller.d.ts.map +1 -1
- package/dist/controllers/module/module-controller.js +0 -45
- 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 +6 -8
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +21 -24
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +254 -189
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +9 -8
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +50 -132
- 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 +21 -26
- 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 -25
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +56 -16
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/resource-context.d.ts +2 -5
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +31 -21
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +13 -2
- package/dist/schema-valiator.js.map +1 -1
- package/package.json +6 -7
- package/src/controller-loader.ts +2 -1
- package/src/controller-registry.ts +11 -26
- package/src/controllers/module/import-controller.ts +30 -30
- package/src/controllers/module/module-controller.ts +0 -51
- package/src/controllers/resource-definition/resource-definition-controller.ts +14 -15
- package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
- package/src/index.ts +1 -1
- package/src/kernel.ts +313 -224
- package/src/loader.ts +54 -165
- package/src/manifest-adapters/local-file-adapter.ts +24 -31
- package/src/manifest-adapters/manifest-adapter.ts +2 -0
- package/src/manifest-schemas.ts +60 -24
- package/src/resource-context.ts +31 -30
- package/src/schema-valiator.ts +14 -3
- package/src/capabilities/component.yaml +0 -4
- package/src/capabilities/executable.yaml +0 -8
- package/src/capabilities/handler.yaml +0 -4
- package/src/capabilities/listener.yaml +0 -4
- package/src/capabilities/provider.yaml +0 -4
- package/src/capabilities/template.yaml +0 -4
- package/src/capabilities/type.yaml +0 -4
- package/src/controllers/capability/capability-controller.ts +0 -41
- package/src/controllers/module/module.json +0 -48
- package/src/controllers/module/module.yaml +0 -32
- package/src/manifest-adapters/http-adapter.ts +0 -35
- package/src/manifest-adapters/registry-adapter.ts +0 -56
package/src/kernel.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import { AnalysisRegistry, StaticAnalyzer } from "@telorun/analyzer";
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
ControllerContext,
|
|
4
|
+
Kernel as IKernel,
|
|
5
|
+
ModuleContext,
|
|
6
|
+
ResourceContext,
|
|
7
|
+
ResourceDefinition,
|
|
8
|
+
ResourceInstance,
|
|
9
|
+
ResourceManifest,
|
|
10
|
+
RuntimeError,
|
|
11
|
+
RuntimeEvent,
|
|
12
|
+
isCompiledValue,
|
|
12
13
|
} from "@telorun/sdk";
|
|
13
14
|
import * as path from "path";
|
|
14
15
|
import { ControllerRegistry } from "./controller-registry.js";
|
|
@@ -23,7 +24,9 @@ import { SchemaValidator } from "./schema-valiator.js";
|
|
|
23
24
|
* Handles resource loading, initialization, and execution through controllers
|
|
24
25
|
*/
|
|
25
26
|
export class Kernel implements IKernel {
|
|
26
|
-
private loader
|
|
27
|
+
private readonly loader = new Loader();
|
|
28
|
+
private readonly analyzer = new StaticAnalyzer();
|
|
29
|
+
private readonly registry = new AnalysisRegistry();
|
|
27
30
|
// private manifests: ManifestRegistry = new ManifestRegistry();
|
|
28
31
|
private controllers: ControllerRegistry = new ControllerRegistry();
|
|
29
32
|
private eventBus: EventBus = new EventBus();
|
|
@@ -34,33 +37,19 @@ export class Kernel implements IKernel {
|
|
|
34
37
|
// string,
|
|
35
38
|
// { resource: ResourceManifest; instance: ResourceInstance }
|
|
36
39
|
// > = new Map();
|
|
37
|
-
|
|
40
|
+
|
|
38
41
|
private holdCount = 0;
|
|
39
42
|
private idleResolvers: Array<() => void> = [];
|
|
40
43
|
private _exitCode = 0;
|
|
41
44
|
// private bootContextRegistry = new BootContextRegistry();
|
|
42
45
|
private readonly sharedSchemaValidator = new SchemaValidator();
|
|
43
46
|
private rootContext!: ModuleContext;
|
|
47
|
+
private staticManifests: ResourceManifest[] = [];
|
|
44
48
|
|
|
45
49
|
constructor() {
|
|
46
50
|
this.setupEventStreaming();
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
// async teardownResource(module: string, kind: string, name: string): Promise<void> {
|
|
50
|
-
// const key = this.getResourceKey(module, kind, name);
|
|
51
|
-
// const entry = this.resourceInstances.get(key);
|
|
52
|
-
// if (!entry) return;
|
|
53
|
-
// const { resource, instance } = entry;
|
|
54
|
-
// if (instance.teardown) {
|
|
55
|
-
// await instance.teardown();
|
|
56
|
-
// }
|
|
57
|
-
// await this.eventBus.emit(`${resource.kind}.${resource.metadata.name}.Teardown`, {
|
|
58
|
-
// resource: { kind: resource.kind, name: resource.metadata.name },
|
|
59
|
-
// });
|
|
60
|
-
// // this.resourceInstances.delete(key);
|
|
61
|
-
// this.resourceEventBuses.delete(key);
|
|
62
|
-
// }
|
|
63
|
-
|
|
64
53
|
async registerController(
|
|
65
54
|
moduleName: string,
|
|
66
55
|
kindName: string,
|
|
@@ -73,16 +62,9 @@ export class Kernel implements IKernel {
|
|
|
73
62
|
/**
|
|
74
63
|
* Register a resource definition with the controller registry
|
|
75
64
|
*/
|
|
76
|
-
registerResourceDefinition(
|
|
77
|
-
definition: ResourceDefinition,
|
|
78
|
-
// basePath?: string,
|
|
79
|
-
// namespace?: string | null,
|
|
80
|
-
): void {
|
|
65
|
+
registerResourceDefinition(definition: ResourceDefinition): void {
|
|
81
66
|
this.controllers.registerDefinition(definition);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
registerCapability(name: string, schema?: Record<string, any>): void {
|
|
85
|
-
this.controllers.registerCapability(name, schema);
|
|
67
|
+
this.registry.registerDefinition(definition);
|
|
86
68
|
}
|
|
87
69
|
|
|
88
70
|
getModuleContext(_moduleName: string): ModuleContext {
|
|
@@ -100,14 +82,14 @@ export class Kernel implements IKernel {
|
|
|
100
82
|
kinds: string[],
|
|
101
83
|
): void {
|
|
102
84
|
this.rootContext.registerImport(alias, targetModule, kinds);
|
|
85
|
+
this.registry.registerImport(alias, targetModule, kinds);
|
|
103
86
|
}
|
|
104
87
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return this.controllers.getCapabilitySchema(name);
|
|
88
|
+
/** Returns the live analysis registry backed by this kernel's known definitions and aliases.
|
|
89
|
+
* Pass to StaticAnalyzer.analyze() for incremental validation of new manifests against
|
|
90
|
+
* already-registered types (e.g. front-end editor validating a manifest before submitting). */
|
|
91
|
+
getAnalysisRegistry(): AnalysisRegistry {
|
|
92
|
+
return this.registry;
|
|
111
93
|
}
|
|
112
94
|
|
|
113
95
|
/**
|
|
@@ -120,81 +102,95 @@ export class Kernel implements IKernel {
|
|
|
120
102
|
// Declare built-in module namespaces upfront so getContext() can distinguish
|
|
121
103
|
// "not yet populated" from a completely unknown module name.
|
|
122
104
|
this.rootContext.registerImport("Kernel", "Kernel", []); // built-ins, unrestricted
|
|
123
|
-
// this.moduleContextRegistry.declareModule("default"); // user resources with no module field
|
|
124
105
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
schema: { type: "object" },
|
|
130
|
-
});
|
|
106
|
+
// Register built-in definitions with the controller registry.
|
|
107
|
+
// AnalysisRegistry's underlying DefinitionRegistry already seeds KERNEL_BUILTINS on construction.
|
|
108
|
+
for (const def of this.registry.builtinDefinitions()) this.controllers.registerDefinition(def);
|
|
109
|
+
|
|
131
110
|
this.controllers.registerController(
|
|
132
111
|
"Kernel.Definition",
|
|
133
112
|
await import("./controllers/resource-definition/resource-definition-controller.js"),
|
|
134
113
|
);
|
|
135
|
-
this.controllers.registerDefinition({
|
|
136
|
-
kind: "Kernel.Definition",
|
|
137
|
-
metadata: { name: "Module", module: "Kernel" },
|
|
138
|
-
capabilities: ["template"],
|
|
139
|
-
schema: { type: "object" },
|
|
140
|
-
});
|
|
141
114
|
this.controllers.registerController(
|
|
142
115
|
"Kernel.Module",
|
|
143
116
|
await import("./controllers/module/module-controller.js"),
|
|
144
117
|
);
|
|
145
|
-
this.controllers.registerDefinition({
|
|
146
|
-
kind: "Kernel.Definition",
|
|
147
|
-
metadata: { name: "Import", module: "Kernel" },
|
|
148
|
-
capabilities: ["template"],
|
|
149
|
-
schema: { type: "object" },
|
|
150
|
-
});
|
|
151
118
|
this.controllers.registerController(
|
|
152
119
|
"Kernel.Import",
|
|
153
120
|
await import("./controllers/module/import-controller.js"),
|
|
154
121
|
);
|
|
155
|
-
this.controllers.registerDefinition({
|
|
156
|
-
kind: "Kernel.Definition",
|
|
157
|
-
metadata: { name: "Capability", module: "Kernel" },
|
|
158
|
-
capabilities: ["template"],
|
|
159
|
-
schema: { type: "object" },
|
|
160
|
-
});
|
|
161
|
-
this.controllers.registerController(
|
|
162
|
-
"Kernel.Capability",
|
|
163
|
-
await import("./controllers/capability/capability-controller.js"),
|
|
164
|
-
);
|
|
165
122
|
}
|
|
166
123
|
|
|
167
124
|
/**
|
|
168
125
|
* Load from runtime configuration file
|
|
169
126
|
*/
|
|
170
127
|
async loadFromConfig(runtimeYamlPath: string): Promise<void> {
|
|
128
|
+
// Resolve directory paths to their module.yaml so that relative imports
|
|
129
|
+
// (e.g. ../../modules/foo) use the correct base directory.
|
|
130
|
+
const sourceUrl = await this.loader.resolveEntryPoint(
|
|
131
|
+
new URL(runtimeYamlPath, `file://${process.cwd()}/`).href,
|
|
132
|
+
);
|
|
171
133
|
this.rootContext = new ModuleContext(
|
|
172
|
-
|
|
134
|
+
sourceUrl,
|
|
173
135
|
{},
|
|
174
136
|
{},
|
|
175
137
|
{},
|
|
176
138
|
[],
|
|
177
139
|
this._createInstance.bind(this),
|
|
178
140
|
(event, payload) => this.eventBus.emit(event, payload),
|
|
141
|
+
process.env,
|
|
179
142
|
);
|
|
180
143
|
// Initialize built-in Runtime definitions first
|
|
181
144
|
await this.loadBuiltinDefinitions();
|
|
182
145
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
146
|
+
// Phase 5: attach injection hook — fires between create() and init() for every resource
|
|
147
|
+
this.rootContext.preInitHook = (resource, getInstance) =>
|
|
148
|
+
this._injectDependencies(resource, getInstance);
|
|
149
|
+
|
|
150
|
+
// Static analysis pre-flight: validates schemas and invocation context compatibility.
|
|
151
|
+
// All errors are fatal — kernel does not start if analysis fails.
|
|
152
|
+
const staticManifests = await this.loader.loadManifests(sourceUrl);
|
|
153
|
+
this.staticManifests = staticManifests;
|
|
154
|
+
|
|
155
|
+
// Register module identities for x-telo-ref resolution (Phase 3 prerequisite).
|
|
156
|
+
// Kernel built-ins ("kernel" → "Kernel") are auto-registered when Kernel.Abstract
|
|
157
|
+
// definitions are registered in loadBuiltinDefinitions() above.
|
|
158
|
+
for (const m of staticManifests) {
|
|
159
|
+
if (m.kind === "Kernel.Module" && m.metadata?.name && m.metadata?.namespace) {
|
|
160
|
+
this.registry.registerModuleIdentity(
|
|
161
|
+
m.metadata.namespace as string,
|
|
162
|
+
m.metadata.name as string,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const errors = this.analyzer.analyzeErrors(staticManifests, {}, this.registry);
|
|
168
|
+
if (errors.length > 0) {
|
|
169
|
+
throw new RuntimeError(
|
|
170
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
171
|
+
"Manifest validation failed",
|
|
172
|
+
errors.map((d) => ({
|
|
173
|
+
severity: "error" as const,
|
|
174
|
+
message: d.message,
|
|
175
|
+
code: d.code !== undefined ? String(d.code) : undefined,
|
|
176
|
+
resource: (d.data as any)?.resource
|
|
177
|
+
? `${(d.data as any).resource.kind}.${(d.data as any).resource.name}`
|
|
178
|
+
: undefined,
|
|
179
|
+
})),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
188
182
|
|
|
189
183
|
// Load runtime configuration — root module gets access to host env
|
|
190
|
-
const
|
|
191
|
-
runtimeYamlPath,
|
|
192
|
-
`file://${process.cwd()}/`,
|
|
193
|
-
{ env: process.env },
|
|
194
|
-
);
|
|
195
|
-
const allManifests = [...capabilityManifests, ...userManifests];
|
|
184
|
+
const allManifests = await this.loader.loadModule(sourceUrl, { compile: true });
|
|
196
185
|
|
|
197
|
-
|
|
186
|
+
// Phase 2: normalize inline resources — extract inline values from x-telo-ref slots
|
|
187
|
+
// into first-class named manifests and replace them in-place with {kind, name} references.
|
|
188
|
+
// Update staticManifests so Phase 3 (validateReferences) and Phase 4 (DAG) see
|
|
189
|
+
// the same normalized structure.
|
|
190
|
+
const normalizedManifests = this.analyzer.normalize(allManifests, this.registry);
|
|
191
|
+
this.staticManifests = normalizedManifests;
|
|
192
|
+
|
|
193
|
+
for (const manifest of normalizedManifests) {
|
|
198
194
|
if (manifest.kind === "Kernel.Module") {
|
|
199
195
|
this.rootContext.setSecrets(manifest.secrets ?? {});
|
|
200
196
|
this.rootContext.setVariables(manifest.variables ?? {});
|
|
@@ -226,6 +222,36 @@ export class Kernel implements IKernel {
|
|
|
226
222
|
}
|
|
227
223
|
}
|
|
228
224
|
|
|
225
|
+
// Phase 3+4: reference validation, cycle detection, and topo sort
|
|
226
|
+
const {
|
|
227
|
+
diagnostics: refErrors,
|
|
228
|
+
order,
|
|
229
|
+
cycleError,
|
|
230
|
+
} = this.analyzer.prepare(this.staticManifests, this.registry);
|
|
231
|
+
if (refErrors.length > 0) {
|
|
232
|
+
throw new RuntimeError(
|
|
233
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
234
|
+
"Manifest validation failed",
|
|
235
|
+
refErrors.map((d) => ({
|
|
236
|
+
severity: "error" as const,
|
|
237
|
+
message: d.message,
|
|
238
|
+
code: d.code !== undefined ? String(d.code) : undefined,
|
|
239
|
+
resource: (d.data as any)?.resource
|
|
240
|
+
? `${(d.data as any).resource.kind}.${(d.data as any).resource.name}`
|
|
241
|
+
: undefined,
|
|
242
|
+
})),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (cycleError) {
|
|
246
|
+
throw new RuntimeError("ERR_CIRCULAR_DEPENDENCY", cycleError);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Phase 5: sort pending resources into topo order so injection always finds
|
|
250
|
+
// initialized dependencies, then run the init loop.
|
|
251
|
+
if (order) {
|
|
252
|
+
this.rootContext.setInitOrder(order);
|
|
253
|
+
}
|
|
254
|
+
|
|
229
255
|
// Initialize resources
|
|
230
256
|
try {
|
|
231
257
|
await this.rootContext.initializeResources();
|
|
@@ -241,14 +267,6 @@ export class Kernel implements IKernel {
|
|
|
241
267
|
}
|
|
242
268
|
}
|
|
243
269
|
|
|
244
|
-
// async runInstances(): Promise<void> {
|
|
245
|
-
// for (const { instance } of this.resourceInstances.values()) {
|
|
246
|
-
// if (instance.run) {
|
|
247
|
-
// await instance.run();
|
|
248
|
-
// }
|
|
249
|
-
// }
|
|
250
|
-
// }
|
|
251
|
-
|
|
252
270
|
async emitRuntimeEvent(event: string, payload?: any): Promise<void> {
|
|
253
271
|
await this.eventBus.emit(event, payload);
|
|
254
272
|
}
|
|
@@ -327,7 +345,7 @@ export class Kernel implements IKernel {
|
|
|
327
345
|
},
|
|
328
346
|
acquireHold: (reason?: string) => this.acquireHold(reason),
|
|
329
347
|
expandValue: (value: any, context: Record<string, any>) =>
|
|
330
|
-
this.rootContext.
|
|
348
|
+
this.rootContext.expandWith(value, context),
|
|
331
349
|
requestExit: (code: number) => this.requestExit(code),
|
|
332
350
|
};
|
|
333
351
|
}
|
|
@@ -345,15 +363,15 @@ export class Kernel implements IKernel {
|
|
|
345
363
|
}
|
|
346
364
|
|
|
347
365
|
/**
|
|
348
|
-
* Create
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
366
|
+
* Create phase only: resolves the controller, validates the schema, and calls
|
|
367
|
+
* controller.create(). Returns { instance, ctx } so initializeResources can
|
|
368
|
+
* run init() separately in its second phase. Returns null when the controller
|
|
369
|
+
* is not yet registered (retry signal).
|
|
352
370
|
*/
|
|
353
371
|
private async _createInstance(
|
|
354
372
|
evalContext: ModuleContext,
|
|
355
373
|
resource: ResourceManifest,
|
|
356
|
-
): Promise<ResourceInstance | null> {
|
|
374
|
+
): Promise<{ instance: ResourceInstance; ctx: ResourceContext } | null> {
|
|
357
375
|
const kind = resource.kind;
|
|
358
376
|
|
|
359
377
|
// Resolve the alias-prefixed kind to its real fully-qualified kind.
|
|
@@ -379,8 +397,27 @@ export class Kernel implements IKernel {
|
|
|
379
397
|
throw new Error(`No schema defined for ${kind} controller`);
|
|
380
398
|
}
|
|
381
399
|
|
|
400
|
+
// Resolve eval paths from x-telo-eval annotations in the parent and own schema
|
|
401
|
+
const definition = this.controllers.getDefinition(resolvedKind);
|
|
402
|
+
const parentDef = definition?.capability
|
|
403
|
+
? this.controllers.getDefinition(definition.capability)
|
|
404
|
+
: undefined;
|
|
405
|
+
const parentEval = parentDef?.schema
|
|
406
|
+
? buildEvalPaths(parentDef.schema)
|
|
407
|
+
: { compile: [], runtime: [] };
|
|
408
|
+
const ownEval = definition?.schema
|
|
409
|
+
? buildEvalPaths(definition.schema)
|
|
410
|
+
: { compile: [], runtime: [] };
|
|
411
|
+
const compile = [...parentEval.compile, ...ownEval.compile];
|
|
412
|
+
const runtime = [...parentEval.runtime, ...ownEval.runtime];
|
|
413
|
+
|
|
414
|
+
// Schema validation runs before CEL evaluation so it sees the original manifest
|
|
415
|
+
// shape. CompiledValue wrappers (from load-time precompilation) are stripped,
|
|
416
|
+
// restoring the pre-CEL string view that the schema expects.
|
|
382
417
|
try {
|
|
383
|
-
this.sharedSchemaValidator
|
|
418
|
+
this.sharedSchemaValidator
|
|
419
|
+
.compile(controller.schema)
|
|
420
|
+
.validate(stripCompiledValues(resource, controller.schema as Record<string, unknown>));
|
|
384
421
|
} catch (error) {
|
|
385
422
|
throw new RuntimeError(
|
|
386
423
|
"ERR_RESOURCE_SCHEMA_VALIDATION_FAILED",
|
|
@@ -388,138 +425,55 @@ export class Kernel implements IKernel {
|
|
|
388
425
|
);
|
|
389
426
|
}
|
|
390
427
|
|
|
391
|
-
|
|
392
|
-
const
|
|
428
|
+
// Expand compile-time CEL fields before passing to the controller.
|
|
429
|
+
const processedResource = compile.length
|
|
430
|
+
? (evalContext.expandPaths(
|
|
431
|
+
resource as Record<string, unknown>,
|
|
432
|
+
compile,
|
|
433
|
+
runtime,
|
|
434
|
+
) as ResourceManifest)
|
|
435
|
+
: resource;
|
|
436
|
+
|
|
437
|
+
const ctx = this.createResourceContext(evalContext, processedResource);
|
|
438
|
+
const instance = await controller.create(processedResource, ctx);
|
|
393
439
|
if (!instance) return null;
|
|
394
440
|
|
|
395
|
-
if (
|
|
396
|
-
|
|
397
|
-
// if (isContextProvider(instance)) {
|
|
398
|
-
// this.bootContextRegistry.register(
|
|
399
|
-
// resource.kind,
|
|
400
|
-
// resource.metadata.name,
|
|
401
|
-
// resource.metadata.module,
|
|
402
|
-
// resource.grants as string[] | undefined,
|
|
403
|
-
// instance.provideContext(),
|
|
404
|
-
// );
|
|
405
|
-
// }
|
|
406
|
-
|
|
407
|
-
if (instance.snapshot) {
|
|
408
|
-
const snap = await Promise.resolve(instance.snapshot()).catch(() => ({}));
|
|
409
|
-
if (evalContext instanceof ModuleContext) {
|
|
410
|
-
evalContext.setResource(resource.metadata.name, (snap as Record<string, unknown>) ?? {});
|
|
411
|
-
}
|
|
412
|
-
}
|
|
441
|
+
if (!runtime.length) return { instance, ctx };
|
|
413
442
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
*/
|
|
423
|
-
async teardownContext(ctx: EvaluationContext): Promise<void> {
|
|
424
|
-
for (const child of [...ctx.children].reverse()) {
|
|
425
|
-
await this.teardownContext(child);
|
|
426
|
-
}
|
|
427
|
-
const entries = [...ctx.resourceInstances.entries()].reverse();
|
|
428
|
-
for (const [key, { resource, instance }] of entries) {
|
|
429
|
-
if (instance.teardown) await instance.teardown();
|
|
430
|
-
await this.eventBus.emit(`${resource.kind}.${resource.metadata.name}.Teardown`, {
|
|
431
|
-
resource: { kind: resource.kind, name: resource.metadata.name },
|
|
432
|
-
});
|
|
433
|
-
// this.resourceInstances.delete(key);
|
|
434
|
-
ctx.resourceInstances.delete(key);
|
|
435
|
-
this.resourceEventBuses.delete(key);
|
|
436
|
-
}
|
|
443
|
+
const wrapped: ResourceInstance = {
|
|
444
|
+
...instance,
|
|
445
|
+
invoke: async (inputs: any) => {
|
|
446
|
+
const expanded = evalContext.expandPaths(inputs as Record<string, unknown>, runtime);
|
|
447
|
+
return instance.invoke!(expanded);
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
return { instance: wrapped, ctx };
|
|
437
451
|
}
|
|
438
452
|
|
|
439
|
-
// getResourcesByKind(kind: string): RuntimeResource[] {
|
|
440
|
-
// const resources: RuntimeResource[] = [];
|
|
441
|
-
// for (const entry of this.resourceInstances.values()) {
|
|
442
|
-
// if (entry.resource.kind === kind) {
|
|
443
|
-
// resources.push(entry.instance as any);
|
|
444
|
-
// }
|
|
445
|
-
// }
|
|
446
|
-
// return resources;
|
|
447
|
-
// }
|
|
448
|
-
|
|
449
|
-
// getResourceByName(declaringModule: string, alias: string, name: string): RuntimeResource | null {
|
|
450
|
-
// const realModule = this.moduleContextRegistry.resolveAlias(declaringModule, alias) ?? alias;
|
|
451
|
-
// for (const { resource, instance } of this.resourceInstances.values()) {
|
|
452
|
-
// if (resource.metadata.module === realModule && resource.metadata.name === name) {
|
|
453
|
-
// return instance as RuntimeResource;
|
|
454
|
-
// }
|
|
455
|
-
// }
|
|
456
|
-
// return null;
|
|
457
|
-
// }
|
|
458
|
-
|
|
459
453
|
/**
|
|
460
|
-
*
|
|
461
|
-
*
|
|
454
|
+
* Phase 5 — Inject live instances into reference fields of a resource config.
|
|
455
|
+
*
|
|
456
|
+
* Called between create() and init() for every resource. Walks the definition's
|
|
457
|
+
* field map and replaces each {kind, name} reference value (outside scope visibility
|
|
458
|
+
* paths) with the live ResourceInstance returned by getInstance(name). Fields within
|
|
459
|
+
* scope paths are left as {kind, name} — the controller resolves them at runtime.
|
|
462
460
|
*/
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// // Parse first — bail before touching running resources if the file is invalid
|
|
480
|
-
// const newManifests = await this.loader.loadManifest(sourcePath, `file://${process.cwd()}/`, {
|
|
481
|
-
// env: process.env,
|
|
482
|
-
// });
|
|
483
|
-
|
|
484
|
-
// // Collect keys of resources loaded from this source (in insertion order)
|
|
485
|
-
// const keysFromSource: string[] = [];
|
|
486
|
-
// for (const [key, { resource }] of this.resourceInstances.entries()) {
|
|
487
|
-
// if (resource.metadata.source === sourcePath) {
|
|
488
|
-
// keysFromSource.push(key);
|
|
489
|
-
// }
|
|
490
|
-
// }
|
|
491
|
-
// // Tear down in reverse order (children first via cascade)
|
|
492
|
-
// for (const key of [...keysFromSource].reverse()) {
|
|
493
|
-
// const entry = this.resourceInstances.get(key);
|
|
494
|
-
// if (!entry) continue; // already removed by a cascade
|
|
495
|
-
// await this.teardownResource(
|
|
496
|
-
// entry.resource.metadata.module,
|
|
497
|
-
// entry.resource.kind,
|
|
498
|
-
// entry.resource.metadata.name,
|
|
499
|
-
// );
|
|
500
|
-
// }
|
|
501
|
-
|
|
502
|
-
// for (const manifest of newManifests) {
|
|
503
|
-
// this.rootContext.registerManifest(manifest);
|
|
504
|
-
// }
|
|
505
|
-
// const keysBefore = new Set(this.resourceInstances.keys());
|
|
506
|
-
// await this.initializeResources();
|
|
507
|
-
|
|
508
|
-
// // Run only newly created instances (not all instances — avoids double-run)
|
|
509
|
-
// const newKeys = Array.from(this.resourceInstances.keys()).filter((k) => !keysBefore.has(k));
|
|
510
|
-
// for (const key of newKeys) {
|
|
511
|
-
// const entry = this.resourceInstances.get(key);
|
|
512
|
-
// if (entry?.instance.run) {
|
|
513
|
-
// await entry.instance.run();
|
|
514
|
-
// }
|
|
515
|
-
// }
|
|
516
|
-
// }
|
|
517
|
-
|
|
518
|
-
private getResourceKey(module: string, kind: string, name: string): string {
|
|
519
|
-
if (!kind.includes(".")) {
|
|
520
|
-
throw new Error(`Resource kind must include module prefix: ${kind}`);
|
|
521
|
-
}
|
|
522
|
-
return `${module}.${kind}.${name}`;
|
|
461
|
+
private _injectDependencies(
|
|
462
|
+
resource: ResourceManifest,
|
|
463
|
+
getInstance: (name: string) => ResourceInstance | undefined,
|
|
464
|
+
): void {
|
|
465
|
+
this.registry.iterateFieldEntries(
|
|
466
|
+
resource,
|
|
467
|
+
(fieldPath) => injectAtPath(resource, fieldPath, getInstance),
|
|
468
|
+
(fieldPath) => {
|
|
469
|
+
const val = (resource as Record<string, unknown>)[fieldPath];
|
|
470
|
+
if (Array.isArray(val)) {
|
|
471
|
+
(resource as Record<string, unknown>)[fieldPath] = this.rootContext.createScopeHandle(
|
|
472
|
+
val as ResourceManifest[],
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
);
|
|
523
477
|
}
|
|
524
478
|
|
|
525
479
|
/**
|
|
@@ -556,3 +510,138 @@ export class Kernel implements IKernel {
|
|
|
556
510
|
};
|
|
557
511
|
}
|
|
558
512
|
}
|
|
513
|
+
|
|
514
|
+
/** Returns a schema-appropriate placeholder value for a CompiledValue field. */
|
|
515
|
+
function placeholderForSchema(schema: Record<string, unknown>): unknown {
|
|
516
|
+
if (schema.default !== undefined) return schema.default;
|
|
517
|
+
switch (schema.type) {
|
|
518
|
+
case "integer":
|
|
519
|
+
case "number":
|
|
520
|
+
return (schema.minimum as number | undefined) ?? 0;
|
|
521
|
+
case "boolean":
|
|
522
|
+
return false;
|
|
523
|
+
case "array":
|
|
524
|
+
return [];
|
|
525
|
+
case "object":
|
|
526
|
+
return {};
|
|
527
|
+
default:
|
|
528
|
+
return "";
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Replaces CompiledValue wrappers with schema-appropriate placeholders for schema validation.
|
|
533
|
+
* Template strings were compiled from YAML at load time; this restores a shape
|
|
534
|
+
* that AJV can validate without evaluating expressions. */
|
|
535
|
+
function stripCompiledValues(v: unknown, schema: Record<string, unknown> = {}): unknown {
|
|
536
|
+
if (isCompiledValue(v)) return placeholderForSchema(schema);
|
|
537
|
+
if (Array.isArray(v)) {
|
|
538
|
+
const itemSchema = (schema.items ?? {}) as Record<string, unknown>;
|
|
539
|
+
return v.map((item) => stripCompiledValues(item, itemSchema));
|
|
540
|
+
}
|
|
541
|
+
if (v !== null && typeof v === "object") {
|
|
542
|
+
const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;
|
|
543
|
+
const out: Record<string, unknown> = {};
|
|
544
|
+
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
545
|
+
out[k] = stripCompiledValues(val, props[k] ?? {});
|
|
546
|
+
}
|
|
547
|
+
return out;
|
|
548
|
+
}
|
|
549
|
+
return v;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
|
|
554
|
+
* For each leaf value that looks like a {kind, name} reference, calls getInstance(name)
|
|
555
|
+
* and replaces the value in-place with the returned live ResourceInstance.
|
|
556
|
+
* Values where getInstance returns undefined are left unchanged.
|
|
557
|
+
*/
|
|
558
|
+
/**
|
|
559
|
+
* Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
|
|
560
|
+
* Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
|
|
561
|
+
* Property-level annotations produce the dot-notation path to that property.
|
|
562
|
+
*/
|
|
563
|
+
function buildEvalPaths(schema: Record<string, any>): { compile: string[]; runtime: string[] } {
|
|
564
|
+
const compile: string[] = [];
|
|
565
|
+
const runtime: string[] = [];
|
|
566
|
+
|
|
567
|
+
if (schema["x-telo-eval"] === "compile") compile.push("**");
|
|
568
|
+
else if (schema["x-telo-eval"] === "runtime") runtime.push("**");
|
|
569
|
+
|
|
570
|
+
if (schema.properties) {
|
|
571
|
+
for (const [key, propSchema] of Object.entries(schema.properties as Record<string, any>)) {
|
|
572
|
+
collectEvalPathsNode(propSchema, key, compile, runtime);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return { compile, runtime };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function collectEvalPathsNode(
|
|
580
|
+
node: Record<string, any>,
|
|
581
|
+
path: string,
|
|
582
|
+
compile: string[],
|
|
583
|
+
runtime: string[],
|
|
584
|
+
): void {
|
|
585
|
+
if (node["x-telo-eval"] === "compile") {
|
|
586
|
+
compile.push(path);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (node["x-telo-eval"] === "runtime") {
|
|
590
|
+
runtime.push(path);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (node.properties) {
|
|
594
|
+
for (const [key, propSchema] of Object.entries(node.properties as Record<string, any>)) {
|
|
595
|
+
collectEvalPathsNode(propSchema, `${path}.${key}`, compile, runtime);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function injectAtPath(
|
|
601
|
+
resource: ResourceManifest,
|
|
602
|
+
fieldPath: string,
|
|
603
|
+
getInstance: (name: string) => ResourceInstance | undefined,
|
|
604
|
+
): void {
|
|
605
|
+
const parts = fieldPath.split(".");
|
|
606
|
+
|
|
607
|
+
function traverse(obj: unknown, partsLeft: string[]): void {
|
|
608
|
+
if (!obj || typeof obj !== "object" || partsLeft.length === 0) return;
|
|
609
|
+
const [head, ...rest] = partsLeft;
|
|
610
|
+
const isArr = head.endsWith("[]");
|
|
611
|
+
const key = isArr ? head.slice(0, -2) : head;
|
|
612
|
+
const container = obj as Record<string, unknown>;
|
|
613
|
+
const val = container[key];
|
|
614
|
+
if (val == null) return;
|
|
615
|
+
|
|
616
|
+
if (isArr) {
|
|
617
|
+
if (!Array.isArray(val)) return;
|
|
618
|
+
for (let i = 0; i < val.length; i++) {
|
|
619
|
+
const elem = val[i];
|
|
620
|
+
if (!elem || typeof elem !== "object") continue;
|
|
621
|
+
if (rest.length === 0) {
|
|
622
|
+
const ref = elem as Record<string, unknown>;
|
|
623
|
+
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
624
|
+
const instance = getInstance(ref.name);
|
|
625
|
+
if (instance) val[i] = instance;
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
traverse(elem, rest);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
if (rest.length === 0) {
|
|
633
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
634
|
+
const ref = val as Record<string, unknown>;
|
|
635
|
+
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
636
|
+
const instance = getInstance(ref.name);
|
|
637
|
+
if (instance) container[key] = instance;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
traverse(val, rest);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
traverse(resource, parts);
|
|
647
|
+
}
|