@telorun/kernel 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +2 -2
- 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 +38 -24
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +296 -201
- 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 +11 -10
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +70 -25
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts +6 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +76 -4
- 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 +2 -2
- package/src/kernel.ts +364 -226
- 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 +90 -35
- package/src/schema-valiator.ts +90 -13
- 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/loader.ts +0 -245
- package/src/manifest-adapters/http-adapter.ts +0 -35
- package/src/manifest-adapters/registry-adapter.ts +0 -56
package/src/kernel.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { AnalysisRegistry, Loader, StaticAnalyzer } from "@telorun/analyzer";
|
|
1
2
|
import {
|
|
2
3
|
ControllerContext,
|
|
3
|
-
EvaluationContext,
|
|
4
4
|
Kernel as IKernel,
|
|
5
5
|
ModuleContext,
|
|
6
6
|
ResourceContext,
|
|
@@ -9,58 +9,61 @@ import {
|
|
|
9
9
|
ResourceManifest,
|
|
10
10
|
RuntimeError,
|
|
11
11
|
RuntimeEvent,
|
|
12
|
+
isCompiledValue,
|
|
13
|
+
type ParsedArgs,
|
|
12
14
|
} from "@telorun/sdk";
|
|
13
15
|
import * as path from "path";
|
|
16
|
+
import { parseArgs } from "util";
|
|
14
17
|
import { ControllerRegistry } from "./controller-registry.js";
|
|
15
18
|
import { EventStream } from "./event-stream.js";
|
|
16
19
|
import { EventBus } from "./events.js";
|
|
17
|
-
import {
|
|
20
|
+
import { LocalFileAdapter } from "./manifest-adapters/local-file-adapter.js";
|
|
18
21
|
import { ResourceContextImpl } from "./resource-context.js";
|
|
19
22
|
import { SchemaValidator } from "./schema-valiator.js";
|
|
20
23
|
|
|
24
|
+
export interface KernelOptions {
|
|
25
|
+
stdin?: NodeJS.ReadableStream;
|
|
26
|
+
stdout?: NodeJS.WritableStream;
|
|
27
|
+
stderr?: NodeJS.WritableStream;
|
|
28
|
+
env?: Record<string, string | undefined>;
|
|
29
|
+
argv?: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
/**
|
|
22
33
|
* Kernel: Central orchestrator managing lifecycle and message bus
|
|
23
34
|
* Handles resource loading, initialization, and execution through controllers
|
|
24
35
|
*/
|
|
25
36
|
export class Kernel implements IKernel {
|
|
26
|
-
private loader
|
|
27
|
-
|
|
37
|
+
private readonly loader = new Loader();
|
|
38
|
+
private readonly analyzer = new StaticAnalyzer();
|
|
39
|
+
private readonly registry = new AnalysisRegistry();
|
|
28
40
|
private controllers: ControllerRegistry = new ControllerRegistry();
|
|
29
41
|
private eventBus: EventBus = new EventBus();
|
|
30
42
|
private eventStream: EventStream = new EventStream();
|
|
31
|
-
|
|
32
|
-
// private runtimeManifests: ResourceManifest[] | null = null;
|
|
33
|
-
// private resourceInstances: Map<
|
|
34
|
-
// string,
|
|
35
|
-
// { resource: ResourceManifest; instance: ResourceInstance }
|
|
36
|
-
// > = new Map();
|
|
37
|
-
private resourceEventBuses: Map<string, EventBus> = new Map();
|
|
43
|
+
|
|
38
44
|
private holdCount = 0;
|
|
39
45
|
private idleResolvers: Array<() => void> = [];
|
|
40
46
|
private _exitCode = 0;
|
|
41
|
-
// private bootContextRegistry = new BootContextRegistry();
|
|
42
47
|
private readonly sharedSchemaValidator = new SchemaValidator();
|
|
43
48
|
private rootContext!: ModuleContext;
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
private staticManifests: ResourceManifest[] = [];
|
|
50
|
+
|
|
51
|
+
readonly stdin: NodeJS.ReadableStream;
|
|
52
|
+
readonly stdout: NodeJS.WritableStream;
|
|
53
|
+
readonly stderr: NodeJS.WritableStream;
|
|
54
|
+
readonly env: Record<string, string | undefined>;
|
|
55
|
+
readonly argv: string[];
|
|
56
|
+
|
|
57
|
+
constructor(options: KernelOptions = {}) {
|
|
58
|
+
this.stdin = options.stdin ?? process.stdin;
|
|
59
|
+
this.stdout = options.stdout ?? process.stdout;
|
|
60
|
+
this.stderr = options.stderr ?? process.stderr;
|
|
61
|
+
this.env = options.env ?? process.env;
|
|
62
|
+
this.argv = options.argv ?? [];
|
|
63
|
+
this.loader.register(new LocalFileAdapter());
|
|
46
64
|
this.setupEventStreaming();
|
|
47
65
|
}
|
|
48
66
|
|
|
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
67
|
async registerController(
|
|
65
68
|
moduleName: string,
|
|
66
69
|
kindName: string,
|
|
@@ -73,16 +76,9 @@ export class Kernel implements IKernel {
|
|
|
73
76
|
/**
|
|
74
77
|
* Register a resource definition with the controller registry
|
|
75
78
|
*/
|
|
76
|
-
registerResourceDefinition(
|
|
77
|
-
definition: ResourceDefinition,
|
|
78
|
-
// basePath?: string,
|
|
79
|
-
// namespace?: string | null,
|
|
80
|
-
): void {
|
|
79
|
+
registerResourceDefinition(definition: ResourceDefinition): void {
|
|
81
80
|
this.controllers.registerDefinition(definition);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
registerCapability(name: string, schema?: Record<string, any>): void {
|
|
85
|
-
this.controllers.registerCapability(name, schema);
|
|
81
|
+
this.registry.registerDefinition(definition);
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
getModuleContext(_moduleName: string): ModuleContext {
|
|
@@ -100,14 +96,14 @@ export class Kernel implements IKernel {
|
|
|
100
96
|
kinds: string[],
|
|
101
97
|
): void {
|
|
102
98
|
this.rootContext.registerImport(alias, targetModule, kinds);
|
|
99
|
+
this.registry.registerImport(alias, targetModule, kinds);
|
|
103
100
|
}
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return this.controllers.getCapabilitySchema(name);
|
|
102
|
+
/** Returns the live analysis registry backed by this kernel's known definitions and aliases.
|
|
103
|
+
* Pass to StaticAnalyzer.analyze() for incremental validation of new manifests against
|
|
104
|
+
* already-registered types (e.g. front-end editor validating a manifest before submitting). */
|
|
105
|
+
getAnalysisRegistry(): AnalysisRegistry {
|
|
106
|
+
return this.registry;
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
/**
|
|
@@ -120,81 +116,92 @@ export class Kernel implements IKernel {
|
|
|
120
116
|
// Declare built-in module namespaces upfront so getContext() can distinguish
|
|
121
117
|
// "not yet populated" from a completely unknown module name.
|
|
122
118
|
this.rootContext.registerImport("Kernel", "Kernel", []); // built-ins, unrestricted
|
|
123
|
-
// this.moduleContextRegistry.declareModule("default"); // user resources with no module field
|
|
124
119
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
schema: { type: "object" },
|
|
130
|
-
});
|
|
120
|
+
// Register built-in definitions with the controller registry.
|
|
121
|
+
// AnalysisRegistry's underlying DefinitionRegistry already seeds KERNEL_BUILTINS on construction.
|
|
122
|
+
for (const def of this.registry.builtinDefinitions()) this.controllers.registerDefinition(def);
|
|
123
|
+
|
|
131
124
|
this.controllers.registerController(
|
|
132
125
|
"Kernel.Definition",
|
|
133
126
|
await import("./controllers/resource-definition/resource-definition-controller.js"),
|
|
134
127
|
);
|
|
135
|
-
this.controllers.registerDefinition({
|
|
136
|
-
kind: "Kernel.Definition",
|
|
137
|
-
metadata: { name: "Module", module: "Kernel" },
|
|
138
|
-
capabilities: ["template"],
|
|
139
|
-
schema: { type: "object" },
|
|
140
|
-
});
|
|
141
128
|
this.controllers.registerController(
|
|
142
129
|
"Kernel.Module",
|
|
143
130
|
await import("./controllers/module/module-controller.js"),
|
|
144
131
|
);
|
|
145
|
-
this.controllers.registerDefinition({
|
|
146
|
-
kind: "Kernel.Definition",
|
|
147
|
-
metadata: { name: "Import", module: "Kernel" },
|
|
148
|
-
capabilities: ["template"],
|
|
149
|
-
schema: { type: "object" },
|
|
150
|
-
});
|
|
151
132
|
this.controllers.registerController(
|
|
152
133
|
"Kernel.Import",
|
|
153
134
|
await import("./controllers/module/import-controller.js"),
|
|
154
135
|
);
|
|
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
136
|
}
|
|
166
137
|
|
|
167
138
|
/**
|
|
168
139
|
* Load from runtime configuration file
|
|
169
140
|
*/
|
|
170
141
|
async loadFromConfig(runtimeYamlPath: string): Promise<void> {
|
|
142
|
+
const resolvedUrl = new URL(runtimeYamlPath, `file://${process.cwd()}/`).href;
|
|
143
|
+
const sourceUrl = await this.loader.resolveEntryPoint(resolvedUrl);
|
|
171
144
|
this.rootContext = new ModuleContext(
|
|
172
|
-
|
|
145
|
+
sourceUrl,
|
|
173
146
|
{},
|
|
174
147
|
{},
|
|
175
148
|
{},
|
|
176
149
|
[],
|
|
177
150
|
this._createInstance.bind(this),
|
|
178
151
|
(event, payload) => this.eventBus.emit(event, payload),
|
|
152
|
+
this.env,
|
|
179
153
|
);
|
|
180
154
|
// Initialize built-in Runtime definitions first
|
|
181
155
|
await this.loadBuiltinDefinitions();
|
|
182
156
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
157
|
+
// Phase 5: attach injection hook — fires between create() and init() for every resource
|
|
158
|
+
this.rootContext.preInitHook = (resource, getInstance) =>
|
|
159
|
+
this._injectDependencies(resource, getInstance);
|
|
160
|
+
|
|
161
|
+
// Static analysis pre-flight: validates schemas and invocation context compatibility.
|
|
162
|
+
// All errors are fatal — kernel does not start if analysis fails.
|
|
163
|
+
const staticManifests = await this.loader.loadManifests(sourceUrl);
|
|
164
|
+
this.staticManifests = staticManifests;
|
|
165
|
+
|
|
166
|
+
// Register module identities for x-telo-ref resolution (Phase 3 prerequisite).
|
|
167
|
+
// Kernel built-ins ("kernel" → "Kernel") are auto-registered when Kernel.Abstract
|
|
168
|
+
// definitions are registered in loadBuiltinDefinitions() above.
|
|
169
|
+
for (const m of staticManifests) {
|
|
170
|
+
if (m.kind === "Kernel.Module" && m.metadata?.name && m.metadata?.namespace) {
|
|
171
|
+
this.registry.registerModuleIdentity(
|
|
172
|
+
m.metadata.namespace as string,
|
|
173
|
+
m.metadata.name as string,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const errors = this.analyzer.analyzeErrors(staticManifests, {}, this.registry);
|
|
179
|
+
if (errors.length > 0) {
|
|
180
|
+
throw new RuntimeError(
|
|
181
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
182
|
+
"Manifest validation failed",
|
|
183
|
+
errors.map((d) => ({
|
|
184
|
+
severity: "error" as const,
|
|
185
|
+
message: d.message,
|
|
186
|
+
code: d.code !== undefined ? String(d.code) : undefined,
|
|
187
|
+
resource: (d.data as any)?.resource
|
|
188
|
+
? `${(d.data as any).resource.kind}.${(d.data as any).resource.name}`
|
|
189
|
+
: undefined,
|
|
190
|
+
})),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
188
193
|
|
|
189
194
|
// Load runtime configuration — root module gets access to host env
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
195
|
-
|
|
195
|
+
const allManifests = await this.loader.loadModule(sourceUrl, { compile: true });
|
|
196
|
+
|
|
197
|
+
// Phase 2: normalize inline resources — extract inline values from x-telo-ref slots
|
|
198
|
+
// into first-class named manifests and replace them in-place with {kind, name} references.
|
|
199
|
+
// Update staticManifests so Phase 3 (validateReferences) and Phase 4 (DAG) see
|
|
200
|
+
// the same normalized structure.
|
|
201
|
+
const normalizedManifests = this.analyzer.normalize(allManifests, this.registry);
|
|
202
|
+
this.staticManifests = normalizedManifests;
|
|
196
203
|
|
|
197
|
-
for (const manifest of
|
|
204
|
+
for (const manifest of normalizedManifests) {
|
|
198
205
|
if (manifest.kind === "Kernel.Module") {
|
|
199
206
|
this.rootContext.setSecrets(manifest.secrets ?? {});
|
|
200
207
|
this.rootContext.setVariables(manifest.variables ?? {});
|
|
@@ -226,6 +233,36 @@ export class Kernel implements IKernel {
|
|
|
226
233
|
}
|
|
227
234
|
}
|
|
228
235
|
|
|
236
|
+
// Phase 3+4: reference validation, cycle detection, and topo sort
|
|
237
|
+
const {
|
|
238
|
+
diagnostics: refErrors,
|
|
239
|
+
order,
|
|
240
|
+
cycleError,
|
|
241
|
+
} = this.analyzer.prepare(this.staticManifests, this.registry);
|
|
242
|
+
if (refErrors.length > 0) {
|
|
243
|
+
throw new RuntimeError(
|
|
244
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
245
|
+
"Manifest validation failed",
|
|
246
|
+
refErrors.map((d) => ({
|
|
247
|
+
severity: "error" as const,
|
|
248
|
+
message: d.message,
|
|
249
|
+
code: d.code !== undefined ? String(d.code) : undefined,
|
|
250
|
+
resource: (d.data as any)?.resource
|
|
251
|
+
? `${(d.data as any).resource.kind}.${(d.data as any).resource.name}`
|
|
252
|
+
: undefined,
|
|
253
|
+
})),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (cycleError) {
|
|
257
|
+
throw new RuntimeError("ERR_CIRCULAR_DEPENDENCY", cycleError);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Phase 5: sort pending resources into topo order so injection always finds
|
|
261
|
+
// initialized dependencies, then run the init loop.
|
|
262
|
+
if (order) {
|
|
263
|
+
this.rootContext.setInitOrder(order);
|
|
264
|
+
}
|
|
265
|
+
|
|
229
266
|
// Initialize resources
|
|
230
267
|
try {
|
|
231
268
|
await this.rootContext.initializeResources();
|
|
@@ -241,14 +278,6 @@ export class Kernel implements IKernel {
|
|
|
241
278
|
}
|
|
242
279
|
}
|
|
243
280
|
|
|
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
281
|
async emitRuntimeEvent(event: string, payload?: any): Promise<void> {
|
|
253
282
|
await this.eventBus.emit(event, payload);
|
|
254
283
|
}
|
|
@@ -327,7 +356,7 @@ export class Kernel implements IKernel {
|
|
|
327
356
|
},
|
|
328
357
|
acquireHold: (reason?: string) => this.acquireHold(reason),
|
|
329
358
|
expandValue: (value: any, context: Record<string, any>) =>
|
|
330
|
-
this.rootContext.
|
|
359
|
+
this.rootContext.expandWith(value, context),
|
|
331
360
|
requestExit: (code: number) => this.requestExit(code),
|
|
332
361
|
};
|
|
333
362
|
}
|
|
@@ -335,25 +364,62 @@ export class Kernel implements IKernel {
|
|
|
335
364
|
private createResourceContext(
|
|
336
365
|
moduleContext: ModuleContext,
|
|
337
366
|
resource: ResourceManifest,
|
|
367
|
+
args?: ParsedArgs,
|
|
338
368
|
): ResourceContext {
|
|
339
369
|
return new ResourceContextImpl(
|
|
340
370
|
this,
|
|
341
371
|
moduleContext,
|
|
342
372
|
resource.metadata,
|
|
343
373
|
this.sharedSchemaValidator,
|
|
374
|
+
this.stdin,
|
|
375
|
+
this.stdout,
|
|
376
|
+
this.stderr,
|
|
377
|
+
args,
|
|
344
378
|
);
|
|
345
379
|
}
|
|
346
380
|
|
|
347
381
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
|
|
351
|
-
|
|
382
|
+
* Parse kernel.argv using a controller's args spec (if present).
|
|
383
|
+
* If the controller exports no args spec, does a generic parse.
|
|
384
|
+
*/
|
|
385
|
+
private parseArgsForController(controller: any): ParsedArgs {
|
|
386
|
+
if (this.argv.length === 0) return { _: [] };
|
|
387
|
+
|
|
388
|
+
const argSpec = controller.args;
|
|
389
|
+
if (argSpec) {
|
|
390
|
+
const options: Record<string, { type: "string" | "boolean"; short?: string }> = {};
|
|
391
|
+
for (const [name, def] of Object.entries(argSpec) as [string, any][]) {
|
|
392
|
+
options[name] = { type: def.type ?? "string" };
|
|
393
|
+
if (def.alias) options[name].short = def.alias;
|
|
394
|
+
}
|
|
395
|
+
const { values, positionals } = parseArgs({
|
|
396
|
+
args: this.argv,
|
|
397
|
+
options,
|
|
398
|
+
allowPositionals: true,
|
|
399
|
+
strict: false,
|
|
400
|
+
});
|
|
401
|
+
return { ...values, _: positionals } as ParsedArgs;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Generic parse: no spec, best-effort
|
|
405
|
+
const { values, positionals } = parseArgs({
|
|
406
|
+
args: this.argv,
|
|
407
|
+
allowPositionals: true,
|
|
408
|
+
strict: false,
|
|
409
|
+
});
|
|
410
|
+
return { ...values, _: positionals } as ParsedArgs;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Create phase only: resolves the controller, validates the schema, and calls
|
|
415
|
+
* controller.create(). Returns { instance, ctx } so initializeResources can
|
|
416
|
+
* run init() separately in its second phase. Returns null when the controller
|
|
417
|
+
* is not yet registered (retry signal).
|
|
352
418
|
*/
|
|
353
419
|
private async _createInstance(
|
|
354
420
|
evalContext: ModuleContext,
|
|
355
421
|
resource: ResourceManifest,
|
|
356
|
-
): Promise<ResourceInstance | null> {
|
|
422
|
+
): Promise<{ instance: ResourceInstance; ctx: ResourceContext } | null> {
|
|
357
423
|
const kind = resource.kind;
|
|
358
424
|
|
|
359
425
|
// Resolve the alias-prefixed kind to its real fully-qualified kind.
|
|
@@ -379,8 +445,27 @@ export class Kernel implements IKernel {
|
|
|
379
445
|
throw new Error(`No schema defined for ${kind} controller`);
|
|
380
446
|
}
|
|
381
447
|
|
|
448
|
+
// Resolve eval paths from x-telo-eval annotations in the parent and own schema
|
|
449
|
+
const definition = this.controllers.getDefinition(resolvedKind);
|
|
450
|
+
const parentDef = definition?.capability
|
|
451
|
+
? this.controllers.getDefinition(definition.capability)
|
|
452
|
+
: undefined;
|
|
453
|
+
const parentEval = parentDef?.schema
|
|
454
|
+
? buildEvalPaths(parentDef.schema)
|
|
455
|
+
: { compile: [], runtime: [] };
|
|
456
|
+
const ownEval = definition?.schema
|
|
457
|
+
? buildEvalPaths(definition.schema)
|
|
458
|
+
: { compile: [], runtime: [] };
|
|
459
|
+
const compile = [...parentEval.compile, ...ownEval.compile];
|
|
460
|
+
const runtime = [...parentEval.runtime, ...ownEval.runtime];
|
|
461
|
+
|
|
462
|
+
// Schema validation runs before CEL evaluation so it sees the original manifest
|
|
463
|
+
// shape. CompiledValue wrappers (from load-time precompilation) are stripped,
|
|
464
|
+
// restoring the pre-CEL string view that the schema expects.
|
|
382
465
|
try {
|
|
383
|
-
this.sharedSchemaValidator
|
|
466
|
+
this.sharedSchemaValidator
|
|
467
|
+
.compile(controller.schema)
|
|
468
|
+
.validate(stripCompiledValues(resource, controller.schema as Record<string, unknown>));
|
|
384
469
|
} catch (error) {
|
|
385
470
|
throw new RuntimeError(
|
|
386
471
|
"ERR_RESOURCE_SCHEMA_VALIDATION_FAILED",
|
|
@@ -388,138 +473,56 @@ export class Kernel implements IKernel {
|
|
|
388
473
|
);
|
|
389
474
|
}
|
|
390
475
|
|
|
391
|
-
|
|
392
|
-
const
|
|
476
|
+
// Expand compile-time CEL fields before passing to the controller.
|
|
477
|
+
const processedResource = compile.length
|
|
478
|
+
? (evalContext.expandPaths(
|
|
479
|
+
resource as Record<string, unknown>,
|
|
480
|
+
compile,
|
|
481
|
+
runtime,
|
|
482
|
+
) as ResourceManifest)
|
|
483
|
+
: resource;
|
|
484
|
+
|
|
485
|
+
const parsedArgs = this.parseArgsForController(controller);
|
|
486
|
+
const ctx = this.createResourceContext(evalContext, processedResource, parsedArgs);
|
|
487
|
+
const instance = await controller.create(processedResource, ctx);
|
|
393
488
|
if (!instance) return null;
|
|
394
489
|
|
|
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
|
-
}
|
|
413
|
-
|
|
414
|
-
return instance;
|
|
415
|
-
}
|
|
490
|
+
if (!runtime.length) return { instance, ctx };
|
|
416
491
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
}
|
|
492
|
+
const wrapped: ResourceInstance = {
|
|
493
|
+
...instance,
|
|
494
|
+
invoke: async (inputs: any) => {
|
|
495
|
+
const expanded = evalContext.expandPaths(inputs as Record<string, unknown>, runtime);
|
|
496
|
+
return instance.invoke!(expanded);
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
return { instance: wrapped, ctx };
|
|
437
500
|
}
|
|
438
501
|
|
|
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
|
-
/**
|
|
460
|
-
* Returns the unique set of local file paths from which resources were loaded.
|
|
461
|
-
* HTTP/HTTPS sources are excluded — they cannot be watched on disk.
|
|
462
|
-
*/
|
|
463
|
-
// getSourceFiles(): string[] {
|
|
464
|
-
// const seen = new Set<string>();
|
|
465
|
-
// for (const { resource } of this.resourceInstances.values()) {
|
|
466
|
-
// const src = resource.metadata.source;
|
|
467
|
-
// if (src && !src.startsWith("http://") && !src.startsWith("https://")) {
|
|
468
|
-
// seen.add(src);
|
|
469
|
-
// }
|
|
470
|
-
// }
|
|
471
|
-
// return Array.from(seen);
|
|
472
|
-
// }
|
|
473
|
-
|
|
474
502
|
/**
|
|
475
|
-
*
|
|
476
|
-
*
|
|
503
|
+
* Phase 5 — Inject live instances into reference fields of a resource config.
|
|
504
|
+
*
|
|
505
|
+
* Called between create() and init() for every resource. Walks the definition's
|
|
506
|
+
* field map and replaces each {kind, name} reference value (outside scope visibility
|
|
507
|
+
* paths) with the live ResourceInstance returned by getInstance(name). Fields within
|
|
508
|
+
* scope paths are left as {kind, name} — the controller resolves them at runtime.
|
|
477
509
|
*/
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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}`;
|
|
510
|
+
private _injectDependencies(
|
|
511
|
+
resource: ResourceManifest,
|
|
512
|
+
getInstance: (name: string) => ResourceInstance | undefined,
|
|
513
|
+
): void {
|
|
514
|
+
this.registry.iterateFieldEntries(
|
|
515
|
+
resource,
|
|
516
|
+
(fieldPath) => injectAtPath(resource, fieldPath, getInstance),
|
|
517
|
+
(fieldPath) => {
|
|
518
|
+
const val = (resource as Record<string, unknown>)[fieldPath];
|
|
519
|
+
if (Array.isArray(val)) {
|
|
520
|
+
(resource as Record<string, unknown>)[fieldPath] = this.rootContext.createScopeHandle(
|
|
521
|
+
val as ResourceManifest[],
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
);
|
|
523
526
|
}
|
|
524
527
|
|
|
525
528
|
/**
|
|
@@ -556,3 +559,138 @@ export class Kernel implements IKernel {
|
|
|
556
559
|
};
|
|
557
560
|
}
|
|
558
561
|
}
|
|
562
|
+
|
|
563
|
+
/** Returns a schema-appropriate placeholder value for a CompiledValue field. */
|
|
564
|
+
function placeholderForSchema(schema: Record<string, unknown>): unknown {
|
|
565
|
+
if (schema.default !== undefined) return schema.default;
|
|
566
|
+
switch (schema.type) {
|
|
567
|
+
case "integer":
|
|
568
|
+
case "number":
|
|
569
|
+
return (schema.minimum as number | undefined) ?? 0;
|
|
570
|
+
case "boolean":
|
|
571
|
+
return false;
|
|
572
|
+
case "array":
|
|
573
|
+
return [];
|
|
574
|
+
case "object":
|
|
575
|
+
return {};
|
|
576
|
+
default:
|
|
577
|
+
return "";
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Replaces CompiledValue wrappers with schema-appropriate placeholders for schema validation.
|
|
582
|
+
* Template strings were compiled from YAML at load time; this restores a shape
|
|
583
|
+
* that AJV can validate without evaluating expressions. */
|
|
584
|
+
function stripCompiledValues(v: unknown, schema: Record<string, unknown> = {}): unknown {
|
|
585
|
+
if (isCompiledValue(v)) return placeholderForSchema(schema);
|
|
586
|
+
if (Array.isArray(v)) {
|
|
587
|
+
const itemSchema = (schema.items ?? {}) as Record<string, unknown>;
|
|
588
|
+
return v.map((item) => stripCompiledValues(item, itemSchema));
|
|
589
|
+
}
|
|
590
|
+
if (v !== null && typeof v === "object") {
|
|
591
|
+
const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;
|
|
592
|
+
const out: Record<string, unknown> = {};
|
|
593
|
+
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
594
|
+
out[k] = stripCompiledValues(val, props[k] ?? {});
|
|
595
|
+
}
|
|
596
|
+
return out;
|
|
597
|
+
}
|
|
598
|
+
return v;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
|
|
603
|
+
* For each leaf value that looks like a {kind, name} reference, calls getInstance(name)
|
|
604
|
+
* and replaces the value in-place with the returned live ResourceInstance.
|
|
605
|
+
* Values where getInstance returns undefined are left unchanged.
|
|
606
|
+
*/
|
|
607
|
+
/**
|
|
608
|
+
* Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
|
|
609
|
+
* Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
|
|
610
|
+
* Property-level annotations produce the dot-notation path to that property.
|
|
611
|
+
*/
|
|
612
|
+
function buildEvalPaths(schema: Record<string, any>): { compile: string[]; runtime: string[] } {
|
|
613
|
+
const compile: string[] = [];
|
|
614
|
+
const runtime: string[] = [];
|
|
615
|
+
|
|
616
|
+
if (schema["x-telo-eval"] === "compile") compile.push("**");
|
|
617
|
+
else if (schema["x-telo-eval"] === "runtime") runtime.push("**");
|
|
618
|
+
|
|
619
|
+
if (schema.properties) {
|
|
620
|
+
for (const [key, propSchema] of Object.entries(schema.properties as Record<string, any>)) {
|
|
621
|
+
collectEvalPathsNode(propSchema, key, compile, runtime);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return { compile, runtime };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function collectEvalPathsNode(
|
|
629
|
+
node: Record<string, any>,
|
|
630
|
+
path: string,
|
|
631
|
+
compile: string[],
|
|
632
|
+
runtime: string[],
|
|
633
|
+
): void {
|
|
634
|
+
if (node["x-telo-eval"] === "compile") {
|
|
635
|
+
compile.push(path);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (node["x-telo-eval"] === "runtime") {
|
|
639
|
+
runtime.push(path);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (node.properties) {
|
|
643
|
+
for (const [key, propSchema] of Object.entries(node.properties as Record<string, any>)) {
|
|
644
|
+
collectEvalPathsNode(propSchema, `${path}.${key}`, compile, runtime);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function injectAtPath(
|
|
650
|
+
resource: ResourceManifest,
|
|
651
|
+
fieldPath: string,
|
|
652
|
+
getInstance: (name: string) => ResourceInstance | undefined,
|
|
653
|
+
): void {
|
|
654
|
+
const parts = fieldPath.split(".");
|
|
655
|
+
|
|
656
|
+
function traverse(obj: unknown, partsLeft: string[]): void {
|
|
657
|
+
if (!obj || typeof obj !== "object" || partsLeft.length === 0) return;
|
|
658
|
+
const [head, ...rest] = partsLeft;
|
|
659
|
+
const isArr = head.endsWith("[]");
|
|
660
|
+
const key = isArr ? head.slice(0, -2) : head;
|
|
661
|
+
const container = obj as Record<string, unknown>;
|
|
662
|
+
const val = container[key];
|
|
663
|
+
if (val == null) return;
|
|
664
|
+
|
|
665
|
+
if (isArr) {
|
|
666
|
+
if (!Array.isArray(val)) return;
|
|
667
|
+
for (let i = 0; i < val.length; i++) {
|
|
668
|
+
const elem = val[i];
|
|
669
|
+
if (!elem || typeof elem !== "object") continue;
|
|
670
|
+
if (rest.length === 0) {
|
|
671
|
+
const ref = elem as Record<string, unknown>;
|
|
672
|
+
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
673
|
+
const instance = getInstance(ref.name);
|
|
674
|
+
if (instance) val[i] = instance;
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
traverse(elem, rest);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} else {
|
|
681
|
+
if (rest.length === 0) {
|
|
682
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
683
|
+
const ref = val as Record<string, unknown>;
|
|
684
|
+
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
685
|
+
const instance = getInstance(ref.name);
|
|
686
|
+
if (instance) container[key] = instance;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
traverse(val, rest);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
traverse(resource, parts);
|
|
696
|
+
}
|