@telorun/kernel 0.23.0 → 0.24.1
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/controller-loaders/bundle-loader.d.ts.map +1 -1
- package/dist/controller-loaders/bundle-loader.js +25 -3
- package/dist/controller-loaders/bundle-loader.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +31 -14
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +7 -2
- package/dist/evaluation-context.js.map +1 -1
- package/dist/kernel.d.ts +5 -0
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +11 -0
- package/dist/kernel.js.map +1 -1
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +69 -17
- package/dist/module-context.js.map +1 -1
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +20 -7
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +8 -2
- package/dist/schema-validator.js.map +1 -1
- package/package.json +3 -3
- package/src/controller-loaders/bundle-loader.ts +21 -2
- package/src/controllers/module/import-controller.ts +37 -14
- package/src/evaluation-context.ts +8 -2
- package/src/kernel.ts +11 -0
- package/src/module-context.ts +79 -23
- package/src/resource-context.ts +21 -10
- package/src/schema-validator.ts +9 -2
package/src/module-context.ts
CHANGED
|
@@ -47,6 +47,32 @@ function collectSecretValues(secrets: Record<string, unknown>): Set<string> {
|
|
|
47
47
|
return values;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** A boot target whose ref slot Phase 5 injection already replaced with the
|
|
51
|
+
* live instance (the documented "pre-resolved instance once Phase 5 ran"
|
|
52
|
+
* shape from `BootTarget`). Distinguished from a structural `{kind, name}` ref
|
|
53
|
+
* by carrying a `run()` method. */
|
|
54
|
+
function isRunnableInstance(value: unknown): value is ResourceInstance {
|
|
55
|
+
return (
|
|
56
|
+
!!value &&
|
|
57
|
+
typeof value === "object" &&
|
|
58
|
+
typeof (value as { run?: unknown }).run === "function"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Cycle-safe serialization for diagnostics. A boot target can be a live
|
|
63
|
+
* instance whose object graph (e.g. a Kysely/Ajv schema) is cyclic, which
|
|
64
|
+
* would make a plain JSON.stringify throw and mask the real error. */
|
|
65
|
+
function safeStringify(value: unknown): string {
|
|
66
|
+
const seen = new WeakSet<object>();
|
|
67
|
+
return JSON.stringify(value, (_key, val) => {
|
|
68
|
+
if (val && typeof val === "object") {
|
|
69
|
+
if (seen.has(val)) return "[Circular]";
|
|
70
|
+
seen.add(val);
|
|
71
|
+
}
|
|
72
|
+
return val;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
50
76
|
/**
|
|
51
77
|
* Persistent, module-scoped context. Reserved CEL namespaces:
|
|
52
78
|
* variables, secrets, resources, ports (Application-only).
|
|
@@ -319,6 +345,18 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
319
345
|
this.invokeResolved(kind, name, instance, inputs, ctx),
|
|
320
346
|
resolveImportedInstance: (alias, name) => this.resolveImportedInstance(alias, name),
|
|
321
347
|
};
|
|
348
|
+
// Mirror the local-run gate: refuse a target reached after the boot run was
|
|
349
|
+
// cancelled, then run the pre-resolved instance directly.
|
|
350
|
+
const runResolvedInstance = async (inst: ResourceInstance, label: string) => {
|
|
351
|
+
const token = ctx?.cancellation;
|
|
352
|
+
if (token?.isCancelled) {
|
|
353
|
+
throw new RuntimeError(
|
|
354
|
+
"ERR_INVOKE_CANCELLED",
|
|
355
|
+
`Run ${label} was cancelled${token.reason ? `: ${token.reason}` : ""}`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
await inst.run!(ctx);
|
|
359
|
+
};
|
|
322
360
|
for (let i = 0; i < this.targets.length; i++) {
|
|
323
361
|
const target = this.targets[i]!;
|
|
324
362
|
if (typeof target === "string") {
|
|
@@ -341,36 +379,54 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
341
379
|
if ("ref" in target && target.ref != null) {
|
|
342
380
|
if (target.when === undefined || this.expandWith(target.when, { steps })) {
|
|
343
381
|
const ref = target.ref as unknown;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
if (!inst || typeof inst.run !== "function") {
|
|
349
|
-
throw new Error(
|
|
350
|
-
`Boot target '${r.alias}.${r.name}' is not a runnable exported instance`,
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
// Mirror the local-run gate: refuse a cross-module target reached
|
|
354
|
-
// after the boot run was cancelled.
|
|
355
|
-
const token = ctx?.cancellation;
|
|
356
|
-
if (token?.isCancelled) {
|
|
357
|
-
throw new RuntimeError(
|
|
358
|
-
"ERR_INVOKE_CANCELLED",
|
|
359
|
-
`Run ${r.alias}.${r.name} was cancelled${token.reason ? `: ${token.reason}` : ""}`,
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
await inst.run(ctx);
|
|
382
|
+
// Phase 5 injection may have replaced the ref slot with the live
|
|
383
|
+
// instance; run it directly.
|
|
384
|
+
if (isRunnableInstance(ref)) {
|
|
385
|
+
await runResolvedInstance(ref, `target[${i}]`);
|
|
363
386
|
} else {
|
|
364
|
-
|
|
387
|
+
const r =
|
|
388
|
+
ref && typeof ref === "object"
|
|
389
|
+
? (ref as { name: string; alias?: string })
|
|
390
|
+
: undefined;
|
|
391
|
+
if (r && typeof r.alias === "string" && r.alias !== "Self") {
|
|
392
|
+
const inst = this.resolveImportedInstance(r.alias, r.name);
|
|
393
|
+
if (!inst || typeof inst.run !== "function") {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`Boot target '${r.alias}.${r.name}' is not a runnable exported instance`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
await runResolvedInstance(inst, `${r.alias}.${r.name}`);
|
|
399
|
+
} else {
|
|
400
|
+
await this.run(typeof ref === "string" ? ref : r!.name, ctx);
|
|
401
|
+
}
|
|
365
402
|
}
|
|
366
403
|
}
|
|
367
404
|
continue;
|
|
368
405
|
}
|
|
369
|
-
|
|
370
|
-
|
|
406
|
+
// Bare run target. Phase 5 injection resolves a `!ref name` /
|
|
407
|
+
// cross-module `!ref Alias.name` slot into the live instance before boot,
|
|
408
|
+
// so the common runtime shape here is a pre-resolved ResourceInstance;
|
|
409
|
+
// fall back to the structural ref forms when injection left them in place.
|
|
410
|
+
if (isRunnableInstance(target)) {
|
|
411
|
+
await runResolvedInstance(target, `target[${i}]`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const bare = target as { name?: unknown; alias?: unknown };
|
|
415
|
+
if (typeof bare.alias === "string" && bare.alias !== "Self" && typeof bare.name === "string") {
|
|
416
|
+
const inst = this.resolveImportedInstance(bare.alias, bare.name);
|
|
417
|
+
if (!inst || typeof inst.run !== "function") {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Boot target '${bare.alias}.${bare.name}' is not a runnable exported instance`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
await runResolvedInstance(inst, `${bare.alias}.${bare.name}`);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (typeof bare.name === "string") {
|
|
426
|
+
await this.run(bare.name, ctx);
|
|
371
427
|
continue;
|
|
372
428
|
}
|
|
373
|
-
throw new Error(`Unrecognized target shape at index ${i}: ${
|
|
429
|
+
throw new Error(`Unrecognized target shape at index ${i}: ${safeStringify(target)}`);
|
|
374
430
|
}
|
|
375
431
|
}
|
|
376
432
|
}
|
package/src/resource-context.ts
CHANGED
|
@@ -251,14 +251,19 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
251
251
|
return { kind: ref.kind, name: ref.name, alias };
|
|
252
252
|
}
|
|
253
253
|
const refName = alias === "Self" ? source.slice(dot + 1) : source;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
254
|
+
// Mirror the legacy `{kind, name}` path: return a `{kind, name}` ref and let
|
|
255
|
+
// the downstream resolution (module-global `invoke`, or scope-local
|
|
256
|
+
// `executeInvokeStep → ScopeContext.getInstance`) find it by name at invoke
|
|
257
|
+
// time. The kind is carried for invocation-event naming. Prefer an
|
|
258
|
+
// already-initialized instance's kind, else the authored kind from the
|
|
259
|
+
// static manifest (init-order-independent — the target may not be
|
|
260
|
+
// initialized yet when this resolves, and scope-local resources never enter
|
|
261
|
+
// `resourceInstances`).
|
|
262
|
+
const kind =
|
|
263
|
+
(this.moduleContext.resourceInstances.get(refName)?.resource.kind as string | undefined) ??
|
|
264
|
+
this.kernel.resourceKindByName(refName) ??
|
|
265
|
+
"";
|
|
266
|
+
return { kind, name: refName };
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
if (!resource.kind) {
|
|
@@ -274,6 +279,12 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
274
279
|
resource.metadata?.name ??
|
|
275
280
|
resourceName ??
|
|
276
281
|
`Unnamed${Math.random().toString(16).slice(2, 8)}`;
|
|
282
|
+
// `alias` marks a resolved cross-module reference (`{kind, name, alias}`)
|
|
283
|
+
// produced from a `!ref Alias.name`. It is always a reference into an
|
|
284
|
+
// imported library's exported instance — never an inline definition — so it
|
|
285
|
+
// is a reference key alongside kind/name/metadata (not inline config) and is
|
|
286
|
+
// carried through so downstream scope resolution routes into the import.
|
|
287
|
+
const alias = typeof resource.alias === "string" ? resource.alias : undefined;
|
|
277
288
|
|
|
278
289
|
// Register an inline manifest when:
|
|
279
290
|
// - the ref carries definition properties (clearly an inline definition), or
|
|
@@ -287,7 +298,7 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
287
298
|
// the contract here; previous silent-skip-on-duplicate hid real bugs
|
|
288
299
|
// (e.g. inline auto-names colliding across sibling Run.Sequence steps).
|
|
289
300
|
const hasInlineProperties = Object.keys(resource).some(
|
|
290
|
-
(k) => k !== "kind" && k !== "name" && k !== "metadata",
|
|
301
|
+
(k) => k !== "kind" && k !== "name" && k !== "metadata" && k !== "alias",
|
|
291
302
|
);
|
|
292
303
|
const hasExplicitName = resource.name !== undefined || resource.metadata?.name !== undefined;
|
|
293
304
|
const shouldRegister =
|
|
@@ -304,7 +315,7 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
304
315
|
});
|
|
305
316
|
}
|
|
306
317
|
|
|
307
|
-
return { kind, name };
|
|
318
|
+
return alias ? { kind, name, alias } : { kind, name };
|
|
308
319
|
}
|
|
309
320
|
|
|
310
321
|
getResourcesByName(_kind: string, name: string): RuntimeResource | null {
|
package/src/schema-validator.ts
CHANGED
|
@@ -8,7 +8,7 @@ import * as fs from "node:fs";
|
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { formatAjvErrors } from "./manifest-schemas.js";
|
|
11
|
-
import { ManifestRootSchema } from "@telorun/templating";
|
|
11
|
+
import { ManifestRootSchema, normalizeRefSlots } from "@telorun/templating";
|
|
12
12
|
|
|
13
13
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
14
14
|
// AJV's standalone subpath is CJS — the default export shows up as either
|
|
@@ -174,7 +174,7 @@ export class SchemaValidator {
|
|
|
174
174
|
required: Object.keys(schema),
|
|
175
175
|
additionalProperties: false,
|
|
176
176
|
};
|
|
177
|
-
const
|
|
177
|
+
const withImplicit =
|
|
178
178
|
normalized.additionalProperties === false
|
|
179
179
|
? {
|
|
180
180
|
...normalized,
|
|
@@ -186,6 +186,13 @@ export class SchemaValidator {
|
|
|
186
186
|
}
|
|
187
187
|
: normalized;
|
|
188
188
|
|
|
189
|
+
// Drop the legacy scalar `type` an older published module may still pin on
|
|
190
|
+
// its `x-telo-ref` slots. Schema validation runs in create() before Phase 5
|
|
191
|
+
// injection, so a ref slot holds the resolved `{kind, name, alias?}` object
|
|
192
|
+
// (or an unresolved sentinel) — both objects the stale `type: "string"`
|
|
193
|
+
// would otherwise reject.
|
|
194
|
+
const injected = normalizeRefSlots(withImplicit) as typeof withImplicit;
|
|
195
|
+
|
|
189
196
|
const hash = createHash("sha256")
|
|
190
197
|
.update(JSON.stringify({ runtime: VALIDATOR_RUNTIME_TAG, schema: injected }))
|
|
191
198
|
.digest("hex")
|