@telorun/kernel 0.73.0 → 0.75.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/dist/controller-loaders/source-bundle-builder.d.ts +19 -0
- package/dist/controller-loaders/source-bundle-builder.d.ts.map +1 -1
- package/dist/controller-loaders/source-bundle-builder.js +19 -0
- package/dist/controller-loaders/source-bundle-builder.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +5 -1
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/invocation-contract-binding.d.ts +37 -1
- package/dist/invocation-contract-binding.d.ts.map +1 -1
- package/dist/invocation-contract-binding.js +108 -7
- package/dist/invocation-contract-binding.js.map +1 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +7 -1
- package/dist/kernel.js.map +1 -1
- package/dist/manifest-schemas.d.ts +1 -1
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +6 -5
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/module-context.d.ts +7 -0
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +27 -7
- package/dist/module-context.js.map +1 -1
- package/dist/schema-compiled-values.d.ts.map +1 -1
- package/dist/schema-compiled-values.js +25 -1
- package/dist/schema-compiled-values.js.map +1 -1
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +2 -2
- package/dist/schema-validator.js.map +1 -1
- package/package.json +4 -4
- package/src/controller-loaders/source-bundle-builder.ts +23 -0
- package/src/controllers/module/import-controller.ts +8 -1
- package/src/index.ts +5 -1
- package/src/invocation-contract-binding.ts +118 -6
- package/src/kernel.ts +9 -0
- package/src/manifest-schemas.ts +9 -5
- package/src/module-context.ts +35 -7
- package/src/schema-compiled-values.ts +28 -1
- package/src/schema-validator.ts +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.0",
|
|
4
4
|
"description": "Telo Runtime - A lightweight, polyglot execution host.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@marcbachmann/cel-js": "^7.6.1",
|
|
63
63
|
"@sinclair/typebox": "^0.34.48",
|
|
64
|
-
"@telorun/analyzer": "0.
|
|
64
|
+
"@telorun/analyzer": "0.60.0",
|
|
65
65
|
"@telorun/glob": "0.2.0",
|
|
66
|
-
"@telorun/templating": "0.
|
|
66
|
+
"@telorun/templating": "0.16.0",
|
|
67
67
|
"ajv": "^8.17.1",
|
|
68
68
|
"ajv-formats": "^3.0.1",
|
|
69
69
|
"packageurl-js": "^2.0.1",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@types/tar-stream": "^3.1.3",
|
|
76
76
|
"typescript": "^5.0.0",
|
|
77
77
|
"vitest": "^2.1.8",
|
|
78
|
-
"@telorun/sdk": "0.
|
|
78
|
+
"@telorun/sdk": "0.75.0"
|
|
79
79
|
},
|
|
80
80
|
"optionalDependencies": {
|
|
81
81
|
"esbuild": "^0.25.12"
|
|
@@ -254,6 +254,29 @@ export async function buildControllerFromSource(
|
|
|
254
254
|
return work;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* The bundle for `entryFile` **and the file set that produced it**.
|
|
259
|
+
*
|
|
260
|
+
* The publish and release paths need both halves of one build: the bytes become
|
|
261
|
+
* the module's controller layer, and the metafile inputs become the release
|
|
262
|
+
* edge graph — which module's source got inlined into whose artifact. Asking for
|
|
263
|
+
* them separately would either build twice or read an index written by a
|
|
264
|
+
* different build.
|
|
265
|
+
*
|
|
266
|
+
* Unlike the loader's path there is **no fallthrough here**. A host without
|
|
267
|
+
* esbuild raises `ControllerEnvMissingError` from the build, and this caller
|
|
268
|
+
* must let it: selecting the prebuilt `path=` file instead would digest and ship
|
|
269
|
+
* bytes other than the source it claims to be built from, which is the one
|
|
270
|
+
* outcome the whole release design exists to prevent.
|
|
271
|
+
*/
|
|
272
|
+
export async function buildControllerBundle(
|
|
273
|
+
entryFile: string,
|
|
274
|
+
cacheRoot: string,
|
|
275
|
+
): Promise<{ path: string; inputs: string[] }> {
|
|
276
|
+
const path = await buildControllerFromSource(entryFile, cacheRoot);
|
|
277
|
+
return { path, inputs: await lastBuildInputs(entryFile, cacheRoot) };
|
|
278
|
+
}
|
|
279
|
+
|
|
257
280
|
async function build(entryFile: string, cacheDir: string): Promise<string> {
|
|
258
281
|
const esbuild = await loadEsbuild();
|
|
259
282
|
if (!esbuild) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnalysisRegistry, DiagnosticSeverity, foldIntegrity, parseExportEntry, StaticAnalyzer } from "@telorun/analyzer";
|
|
1
|
+
import { AnalysisRegistry, DiagnosticSeverity, authoredModuleMetadata, foldIntegrity, parseExportEntry, StaticAnalyzer } from "@telorun/analyzer";
|
|
2
2
|
import type { ResourceInstance } from "@telorun/sdk";
|
|
3
3
|
import { RuntimeError } from "@telorun/sdk";
|
|
4
4
|
import { publishedPropsOf } from "../../evaluation-context.js";
|
|
@@ -166,6 +166,13 @@ export async function create(
|
|
|
166
166
|
ctx.moduleContext.createInstance,
|
|
167
167
|
ctx.moduleContext.emit,
|
|
168
168
|
);
|
|
169
|
+
// `module.<field>` inside the library reads the LIBRARY's own metadata. Its
|
|
170
|
+
// version is its own — carrying the importer's would make a library reporting
|
|
171
|
+
// `module.version` report whatever happened to import it.
|
|
172
|
+
childCtx.setModuleMetadata(
|
|
173
|
+
authoredModuleMetadata(moduleManifest.metadata as Record<string, unknown> | undefined),
|
|
174
|
+
);
|
|
175
|
+
|
|
169
176
|
const child = ctx.moduleContext.spawnChild(childCtx);
|
|
170
177
|
|
|
171
178
|
// A library references its own kinds via `Self.<Kind>` (e.g. when it declares an
|
package/src/index.ts
CHANGED
|
@@ -35,7 +35,11 @@ export {
|
|
|
35
35
|
type ResolvedControllerLayer,
|
|
36
36
|
} from "./bundle/module-artifact.js";
|
|
37
37
|
export { readOwnerManifest, type OwnerManifest } from "./bundle/module-manifest.js";
|
|
38
|
-
export {
|
|
38
|
+
export {
|
|
39
|
+
buildControllerBundle,
|
|
40
|
+
canBuildFromSource,
|
|
41
|
+
lastBuildInputs,
|
|
42
|
+
} from "./controller-loaders/source-bundle-builder.js";
|
|
39
43
|
export type {
|
|
40
44
|
PayloadLayer,
|
|
41
45
|
PublishBundle,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ContractDirection,
|
|
3
|
+
declaredScalarPaths,
|
|
4
|
+
type DeclaredScalarForm,
|
|
5
|
+
type DeclaredScalarPath,
|
|
3
6
|
defaultBearingPaths,
|
|
4
7
|
effectiveContractField,
|
|
5
8
|
type DefResolver,
|
|
@@ -63,6 +66,10 @@ export interface BoundContract {
|
|
|
63
66
|
/** Paths a default can be written to — how far the caller's value must be
|
|
64
67
|
* copied before validation runs. Empty when the contract declares none. */
|
|
65
68
|
defaultPaths(): string[][];
|
|
69
|
+
/** Paths whose declared type fixes a scalar representation — the leaves a
|
|
70
|
+
* value is normalized at, in either direction. Empty when the contract
|
|
71
|
+
* declares none. */
|
|
72
|
+
scalarPaths(): DeclaredScalarPath[];
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
const CONTRACT_ERROR: Record<ContractDirection, string> = {
|
|
@@ -123,6 +130,7 @@ export function resolveBoundContract(
|
|
|
123
130
|
|
|
124
131
|
let compiled: { validate(value: unknown): void } | undefined;
|
|
125
132
|
let paths: string[][] | undefined;
|
|
133
|
+
let scalars: DeclaredScalarPath[] | undefined;
|
|
126
134
|
|
|
127
135
|
const resolve = (): { validate(value: unknown): void } => {
|
|
128
136
|
if (compiled !== undefined) return compiled;
|
|
@@ -140,6 +148,7 @@ export function resolveBoundContract(
|
|
|
140
148
|
}
|
|
141
149
|
const stripped = withLiveValuesSkipped(schema, factory.resolveRef);
|
|
142
150
|
paths = defaultBearingPaths(stripped, factory.resolveRef);
|
|
151
|
+
scalars = declaredScalarPaths(stripped, factory.resolveRef);
|
|
143
152
|
// Compile by NAME whenever the declaration is one, so the type's CEL
|
|
144
153
|
// `rules:` are composed in — including when a stream had to be stripped, in
|
|
145
154
|
// which case the stream-bearing properties are dropped from the schema the
|
|
@@ -159,6 +168,10 @@ export function resolveBoundContract(
|
|
|
159
168
|
resolve();
|
|
160
169
|
return paths ?? [];
|
|
161
170
|
},
|
|
171
|
+
scalarPaths: () => {
|
|
172
|
+
resolve();
|
|
173
|
+
return scalars ?? [];
|
|
174
|
+
},
|
|
162
175
|
};
|
|
163
176
|
}
|
|
164
177
|
|
|
@@ -214,6 +227,91 @@ function copyAlong(node: unknown, segments: readonly string[]): unknown {
|
|
|
214
227
|
return node;
|
|
215
228
|
}
|
|
216
229
|
|
|
230
|
+
/**
|
|
231
|
+
* `value` with every leaf normalized to the representation its declaration
|
|
232
|
+
* names, copied along the containers above each one so the producer's own object
|
|
233
|
+
* is not rewritten under it.
|
|
234
|
+
*
|
|
235
|
+
* A declared shape says what the value IS, not only what it must pass — the same
|
|
236
|
+
* service a JSON Schema gives an HTTP response serializer. Telo's CEL layer
|
|
237
|
+
* takes the declaration literally: `integer` types as CEL `int` and a CEL int is
|
|
238
|
+
* a BigInt, so a controller handing back a plain JS number at an `integer` slot
|
|
239
|
+
* makes the contract a lie that surfaces nowhere until an expression composes it
|
|
240
|
+
* — `result.n + 1` type-checking statically and then dying at dispatch with
|
|
241
|
+
* `no such overload: dyn<double> + int`. Normalizing at the boundary that
|
|
242
|
+
* already knows the declared shape closes it for every kind at once, instead of
|
|
243
|
+
* once per module after each report.
|
|
244
|
+
*
|
|
245
|
+
* BOTH DIRECTIONS, deliberately. A controller cannot be written correctly
|
|
246
|
+
* against a slot that hands it a JS number when the manifest wrote a literal and
|
|
247
|
+
* a BigInt when CEL computed the same value — it would have to accept either at
|
|
248
|
+
* every declared-integer field, which is what `typeof x === "number"` guards
|
|
249
|
+
* around the standard library were quietly relying on. One representation per
|
|
250
|
+
* declaration is what makes the declared type something a controller can read.
|
|
251
|
+
*
|
|
252
|
+
* Only an EXACT conversion is performed: an integral number becomes an int64,
|
|
253
|
+
* and a BigInt becomes a double only when the round-trip is lossless. A
|
|
254
|
+
* fractional number at an integer slot, a string, a null, a magnitude no double
|
|
255
|
+
* can hold — all are left exactly as they arrived, so a value that genuinely
|
|
256
|
+
* violates the contract is still rejected rather than quietly repaired into
|
|
257
|
+
* something that passes, and a 64-bit integer is never truncated to reach a
|
|
258
|
+
* `number` slot (which is the `double(...)` defect this whole line of work
|
|
259
|
+
* exists to retire).
|
|
260
|
+
*/
|
|
261
|
+
export function normalizeDeclaredScalars(
|
|
262
|
+
value: unknown,
|
|
263
|
+
paths: readonly DeclaredScalarPath[],
|
|
264
|
+
): unknown {
|
|
265
|
+
if (paths.length === 0 || !value || typeof value !== "object") return value;
|
|
266
|
+
let out: unknown = value;
|
|
267
|
+
for (const { path, form } of paths) out = normalizeAlong(out, path, form);
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function normalizeAlong(
|
|
272
|
+
node: unknown,
|
|
273
|
+
segments: readonly string[],
|
|
274
|
+
form: DeclaredScalarForm,
|
|
275
|
+
): unknown {
|
|
276
|
+
if (!node || typeof node !== "object") return node;
|
|
277
|
+
const [head, ...rest] = segments;
|
|
278
|
+
|
|
279
|
+
if (head === "[]") {
|
|
280
|
+
if (!Array.isArray(node)) return node;
|
|
281
|
+
let changed = false;
|
|
282
|
+
const next = node.map((item) => {
|
|
283
|
+
const value = rest.length === 0 ? asForm(item, form) : normalizeAlong(item, rest, form);
|
|
284
|
+
if (value !== item) changed = true;
|
|
285
|
+
return value;
|
|
286
|
+
});
|
|
287
|
+
return changed ? next : node;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const container = node as Record<string, unknown>;
|
|
291
|
+
if (!(head! in container)) return node;
|
|
292
|
+
const child = container[head!];
|
|
293
|
+
const next = rest.length === 0 ? asForm(child, form) : normalizeAlong(child, rest, form);
|
|
294
|
+
if (next === child) return node;
|
|
295
|
+
// Copy-on-write, and only once a leaf actually moved: a producer may hand back
|
|
296
|
+
// an object it retains (a cached record, a live config), and rewriting it in
|
|
297
|
+
// place would change what it holds.
|
|
298
|
+
const copy = shallowCopy(container);
|
|
299
|
+
copy[head!] = next;
|
|
300
|
+
return copy;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function asForm(value: unknown, form: DeclaredScalarForm): unknown {
|
|
304
|
+
if (form === "int64") {
|
|
305
|
+
return typeof value === "number" && Number.isInteger(value) ? BigInt(value) : value;
|
|
306
|
+
}
|
|
307
|
+
if (typeof value !== "bigint") return value;
|
|
308
|
+
const asNumber = Number(value);
|
|
309
|
+
// Lossless only. Past the safe range a double cannot hold the integer, and
|
|
310
|
+
// handing back a silently-rounded one is the precision loss the int64 work
|
|
311
|
+
// exists to remove.
|
|
312
|
+
return BigInt(asNumber) === value ? asNumber : value;
|
|
313
|
+
}
|
|
314
|
+
|
|
217
315
|
/**
|
|
218
316
|
* Raise a contract violation as a structured {@link InvokeError}.
|
|
219
317
|
*
|
|
@@ -306,11 +404,19 @@ export function bindContract(instance: ResourceInstance, binding: ContractBindin
|
|
|
306
404
|
let effective = inputs;
|
|
307
405
|
if (input) {
|
|
308
406
|
// Copied only so the validator's default-fill cannot mutate what the
|
|
309
|
-
// caller still holds.
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
407
|
+
// caller still holds. Making the value READABLE to a JSON Schema
|
|
408
|
+
// validator regardless of representation is the validator's own concern
|
|
409
|
+
// and is applied there (`bigint-schema-view.ts`).
|
|
410
|
+
//
|
|
411
|
+
// Deliberately NOT normalized on this side. Normalization states what a
|
|
412
|
+
// value IS to whoever reads it next, and on the way in that reader is a
|
|
413
|
+
// controller written in the host language — a declared `integer` reaching
|
|
414
|
+
// one as an int64 is a change to the authoring surface of every module,
|
|
415
|
+
// not a repair to a lie. On the way OUT the reader is CEL, which already
|
|
416
|
+
// types the declaration as `int`, so there the declaration and the value
|
|
417
|
+
// genuinely disagree. The asymmetry is the point: a controller still sees
|
|
418
|
+
// whatever the call site produced and must accept both, which is what
|
|
419
|
+
// `bigint-schema-view.ts` documents.
|
|
314
420
|
effective = copyForDefaults(inputs, input.defaultPaths());
|
|
315
421
|
try {
|
|
316
422
|
input.validate(effective);
|
|
@@ -325,6 +431,12 @@ export function bindContract(instance: ResourceInstance, binding: ContractBindin
|
|
|
325
431
|
} catch (error) {
|
|
326
432
|
throw contractViolation("outputType", describeTarget, error);
|
|
327
433
|
}
|
|
434
|
+
// AFTER validation, not before: the validator fills declared defaults,
|
|
435
|
+
// and a default written as a plain YAML number is exactly the leaf that
|
|
436
|
+
// has to come out normalized too. Validation itself is indifferent to
|
|
437
|
+
// which representation arrives (`bigint-schema-view.ts`), so ordering
|
|
438
|
+
// costs nothing there.
|
|
439
|
+
return normalizeDeclaredScalars(result, output.scalarPaths());
|
|
328
440
|
}
|
|
329
441
|
return result;
|
|
330
442
|
};
|
|
@@ -339,7 +451,7 @@ export function bindContract(instance: ResourceInstance, binding: ContractBindin
|
|
|
339
451
|
} catch (error) {
|
|
340
452
|
throw contractViolation("outputType", describeTarget, error);
|
|
341
453
|
}
|
|
342
|
-
return result;
|
|
454
|
+
return normalizeDeclaredScalars(result, output.scalarPaths());
|
|
343
455
|
};
|
|
344
456
|
}
|
|
345
457
|
}
|
package/src/kernel.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnalysisRegistry,
|
|
3
|
+
authoredModuleMetadata,
|
|
3
4
|
buildEvalPaths,
|
|
4
5
|
collectZoneModuleDocuments,
|
|
5
6
|
flattenForAnalyzer,
|
|
@@ -689,6 +690,14 @@ export class Kernel implements IKernel {
|
|
|
689
690
|
}
|
|
690
691
|
});
|
|
691
692
|
this.rootContext.setTargets(rawTargets as BootTarget[]);
|
|
693
|
+
// `module.<field>` — the module doc's own metadata, so a manifest reads
|
|
694
|
+
// its version instead of restating it in a second place nothing keeps in
|
|
695
|
+
// sync. Set from the module doc rather than the application specifically:
|
|
696
|
+
// an imported library's context gets its own, which is the only reading
|
|
697
|
+
// that is true of the resources inside it.
|
|
698
|
+
this.rootContext.setModuleMetadata(
|
|
699
|
+
authoredModuleMetadata(manifest.metadata as Record<string, unknown> | undefined),
|
|
700
|
+
);
|
|
692
701
|
if (manifest.kind === "Telo.Application") {
|
|
693
702
|
rootApplicationManifest = manifest;
|
|
694
703
|
this._appName = (manifest.metadata as { name?: string } | undefined)?.name;
|
package/src/manifest-schemas.ts
CHANGED
|
@@ -5,15 +5,19 @@ import addFormats from "ajv-formats";
|
|
|
5
5
|
import { OBSERVED_STATE_SCHEMA, registerTeloKeywords } from "@telorun/analyzer";
|
|
6
6
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
7
7
|
|
|
8
|
-
// Re-export the shared
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// Re-export the shared manifest fragments so consumers reaching them through the
|
|
9
|
+
// kernel's surface keep working. The canonical home is `@telorun/analyzer`: they
|
|
10
|
+
// describe manifest STRUCTURE, and the editor validates in a browser through the
|
|
11
|
+
// analyzer, which must not depend on this package — a fragment here would exist
|
|
12
|
+
// only at runtime, invisible to `telo check` and to the editor.
|
|
12
13
|
export {
|
|
14
|
+
InvokeStepSchema,
|
|
13
15
|
MANIFEST_SCHEMA_URI,
|
|
14
16
|
ManifestRootSchema,
|
|
17
|
+
manifestFragmentRef,
|
|
15
18
|
ResourceRefSchema,
|
|
16
|
-
|
|
19
|
+
RetryPolicySchema,
|
|
20
|
+
} from "@telorun/analyzer";
|
|
17
21
|
|
|
18
22
|
const metadataSchema = {
|
|
19
23
|
type: "object",
|
package/src/module-context.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
deriveContext,
|
|
3
|
+
executeInvokeStep,
|
|
4
|
+
getRefIdentity,
|
|
5
|
+
type InlineInvokeTarget,
|
|
6
|
+
RuntimeError,
|
|
7
|
+
} from "@telorun/sdk";
|
|
2
8
|
import type { ScopeConfig } from "./logging/scope-config.js";
|
|
3
9
|
import type {
|
|
4
10
|
BootTarget,
|
|
@@ -69,6 +75,11 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
69
75
|
* populated on the root context from the Application's `ports` block;
|
|
70
76
|
* imported child modules keep this empty. */
|
|
71
77
|
private _ports: Record<string, unknown> = {};
|
|
78
|
+
/** The module doc's own `metadata`, published as `module.<field>` so a
|
|
79
|
+
* manifest can read its own version instead of restating it. Per module, not
|
|
80
|
+
* per application: an imported library's resources read the LIBRARY's
|
|
81
|
+
* metadata, which is the only reading that is true of them. */
|
|
82
|
+
private _module: Record<string, unknown> = {};
|
|
72
83
|
|
|
73
84
|
/** Maps import alias → real module name for kind resolution. */
|
|
74
85
|
private readonly importAliases = new Map<string, string>();
|
|
@@ -175,6 +186,10 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
175
186
|
return this._ports;
|
|
176
187
|
}
|
|
177
188
|
|
|
189
|
+
get module(): Record<string, unknown> {
|
|
190
|
+
return this._module;
|
|
191
|
+
}
|
|
192
|
+
|
|
178
193
|
setVariables(vars: Record<string, unknown>): void {
|
|
179
194
|
this._variables = vars;
|
|
180
195
|
this._rebuildContext();
|
|
@@ -185,6 +200,11 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
185
200
|
this._rebuildContext();
|
|
186
201
|
}
|
|
187
202
|
|
|
203
|
+
setModuleMetadata(metadata: Record<string, unknown>): void {
|
|
204
|
+
this._module = metadata;
|
|
205
|
+
this._rebuildContext();
|
|
206
|
+
}
|
|
207
|
+
|
|
188
208
|
setTargets(vars: BootTarget[]): void {
|
|
189
209
|
this.targets = vars;
|
|
190
210
|
}
|
|
@@ -515,6 +535,7 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
515
535
|
secrets,
|
|
516
536
|
resources: this._resources,
|
|
517
537
|
ports: this._ports,
|
|
538
|
+
module: this._module,
|
|
518
539
|
};
|
|
519
540
|
}
|
|
520
541
|
|
|
@@ -524,6 +545,7 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
524
545
|
secrets: this._secrets,
|
|
525
546
|
resources: this._resources,
|
|
526
547
|
ports: this._ports,
|
|
548
|
+
module: this._module,
|
|
527
549
|
};
|
|
528
550
|
this._secretValues = collectSecretValues(this._secrets);
|
|
529
551
|
}
|
|
@@ -647,16 +669,22 @@ export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
|
647
669
|
// step — only a structural `{ invoke: <ref>, inputs }` spec (no `run`)
|
|
648
670
|
// belongs here; the live instance falls through to the runnable branch.
|
|
649
671
|
if (!isRunnableInstance(target) && "invoke" in target && target.invoke !== undefined) {
|
|
672
|
+
// SPREAD, not field-by-field. A dispatch site is one kernel-owned shape,
|
|
673
|
+
// and rebuilding it here by hand is what silently dropped `retry:` —
|
|
674
|
+
// the schema admitted the field and this reconstruction threw it away, so
|
|
675
|
+
// a boot target asking for three attempts got one. Forwarding the entry
|
|
676
|
+
// keeps this correct the next time the shape grows.
|
|
677
|
+
// A cross-module `!ref Alias.name` stays a `{kind, name, alias}` ref; the leaf's
|
|
678
|
+
// alias branch resolves it via `resolveImportedInstance` and dispatches through
|
|
679
|
+
// `invokeResolved` so invocation events and error wrapping still fire.
|
|
650
680
|
const step: InvokeStep = {
|
|
681
|
+
...(target as InlineInvokeTarget),
|
|
651
682
|
name: target.name ?? `Target${i}`,
|
|
652
|
-
when: target.when,
|
|
653
|
-
// A cross-module `!ref Alias.name` stays a `{kind, name, alias}` ref; the leaf's
|
|
654
|
-
// alias branch resolves it via `resolveImportedInstance` and dispatches through
|
|
655
|
-
// `invokeResolved` so invocation events and error wrapping still fire.
|
|
656
683
|
invoke: target.invoke,
|
|
657
|
-
inputs: target.inputs,
|
|
658
684
|
};
|
|
659
|
-
|
|
685
|
+
// The boot cancellation, so a target parked in a retry backoff ends on
|
|
686
|
+
// SIGINT rather than sitting out the remaining delay.
|
|
687
|
+
await executeInvokeStep(step, stepCtx, { steps, invokeCtx: ctx });
|
|
660
688
|
continue;
|
|
661
689
|
}
|
|
662
690
|
if ("ref" in target && target.ref != null) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isCompiledValue } from "@telorun/sdk";
|
|
2
|
+
import { selectUnionBranch } from "@telorun/analyzer";
|
|
2
3
|
|
|
3
4
|
/** Returns a schema-appropriate placeholder value for a CompiledValue field. */
|
|
4
5
|
function placeholderForSchema(schema: Record<string, unknown>): unknown {
|
|
@@ -9,6 +10,19 @@ function placeholderForSchema(schema: Record<string, unknown>): unknown {
|
|
|
9
10
|
// author never wrote. Mirrors `celPlaceholderForSchema` in the analyzer, which
|
|
10
11
|
// performs the same substitution for the static half.
|
|
11
12
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
13
|
+
// A union with no `type` of its own: take the first branch that yields a
|
|
14
|
+
// placeholder, so a whole-field CEL leaf at an `anyOf` slot is not handed the
|
|
15
|
+
// `""` fallback that only a string branch would accept. Mirrors the analyzer's
|
|
16
|
+
// `celPlaceholderForSchema`, which makes the same substitution for the static
|
|
17
|
+
// half — the two must agree or one rejects what the other passes.
|
|
18
|
+
if (schema.type === undefined) {
|
|
19
|
+
const branches = (schema.anyOf ?? schema.oneOf) as Record<string, unknown>[] | undefined;
|
|
20
|
+
if (Array.isArray(branches)) {
|
|
21
|
+
for (const branch of branches) {
|
|
22
|
+
if (branch && typeof branch === "object") return placeholderForSchema(branch);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
12
26
|
switch (schema.type) {
|
|
13
27
|
case "integer":
|
|
14
28
|
case "number":
|
|
@@ -113,7 +127,20 @@ export function stripCompiledValues(
|
|
|
113
127
|
// that merely appears twice is still stripped both times.
|
|
114
128
|
const ancestors = new Set<object>();
|
|
115
129
|
|
|
116
|
-
const walk = (value: unknown,
|
|
130
|
+
const walk = (value: unknown, rawNodeSchema: Record<string, unknown>): unknown => {
|
|
131
|
+
// A UNION carries no `type` / `items` / `properties` of its own, so descending
|
|
132
|
+
// through one hands every CEL leaf underneath the schema-unaware `""`
|
|
133
|
+
// placeholder — which the branches then reject, reporting violations against
|
|
134
|
+
// a value the author never wrote (`/success/1 must be integer` for a `!cel`
|
|
135
|
+
// inside a list). Resolving the branch first is what lets the leaves be
|
|
136
|
+
// placed. Shared with the analyzer rather than reimplemented: the static and
|
|
137
|
+
// dispatch halves must choose the same branch, or one reports what the other
|
|
138
|
+
// accepts.
|
|
139
|
+
const nodeSchema = selectUnionBranch(
|
|
140
|
+
resolveSchemaRef(rawNodeSchema, root),
|
|
141
|
+
value,
|
|
142
|
+
root as Record<string, any>,
|
|
143
|
+
) as Record<string, unknown>;
|
|
117
144
|
const resolved = resolveSchemaRef(nodeSchema, root);
|
|
118
145
|
|
|
119
146
|
if (isCompiledValue(value)) return placeholderForSchema(resolved);
|
package/src/schema-validator.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
-
import { registerTeloKeywords } from "@telorun/analyzer";
|
|
10
|
+
import { ManifestRootSchema, registerTeloKeywords } from "@telorun/analyzer";
|
|
11
11
|
import { X_TELO_TYPE } from "@telorun/sdk";
|
|
12
12
|
import { mergeFilledDefaults, withBigIntsAsNumbers } from "./bigint-schema-view.js";
|
|
13
13
|
import { formatAjvErrors } from "./manifest-schemas.js";
|
|
@@ -25,7 +25,6 @@ function describeValue(data: unknown): string {
|
|
|
25
25
|
import {
|
|
26
26
|
EXACT_TEMPLATE_REGEX,
|
|
27
27
|
isTaggedSentinel,
|
|
28
|
-
ManifestRootSchema,
|
|
29
28
|
} from "@telorun/templating";
|
|
30
29
|
|
|
31
30
|
const Ajv = AjvModule.default ?? AjvModule;
|