@telorun/kernel 0.69.0 → 0.72.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/bigint-json.d.ts +45 -0
- package/dist/bigint-json.d.ts.map +1 -0
- package/dist/bigint-json.js +65 -0
- package/dist/bigint-json.js.map +1 -0
- package/dist/bigint-schema-view.d.ts +34 -0
- package/dist/bigint-schema-view.d.ts.map +1 -0
- package/dist/bigint-schema-view.js +85 -0
- package/dist/bigint-schema-view.js.map +1 -0
- package/dist/cel-handlers.d.ts.map +1 -1
- package/dist/cel-handlers.js +6 -8
- package/dist/cel-handlers.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +15 -1
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +8 -1
- package/dist/evaluation-context.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/invocation-contract-binding.d.ts +0 -6
- package/dist/invocation-contract-binding.d.ts.map +1 -1
- package/dist/invocation-contract-binding.js +9 -64
- package/dist/invocation-contract-binding.js.map +1 -1
- package/dist/kernel.d.ts +4 -0
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +22 -3
- package/dist/kernel.js.map +1 -1
- package/dist/logging/encode-json.d.ts.map +1 -1
- package/dist/logging/encode-json.js +13 -9
- package/dist/logging/encode-json.js.map +1 -1
- package/dist/logging/encode-pretty.d.ts.map +1 -1
- package/dist/logging/encode-pretty.js +13 -4
- package/dist/logging/encode-pretty.js.map +1 -1
- package/dist/module-file-resolution.d.ts +22 -0
- package/dist/module-file-resolution.d.ts.map +1 -0
- package/dist/module-file-resolution.js +51 -0
- package/dist/module-file-resolution.js.map +1 -0
- package/dist/resolve-include-sentinels.d.ts +37 -0
- package/dist/resolve-include-sentinels.d.ts.map +1 -0
- package/dist/resolve-include-sentinels.js +175 -0
- package/dist/resolve-include-sentinels.js.map +1 -0
- package/dist/resource-context.d.ts +10 -1
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +42 -45
- package/dist/resource-context.js.map +1 -1
- package/dist/runtime-seam.d.ts.map +1 -1
- package/dist/runtime-seam.js +10 -1
- package/dist/runtime-seam.js.map +1 -1
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +23 -12
- package/dist/schema-validator.js.map +1 -1
- package/package.json +4 -4
- package/src/bigint-json.ts +69 -0
- package/src/bigint-schema-view.ts +76 -0
- package/src/cel-handlers.ts +6 -9
- package/src/controllers/module/import-controller.ts +14 -1
- package/src/evaluation-context.ts +8 -1
- package/src/index.ts +1 -0
- package/src/invocation-contract-binding.ts +9 -55
- package/src/kernel.ts +29 -0
- package/src/logging/encode-json.ts +14 -9
- package/src/logging/encode-pretty.ts +12 -3
- package/src/module-file-resolution.ts +64 -0
- package/src/resolve-include-sentinels.ts +219 -0
- package/src/resource-context.ts +46 -45
- package/src/runtime-seam.ts +11 -0
- package/src/schema-validator.ts +23 -12
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BigInt-normalized VIEW a JSON Schema validator can check, and merging its
|
|
3
|
+
* default-fills back onto the real value.
|
|
4
|
+
*
|
|
5
|
+
* CEL evaluates an integer to a BigInt, which AJV does not recognise as `integer`
|
|
6
|
+
* or `number` — `typeof data == "number"` is the whole of its type check. So a
|
|
7
|
+
* computed integer reaching a declared integer slot is rejected for a reason the
|
|
8
|
+
* author cannot act on, and cannot fix without casting the value to a float. The
|
|
9
|
+
* value is not wrong; the validator cannot see it.
|
|
10
|
+
*
|
|
11
|
+
* Validating a normalized view rather than coercing in place is what keeps that a
|
|
12
|
+
* validator concern: the dispatched value keeps its BigInts, since a controller may
|
|
13
|
+
* need the full 64-bit range, and serialization emits them exactly (see
|
|
14
|
+
* `enableBigIntJson` in `@telorun/sdk`). A value beyond the safe-integer range loses
|
|
15
|
+
* precision in the VIEW, which can only affect a bound check at the extremes.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** A structural copy of `value` with every BigInt rendered as a Number. Returns
|
|
19
|
+
* the SAME reference when there was nothing to change, which is what lets a
|
|
20
|
+
* caller skip the merge-back entirely on the common path. Non-plain objects (a
|
|
21
|
+
* live `Stream`, a resource instance) pass through by reference — they are not
|
|
22
|
+
* data to be walked. */
|
|
23
|
+
export function withBigIntsAsNumbers(value: unknown): unknown {
|
|
24
|
+
if (typeof value === "bigint") return Number(value);
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
let changed = false;
|
|
27
|
+
const items = value.map((item) => {
|
|
28
|
+
const next = withBigIntsAsNumbers(item);
|
|
29
|
+
if (next !== item) changed = true;
|
|
30
|
+
return next;
|
|
31
|
+
});
|
|
32
|
+
return changed ? items : value;
|
|
33
|
+
}
|
|
34
|
+
if (!value || typeof value !== "object") return value;
|
|
35
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return value;
|
|
36
|
+
let changed = false;
|
|
37
|
+
const out: Record<string, unknown> = {};
|
|
38
|
+
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
39
|
+
const next = withBigIntsAsNumbers(item);
|
|
40
|
+
if (next !== item) changed = true;
|
|
41
|
+
out[key] = next;
|
|
42
|
+
}
|
|
43
|
+
return changed ? out : value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Copy keys an AJV `useDefaults` fill added to `view` back onto `target`.
|
|
47
|
+
* Only ADDITIONS are taken: a key already present came from the caller and its
|
|
48
|
+
* original (possibly BigInt) value is the one to keep.
|
|
49
|
+
*
|
|
50
|
+
* Arrays are walked index-wise, not treated as leaves: AJV writes a default at
|
|
51
|
+
* every level it finds one, including inside `items`, and `Collection.Sort`'s
|
|
52
|
+
* `orderBy[].descending` is exactly that shape. Bailing at the array boundary
|
|
53
|
+
* would drop those fills silently — a worse failure than the throw it replaced,
|
|
54
|
+
* because the call then succeeds with the default missing. This mirrors the
|
|
55
|
+
* copy side, where `copyForDefaults` already fans out through an `[]` segment. */
|
|
56
|
+
export function mergeFilledDefaults(target: unknown, view: unknown): unknown {
|
|
57
|
+
if (target === view) return target;
|
|
58
|
+
if (!target || typeof target !== "object") return target;
|
|
59
|
+
if (!view || typeof view !== "object") return target;
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(target)) {
|
|
62
|
+
if (!Array.isArray(view)) return target;
|
|
63
|
+
for (let i = 0; i < target.length && i < view.length; i++) {
|
|
64
|
+
target[i] = mergeFilledDefaults(target[i], view[i]);
|
|
65
|
+
}
|
|
66
|
+
return target;
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(view)) return target;
|
|
69
|
+
|
|
70
|
+
const out = target as Record<string, unknown>;
|
|
71
|
+
for (const [key, filled] of Object.entries(view as Record<string, unknown>)) {
|
|
72
|
+
if (!(key in out)) out[key] = filled;
|
|
73
|
+
else out[key] = mergeFilledDefaults(out[key], filled);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
package/src/cel-handlers.ts
CHANGED
|
@@ -12,13 +12,10 @@ export const nodeCelHandlers = {
|
|
|
12
12
|
createHmac(algorithm, key).update(message).digest("hex"),
|
|
13
13
|
base64Encode: (s: string) => Buffer.from(s, "utf8").toString("base64"),
|
|
14
14
|
base64Decode: (s: string) => Buffer.from(s, "base64").toString("utf8"),
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
// than break the contract. (CEL `null` already serializes to "null".)
|
|
22
|
-
json: (value: unknown) =>
|
|
23
|
-
JSON.stringify(value, (_k, v) => (typeof v === "bigint" ? Number(v) : v)) ?? "null",
|
|
15
|
+
// An int / uint is a BigInt here and serializes as its exact digits — see
|
|
16
|
+
// `enableBigIntJson` in `@telorun/sdk`, installed at boot. JSON.stringify
|
|
17
|
+
// returns undefined for top-level undefined / function / symbol — the CEL
|
|
18
|
+
// signature is `json(dyn): string`, so coerce that to "null" rather than break
|
|
19
|
+
// the contract. (CEL `null` already serializes to "null".)
|
|
20
|
+
json: (value: unknown) => JSON.stringify(value) ?? "null",
|
|
24
21
|
};
|
|
@@ -144,7 +144,20 @@ export async function create(
|
|
|
144
144
|
moduleManifest.secrets ?? {},
|
|
145
145
|
);
|
|
146
146
|
const childCtx = new ModuleContext(
|
|
147
|
-
|
|
147
|
+
// The LIBRARY's own manifest URL, not the importer's. `source` is what every
|
|
148
|
+
// module-relative file reference is measured from — `ctx.resolveModuleFile`
|
|
149
|
+
// for a controller, an `!include-*` embed for a manifest value — so carrying
|
|
150
|
+
// the parent's here made a library read the CONSUMER's directory. That is
|
|
151
|
+
// worse than a hard error: a consumer who happens to have a file at the same
|
|
152
|
+
// relative path gets theirs silently. It also contradicted packaging, which
|
|
153
|
+
// is per-module and had already put the library's file in the library's own
|
|
154
|
+
// artifact.
|
|
155
|
+
//
|
|
156
|
+
// The MANIFEST URL, stamped by the loader — not `resolvedUrl`, which is the
|
|
157
|
+
// import source as written (`./lib`, a directory). Resolving `assets/x` against
|
|
158
|
+
// a directory URL with no trailing slash drops its last segment, which is how
|
|
159
|
+
// the consumer's directory got read in the first place.
|
|
160
|
+
((moduleManifest.metadata as { source?: string } | undefined)?.source ?? resolvedUrl),
|
|
148
161
|
importVariables,
|
|
149
162
|
importSecrets,
|
|
150
163
|
{},
|
|
@@ -317,7 +317,14 @@ function compileWalker(value: unknown): Walker {
|
|
|
317
317
|
return out;
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
|
-
|
|
320
|
+
// Only PLAIN objects are rebuilt. Anything else — a `Uint8Array` embedded by
|
|
321
|
+
// `!include-bytes`, a class instance — is opaque and passes through by
|
|
322
|
+
// reference, the same rule `precompileDoc` follows. Rebuilding from
|
|
323
|
+
// `Object.entries` would turn a byte buffer into `{"0":137,…}` silently, with
|
|
324
|
+
// no error anywhere: the bytes would simply arrive at the controller as the
|
|
325
|
+
// wrong shape.
|
|
326
|
+
const proto = value !== null && typeof value === "object" ? Object.getPrototypeOf(value) : false;
|
|
327
|
+
if (proto === Object.prototype || proto === null) {
|
|
321
328
|
const entries = Object.entries(value as Record<string, unknown>).map(
|
|
322
329
|
([k, v]) => [k, compileWalker(v)] as const,
|
|
323
330
|
);
|
package/src/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ export type {
|
|
|
44
44
|
} from "./transports/transport.js";
|
|
45
45
|
export { ExecutionContext } from "./execution-context.js";
|
|
46
46
|
export { Kernel, type KernelOptions } from "./kernel.js";
|
|
47
|
+
export { enableBigIntJson } from "./bigint-json.js";
|
|
47
48
|
export { nodeCelHandlers } from "./cel-handlers.js";
|
|
48
49
|
export { ModuleContext } from "./module-context.js";
|
|
49
50
|
export { ManifestRegistry as Registry } from "./registry.js";
|
|
@@ -305,27 +305,23 @@ export function bindContract(instance: ResourceInstance, binding: ContractBindin
|
|
|
305
305
|
instance.invoke = async (inputs: any, ...rest: unknown[]) => {
|
|
306
306
|
let effective = inputs;
|
|
307
307
|
if (input) {
|
|
308
|
+
// Copied only so the validator's default-fill cannot mutate what the
|
|
309
|
+
// caller still holds. The BigInt normalization a JSON Schema validator
|
|
310
|
+
// needs (CEL evaluates an integer to one, which AJV does not recognise
|
|
311
|
+
// as `integer`) belongs to the validator and is applied there — see
|
|
312
|
+
// `bigint-schema-view.ts`. Doing it here as well would walk every input
|
|
313
|
+
// tree twice per dispatch, and split one concern across two layers.
|
|
308
314
|
effective = copyForDefaults(inputs, input.defaultPaths());
|
|
309
|
-
// Validate a BIGINT-NORMALIZED view, not the values themselves. CEL
|
|
310
|
-
// evaluates an integer literal to a BigInt, which a JSON Schema
|
|
311
|
-
// validator does not recognise as `integer` — so every computed integer
|
|
312
|
-
// reaching a declared integer input would be rejected for a reason the
|
|
313
|
-
// author cannot act on. The dispatched values keep their BigInts, since
|
|
314
|
-
// a controller may need the full 64-bit range.
|
|
315
|
-
const view = withBigIntsAsNumbers(effective);
|
|
316
315
|
try {
|
|
317
|
-
input.validate(
|
|
316
|
+
input.validate(effective);
|
|
318
317
|
} catch (error) {
|
|
319
318
|
throw contractViolation("inputType", describeTarget, error);
|
|
320
319
|
}
|
|
321
|
-
// Defaults are additive, so anything the validator filled into the view
|
|
322
|
-
// is a key the caller omitted — copy exactly those back.
|
|
323
|
-
effective = mergeFilledDefaults(effective, view);
|
|
324
320
|
}
|
|
325
321
|
const result = await original(effective, ...rest);
|
|
326
322
|
if (output) {
|
|
327
323
|
try {
|
|
328
|
-
output.validate(
|
|
324
|
+
output.validate(result);
|
|
329
325
|
} catch (error) {
|
|
330
326
|
throw contractViolation("outputType", describeTarget, error);
|
|
331
327
|
}
|
|
@@ -339,7 +335,7 @@ export function bindContract(instance: ResourceInstance, binding: ContractBindin
|
|
|
339
335
|
instance.provide = async (...args: unknown[]) => {
|
|
340
336
|
const result = await original(...args);
|
|
341
337
|
try {
|
|
342
|
-
output.validate(
|
|
338
|
+
output.validate(result);
|
|
343
339
|
} catch (error) {
|
|
344
340
|
throw contractViolation("outputType", describeTarget, error);
|
|
345
341
|
}
|
|
@@ -348,45 +344,3 @@ export function bindContract(instance: ResourceInstance, binding: ContractBindin
|
|
|
348
344
|
}
|
|
349
345
|
}
|
|
350
346
|
|
|
351
|
-
/** A structural copy with every BigInt rendered as a Number, for validation
|
|
352
|
-
* only. A value beyond the safe-integer range loses precision in the VIEW,
|
|
353
|
-
* which can only affect a bound check at the extremes; the dispatched value is
|
|
354
|
-
* untouched. Non-plain objects (a live `Stream`, a resource instance) pass
|
|
355
|
-
* through by reference — they are not data to be walked. */
|
|
356
|
-
export function withBigIntsAsNumbers(value: unknown): unknown {
|
|
357
|
-
if (typeof value === "bigint") return Number(value);
|
|
358
|
-
if (Array.isArray(value)) {
|
|
359
|
-
let changed = false;
|
|
360
|
-
const items = value.map((item) => {
|
|
361
|
-
const next = withBigIntsAsNumbers(item);
|
|
362
|
-
if (next !== item) changed = true;
|
|
363
|
-
return next;
|
|
364
|
-
});
|
|
365
|
-
return changed ? items : value;
|
|
366
|
-
}
|
|
367
|
-
if (!value || typeof value !== "object") return value;
|
|
368
|
-
if (Object.getPrototypeOf(value) !== Object.prototype) return value;
|
|
369
|
-
let changed = false;
|
|
370
|
-
const out: Record<string, unknown> = {};
|
|
371
|
-
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
372
|
-
const next = withBigIntsAsNumbers(item);
|
|
373
|
-
if (next !== item) changed = true;
|
|
374
|
-
out[key] = next;
|
|
375
|
-
}
|
|
376
|
-
return changed ? out : value;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/** Copy keys the validator's default-fill added to `view` back onto `target`.
|
|
380
|
-
* Only ADDITIONS are taken: a key already present came from the caller and its
|
|
381
|
-
* original (possibly BigInt) value is the one to dispatch. */
|
|
382
|
-
function mergeFilledDefaults(target: unknown, view: unknown): unknown {
|
|
383
|
-
if (target === view) return target;
|
|
384
|
-
if (!target || typeof target !== "object" || Array.isArray(target)) return target;
|
|
385
|
-
if (!view || typeof view !== "object" || Array.isArray(view)) return target;
|
|
386
|
-
const out = target as Record<string, unknown>;
|
|
387
|
-
for (const [key, filled] of Object.entries(view as Record<string, unknown>)) {
|
|
388
|
-
if (!(key in out)) out[key] = filled;
|
|
389
|
-
else out[key] = mergeFilledDefaults(out[key], filled);
|
|
390
|
-
}
|
|
391
|
-
return out;
|
|
392
|
-
}
|
package/src/kernel.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { parseArgs } from "util";
|
|
39
39
|
import { ControllerRegistry } from "./controller-registry.js";
|
|
40
40
|
import { EventBus } from "./events.js";
|
|
41
|
+
import { enableBigIntJson } from "./bigint-json.js";
|
|
41
42
|
import { hostEnv, lockControllerEnv } from "./host-env.js";
|
|
42
43
|
import { KernelTracer } from "./tracing.js";
|
|
43
44
|
import { KernelLogging, type LoggingManifestBlock } from "./logging/kernel-logging.js";
|
|
@@ -51,6 +52,7 @@ import { nodeCelHandlers } from "./cel-handlers.js";
|
|
|
51
52
|
import { parseRef, seedInvokeSource } from "./invoke-dispatch.js";
|
|
52
53
|
import { stripCompiledValues } from "./schema-compiled-values.js";
|
|
53
54
|
import { injectAtPath } from "./dependency-injection.js";
|
|
55
|
+
import { resolveIncludeSentinels, type IncludeCache } from "./resolve-include-sentinels.js";
|
|
54
56
|
import {
|
|
55
57
|
computeAnalysisSignature,
|
|
56
58
|
readAnalysisStamp,
|
|
@@ -136,6 +138,10 @@ export class Kernel implements IKernel {
|
|
|
136
138
|
private idleResolvers: Array<() => void> = [];
|
|
137
139
|
private _exitCode = 0;
|
|
138
140
|
private readonly sharedSchemaValidator = new SchemaValidator();
|
|
141
|
+
/** `!include-*` reads, keyed by resolved URI, so two resources embedding the
|
|
142
|
+
* same asset read it once. Kernel-scoped: the values are retained by the
|
|
143
|
+
* resources holding them anyway, so this deduplicates rather than retains. */
|
|
144
|
+
private readonly includeCache: IncludeCache = new Map();
|
|
139
145
|
private rootContext!: ModuleContext;
|
|
140
146
|
private staticManifests: ResourceManifest[] = [];
|
|
141
147
|
private _entryUrl?: string;
|
|
@@ -761,6 +767,13 @@ export class Kernel implements IKernel {
|
|
|
761
767
|
}
|
|
762
768
|
this._bootCalled = true;
|
|
763
769
|
|
|
770
|
+
// Make a CEL integer JSON-serializable before any controller runs. CEL
|
|
771
|
+
// models `int` as int64 (a JS BigInt here), and `JSON.stringify` throws on
|
|
772
|
+
// one — so every JSON boundary a manifest can reach, in this kernel and in
|
|
773
|
+
// any module, needs the answer installed before the first value crosses it.
|
|
774
|
+
// Process-global and idempotent, like the env guardrail below.
|
|
775
|
+
enableBigIntJson();
|
|
776
|
+
|
|
764
777
|
// Lock the ambient host environment before any controller runs: a key the
|
|
765
778
|
// manifest binds via `variables`/`secrets`/`ports` must be read through
|
|
766
779
|
// `ctx.env` or the declared binding, never the raw `process.env` var. Every
|
|
@@ -1145,6 +1158,7 @@ export class Kernel implements IKernel {
|
|
|
1145
1158
|
args?: ParsedArgs,
|
|
1146
1159
|
ownerPrefix = "",
|
|
1147
1160
|
owningContext?: IEvaluationContext,
|
|
1161
|
+
resolvedKind?: string,
|
|
1148
1162
|
): ResourceContext {
|
|
1149
1163
|
return new ResourceContextImpl(
|
|
1150
1164
|
this,
|
|
@@ -1158,6 +1172,7 @@ export class Kernel implements IKernel {
|
|
|
1158
1172
|
args,
|
|
1159
1173
|
ownerPrefix,
|
|
1160
1174
|
owningContext ?? moduleContext,
|
|
1175
|
+
resolvedKind,
|
|
1161
1176
|
);
|
|
1162
1177
|
}
|
|
1163
1178
|
|
|
@@ -1275,6 +1290,19 @@ export class Kernel implements IKernel {
|
|
|
1275
1290
|
const compile = [...parentEval.compile, ...ownEval.compile];
|
|
1276
1291
|
const runtime = [...parentEval.runtime, ...ownEval.runtime];
|
|
1277
1292
|
|
|
1293
|
+
// Embedded files are read here, at the single instance-production site, and
|
|
1294
|
+
// not at manifest load: `telo.yaml` is its own artifact layer so that reading
|
|
1295
|
+
// a manifest cannot pull the payload, and an app loads every imported
|
|
1296
|
+
// library's manifest. Before schema validation, so the schema sees the
|
|
1297
|
+
// resolved value — bytes arrive at an `x-telo-binary` slot as the
|
|
1298
|
+
// `Uint8Array` that annotation demands.
|
|
1299
|
+
await resolveIncludeSentinels(
|
|
1300
|
+
resource,
|
|
1301
|
+
this.findModuleContext(evalContext).source,
|
|
1302
|
+
this,
|
|
1303
|
+
this.includeCache,
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1278
1306
|
// Schema validation runs before CEL evaluation so it sees the original manifest
|
|
1279
1307
|
// shape. CompiledValue wrappers (from load-time precompilation) are stripped,
|
|
1280
1308
|
// restoring the pre-CEL string view that the schema expects.
|
|
@@ -1308,6 +1336,7 @@ export class Kernel implements IKernel {
|
|
|
1308
1336
|
// Snapshot publication targets the context that OWNS the instance — for a
|
|
1309
1337
|
// `with:`-scoped resource that is the per-run scope child, not the module.
|
|
1310
1338
|
evalContext,
|
|
1339
|
+
resolvedKind,
|
|
1311
1340
|
);
|
|
1312
1341
|
const instance = await controller.create(processedResource, ctx);
|
|
1313
1342
|
if (!instance) return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatUnixNano, type AnyValue, type ErrorValue, type LogRecord } from "@telorun/sdk";
|
|
1
|
+
import { bigIntAt, formatUnixNano, type AnyValue, type ErrorValue, type LogRecord } from "@telorun/sdk";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The `json` encoding — `kernel/specs/logging.md` §11.1. One JSON object per
|
|
@@ -83,16 +83,21 @@ function sortKeysDeep(value: AnyValue): AnyValue {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function makeReplacer(encodeBytes: BytesEncoder) {
|
|
86
|
-
return function replacer(this: unknown,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
return function replacer(this: unknown, key: string, value: unknown): unknown {
|
|
87
|
+
// Read the BigInt off the HOLDER, not off `value`: `BigInt.prototype.toJSON`
|
|
88
|
+
// has already rewritten it to the exact-digits form every other JSON boundary
|
|
89
|
+
// wants (see `enableBigIntJson`). A log record is the one destination where
|
|
90
|
+
// that is wrong — a value beyond 2^53 loses precision in a JS receiver, so it
|
|
91
|
+
// degrades to a decimal string rather than to a wrong number, the same
|
|
92
|
+
// reasoning OTLP gives for quoting its 64-bit fields.
|
|
93
|
+
const source = bigIntAt(this, key);
|
|
94
|
+
if (source !== undefined) {
|
|
95
|
+
return source >= MIN_SAFE && source <= MAX_SAFE ? Number(source) : source.toString();
|
|
94
96
|
}
|
|
95
97
|
if (value instanceof Uint8Array) return encodeBytes(value);
|
|
96
98
|
return value;
|
|
97
99
|
};
|
|
98
100
|
}
|
|
101
|
+
|
|
102
|
+
const MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
|
|
103
|
+
const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { severityFloor, type AnyValue, type ErrorValue, type LogRecord } from "@telorun/sdk";
|
|
1
|
+
import { bigIntAt, severityFloor, type AnyValue, type ErrorValue, type LogRecord } from "@telorun/sdk";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The `pretty` encoding — `kernel/specs/logging.md` §11.2. For humans on a
|
|
@@ -90,8 +90,17 @@ function quoteIfNeeded(text: string): string {
|
|
|
90
90
|
return /[\s"=]/.test(text) ? JSON.stringify(text) : text;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function jsonSafe(
|
|
94
|
-
|
|
93
|
+
function jsonSafe(this: unknown, key: string, value: unknown): unknown {
|
|
94
|
+
// Read the integer off the HOLDER, not off `value`: `BigInt.prototype.toJSON`
|
|
95
|
+
// (see `enableBigIntJson`) runs BEFORE a replacer, so by the time this is
|
|
96
|
+
// called a wide integer is already the exact-digits form every wire encoding
|
|
97
|
+
// wants. This console encoding renders it as a decimal string instead, and
|
|
98
|
+
// matching on `typeof value === "bigint"` would silently stop doing so —
|
|
99
|
+
// a changed encoding, not an error. Reading the holder keeps the rendering
|
|
100
|
+
// identical whether or not the patch is installed, which matters because a
|
|
101
|
+
// test can call this encoder outside a booted kernel.
|
|
102
|
+
const source = bigIntAt(this, key);
|
|
103
|
+
if (source !== undefined) return source.toString();
|
|
95
104
|
if (value instanceof Uint8Array) return `<${value.byteLength} bytes>`;
|
|
96
105
|
return value;
|
|
97
106
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { pathToFileURL } from "url";
|
|
3
|
+
import { RuntimeError } from "@telorun/sdk";
|
|
4
|
+
import type { ModuleArtifact } from "./bundle/module-artifact.js";
|
|
5
|
+
|
|
6
|
+
/** The slice of the kernel this module needs: a module's artifact, when it has
|
|
7
|
+
* one. A module already on disk has none — that is normal, not an error. */
|
|
8
|
+
export interface ModuleArtifactLookup {
|
|
9
|
+
getModuleArtifact(source: string | undefined): ModuleArtifact | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a module-relative reference against the declaring module's own
|
|
14
|
+
* directory, materializing the layers that could carry it on first use.
|
|
15
|
+
*
|
|
16
|
+
* A URI, not a filesystem path: the SDK is cross-runtime, and a path is only
|
|
17
|
+
* what *this* kernel happens to return for a module whose files are local. An
|
|
18
|
+
* already-absolute URI (one with a scheme) passes through untouched; a bare
|
|
19
|
+
* absolute filesystem path is returned as a `file://` URI rather than being
|
|
20
|
+
* rebased onto the module directory.
|
|
21
|
+
*
|
|
22
|
+
* Shared by `ctx.resolveModuleFile` and by `!include-*` resolution, so a file
|
|
23
|
+
* reached by a controller and a file embedded by a tag are located by one rule
|
|
24
|
+
* — including which layers get materialized on the way.
|
|
25
|
+
*/
|
|
26
|
+
export async function resolveModuleFileUri(
|
|
27
|
+
relative: string,
|
|
28
|
+
source: string,
|
|
29
|
+
lookup: ModuleArtifactLookup,
|
|
30
|
+
): Promise<string> {
|
|
31
|
+
// An absolute URI names its own location; a bare absolute path is already
|
|
32
|
+
// resolved and must not be rebased onto the module directory.
|
|
33
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(relative)) return relative;
|
|
34
|
+
if (path.isAbsolute(relative)) return pathToFileURL(relative).href;
|
|
35
|
+
|
|
36
|
+
const artifact = lookup.getModuleArtifact(source);
|
|
37
|
+
if (artifact) {
|
|
38
|
+
// Both the `assets` layer and `common` — the sink rule puts a file the
|
|
39
|
+
// author did not claim via `assets:` into `common`, and a module that ships
|
|
40
|
+
// static files with no bundled controller has no other route to its payload.
|
|
41
|
+
// Fetching only assets would leave such a module resolving into an empty
|
|
42
|
+
// directory.
|
|
43
|
+
await artifact.materializeModuleFiles();
|
|
44
|
+
return new URL(relative, pathToFileURL(path.join(artifact.directory, "/")).href).href;
|
|
45
|
+
}
|
|
46
|
+
// No artifact means no payload to fetch. That is normal for a module already
|
|
47
|
+
// on disk (development) or one that ships no files — but for a module reached
|
|
48
|
+
// over a non-local scheme it means the artifact carries no layer index, i.e. it
|
|
49
|
+
// predates layers. Raise the actionable error here rather than leaving each
|
|
50
|
+
// caller to invent its own message from a URI it cannot open.
|
|
51
|
+
if (!source.startsWith("file://") && !path.isAbsolute(source)) {
|
|
52
|
+
throw new RuntimeError(
|
|
53
|
+
"ERR_MODULE_FILES_UNAVAILABLE",
|
|
54
|
+
`Cannot resolve '${relative}' against module '${source}': the module's artifact ` +
|
|
55
|
+
`carries no layer index, so its files cannot be located. It was published by an ` +
|
|
56
|
+
`older Telo that wrote a single-blob artifact — republish the module, or import it ` +
|
|
57
|
+
`from a local path during development.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
// Local module: resolve against the manifest URL, the same rule `include:`
|
|
61
|
+
// and sibling imports follow.
|
|
62
|
+
const base = source.startsWith("file://") ? source : pathToFileURL(source).href;
|
|
63
|
+
return new URL(relative, base).href;
|
|
64
|
+
}
|