@telorun/kernel 0.2.9 → 0.3.0
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/README.md +7 -7
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +22 -23
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/evaluation-context.d.ts +21 -4
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +85 -13
- package/dist/evaluation-context.js.map +1 -1
- package/dist/kernel.d.ts +11 -8
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +38 -16
- package/dist/kernel.js.map +1 -1
- package/dist/manifest-schemas.d.ts +44 -1
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +59 -15
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/module-context.d.ts +1 -1
- package/dist/module-context.js +1 -1
- package/dist/resource-context.d.ts +6 -2
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +24 -4
- package/dist/resource-context.js.map +1 -1
- package/package.json +3 -3
- package/src/controllers/module/import-controller.ts +28 -28
- package/src/controllers/resource-definition/resource-definition-controller.ts +1 -1
- package/src/evaluation-context.ts +107 -18
- package/src/kernel.ts +52 -18
- package/src/manifest-schemas.ts +61 -15
- package/src/module-context.ts +1 -1
- package/src/resource-context.ts +38 -3
- package/dist/base-definition.d.ts +0 -14
- package/dist/base-definition.d.ts.map +0 -1
- package/dist/base-definition.js +0 -17
- package/dist/base-definition.js.map +0 -1
- package/dist/capabilities/capabilities/component.yaml +0 -4
- package/dist/capabilities/capabilities/executable.yaml +0 -8
- package/dist/capabilities/capabilities/handler.yaml +0 -4
- package/dist/capabilities/capabilities/listener.yaml +0 -4
- package/dist/capabilities/capabilities/provider.yaml +0 -4
- package/dist/capabilities/capabilities/template.yaml +0 -4
- package/dist/capabilities/capabilities/type.yaml +0 -4
- package/dist/capabilities/component.d.ts +0 -3
- package/dist/capabilities/component.d.ts.map +0 -1
- package/dist/capabilities/component.js +0 -4
- package/dist/capabilities/component.js.map +0 -1
- package/dist/capabilities/component.yaml +0 -3
- package/dist/capabilities/executable.d.ts +0 -3
- package/dist/capabilities/executable.d.ts.map +0 -1
- package/dist/capabilities/executable.js +0 -5
- package/dist/capabilities/executable.js.map +0 -1
- package/dist/capabilities/executable.yaml +0 -7
- package/dist/capabilities/handler.d.ts +0 -3
- package/dist/capabilities/handler.d.ts.map +0 -1
- package/dist/capabilities/handler.js +0 -4
- package/dist/capabilities/handler.js.map +0 -1
- package/dist/capabilities/handler.yaml +0 -3
- package/dist/capabilities/invokable.d.ts +0 -3
- package/dist/capabilities/invokable.d.ts.map +0 -1
- package/dist/capabilities/invokable.js +0 -5
- package/dist/capabilities/invokable.js.map +0 -1
- package/dist/capabilities/listener.d.ts +0 -3
- package/dist/capabilities/listener.d.ts.map +0 -1
- package/dist/capabilities/listener.js +0 -5
- package/dist/capabilities/listener.js.map +0 -1
- package/dist/capabilities/listener.yaml +0 -3
- package/dist/capabilities/mount.d.ts +0 -3
- package/dist/capabilities/mount.d.ts.map +0 -1
- package/dist/capabilities/mount.js +0 -5
- package/dist/capabilities/mount.js.map +0 -1
- package/dist/capabilities/provider.d.ts +0 -3
- package/dist/capabilities/provider.d.ts.map +0 -1
- package/dist/capabilities/provider.js +0 -8
- package/dist/capabilities/provider.js.map +0 -1
- package/dist/capabilities/provider.yaml +0 -3
- package/dist/capabilities/runnable.d.ts +0 -3
- package/dist/capabilities/runnable.d.ts.map +0 -1
- package/dist/capabilities/runnable.js +0 -5
- package/dist/capabilities/runnable.js.map +0 -1
- package/dist/capabilities/service.d.ts +0 -3
- package/dist/capabilities/service.d.ts.map +0 -1
- package/dist/capabilities/service.js +0 -5
- package/dist/capabilities/service.js.map +0 -1
- package/dist/capabilities/template.d.ts +0 -3
- package/dist/capabilities/template.d.ts.map +0 -1
- package/dist/capabilities/template.js +0 -5
- package/dist/capabilities/template.js.map +0 -1
- package/dist/capabilities/template.yaml +0 -3
- package/dist/capabilities/type.d.ts +0 -3
- package/dist/capabilities/type.d.ts.map +0 -1
- package/dist/capabilities/type.js +0 -5
- package/dist/capabilities/type.js.map +0 -1
- package/dist/capabilities/type.yaml +0 -3
- package/dist/controllers/capability/capability-controller.d.ts +0 -32
- package/dist/controllers/capability/capability-controller.d.ts.map +0 -1
- package/dist/controllers/capability/capability-controller.js +0 -26
- package/dist/controllers/capability/capability-controller.js.map +0 -1
- package/dist/controllers/module/module.json +0 -48
- package/dist/loader.d.ts +0 -18
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -127
- package/dist/loader.js.map +0 -1
- package/dist/manifest-adapters/http-adapter.d.ts +0 -8
- package/dist/manifest-adapters/http-adapter.d.ts.map +0 -1
- package/dist/manifest-adapters/http-adapter.js +0 -31
- package/dist/manifest-adapters/http-adapter.js.map +0 -1
- package/dist/manifest-adapters/registry-adapter.d.ts +0 -9
- package/dist/manifest-adapters/registry-adapter.d.ts.map +0 -1
- package/dist/manifest-adapters/registry-adapter.js +0 -48
- package/dist/manifest-adapters/registry-adapter.js.map +0 -1
- package/dist/module-context-registry.d.ts +0 -48
- package/dist/module-context-registry.d.ts.map +0 -1
- package/dist/module-context-registry.js +0 -91
- package/dist/module-context-registry.js.map +0 -1
- package/dist/schema-valiator.d.ts +0 -15
- package/dist/schema-valiator.d.ts.map +0 -1
- package/dist/schema-valiator.js +0 -127
- package/dist/schema-valiator.js.map +0 -1
- package/dist/snapshot-serializer.d.ts +0 -62
- package/dist/snapshot-serializer.d.ts.map +0 -1
- package/dist/snapshot-serializer.js +0 -164
- package/dist/snapshot-serializer.js.map +0 -1
- package/dist/types.d.ts +0 -65
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -8
- package/dist/types.js.map +0 -1
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
import { DiagnosticSeverity,
|
|
1
|
+
import { DiagnosticSeverity, StaticAnalyzer } from "@telorun/analyzer";
|
|
2
2
|
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
3
3
|
import { RuntimeError } from "@telorun/sdk";
|
|
4
4
|
import { ModuleContext } from "../../module-context.js";
|
|
5
|
-
import { LocalFileAdapter } from "../../manifest-adapters/local-file-adapter.js";
|
|
6
5
|
|
|
7
6
|
const importAnalysisCache = new Map<
|
|
8
7
|
string,
|
|
9
8
|
{ signature: string; errors: string[] }
|
|
10
9
|
>();
|
|
11
10
|
|
|
11
|
+
// Only resolve relative/absolute-path sources against the importer's URL. Registry refs
|
|
12
|
+
// (std/foo@1.2.3) and absolute URLs (https://, file://) must pass through unchanged so the
|
|
13
|
+
// loader's adapter chain can dispatch them — otherwise `new URL("std/foo@1", "file:///srv/telo.yaml")`
|
|
14
|
+
// turns a registry ref into a bogus file path and LocalFileAdapter ENOENTs on it.
|
|
15
|
+
function resolveImportSource(source: string, baseSource: string): string {
|
|
16
|
+
if (source.startsWith(".") || source.startsWith("/")) {
|
|
17
|
+
return new URL(source, baseSource).toString();
|
|
18
|
+
}
|
|
19
|
+
return source;
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
export async function create(resource: any, ctx: ResourceContext): Promise<ResourceInstance> {
|
|
13
23
|
const alias = resource.metadata.name as string;
|
|
14
|
-
const loader = new Loader([new LocalFileAdapter()]);
|
|
15
24
|
|
|
16
25
|
const moduleSource: string = resource.module ?? resource.source;
|
|
17
26
|
|
|
18
27
|
// Validate the imported module and all its transitive imports before loading for runtime.
|
|
19
|
-
// loadManifests() follows
|
|
28
|
+
// loadManifests() follows Telo.Import chains so definitions from sub-imports are present,
|
|
20
29
|
// preventing false UNDEFINED_KIND errors for kinds that come from the module's own imports.
|
|
21
|
-
const resolvedUrl =
|
|
22
|
-
const analysisManifests = await
|
|
30
|
+
const resolvedUrl = resolveImportSource(moduleSource, ctx.moduleContext.source);
|
|
31
|
+
const analysisManifests = await ctx.loadManifests(resolvedUrl);
|
|
23
32
|
const signature = JSON.stringify(analysisManifests);
|
|
24
33
|
const cached = importAnalysisCache.get(resolvedUrl);
|
|
25
34
|
let errors: string[];
|
|
@@ -44,16 +53,20 @@ export async function create(resource: any, ctx: ResourceContext): Promise<Resou
|
|
|
44
53
|
// Load target module manifests for runtime. Inject variables/secrets as compile context so
|
|
45
54
|
// that ${{ variables.x }} / ${{ secrets.y }} templates in the child module resolve correctly.
|
|
46
55
|
// No env — child modules are isolated from host environment.
|
|
47
|
-
const manifests = await
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
);
|
|
53
|
-
// Find the kind: Module manifest to learn the target module name and contract.
|
|
54
|
-
const moduleManifest = manifests.find((m: any) => m.kind === "Kernel.Module");
|
|
56
|
+
const manifests = await ctx.loadModule(resolvedUrl, {
|
|
57
|
+
compile: true,
|
|
58
|
+
});
|
|
59
|
+
// Import targets must be Telo.Library — Applications are run directly, not imported.
|
|
60
|
+
const moduleManifest = manifests.find((m: any) => m.kind === "Telo.Library");
|
|
55
61
|
if (!moduleManifest) {
|
|
56
|
-
|
|
62
|
+
const applicationManifest = manifests.find((m: any) => m.kind === "Telo.Application");
|
|
63
|
+
if (applicationManifest) {
|
|
64
|
+
throw new RuntimeError(
|
|
65
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
66
|
+
`Telo.Import target '${resource.source as string}' is a Telo.Application. Only Telo.Library modules may be imported. Applications are run directly, not imported.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`No Telo.Library manifest found in source "${resource.source as string}"`);
|
|
57
70
|
}
|
|
58
71
|
const targetModule: string = moduleManifest.metadata.name;
|
|
59
72
|
|
|
@@ -102,19 +115,6 @@ export async function create(resource: any, ctx: ResourceContext): Promise<Resou
|
|
|
102
115
|
variables: ctx.expandValue(resource.variables, {}) ?? {},
|
|
103
116
|
secrets: ctx.expandValue(resource.secrets, {}) ?? {},
|
|
104
117
|
}),
|
|
105
|
-
run: async () => {
|
|
106
|
-
// Proxy run to target module
|
|
107
|
-
for (const target of (moduleManifest.targets as string[]) ?? []) {
|
|
108
|
-
await child.run(target);
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
invoke: async () => {
|
|
112
|
-
// Proxy run to target module
|
|
113
|
-
// for (const target of (moduleManifest.targets as string[]) ?? []) {
|
|
114
|
-
// child.invoke(target);
|
|
115
|
-
// }
|
|
116
|
-
console.log("invoking");
|
|
117
|
-
},
|
|
118
118
|
init: async () => {
|
|
119
119
|
await child.initializeResources();
|
|
120
120
|
},
|
|
@@ -9,7 +9,7 @@ import { formatAjvErrors, validateResourceDefinition } from "../../manifest-sche
|
|
|
9
9
|
import { createTemplateController } from "./resource-template-controller.js";
|
|
10
10
|
|
|
11
11
|
type ResourceDefinitionResource = RuntimeResource & {
|
|
12
|
-
kind: "
|
|
12
|
+
kind: "Telo.Definition";
|
|
13
13
|
metadata: {
|
|
14
14
|
[key: string]: any;
|
|
15
15
|
name: string;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isCompiledValue,
|
|
3
|
+
isInvokeError,
|
|
3
4
|
resourceKey,
|
|
4
5
|
type EvaluationContext as IEvaluationContext,
|
|
5
6
|
type EmitEvent,
|
|
6
7
|
type InstanceFactory,
|
|
7
8
|
type LifecycleState,
|
|
8
9
|
type PreInitHook,
|
|
10
|
+
type ResourceDefinition,
|
|
9
11
|
type ResourceInstance,
|
|
10
12
|
type ResourceManifest,
|
|
11
13
|
type RuntimeDiagnostic,
|
|
@@ -62,6 +64,13 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
62
64
|
*/
|
|
63
65
|
preInitHook?: PreInitHook;
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Optional definition lookup used by invoke()/invokeResolved() to check
|
|
69
|
+
* thrown InvokeError.code against the declared throw union (rule 9).
|
|
70
|
+
* Set by the kernel; propagates through spawnChild() like preInitHook.
|
|
71
|
+
*/
|
|
72
|
+
getDefinition?: (kind: string) => ResourceDefinition | undefined;
|
|
73
|
+
|
|
65
74
|
constructor(
|
|
66
75
|
readonly source: string,
|
|
67
76
|
context: Record<string, unknown>,
|
|
@@ -139,6 +148,9 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
139
148
|
if (this.preInitHook && !child.preInitHook) {
|
|
140
149
|
child.preInitHook = this.preInitHook;
|
|
141
150
|
}
|
|
151
|
+
if (this.getDefinition && !child.getDefinition) {
|
|
152
|
+
child.getDefinition = this.getDefinition;
|
|
153
|
+
}
|
|
142
154
|
return child;
|
|
143
155
|
}
|
|
144
156
|
|
|
@@ -152,13 +164,13 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
152
164
|
* resource. Successful results go into resourceInstances.
|
|
153
165
|
*
|
|
154
166
|
* Interleaving is necessary because some resources' create() depends on effects
|
|
155
|
-
* produced by other resources' init() (e.g.
|
|
167
|
+
* produced by other resources' init() (e.g. Telo.Import.init() runs
|
|
156
168
|
* child.initializeResources() which registers controllers needed by sibling
|
|
157
169
|
* resources' create()). Running both sub-phases each pass lets those effects
|
|
158
170
|
* propagate before the next create attempt.
|
|
159
171
|
*
|
|
160
172
|
* Each resource is created at most once and inited at most once.
|
|
161
|
-
* ERR_VISIBILITY_DENIED
|
|
173
|
+
* ERR_VISIBILITY_DENIED and ERR_FATAL are re-thrown immediately.
|
|
162
174
|
* All other errors are tracked and retried until no progress is made.
|
|
163
175
|
*/
|
|
164
176
|
async initializeResources(): Promise<void> {
|
|
@@ -189,7 +201,7 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
189
201
|
progress = true;
|
|
190
202
|
}
|
|
191
203
|
} catch (error) {
|
|
192
|
-
if (error instanceof RuntimeError && error.code === "ERR_VISIBILITY_DENIED") throw error;
|
|
204
|
+
if (error instanceof RuntimeError && (error.code === "ERR_VISIBILITY_DENIED" || error.code === "ERR_FATAL")) throw error;
|
|
193
205
|
errors.set(name, error instanceof Error ? error.message : String(error));
|
|
194
206
|
}
|
|
195
207
|
}
|
|
@@ -211,7 +223,7 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
211
223
|
errors.delete(name);
|
|
212
224
|
progress = true;
|
|
213
225
|
} catch (error) {
|
|
214
|
-
if (error instanceof RuntimeError && error.code === "ERR_VISIBILITY_DENIED") throw error;
|
|
226
|
+
if (error instanceof RuntimeError && (error.code === "ERR_VISIBILITY_DENIED" || error.code === "ERR_FATAL")) throw error;
|
|
215
227
|
errors.set(name, error instanceof Error ? error.message : String(error));
|
|
216
228
|
}
|
|
217
229
|
}
|
|
@@ -362,27 +374,98 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
362
374
|
|
|
363
375
|
/**
|
|
364
376
|
* Invoke a resource by kind and name within this context's resourceInstances.
|
|
365
|
-
* Emits a scoped Invoked event via the injected
|
|
377
|
+
* Emits a scoped Invoked/InvokeRejected/InvokeFailed event via the injected
|
|
378
|
+
* emit callback. The single emission point for invoke-level events — callers
|
|
379
|
+
* holding an already-resolved instance should use invokeResolved() instead.
|
|
366
380
|
*/
|
|
367
381
|
async invoke<TInputs>(kind: string, name: string, inputs: TInputs): Promise<any> {
|
|
368
382
|
const entry = this.resourceInstances.get(name);
|
|
369
383
|
|
|
370
|
-
if (entry) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
384
|
+
if (!entry) {
|
|
385
|
+
throw new RuntimeError(
|
|
386
|
+
"ERR_RESOURCE_NOT_FOUND",
|
|
387
|
+
`Resource not found for invocation: ${kind}.${name}. Available resources: ${[...this.resourceInstances.keys()].join(", ")}`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (typeof entry.instance.invoke !== "function") {
|
|
392
|
+
throw new RuntimeError(
|
|
393
|
+
"ERR_RESOURCE_NOT_INVOKABLE",
|
|
394
|
+
`Resource ${kind}.${name} does not have an invoke method`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return this.runInvoke(kind, name, entry.instance, inputs);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Like invoke(), but the caller has already resolved the instance (e.g. the
|
|
403
|
+
* scope path in Run.Sequence, or the live-injected Http.Api route handler).
|
|
404
|
+
* Shares the single emission point so events fire exactly once per call
|
|
405
|
+
* regardless of which path reached the instance.
|
|
406
|
+
*/
|
|
407
|
+
async invokeResolved<TInputs>(
|
|
408
|
+
kind: string,
|
|
409
|
+
name: string,
|
|
410
|
+
instance: ResourceInstance,
|
|
411
|
+
inputs: TInputs,
|
|
412
|
+
): Promise<any> {
|
|
413
|
+
if (typeof instance.invoke !== "function") {
|
|
414
|
+
throw new RuntimeError(
|
|
415
|
+
"ERR_RESOURCE_NOT_INVOKABLE",
|
|
416
|
+
`Resource ${kind}.${name} does not have an invoke method`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return this.runInvoke(kind, name, instance, inputs);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private async runInvoke<TInputs>(
|
|
423
|
+
kind: string,
|
|
424
|
+
name: string,
|
|
425
|
+
instance: ResourceInstance,
|
|
426
|
+
inputs: TInputs,
|
|
427
|
+
): Promise<any> {
|
|
428
|
+
try {
|
|
429
|
+
const outputs = await (instance.invoke as (i: any) => any)(inputs as any);
|
|
378
430
|
await this.emit(`${kind}.${name}.Invoked`, { outputs });
|
|
379
431
|
return outputs;
|
|
432
|
+
} catch (err) {
|
|
433
|
+
if (isInvokeError(err)) {
|
|
434
|
+
const payload = { code: err.code, message: err.message, data: err.data };
|
|
435
|
+
await this.emit(`${kind}.${name}.InvokeRejected`, payload);
|
|
436
|
+
const declaredCodes = this.getDeclaredThrowCodes(kind);
|
|
437
|
+
if (declaredCodes && !declaredCodes.has(err.code)) {
|
|
438
|
+
await this.emit(`${kind}.${name}.InvokeRejected.Undeclared`, payload);
|
|
439
|
+
}
|
|
440
|
+
} else if (err instanceof Error) {
|
|
441
|
+
await this.emit(`${kind}.${name}.InvokeFailed`, {
|
|
442
|
+
name: err.name,
|
|
443
|
+
message: err.message,
|
|
444
|
+
});
|
|
445
|
+
} else {
|
|
446
|
+
await this.emit(`${kind}.${name}.InvokeFailed`, {
|
|
447
|
+
name: "UnknownError",
|
|
448
|
+
message: String(err),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
throw err;
|
|
380
452
|
}
|
|
453
|
+
}
|
|
381
454
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
);
|
|
455
|
+
private getDeclaredThrowCodes(kind: string): Set<string> | null {
|
|
456
|
+
if (!this.getDefinition) return null;
|
|
457
|
+
const def = this.getDefinition(kind);
|
|
458
|
+
if (!def) return null;
|
|
459
|
+
const throws = def.throws;
|
|
460
|
+
if (!throws) return new Set();
|
|
461
|
+
// inherit / passthrough unions are dynamic — resolved statically by the
|
|
462
|
+
// analyzer, not re-derivable here without a manifest-wide traversal. Skip
|
|
463
|
+
// the rule 9 check rather than mis-report every propagated code as
|
|
464
|
+
// undeclared at runtime.
|
|
465
|
+
if (throws.inherit || throws.passthrough) return null;
|
|
466
|
+
const codes = throws.codes;
|
|
467
|
+
if (!codes) return new Set();
|
|
468
|
+
return new Set(Object.keys(codes));
|
|
386
469
|
}
|
|
387
470
|
|
|
388
471
|
async run(name: string): Promise<void> {
|
|
@@ -402,7 +485,13 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
402
485
|
*/
|
|
403
486
|
expand(value: unknown): unknown {
|
|
404
487
|
if (isCompiledValue(value)) {
|
|
405
|
-
|
|
488
|
+
try {
|
|
489
|
+
return value.call(this._context);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
const expr = value.source ? `\${{ ${value.source} }}` : "unknown expression";
|
|
492
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
493
|
+
throw new Error(`Expression ${expr} failed: ${msg}`);
|
|
494
|
+
}
|
|
406
495
|
}
|
|
407
496
|
if (Array.isArray(value)) {
|
|
408
497
|
return value.map((entry) => this.expand(entry));
|
package/src/kernel.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AnalysisRegistry, DEFAULT_MANIFEST_FILENAME, Loader, StaticAnalyzer } from "@telorun/analyzer";
|
|
1
|
+
import { AnalysisRegistry, DEFAULT_MANIFEST_FILENAME, isModuleKind, Loader, StaticAnalyzer } from "@telorun/analyzer";
|
|
2
2
|
import {
|
|
3
3
|
ControllerContext,
|
|
4
4
|
Kernel as IKernel,
|
|
5
|
+
type LoadOptions,
|
|
5
6
|
ResourceContext,
|
|
6
7
|
ResourceDefinition,
|
|
7
8
|
ResourceInstance,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type ModuleContext as IModuleContext,
|
|
14
15
|
type ParsedArgs,
|
|
15
16
|
} from "@telorun/sdk";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
16
18
|
import { ModuleContext } from "./module-context.js";
|
|
17
19
|
import * as path from "path";
|
|
18
20
|
import { parseArgs } from "util";
|
|
@@ -29,15 +31,23 @@ export interface KernelOptions {
|
|
|
29
31
|
stderr?: NodeJS.WritableStream;
|
|
30
32
|
env?: Record<string, string | undefined>;
|
|
31
33
|
argv?: string[];
|
|
34
|
+
/** Base URL for the registry adapter. When unset, the RegistryAdapter
|
|
35
|
+
* default applies. Callers (e.g. the CLI) are responsible for resolving
|
|
36
|
+
* `TELO_REGISTRY_URL` or any other env-based fallback before passing. */
|
|
37
|
+
registryUrl?: string;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* Kernel: Central orchestrator managing lifecycle and message bus
|
|
36
42
|
* Handles resource loading, initialization, and execution through controllers
|
|
37
43
|
*/
|
|
44
|
+
const celHandlers = {
|
|
45
|
+
sha256: (s: string) => createHash("sha256").update(s).digest("hex"),
|
|
46
|
+
};
|
|
47
|
+
|
|
38
48
|
export class Kernel implements IKernel {
|
|
39
|
-
private readonly loader
|
|
40
|
-
private readonly analyzer = new StaticAnalyzer();
|
|
49
|
+
private readonly loader: Loader;
|
|
50
|
+
private readonly analyzer = new StaticAnalyzer({ celHandlers });
|
|
41
51
|
private readonly registry = new AnalysisRegistry();
|
|
42
52
|
private controllers: ControllerRegistry = new ControllerRegistry();
|
|
43
53
|
private eventBus: EventBus = new EventBus();
|
|
@@ -55,6 +65,7 @@ export class Kernel implements IKernel {
|
|
|
55
65
|
readonly stderr: NodeJS.WritableStream;
|
|
56
66
|
readonly env: Record<string, string | undefined>;
|
|
57
67
|
readonly argv: string[];
|
|
68
|
+
readonly registryUrl: string | undefined;
|
|
58
69
|
|
|
59
70
|
constructor(options: KernelOptions = {}) {
|
|
60
71
|
this.stdin = options.stdin ?? process.stdin;
|
|
@@ -62,6 +73,8 @@ export class Kernel implements IKernel {
|
|
|
62
73
|
this.stderr = options.stderr ?? process.stderr;
|
|
63
74
|
this.env = options.env ?? process.env;
|
|
64
75
|
this.argv = options.argv ?? [];
|
|
76
|
+
this.registryUrl = options.registryUrl;
|
|
77
|
+
this.loader = new Loader({ registryUrl: this.registryUrl, celHandlers });
|
|
65
78
|
this.loader.register(new LocalFileAdapter());
|
|
66
79
|
this.setupEventStreaming();
|
|
67
80
|
}
|
|
@@ -101,6 +114,14 @@ export class Kernel implements IKernel {
|
|
|
101
114
|
this.registry.registerImport(alias, targetModule, kinds);
|
|
102
115
|
}
|
|
103
116
|
|
|
117
|
+
loadModule(url: string, options?: LoadOptions): Promise<ResourceManifest[]> {
|
|
118
|
+
return this.loader.loadModule(url, options);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
loadManifests(url: string): Promise<ResourceManifest[]> {
|
|
122
|
+
return this.loader.loadManifests(url);
|
|
123
|
+
}
|
|
124
|
+
|
|
104
125
|
/** Returns the live analysis registry backed by this kernel's known definitions and aliases.
|
|
105
126
|
* Pass to StaticAnalyzer.analyze() for incremental validation of new manifests against
|
|
106
127
|
* already-registered types (e.g. front-end editor validating a manifest before submitting). */
|
|
@@ -109,30 +130,29 @@ export class Kernel implements IKernel {
|
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
/**
|
|
112
|
-
* Load built-in Runtime definitions (e.g.,
|
|
133
|
+
* Load built-in Runtime definitions (e.g., Telo.Application, Telo.Library).
|
|
113
134
|
* Also declares all known module namespaces upfront so that resources can be
|
|
114
|
-
* registered to them. User-defined modules are declared explicitly by
|
|
115
|
-
* resources during the initialization phase.
|
|
135
|
+
* registered to them. User-defined modules are declared explicitly by
|
|
136
|
+
* Telo.Application or Telo.Library resources during the initialization phase.
|
|
116
137
|
*/
|
|
117
138
|
private async loadBuiltinDefinitions(): Promise<void> {
|
|
118
139
|
// Declare built-in module namespaces upfront so getContext() can distinguish
|
|
119
140
|
// "not yet populated" from a completely unknown module name.
|
|
120
|
-
this.rootContext.registerImport("
|
|
141
|
+
this.rootContext.registerImport("Telo", "Telo", []); // built-ins, unrestricted
|
|
121
142
|
|
|
122
143
|
// Register built-in definitions with the controller registry.
|
|
123
144
|
// AnalysisRegistry's underlying DefinitionRegistry already seeds KERNEL_BUILTINS on construction.
|
|
124
145
|
for (const def of this.registry.builtinDefinitions()) this.controllers.registerDefinition(def);
|
|
125
146
|
|
|
126
147
|
this.controllers.registerController(
|
|
127
|
-
"
|
|
148
|
+
"Telo.Definition",
|
|
128
149
|
await import("./controllers/resource-definition/resource-definition-controller.js"),
|
|
129
150
|
);
|
|
151
|
+
const moduleController = await import("./controllers/module/module-controller.js");
|
|
152
|
+
this.controllers.registerController("Telo.Application", moduleController);
|
|
153
|
+
this.controllers.registerController("Telo.Library", moduleController);
|
|
130
154
|
this.controllers.registerController(
|
|
131
|
-
"
|
|
132
|
-
await import("./controllers/module/module-controller.js"),
|
|
133
|
-
);
|
|
134
|
-
this.controllers.registerController(
|
|
135
|
-
"Kernel.Import",
|
|
155
|
+
"Telo.Import",
|
|
136
156
|
await import("./controllers/module/import-controller.js"),
|
|
137
157
|
);
|
|
138
158
|
}
|
|
@@ -160,16 +180,28 @@ export class Kernel implements IKernel {
|
|
|
160
180
|
this.rootContext.preInitHook = (resource, getInstance) =>
|
|
161
181
|
this._injectDependencies(resource, getInstance);
|
|
162
182
|
|
|
183
|
+
// Expose definition lookup so invoke()/invokeResolved() can check thrown
|
|
184
|
+
// InvokeError.code against the declared throw union (rule 9). Propagates
|
|
185
|
+
// through spawnChild() to module imports and scoped handles.
|
|
186
|
+
this.rootContext.getDefinition = (kind) => this.controllers.getDefinition(kind);
|
|
187
|
+
|
|
163
188
|
// Static analysis pre-flight: validates schemas and invocation context compatibility.
|
|
164
189
|
// All errors are fatal — kernel does not start if analysis fails.
|
|
165
190
|
const staticManifests = await this.loader.loadManifests(sourceUrl);
|
|
166
191
|
this.staticManifests = staticManifests;
|
|
167
192
|
|
|
168
193
|
// Register module identities for x-telo-ref resolution (Phase 3 prerequisite).
|
|
169
|
-
//
|
|
194
|
+
// Telo built-ins ("telo" → "Telo") are auto-registered when Telo.Abstract
|
|
170
195
|
// definitions are registered in loadBuiltinDefinitions() above.
|
|
196
|
+
const rootModuleDoc = staticManifests.find((m) => isModuleKind(m.kind));
|
|
197
|
+
if (rootModuleDoc?.kind === "Telo.Library") {
|
|
198
|
+
throw new RuntimeError(
|
|
199
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
200
|
+
`Root manifest '${sourceUrl}' is a Telo.Library. Only Telo.Application manifests can be run directly — libraries are imported via Telo.Import.`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
171
203
|
for (const m of staticManifests) {
|
|
172
|
-
if (m.kind
|
|
204
|
+
if (isModuleKind(m.kind) && m.metadata?.name && m.metadata?.namespace) {
|
|
173
205
|
this.registry.registerModuleIdentity(
|
|
174
206
|
m.metadata.namespace as string,
|
|
175
207
|
m.metadata.name as string,
|
|
@@ -204,9 +236,10 @@ export class Kernel implements IKernel {
|
|
|
204
236
|
this.staticManifests = normalizedManifests;
|
|
205
237
|
|
|
206
238
|
for (const manifest of normalizedManifests) {
|
|
207
|
-
if (manifest.kind
|
|
208
|
-
|
|
209
|
-
|
|
239
|
+
if (isModuleKind(manifest.kind)) {
|
|
240
|
+
// Root is always Telo.Application (Library root rejected above). Applications
|
|
241
|
+
// have no variables/secrets fields — those are a Library-only contract, populated
|
|
242
|
+
// by importers, not by the root manifest itself.
|
|
210
243
|
this.rootContext.setTargets(manifest.targets ?? []);
|
|
211
244
|
}
|
|
212
245
|
this.rootContext.registerManifest(manifest);
|
|
@@ -386,6 +419,7 @@ export class Kernel implements IKernel {
|
|
|
386
419
|
moduleContext,
|
|
387
420
|
resource.metadata,
|
|
388
421
|
this.sharedSchemaValidator,
|
|
422
|
+
this.env,
|
|
389
423
|
this.stdin,
|
|
390
424
|
this.stdout,
|
|
391
425
|
this.stderr,
|
package/src/manifest-schemas.ts
CHANGED
|
@@ -21,41 +21,87 @@ const metadataSchema = {
|
|
|
21
21
|
additionalProperties: true,
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
const throwsSchema = {
|
|
25
|
+
type: "object",
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
properties: {
|
|
28
|
+
codes: {
|
|
29
|
+
type: "object",
|
|
30
|
+
propertyNames: { pattern: "^[A-Z][A-Z0-9_]*$" },
|
|
31
|
+
additionalProperties: {
|
|
32
|
+
type: "object",
|
|
33
|
+
required: ["description"],
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
properties: {
|
|
36
|
+
description: { type: "string" },
|
|
37
|
+
data: { type: "object", additionalProperties: true },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
// "my throw union includes every code thrown by every invocable I call
|
|
42
|
+
// (minus codes caught in an enclosing try/catch)". Analyzer enforces
|
|
43
|
+
// that this is only legal on definitions whose schema declares at least
|
|
44
|
+
// one `x-telo-step-context` array.
|
|
45
|
+
inherit: { type: "boolean" },
|
|
46
|
+
// "my throw union is whatever `inputs.code` resolves to statically." Used
|
|
47
|
+
// by passthrough-style adapters. Analyzer resolves per call site.
|
|
48
|
+
passthrough: { type: "boolean" },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
24
52
|
const baseDefinition = {
|
|
25
53
|
type: "object",
|
|
26
54
|
required: ["kind", "metadata"],
|
|
27
55
|
properties: {
|
|
28
|
-
kind: { const: "
|
|
56
|
+
kind: { const: "Telo.Definition" },
|
|
29
57
|
metadata: metadataSchema,
|
|
30
58
|
capability: { type: "string" },
|
|
31
59
|
schema: { type: "object", additionalProperties: true },
|
|
32
60
|
controllers: { type: "array", items: { type: "string" } },
|
|
61
|
+
throws: throwsSchema,
|
|
33
62
|
},
|
|
34
63
|
unevaluatedProperties: false,
|
|
35
64
|
};
|
|
36
65
|
|
|
37
66
|
const KNOWN_CAPABILITIES = [
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
67
|
+
"Telo.Service",
|
|
68
|
+
"Telo.Runnable",
|
|
69
|
+
"Telo.Invocable",
|
|
70
|
+
"Telo.Provider",
|
|
71
|
+
"Telo.Type",
|
|
72
|
+
"Telo.Mount",
|
|
44
73
|
] as const;
|
|
45
74
|
|
|
75
|
+
/** Rule 8: `throws:` is only meaningful on Telo.Invocable or Telo.Runnable.
|
|
76
|
+
* On Service/Mount/Provider/Type/etc. a thrown error is a boot-time failure,
|
|
77
|
+
* not a structured runtime error for a downstream caller, so declaring one
|
|
78
|
+
* is a schema error. */
|
|
79
|
+
const forbidThrows = { not: { required: ["throws"] } };
|
|
80
|
+
|
|
46
81
|
export const ResourceDefinitionSchema = {
|
|
47
82
|
...baseDefinition,
|
|
48
83
|
oneOf: [
|
|
49
|
-
{ required: ["capability"], properties: { capability: { const: "Kernel.Service" } } },
|
|
50
|
-
{ required: ["capability"], properties: { capability: { const: "Kernel.Runnable" } } },
|
|
51
|
-
{ required: ["capability"], properties: { capability: { const: "Kernel.Invocable" } } },
|
|
52
|
-
{ required: ["capability"], properties: { capability: { const: "Kernel.Provider" } } },
|
|
53
|
-
{ required: ["capability"], properties: { capability: { const: "Kernel.Type" } } },
|
|
54
84
|
{
|
|
55
85
|
required: ["capability"],
|
|
56
|
-
properties: {
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
properties: { capability: { const: "Telo.Service" } },
|
|
87
|
+
...forbidThrows,
|
|
88
|
+
},
|
|
89
|
+
{ required: ["capability"], properties: { capability: { const: "Telo.Runnable" } } },
|
|
90
|
+
{ required: ["capability"], properties: { capability: { const: "Telo.Invocable" } } },
|
|
91
|
+
{
|
|
92
|
+
required: ["capability"],
|
|
93
|
+
properties: { capability: { const: "Telo.Provider" } },
|
|
94
|
+
...forbidThrows,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
required: ["capability"],
|
|
98
|
+
properties: { capability: { const: "Telo.Type" } },
|
|
99
|
+
...forbidThrows,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
required: ["capability"],
|
|
103
|
+
properties: { capability: { const: "Telo.Mount" } },
|
|
104
|
+
...forbidThrows,
|
|
59
105
|
},
|
|
60
106
|
// Unknown/absent capability: open schema for third-party extensibility
|
|
61
107
|
{
|
package/src/module-context.ts
CHANGED
|
@@ -109,7 +109,7 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* Register an imported module under the given alias, with the list of kind names
|
|
112
|
-
* it exports. An empty kinds array means no restriction (used for built-ins like
|
|
112
|
+
* it exports. An empty kinds array means no restriction (used for built-ins like Telo).
|
|
113
113
|
*/
|
|
114
114
|
registerImport(alias: string, targetModule: string, kinds: string[]): void {
|
|
115
115
|
this.importAliases.set(alias, targetModule);
|
package/src/resource-context.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NoopValidator,
|
|
3
3
|
ResourceContext,
|
|
4
|
+
ResourceInstance,
|
|
5
|
+
ResourceManifest,
|
|
4
6
|
RuntimeError,
|
|
5
7
|
RuntimeResource,
|
|
6
8
|
isCompiledValue,
|
|
7
9
|
type EvaluationContext as IEvaluationContext,
|
|
10
|
+
type LoadOptions,
|
|
8
11
|
type ModuleContext,
|
|
9
12
|
type ParsedArgs,
|
|
10
13
|
type TypeRule,
|
|
@@ -19,6 +22,7 @@ import { SchemaValidator } from "./schema-validator.js";
|
|
|
19
22
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
20
23
|
|
|
21
24
|
export class ResourceContextImpl implements ResourceContext {
|
|
25
|
+
readonly env: Record<string, string | undefined>;
|
|
22
26
|
readonly stdin: NodeJS.ReadableStream;
|
|
23
27
|
readonly stdout: NodeJS.WritableStream;
|
|
24
28
|
readonly stderr: NodeJS.WritableStream;
|
|
@@ -29,11 +33,13 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
29
33
|
readonly moduleContext: ModuleContext,
|
|
30
34
|
private readonly metadata: Record<string, any>,
|
|
31
35
|
private readonly validator: SchemaValidator = new SchemaValidator(),
|
|
36
|
+
env?: Record<string, string | undefined>,
|
|
32
37
|
stdin?: NodeJS.ReadableStream,
|
|
33
38
|
stdout?: NodeJS.WritableStream,
|
|
34
39
|
stderr?: NodeJS.WritableStream,
|
|
35
40
|
args?: ParsedArgs,
|
|
36
41
|
) {
|
|
42
|
+
this.env = env ?? process.env;
|
|
37
43
|
this.stdin = stdin ?? process.stdin;
|
|
38
44
|
this.stdout = stdout ?? process.stdout;
|
|
39
45
|
this.stderr = stderr ?? process.stderr;
|
|
@@ -128,6 +134,15 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
128
134
|
return this.moduleContext.invoke(kind, name, inputs);
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
invokeResolved<TInputs>(
|
|
138
|
+
kind: string,
|
|
139
|
+
name: string,
|
|
140
|
+
instance: ResourceInstance,
|
|
141
|
+
inputs: TInputs,
|
|
142
|
+
): Promise<any> {
|
|
143
|
+
return this.moduleContext.invokeResolved(kind, name, instance, inputs);
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
async run(name: string) {
|
|
132
147
|
await this.moduleContext.run(name);
|
|
133
148
|
}
|
|
@@ -136,6 +151,14 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
136
151
|
this.moduleContext.registerManifest(resource);
|
|
137
152
|
}
|
|
138
153
|
|
|
154
|
+
loadModule(url: string, options?: LoadOptions): Promise<ResourceManifest[]> {
|
|
155
|
+
return this.kernel.loadModule(url, options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
loadManifests(url: string): Promise<ResourceManifest[]> {
|
|
159
|
+
return this.kernel.loadManifests(url);
|
|
160
|
+
}
|
|
161
|
+
|
|
139
162
|
/**
|
|
140
163
|
* Resolves a resource into a normalized {kind, name} reference.
|
|
141
164
|
* If the resource contains a definition (kind + properties), registers it as a manifest.
|
|
@@ -168,12 +191,24 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
168
191
|
resourceName ??
|
|
169
192
|
`Unnamed${Math.random().toString(16).slice(2, 8)}`;
|
|
170
193
|
|
|
171
|
-
//
|
|
172
|
-
|
|
194
|
+
// Register an inline manifest when:
|
|
195
|
+
// - the ref carries definition properties (clearly an inline definition), or
|
|
196
|
+
// - the ref is bare `{kind}` with no explicit name and the caller supplied
|
|
197
|
+
// a `resourceName` (the slot is known-inline — e.g. a Run.Sequence step
|
|
198
|
+
// with `invoke: {kind: SomeInvocable}` — and wants a fresh stateless
|
|
199
|
+
// instance registered under the generated name).
|
|
200
|
+
// Pure references (`{kind, name}` pointing at an existing resource) carry
|
|
201
|
+
// an explicit name and skip registration.
|
|
202
|
+
const hasInlineProperties = Object.keys(resource).some(
|
|
173
203
|
(k) => k !== "kind" && k !== "name" && k !== "metadata",
|
|
174
204
|
);
|
|
205
|
+
const hasExplicitName =
|
|
206
|
+
resource.name !== undefined || resource.metadata?.name !== undefined;
|
|
207
|
+
const shouldRegister =
|
|
208
|
+
(hasInlineProperties || (!hasExplicitName && resourceName !== undefined)) &&
|
|
209
|
+
!this.moduleContext.hasManifest(name);
|
|
175
210
|
|
|
176
|
-
if (
|
|
211
|
+
if (shouldRegister) {
|
|
177
212
|
this.registerManifest({
|
|
178
213
|
...resource,
|
|
179
214
|
metadata: {
|