@telorun/kernel 0.2.4 → 0.2.5
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/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.yaml +1 -1
- package/dist/capabilities/executable.yaml +1 -1
- package/dist/capabilities/handler.yaml +1 -1
- package/dist/capabilities/listener.yaml +1 -1
- package/dist/capabilities/provider.yaml +1 -1
- package/dist/capabilities/template.yaml +1 -1
- 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 +4 -2
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +1 -2
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts +38 -0
- package/dist/controllers/module/import-controller.d.ts.map +1 -0
- package/dist/controllers/module/import-controller.js +119 -0
- package/dist/controllers/module/import-controller.js.map +1 -0
- package/dist/controllers/module/module-controller.d.ts +57 -11
- package/dist/controllers/module/module-controller.d.ts.map +1 -1
- package/dist/controllers/module/module-controller.js +46 -82
- package/dist/controllers/module/module-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +12 -4
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- 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 +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +23 -31
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +212 -333
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +2 -2
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +29 -9
- package/dist/loader.js.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.js +21 -12
- package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
- package/dist/manifest-schemas.d.ts +1 -25
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +3 -22
- 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 +25 -5
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +74 -28
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +3 -1
- 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 +9 -6
- package/src/boot-context-registry.ts +169 -0
- package/src/capabilities/component.yaml +4 -0
- package/src/capabilities/executable.yaml +8 -0
- package/src/capabilities/handler.yaml +4 -0
- package/src/capabilities/listener.yaml +4 -0
- package/src/capabilities/provider.yaml +4 -0
- package/src/capabilities/template.yaml +4 -0
- package/src/capabilities/type.yaml +4 -0
- package/src/controller-loader.ts +298 -0
- package/src/controller-registry.ts +206 -0
- package/src/controllers/capability/capability-controller.ts +41 -0
- package/src/controllers/module/import-controller.ts +143 -0
- package/src/controllers/module/module-controller.ts +67 -0
- package/src/controllers/module/module.json +48 -0
- package/src/controllers/resource-definition/resource-definition-controller.ts +87 -0
- package/src/controllers/resource-definition/resource-definition.json +18 -0
- package/src/event-stream.ts +121 -0
- package/src/events.ts +99 -0
- package/src/index.ts +7 -0
- package/src/kernel.ts +558 -0
- package/src/loader.ts +245 -0
- package/src/manifest-adapters/http-adapter.ts +35 -0
- package/src/manifest-adapters/local-file-adapter.ts +69 -0
- package/src/manifest-adapters/manifest-adapter.ts +33 -0
- package/src/manifest-adapters/registry-adapter.ts +56 -0
- package/src/manifest-schemas.ts +49 -0
- package/src/registry.ts +137 -0
- package/src/resource-context.ts +266 -0
- package/src/resource-uri.ts +200 -0
- package/src/schema-valiator.ts +57 -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/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/src → src}/controllers/module/module.yaml +0 -0
package/dist/kernel.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ModuleContext, RuntimeError, } from "@telorun/sdk";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { BootContextRegistry } from "./boot-context-registry.js";
|
|
4
3
|
import { ControllerRegistry } from "./controller-registry.js";
|
|
5
4
|
import { EventStream } from "./event-stream.js";
|
|
6
5
|
import { EventBus } from "./events.js";
|
|
7
|
-
import { evaluateCel, expandValue, resolveManifestWithContext } from "./expressions.js";
|
|
8
6
|
import { Loader } from "./loader.js";
|
|
9
7
|
import { ResourceContextImpl } from "./resource-context.js";
|
|
10
8
|
import { SchemaValidator } from "./schema-valiator.js";
|
|
11
|
-
import { RuntimeError, } from "./types.js";
|
|
12
9
|
/**
|
|
13
10
|
* Kernel: Central orchestrator managing lifecycle and message bus
|
|
14
11
|
* Handles resource loading, initialization, and execution through controllers
|
|
@@ -17,64 +14,37 @@ export class Kernel {
|
|
|
17
14
|
constructor() {
|
|
18
15
|
this.loader = new Loader();
|
|
19
16
|
// private manifests: ManifestRegistry = new ManifestRegistry();
|
|
20
|
-
this.initializationQueue = [];
|
|
21
17
|
this.controllers = new ControllerRegistry();
|
|
22
18
|
this.eventBus = new EventBus();
|
|
23
19
|
this.eventStream = new EventStream();
|
|
24
20
|
// private snapshotSerializer: SnapshotSerializer = new SnapshotSerializer();
|
|
25
21
|
// private runtimeManifests: ResourceManifest[] | null = null;
|
|
26
|
-
|
|
22
|
+
// private resourceInstances: Map<
|
|
23
|
+
// string,
|
|
24
|
+
// { resource: ResourceManifest; instance: ResourceInstance }
|
|
25
|
+
// > = new Map();
|
|
27
26
|
this.resourceEventBuses = new Map();
|
|
28
|
-
this.resourceChildren = new Map();
|
|
29
27
|
this.holdCount = 0;
|
|
30
28
|
this.idleResolvers = [];
|
|
31
29
|
this._exitCode = 0;
|
|
32
|
-
|
|
30
|
+
// private bootContextRegistry = new BootContextRegistry();
|
|
33
31
|
this.sharedSchemaValidator = new SchemaValidator();
|
|
34
32
|
this.setupEventStreaming();
|
|
35
33
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
}
|
|
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
|
+
// }
|
|
78
48
|
async registerController(moduleName, kindName, controllerInstance) {
|
|
79
49
|
this.controllers.registerController(`${moduleName}.${kindName}`, controllerInstance);
|
|
80
50
|
await controllerInstance.register?.(this.createControllerContext(`${moduleName}.${kindName}`));
|
|
@@ -88,6 +58,15 @@ export class Kernel {
|
|
|
88
58
|
registerCapability(name, schema) {
|
|
89
59
|
this.controllers.registerCapability(name, schema);
|
|
90
60
|
}
|
|
61
|
+
getModuleContext(_moduleName) {
|
|
62
|
+
return this.rootContext;
|
|
63
|
+
}
|
|
64
|
+
resolveModuleAlias(_declaringModule, alias) {
|
|
65
|
+
return this.rootContext.importAliases.get(alias);
|
|
66
|
+
}
|
|
67
|
+
registerModuleImport(_declaringModule, alias, targetModule, kinds) {
|
|
68
|
+
this.rootContext.registerImport(alias, targetModule, kinds);
|
|
69
|
+
}
|
|
91
70
|
isCapabilityRegistered(name) {
|
|
92
71
|
return this.controllers.isCapabilityRegistered(name);
|
|
93
72
|
}
|
|
@@ -95,38 +74,50 @@ export class Kernel {
|
|
|
95
74
|
return this.controllers.getCapabilitySchema(name);
|
|
96
75
|
}
|
|
97
76
|
/**
|
|
98
|
-
* Load built-in Runtime definitions (e.g.,
|
|
77
|
+
* Load built-in Runtime definitions (e.g., Kernel.Module)
|
|
78
|
+
* Also declares all known module namespaces upfront so that resources can be
|
|
79
|
+
* registered to them. User-defined modules are declared explicitly by Kernel.Module
|
|
80
|
+
* resources during the initialization phase.
|
|
99
81
|
*/
|
|
100
82
|
async loadBuiltinDefinitions() {
|
|
83
|
+
// Declare built-in module namespaces upfront so getContext() can distinguish
|
|
84
|
+
// "not yet populated" from a completely unknown module name.
|
|
85
|
+
this.rootContext.registerImport("Kernel", "Kernel", []); // built-ins, unrestricted
|
|
86
|
+
// this.moduleContextRegistry.declareModule("default"); // user resources with no module field
|
|
101
87
|
this.controllers.registerDefinition({
|
|
102
|
-
kind: "
|
|
103
|
-
metadata: { name: "Definition", module: "
|
|
88
|
+
kind: "Kernel.Definition",
|
|
89
|
+
metadata: { name: "Definition", module: "Kernel" },
|
|
104
90
|
capabilities: ["template"],
|
|
105
91
|
schema: { type: "object" },
|
|
106
92
|
});
|
|
107
|
-
this.controllers.registerController("
|
|
108
|
-
|
|
109
|
-
|
|
93
|
+
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" },
|
|
110
99
|
});
|
|
100
|
+
this.controllers.registerController("Kernel.Module", await import("./controllers/module/module-controller.js"));
|
|
111
101
|
this.controllers.registerDefinition({
|
|
112
|
-
kind: "
|
|
113
|
-
metadata: { name: "
|
|
102
|
+
kind: "Kernel.Definition",
|
|
103
|
+
metadata: { name: "Import", module: "Kernel" },
|
|
114
104
|
capabilities: ["template"],
|
|
115
|
-
schema:
|
|
105
|
+
schema: { type: "object" },
|
|
116
106
|
});
|
|
117
|
-
this.controllers.registerController("
|
|
107
|
+
this.controllers.registerController("Kernel.Import", await import("./controllers/module/import-controller.js"));
|
|
118
108
|
this.controllers.registerDefinition({
|
|
119
|
-
kind: "
|
|
120
|
-
metadata: { name: "Capability", module: "
|
|
109
|
+
kind: "Kernel.Definition",
|
|
110
|
+
metadata: { name: "Capability", module: "Kernel" },
|
|
121
111
|
capabilities: ["template"],
|
|
122
112
|
schema: { type: "object" },
|
|
123
113
|
});
|
|
124
|
-
this.controllers.registerController("
|
|
114
|
+
this.controllers.registerController("Kernel.Capability", await import("./controllers/capability/capability-controller.js"));
|
|
125
115
|
}
|
|
126
116
|
/**
|
|
127
117
|
* Load from runtime configuration file
|
|
128
118
|
*/
|
|
129
119
|
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));
|
|
130
121
|
// Initialize built-in Runtime definitions first
|
|
131
122
|
await this.loadBuiltinDefinitions();
|
|
132
123
|
// Load built-in capability manifests before user configuration so that
|
|
@@ -134,9 +125,17 @@ export class Kernel {
|
|
|
134
125
|
const { fileURLToPath } = await import("url");
|
|
135
126
|
const capabilitiesDir = fileURLToPath(new URL("./capabilities/", import.meta.url));
|
|
136
127
|
const capabilityManifests = await this.loader.loadDirectory(capabilitiesDir);
|
|
137
|
-
// Load runtime configuration
|
|
138
|
-
const userManifests = await this.loader.loadManifest(runtimeYamlPath);
|
|
139
|
-
|
|
128
|
+
// 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) {
|
|
132
|
+
if (manifest.kind === "Kernel.Module") {
|
|
133
|
+
this.rootContext.setSecrets(manifest.secrets ?? {});
|
|
134
|
+
this.rootContext.setVariables(manifest.variables ?? {});
|
|
135
|
+
this.rootContext.setTargets(manifest.targets ?? []);
|
|
136
|
+
}
|
|
137
|
+
this.rootContext.registerManifest(manifest);
|
|
138
|
+
}
|
|
140
139
|
}
|
|
141
140
|
/**
|
|
142
141
|
* Phase 1: Load - Ingest files from directory and load runtime config
|
|
@@ -152,34 +151,33 @@ export class Kernel {
|
|
|
152
151
|
async start() {
|
|
153
152
|
// Call controller register hooks first (before any initialization)
|
|
154
153
|
for (const kind of this.controllers.getKinds()) {
|
|
155
|
-
const controller =
|
|
154
|
+
const controller = this.controllers.getController(kind);
|
|
156
155
|
if (controller?.register) {
|
|
157
156
|
await controller.register(this.createControllerContext(`controller:${kind}`));
|
|
158
157
|
}
|
|
159
158
|
}
|
|
160
159
|
// Initialize resources
|
|
161
160
|
try {
|
|
162
|
-
await this.initializeResources();
|
|
163
|
-
await this.eventBus.emit("
|
|
164
|
-
await this.eventBus.emit("
|
|
165
|
-
await this.
|
|
166
|
-
await this.eventBus.emit("
|
|
161
|
+
await this.rootContext.initializeResources();
|
|
162
|
+
await this.eventBus.emit("Kernel.Initialized", {});
|
|
163
|
+
await this.eventBus.emit("Kernel.Starting", {});
|
|
164
|
+
await this.rootContext.runTargets();
|
|
165
|
+
await this.eventBus.emit("Kernel.Started", {});
|
|
167
166
|
await this.waitForIdle();
|
|
168
167
|
}
|
|
169
168
|
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
|
-
}
|
|
169
|
+
await this.eventBus.emit("Kernel.Stopping", {});
|
|
170
|
+
await this.rootContext.teardownResources();
|
|
171
|
+
await this.eventBus.emit("Kernel.Stopped", { exitCode: this._exitCode });
|
|
181
172
|
}
|
|
182
173
|
}
|
|
174
|
+
// async runInstances(): Promise<void> {
|
|
175
|
+
// for (const { instance } of this.resourceInstances.values()) {
|
|
176
|
+
// if (instance.run) {
|
|
177
|
+
// await instance.run();
|
|
178
|
+
// }
|
|
179
|
+
// }
|
|
180
|
+
// }
|
|
183
181
|
async emitRuntimeEvent(event, payload) {
|
|
184
182
|
await this.eventBus.emit(event, payload);
|
|
185
183
|
}
|
|
@@ -192,7 +190,7 @@ export class Kernel {
|
|
|
192
190
|
acquireHold(reason) {
|
|
193
191
|
this.holdCount += 1;
|
|
194
192
|
if (this.holdCount === 1) {
|
|
195
|
-
void this.eventBus.emit("
|
|
193
|
+
void this.eventBus.emit("Kernel.Blocked", {
|
|
196
194
|
reason,
|
|
197
195
|
count: this.holdCount,
|
|
198
196
|
});
|
|
@@ -209,7 +207,7 @@ export class Kernel {
|
|
|
209
207
|
for (const resolve of resolvers) {
|
|
210
208
|
resolve();
|
|
211
209
|
}
|
|
212
|
-
void this.eventBus.emit("
|
|
210
|
+
void this.eventBus.emit("Kernel.Unblocked", { count: this.holdCount });
|
|
213
211
|
}
|
|
214
212
|
};
|
|
215
213
|
}
|
|
@@ -234,18 +232,6 @@ export class Kernel {
|
|
|
234
232
|
hasEventHandlers(event) {
|
|
235
233
|
return this.eventBus.hasHandlers(event);
|
|
236
234
|
}
|
|
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
235
|
on(event, handler) {
|
|
250
236
|
this.eventBus.on(event, handler);
|
|
251
237
|
}
|
|
@@ -259,261 +245,164 @@ export class Kernel {
|
|
|
259
245
|
void this.eventBus.emit(namespaced, payload);
|
|
260
246
|
},
|
|
261
247
|
acquireHold: (reason) => this.acquireHold(reason),
|
|
262
|
-
|
|
263
|
-
expandValue: (value, context) => expandValue(value, context),
|
|
248
|
+
expandValue: (value, context) => this.rootContext.merge(context).expand(value),
|
|
264
249
|
requestExit: (code) => this.requestExit(code),
|
|
265
250
|
};
|
|
266
251
|
}
|
|
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;
|
|
252
|
+
createResourceContext(moduleContext, resource) {
|
|
253
|
+
return new ResourceContextImpl(this, moduleContext, resource.metadata, this.sharedSchemaValidator);
|
|
290
254
|
}
|
|
291
255
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
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).
|
|
294
260
|
*/
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
261
|
+
async _createInstance(evalContext, resource) {
|
|
262
|
+
const kind = resource.kind;
|
|
263
|
+
// Resolve the alias-prefixed kind to its real fully-qualified kind.
|
|
264
|
+
// resolveKind() throws with a clear message if the alias or kind is not found.
|
|
265
|
+
const resolvedKind = this.rootContext.resolveKind(kind);
|
|
266
|
+
const controller = this.controllers.getControllerOrUndefined(resolvedKind);
|
|
267
|
+
if (!controller) {
|
|
268
|
+
const kindInfo = resolvedKind !== kind ? `'${kind}' (resolved to '${resolvedKind}')` : `'${kind}'`;
|
|
269
|
+
throw new Error(`No controller registered for kind ${kindInfo}, known controllers are: ${this.controllers.getKinds().join(", ")}`);
|
|
302
270
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
}
|
|
271
|
+
if (!controller.create) {
|
|
272
|
+
throw new RuntimeError("ERR_CONTROLLER_INVALID", `Controller for ${kind} does not implement create method`);
|
|
273
|
+
}
|
|
274
|
+
if (!controller.schema?.type) {
|
|
275
|
+
throw new Error(`No schema defined for ${kind} controller`);
|
|
318
276
|
}
|
|
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);
|
|
277
|
+
try {
|
|
278
|
+
this.sharedSchemaValidator.compile(controller.schema).validate(resource);
|
|
325
279
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
this.initializationQueue.push(manifest);
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw new RuntimeError("ERR_RESOURCE_SCHEMA_VALIDATION_FAILED", `Resource does not match schema for kind ${kind}: ${error instanceof Error ? error.message : String(error)}`);
|
|
329
282
|
}
|
|
330
|
-
const
|
|
331
|
-
await
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
283
|
+
const ctx = this.createResourceContext(evalContext, resource);
|
|
284
|
+
const instance = await controller.create(resource, ctx);
|
|
285
|
+
if (!instance)
|
|
286
|
+
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 ?? {});
|
|
338
302
|
}
|
|
339
303
|
}
|
|
304
|
+
return instance;
|
|
340
305
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const createdResources = [];
|
|
351
|
-
let handledThisPass = [];
|
|
352
|
-
// Track latest error for each resource
|
|
353
|
-
const resourceErrors = new Map();
|
|
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);
|
|
426
|
-
}
|
|
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);
|
|
427
315
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
.
|
|
432
|
-
|
|
433
|
-
.
|
|
434
|
-
|
|
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);
|
|
435
326
|
}
|
|
436
327
|
}
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
// const resource = this.manifests.get(kind, name);
|
|
444
|
-
// if (!resource) {
|
|
445
|
-
// throw new RuntimeError("ERR_RESOURCE_NOT_FOUND", `Resource not found: ${urn}`);
|
|
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
|
+
// }
|
|
446
334
|
// }
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
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
|
+
// }
|
|
454
343
|
// }
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
//
|
|
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
|
-
// );
|
|
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
|
+
// }
|
|
477
357
|
// }
|
|
358
|
+
// return Array.from(seen);
|
|
478
359
|
// }
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
//
|
|
360
|
+
/**
|
|
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.
|
|
363
|
+
*/
|
|
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,
|
|
484
384
|
// );
|
|
485
385
|
// }
|
|
486
|
-
// const
|
|
487
|
-
//
|
|
488
|
-
//
|
|
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
|
+
// }
|
|
489
399
|
// }
|
|
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
|
-
});
|
|
502
|
-
return outputs;
|
|
503
|
-
}
|
|
504
400
|
getResourceKey(module, kind, name) {
|
|
505
401
|
if (!kind.includes(".")) {
|
|
506
402
|
throw new Error(`Resource kind must include module prefix: ${kind}`);
|
|
507
403
|
}
|
|
508
404
|
return `${module}.${kind}.${name}`;
|
|
509
405
|
}
|
|
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
406
|
/**
|
|
518
407
|
* Enable event streaming to a file (JSONL format)
|
|
519
408
|
*/
|
|
@@ -532,16 +421,6 @@ export class Kernel {
|
|
|
532
421
|
getEventStream() {
|
|
533
422
|
return this.eventStream;
|
|
534
423
|
}
|
|
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
424
|
/**
|
|
546
425
|
* Setup event streaming hook to capture all events
|
|
547
426
|
*/
|