@telorun/kernel 0.49.0 → 0.51.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-loader.d.ts +6 -1
- package/dist/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +2 -0
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-loaders/bundle-builder.d.ts +2 -1
- package/dist/controller-loaders/bundle-builder.d.ts.map +1 -1
- package/dist/controller-loaders/bundle-builder.js +14 -9
- package/dist/controller-loaders/bundle-builder.js.map +1 -1
- package/dist/controller-loaders/npm-loader.d.ts +8 -2
- package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
- package/dist/controller-loaders/npm-loader.js +19 -10
- package/dist/controller-loaders/npm-loader.js.map +1 -1
- package/dist/controllers/logging/console-sink-controller.d.ts +15 -0
- package/dist/controllers/logging/console-sink-controller.d.ts.map +1 -0
- package/dist/controllers/logging/console-sink-controller.js +40 -0
- package/dist/controllers/logging/console-sink-controller.js.map +1 -0
- package/dist/controllers/logging/file-sink-controller.d.ts +11 -0
- package/dist/controllers/logging/file-sink-controller.d.ts.map +1 -0
- package/dist/controllers/logging/file-sink-controller.js +33 -0
- package/dist/controllers/logging/file-sink-controller.js.map +1 -0
- package/dist/controllers/logging/sink-identity.d.ts +37 -0
- package/dist/controllers/logging/sink-identity.d.ts.map +1 -0
- package/dist/controllers/logging/sink-identity.js +40 -0
- package/dist/controllers/logging/sink-identity.js.map +1 -0
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +25 -0
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +1 -0
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts +27 -1
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +84 -16
- package/dist/evaluation-context.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/internal-context.d.ts +4 -0
- package/dist/internal-context.d.ts.map +1 -1
- package/dist/kernel.d.ts +26 -0
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +78 -5
- package/dist/kernel.js.map +1 -1
- package/dist/logging/bootstrap-writer.d.ts +23 -0
- package/dist/logging/bootstrap-writer.d.ts.map +1 -0
- package/dist/logging/bootstrap-writer.js +31 -0
- package/dist/logging/bootstrap-writer.js.map +1 -0
- package/dist/logging/color-precedence.d.ts +27 -0
- package/dist/logging/color-precedence.d.ts.map +1 -0
- package/dist/logging/color-precedence.js +47 -0
- package/dist/logging/color-precedence.js.map +1 -0
- package/dist/logging/console-sink.d.ts +45 -0
- package/dist/logging/console-sink.d.ts.map +1 -0
- package/dist/logging/console-sink.js +56 -0
- package/dist/logging/console-sink.js.map +1 -0
- package/dist/logging/debug-wire-sink.d.ts +38 -0
- package/dist/logging/debug-wire-sink.d.ts.map +1 -0
- package/dist/logging/debug-wire-sink.js +85 -0
- package/dist/logging/debug-wire-sink.js.map +1 -0
- package/dist/logging/drop-accounting.d.ts +34 -0
- package/dist/logging/drop-accounting.d.ts.map +1 -0
- package/dist/logging/drop-accounting.js +98 -0
- package/dist/logging/drop-accounting.js.map +1 -0
- package/dist/logging/encode-json.d.ts +28 -0
- package/dist/logging/encode-json.d.ts.map +1 -0
- package/dist/logging/encode-json.js +86 -0
- package/dist/logging/encode-json.js.map +1 -0
- package/dist/logging/encode-pretty.d.ts +7 -0
- package/dist/logging/encode-pretty.d.ts.map +1 -0
- package/dist/logging/encode-pretty.js +104 -0
- package/dist/logging/encode-pretty.js.map +1 -0
- package/dist/logging/file-sink.d.ts +33 -0
- package/dist/logging/file-sink.d.ts.map +1 -0
- package/dist/logging/file-sink.js +72 -0
- package/dist/logging/file-sink.js.map +1 -0
- package/dist/logging/index.d.ts +39 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +27 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/kernel-logging.d.ts +110 -0
- package/dist/logging/kernel-logging.d.ts.map +1 -0
- package/dist/logging/kernel-logging.js +199 -0
- package/dist/logging/kernel-logging.js.map +1 -0
- package/dist/logging/log-sink.d.ts +9 -0
- package/dist/logging/log-sink.d.ts.map +1 -0
- package/dist/logging/log-sink.js +8 -0
- package/dist/logging/log-sink.js.map +1 -0
- package/dist/logging/logging-host.d.ts +14 -0
- package/dist/logging/logging-host.d.ts.map +1 -0
- package/dist/logging/logging-host.js +27 -0
- package/dist/logging/logging-host.js.map +1 -0
- package/dist/logging/logging-pipeline.d.ts +89 -0
- package/dist/logging/logging-pipeline.d.ts.map +1 -0
- package/dist/logging/logging-pipeline.js +349 -0
- package/dist/logging/logging-pipeline.js.map +1 -0
- package/dist/logging/normalize-attributes.d.ts +48 -0
- package/dist/logging/normalize-attributes.d.ts.map +1 -0
- package/dist/logging/normalize-attributes.js +140 -0
- package/dist/logging/normalize-attributes.js.map +1 -0
- package/dist/logging/record-buffer.d.ts +24 -0
- package/dist/logging/record-buffer.d.ts.map +1 -0
- package/dist/logging/record-buffer.js +78 -0
- package/dist/logging/record-buffer.js.map +1 -0
- package/dist/logging/redact-attributes.d.ts +45 -0
- package/dist/logging/redact-attributes.d.ts.map +1 -0
- package/dist/logging/redact-attributes.js +152 -0
- package/dist/logging/redact-attributes.js.map +1 -0
- package/dist/logging/sampler.d.ts +34 -0
- package/dist/logging/sampler.d.ts.map +1 -0
- package/dist/logging/sampler.js +52 -0
- package/dist/logging/sampler.js.map +1 -0
- package/dist/logging/scope-config.d.ts +32 -0
- package/dist/logging/scope-config.d.ts.map +1 -0
- package/dist/logging/scope-config.js +2 -0
- package/dist/logging/scope-config.js.map +1 -0
- package/dist/logging/span-id.d.ts +24 -0
- package/dist/logging/span-id.d.ts.map +1 -0
- package/dist/logging/span-id.js +85 -0
- package/dist/logging/span-id.js.map +1 -0
- package/dist/logging/to-error-value.d.ts +3 -0
- package/dist/logging/to-error-value.d.ts.map +1 -0
- package/dist/logging/to-error-value.js +79 -0
- package/dist/logging/to-error-value.js.map +1 -0
- package/dist/logging/trace-parent.d.ts +54 -0
- package/dist/logging/trace-parent.d.ts.map +1 -0
- package/dist/logging/trace-parent.js +92 -0
- package/dist/logging/trace-parent.js.map +1 -0
- package/dist/manifest-schemas.d.ts +1 -1
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +12 -0
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/module-context.d.ts +16 -0
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +6 -0
- package/dist/module-context.js.map +1 -1
- package/dist/resource-context.d.ts +15 -1
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +45 -0
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-compiled-values.d.ts.map +1 -1
- package/dist/schema-compiled-values.js +7 -0
- package/dist/schema-compiled-values.js.map +1 -1
- package/dist/schema-validator.d.ts +8 -1
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +13 -3
- package/dist/schema-validator.js.map +1 -1
- package/dist/transports/oci/oci-transport.d.ts +2 -0
- package/dist/transports/oci/oci-transport.d.ts.map +1 -1
- package/dist/transports/oci/oci-transport.js +15 -1
- package/dist/transports/oci/oci-transport.js.map +1 -1
- package/dist/transports/registry-transport.d.ts +2 -0
- package/dist/transports/registry-transport.d.ts.map +1 -1
- package/dist/transports/registry-transport.js +13 -0
- package/dist/transports/registry-transport.js.map +1 -1
- package/dist/transports/transport.d.ts +15 -0
- package/dist/transports/transport.d.ts.map +1 -1
- package/package.json +17 -3
- package/src/controller-loader.ts +7 -1
- package/src/controller-loaders/bundle-builder.ts +18 -10
- package/src/controller-loaders/npm-loader.ts +32 -11
- package/src/controllers/logging/console-sink-controller.ts +46 -0
- package/src/controllers/logging/file-sink-controller.ts +40 -0
- package/src/controllers/logging/sink-identity.ts +53 -0
- package/src/controllers/module/import-controller.ts +27 -0
- package/src/controllers/resource-definition/resource-definition-controller.ts +1 -0
- package/src/evaluation-context.ts +93 -15
- package/src/index.ts +54 -0
- package/src/internal-context.ts +4 -0
- package/src/kernel.ts +98 -8
- package/src/logging/bootstrap-writer.ts +38 -0
- package/src/logging/color-precedence.ts +62 -0
- package/src/logging/console-sink.ts +86 -0
- package/src/logging/debug-wire-sink.ts +85 -0
- package/src/logging/drop-accounting.ts +108 -0
- package/src/logging/encode-json.ts +98 -0
- package/src/logging/encode-pretty.ts +117 -0
- package/src/logging/file-sink.ts +87 -0
- package/src/logging/index.ts +65 -0
- package/src/logging/kernel-logging.ts +266 -0
- package/src/logging/log-sink.ts +18 -0
- package/src/logging/logging-host.ts +33 -0
- package/src/logging/logging-pipeline.ts +465 -0
- package/src/logging/normalize-attributes.ts +228 -0
- package/src/logging/record-buffer.ts +72 -0
- package/src/logging/redact-attributes.ts +168 -0
- package/src/logging/sampler.ts +76 -0
- package/src/logging/scope-config.ts +32 -0
- package/src/logging/span-id.ts +84 -0
- package/src/logging/to-error-value.ts +89 -0
- package/src/logging/trace-parent.ts +110 -0
- package/src/manifest-schemas.ts +12 -0
- package/src/module-context.ts +23 -0
- package/src/resource-context.ts +48 -0
- package/src/schema-compiled-values.ts +6 -0
- package/src/schema-validator.ts +20 -5
- package/src/transports/oci/oci-transport.ts +16 -0
- package/src/transports/registry-transport.ts +14 -0
- package/src/transports/transport.ts +17 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NOOP_LOGGER, type Logger } from "@telorun/sdk";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -140,6 +141,7 @@ async function loadEsbuild(): Promise<typeof import("esbuild") | null> {
|
|
|
140
141
|
export async function tryBuildControllerBundle(
|
|
141
142
|
installRoot: string,
|
|
142
143
|
entryFile: string,
|
|
144
|
+
log: Logger = NOOP_LOGGER,
|
|
143
145
|
): Promise<string | null> {
|
|
144
146
|
if (!bundlingEnabled()) return null;
|
|
145
147
|
|
|
@@ -174,7 +176,7 @@ export async function tryBuildControllerBundle(
|
|
|
174
176
|
// would otherwise each launch an esbuild over the same output.
|
|
175
177
|
const inFlight = buildsInFlight.get(bundleFile);
|
|
176
178
|
if (inFlight) return inFlight;
|
|
177
|
-
const work = buildBundle(bundleFile, entryFile).finally(() =>
|
|
179
|
+
const work = buildBundle(bundleFile, entryFile, log).finally(() =>
|
|
178
180
|
buildsInFlight.delete(bundleFile),
|
|
179
181
|
);
|
|
180
182
|
buildsInFlight.set(bundleFile, work);
|
|
@@ -216,7 +218,11 @@ function okMarker(bundleFile: string): string {
|
|
|
216
218
|
return `${bundleFile}.ok`;
|
|
217
219
|
}
|
|
218
220
|
|
|
219
|
-
async function buildBundle(
|
|
221
|
+
async function buildBundle(
|
|
222
|
+
bundleFile: string,
|
|
223
|
+
entryFile: string,
|
|
224
|
+
log: Logger,
|
|
225
|
+
): Promise<string | null> {
|
|
220
226
|
if (await usableCachedBundle(bundleFile)) return bundleFile;
|
|
221
227
|
|
|
222
228
|
const esbuild = await loadEsbuild();
|
|
@@ -245,9 +251,10 @@ async function buildBundle(bundleFile: string, entryFile: string): Promise<strin
|
|
|
245
251
|
// location. esbuild rewrites `__dirname`/`__filename` to `import.meta.url`,
|
|
246
252
|
// so its presence flags this — fall back to the loose import for those.
|
|
247
253
|
if (DIR_RELATIVE.test(out.text)) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
254
|
+
log.trace("controller bundling skipped", {
|
|
255
|
+
"telo.bundle.entry": entryFile,
|
|
256
|
+
"telo.bundle.reason": "directory-relative asset resolution",
|
|
257
|
+
});
|
|
251
258
|
return null;
|
|
252
259
|
}
|
|
253
260
|
await fs.writeFile(tmpFile, REQUIRE_BANNER + out.text);
|
|
@@ -257,11 +264,12 @@ async function buildBundle(bundleFile: string, entryFile: string): Promise<strin
|
|
|
257
264
|
await fs.writeFile(okMarker(bundleFile), "").catch(() => {});
|
|
258
265
|
return bundleFile;
|
|
259
266
|
} catch (err) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
267
|
+
// Previously gated behind an ad-hoc TELO_BUNDLE_DEBUG env var — a second,
|
|
268
|
+
// undeclared configuration channel of exactly the kind D6 forbids. It is now
|
|
269
|
+
// an ordinary trace-level record, so raising `logging.level` reveals it.
|
|
270
|
+
log.trace("controller bundling skipped", {
|
|
271
|
+
"telo.bundle.entry": entryFile,
|
|
272
|
+
}, { error: err });
|
|
265
273
|
await fs.rm(tmpFile, { force: true }).catch(() => {});
|
|
266
274
|
return null; // unbundleable controller → loose import
|
|
267
275
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ControllerInstance } from "@telorun/sdk";
|
|
1
|
+
import { ControllerInstance, NOOP_LOGGER, type Logger } from "@telorun/sdk";
|
|
2
2
|
import { execFile } from "child_process";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
4
|
import * as fs from "fs/promises";
|
|
@@ -153,6 +153,16 @@ export interface NpmControllerLoaderOptions {
|
|
|
153
153
|
* cross-process safety is provided by a filesystem lock on `<root>/.lock`.
|
|
154
154
|
*/
|
|
155
155
|
export class NpmControllerLoader {
|
|
156
|
+
/** Where install-lock diagnostics go. Injected rather than written to
|
|
157
|
+
* `process.stderr`: this loader runs outside the kernel's stdio scope, and
|
|
158
|
+
* §13.1 routes every kernel diagnostic through the logger. */
|
|
159
|
+
private log: Logger = NOOP_LOGGER;
|
|
160
|
+
|
|
161
|
+
/** Route this loader's diagnostics through the kernel's logger. */
|
|
162
|
+
setLogger(log: Logger): void {
|
|
163
|
+
this.log = log;
|
|
164
|
+
}
|
|
165
|
+
|
|
156
166
|
private readonly entryUrl?: string;
|
|
157
167
|
/** Threaded install root (`<cache-root>/npm`); overrides `computeInstallRoot`. */
|
|
158
168
|
private readonly installRootOverride?: string;
|
|
@@ -233,7 +243,7 @@ export class NpmControllerLoader {
|
|
|
233
243
|
const subpath = parsed.subpath ?? null;
|
|
234
244
|
return {
|
|
235
245
|
source,
|
|
236
|
-
importInstance: () => loadFromInstall(installRoot, alias, subpath, purl),
|
|
246
|
+
importInstance: () => loadFromInstall(installRoot, alias, subpath, purl, this.log),
|
|
237
247
|
};
|
|
238
248
|
}
|
|
239
249
|
|
|
@@ -332,7 +342,7 @@ export class NpmControllerLoader {
|
|
|
332
342
|
...PEER_INSTALL_FLAGS,
|
|
333
343
|
]);
|
|
334
344
|
await fs.writeFile(stateFile, JSON.stringify({ rootHash: newHash }, null, 2) + "\n");
|
|
335
|
-
});
|
|
345
|
+
}, this.log);
|
|
336
346
|
|
|
337
347
|
await this.seedDepCaches(installRoot, dependencies);
|
|
338
348
|
return installRoot;
|
|
@@ -480,7 +490,7 @@ export class NpmControllerLoader {
|
|
|
480
490
|
const written = await readDepSpec(installRoot, alias);
|
|
481
491
|
if (written !== undefined) this.rootDeps[alias] = written;
|
|
482
492
|
else this.rootDeps[alias] = spec;
|
|
483
|
-
});
|
|
493
|
+
}, this.log);
|
|
484
494
|
})();
|
|
485
495
|
|
|
486
496
|
this.inFlight.set(cacheKey, work);
|
|
@@ -589,8 +599,12 @@ function withLocalInstallQueue<T>(installRoot: string, fn: () => Promise<T>): Pr
|
|
|
589
599
|
* Acquire the install lock for `installRoot` and run `fn` under it: first the
|
|
590
600
|
* in-process queue above, then the cross-process filesystem lock.
|
|
591
601
|
*/
|
|
592
|
-
async function withInstallLock<T>(
|
|
593
|
-
|
|
602
|
+
async function withInstallLock<T>(
|
|
603
|
+
installRoot: string,
|
|
604
|
+
fn: () => Promise<T>,
|
|
605
|
+
log: Logger = NOOP_LOGGER,
|
|
606
|
+
): Promise<T> {
|
|
607
|
+
return withLocalInstallQueue(installRoot, () => withFileInstallLock(installRoot, fn, log));
|
|
594
608
|
}
|
|
595
609
|
|
|
596
610
|
/**
|
|
@@ -611,7 +625,11 @@ async function withInstallLock<T>(installRoot: string, fn: () => Promise<T>): Pr
|
|
|
611
625
|
* invocation, and any state-file writes. It does NOT serialize *reads* of
|
|
612
626
|
* already-installed controllers — those run lock-free against a stable tree.
|
|
613
627
|
*/
|
|
614
|
-
async function withFileInstallLock<T>(
|
|
628
|
+
async function withFileInstallLock<T>(
|
|
629
|
+
installRoot: string,
|
|
630
|
+
fn: () => Promise<T>,
|
|
631
|
+
log: Logger = NOOP_LOGGER,
|
|
632
|
+
): Promise<T> {
|
|
615
633
|
const lockPath = path.join(installRoot, ".lock");
|
|
616
634
|
|
|
617
635
|
await fs.mkdir(installRoot, { recursive: true });
|
|
@@ -639,7 +657,7 @@ async function withFileInstallLock<T>(installRoot: string, fn: () => Promise<T>)
|
|
|
639
657
|
}
|
|
640
658
|
if (!noticed && waited > LOCK_WAIT_NOTICE_MS) {
|
|
641
659
|
noticed = true;
|
|
642
|
-
|
|
660
|
+
log.info("waiting for controller install lock", { "telo.install.lock_path": lockPath });
|
|
643
661
|
}
|
|
644
662
|
await sleep(LOCK_RETRY_MS);
|
|
645
663
|
}
|
|
@@ -667,8 +685,10 @@ async function withFileInstallLock<T>(installRoot: string, fn: () => Promise<T>)
|
|
|
667
685
|
await fs.rm(lockPath, { force: true });
|
|
668
686
|
} catch (err: any) {
|
|
669
687
|
if (err?.code !== "ENOENT") {
|
|
670
|
-
|
|
671
|
-
|
|
688
|
+
log.warn(
|
|
689
|
+
"failed to release install lock",
|
|
690
|
+
{ "telo.install.lock_path": lockPath },
|
|
691
|
+
{ error: err },
|
|
672
692
|
);
|
|
673
693
|
}
|
|
674
694
|
}
|
|
@@ -950,6 +970,7 @@ async function loadFromInstall(
|
|
|
950
970
|
alias: string,
|
|
951
971
|
subpath: string | null,
|
|
952
972
|
purl: string,
|
|
973
|
+
log: Logger = NOOP_LOGGER,
|
|
953
974
|
): Promise<ControllerInstance> {
|
|
954
975
|
// The package lives under its version-scoped alias folder (see installAlias),
|
|
955
976
|
// not its bare scoped name.
|
|
@@ -959,7 +980,7 @@ async function loadFromInstall(
|
|
|
959
980
|
// Transparent bundling: import a single esbuild bundle of the entry (one file
|
|
960
981
|
// vs a cold loose `node_modules` tree) when available; otherwise the loose
|
|
961
982
|
// entry. Pure accelerator — `null` on any miss, so behavior is unchanged.
|
|
962
|
-
const target = (await tryBuildControllerBundle(installRoot, entryFile)) ?? entryFile;
|
|
983
|
+
const target = (await tryBuildControllerBundle(installRoot, entryFile, log)) ?? entryFile;
|
|
963
984
|
// ESM dynamic `import()` accepts either a relative specifier or a `file://`
|
|
964
985
|
// URL — but NOT a bare absolute filesystem path. On POSIX the `/abs/path`
|
|
965
986
|
// form works by happy accident; on Windows `C:\path\to\file.js` is rejected
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { TEARDOWN_LAST, type ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
import type { BuiltinControllerContext } from "../../internal-context.js";
|
|
3
|
+
import { ConsoleSink } from "../../logging/console-sink.js";
|
|
4
|
+
import { sinkIdFor } from "./sink-identity.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Controller for the `Telo.ConsoleSink` kernel built-in (§10.2).
|
|
8
|
+
*
|
|
9
|
+
* Dependency-free by construction — it needs nothing but a descriptor — which is
|
|
10
|
+
* what qualifies it for eager instantiation before the init loop, so a runtime
|
|
11
|
+
* never has zero destinations.
|
|
12
|
+
*
|
|
13
|
+
* The instance exposes the sink contract directly on itself rather than behind
|
|
14
|
+
* `invoke()`: the logger writes to it through that contract, never through
|
|
15
|
+
* dispatch (§12.1).
|
|
16
|
+
*/
|
|
17
|
+
export async function create(
|
|
18
|
+
resource: any,
|
|
19
|
+
ctx: BuiltinControllerContext,
|
|
20
|
+
): Promise<ResourceInstance> {
|
|
21
|
+
const sink = new ConsoleSink({
|
|
22
|
+
sinkId: sinkIdFor(resource),
|
|
23
|
+
level: ctx.logging.levelFor(resource.level),
|
|
24
|
+
destination: resource.destination,
|
|
25
|
+
encoding: resource.encoding,
|
|
26
|
+
color: resource.color,
|
|
27
|
+
env: ctx.env,
|
|
28
|
+
stdout: ctx.stdout,
|
|
29
|
+
stderr: ctx.stderr,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
ctx.logging.attach(sink);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
sink,
|
|
36
|
+
teardownPriority: TEARDOWN_LAST,
|
|
37
|
+
// Sinks are ordinary resources, so the final flush is their own teardown —
|
|
38
|
+
// they are simply pinned to run after every other resource, so anything
|
|
39
|
+
// logging during its own shutdown still reaches a live destination.
|
|
40
|
+
teardown: async () => {
|
|
41
|
+
await sink.flush();
|
|
42
|
+
ctx.logging.detach(sink);
|
|
43
|
+
await sink.close();
|
|
44
|
+
},
|
|
45
|
+
} as unknown as ResourceInstance;
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { TEARDOWN_LAST, type ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
import type { BuiltinControllerContext } from "../../internal-context.js";
|
|
3
|
+
import { FileSink } from "../../logging/file-sink.js";
|
|
4
|
+
import { bufferPolicyFor, sinkIdFor } from "./sink-identity.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Controller for the `Telo.FileSink` kernel built-in (§10.2). Dependency-free —
|
|
8
|
+
* a path is all it needs — so it joins the console sink in the eager tier.
|
|
9
|
+
*
|
|
10
|
+
* `on_full: block` is rejected at construction with an actionable diagnostic
|
|
11
|
+
* naming the sink; see `sink-identity.ts` for why degrading is the wrong call.
|
|
12
|
+
*/
|
|
13
|
+
export async function create(
|
|
14
|
+
resource: any,
|
|
15
|
+
ctx: BuiltinControllerContext,
|
|
16
|
+
): Promise<ResourceInstance> {
|
|
17
|
+
const sinkId = sinkIdFor(resource);
|
|
18
|
+
const policy = bufferPolicyFor(resource);
|
|
19
|
+
|
|
20
|
+
const sink = new FileSink({
|
|
21
|
+
sinkId,
|
|
22
|
+
level: ctx.logging.levelFor(resource.level),
|
|
23
|
+
destination: resource.destination,
|
|
24
|
+
encoding: resource.encoding,
|
|
25
|
+
policy,
|
|
26
|
+
onDrop: () => ctx.logging.recordDrop(sinkId, "buffer_full"),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
ctx.logging.attach(sink);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
sink,
|
|
33
|
+
teardownPriority: TEARDOWN_LAST,
|
|
34
|
+
teardown: async () => {
|
|
35
|
+
await sink.flush();
|
|
36
|
+
ctx.logging.detach(sink);
|
|
37
|
+
await sink.close();
|
|
38
|
+
},
|
|
39
|
+
} as unknown as ResourceInstance;
|
|
40
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { parseDurationMs } from "@telorun/sdk";
|
|
2
|
+
import { blockUnsupportedMessage, DEFAULT_BUFFER_POLICY, type SinkBufferPolicy } from "../../logging/log-sink.js";
|
|
3
|
+
import { RuntimeError } from "@telorun/sdk";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared sink-manifest reading — `kernel/specs/logging.md` §10.3, §12.1.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Sink identity for drop accounting (§10.4): the resource name for a `!ref`,
|
|
10
|
+
* or the synthetic name inline extraction assigned, which already encodes kind
|
|
11
|
+
* plus position. */
|
|
12
|
+
export function sinkIdFor(resource: { metadata?: { name?: string }; kind?: string }): string {
|
|
13
|
+
return resource.metadata?.name ?? resource.kind ?? "<sink>";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read the buffering policy an async sink inherits from `Telo.LogSink`.
|
|
18
|
+
*
|
|
19
|
+
* `on_full: block` is rejected here rather than silently degraded. Blocking
|
|
20
|
+
* means suspending the producer until the buffer drains, which requires the
|
|
21
|
+
* drain to progress *while* the producer is suspended — true on a runtime with
|
|
22
|
+
* real threads, false on a single-threaded event loop, where suspending the
|
|
23
|
+
* producer suspends the consumer too. There, `block` is a deadlock.
|
|
24
|
+
*
|
|
25
|
+
* Degrading to `drop_new` would be worse than erroring: someone who writes
|
|
26
|
+
* `block` on an audit sink is saying "I would rather go slow than lose a
|
|
27
|
+
* record", and silently handing back the opposite guarantee means they discover
|
|
28
|
+
* it from a gap in an audit trail rather than from a diagnostic.
|
|
29
|
+
*/
|
|
30
|
+
export function bufferPolicyFor(resource: {
|
|
31
|
+
metadata?: { name?: string };
|
|
32
|
+
kind?: string;
|
|
33
|
+
buffer?: number;
|
|
34
|
+
on_full?: string;
|
|
35
|
+
flush_interval?: string;
|
|
36
|
+
}): SinkBufferPolicy {
|
|
37
|
+
const onFull = resource.on_full ?? DEFAULT_BUFFER_POLICY.onFull;
|
|
38
|
+
|
|
39
|
+
if (onFull === "block") {
|
|
40
|
+
throw new RuntimeError(
|
|
41
|
+
"ERR_LOG_SINK_ON_FULL_UNSUPPORTED",
|
|
42
|
+
blockUnsupportedMessage(sinkIdFor(resource)),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
buffer: resource.buffer ?? DEFAULT_BUFFER_POLICY.buffer,
|
|
48
|
+
onFull: onFull as SinkBufferPolicy["onFull"],
|
|
49
|
+
flushIntervalMs: resource.flush_interval
|
|
50
|
+
? parseDurationMs(resource.flush_interval, DEFAULT_BUFFER_POLICY.flushIntervalMs)
|
|
51
|
+
: DEFAULT_BUFFER_POLICY.flushIntervalMs,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -2,6 +2,7 @@ import { AnalysisRegistry, DiagnosticSeverity, foldIntegrity, parseExportEntry,
|
|
|
2
2
|
import type { ResourceInstance } from "@telorun/sdk";
|
|
3
3
|
import { RuntimeError } from "@telorun/sdk";
|
|
4
4
|
import type { BuiltinControllerContext } from "../../internal-context.js";
|
|
5
|
+
import { buildScopeConfig, type LoggingManifestBlock } from "../../logging/kernel-logging.js";
|
|
5
6
|
import { ModuleContext } from "../../module-context.js";
|
|
6
7
|
import { isDefaultPolicy, normalizeRuntime } from "../../runtime-registry.js";
|
|
7
8
|
|
|
@@ -171,6 +172,32 @@ export async function create(
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
// Resolve this import's effective logging configuration once, here, and stamp
|
|
176
|
+
// it as a plain value on the child context — the same shape as the controller
|
|
177
|
+
// policy above.
|
|
178
|
+
//
|
|
179
|
+
// The threshold attaches to the *import* rather than to a map keyed by module
|
|
180
|
+
// name because module names collide (`std/sql` and `acme/sql` share
|
|
181
|
+
// `metadata.name: sql`; the same module imported twice is two subsystems with
|
|
182
|
+
// one name), while an alias is already uniqueness-enforced as a hard
|
|
183
|
+
// DUPLICATE_IMPORT_ALIAS diagnostic. Resolving here means a leaf reads its
|
|
184
|
+
// threshold as an O(1) lookup with no walk up the import chain at emit time —
|
|
185
|
+
// and it is the same scalar a guest runtime caches across an FFI boundary, so
|
|
186
|
+
// scoping and the FFI threshold cache are one mechanism. See §9 and §12.2.
|
|
187
|
+
const parentScope =
|
|
188
|
+
(ctx.moduleContext as unknown as ModuleContext).getLoggingConfig?.() ??
|
|
189
|
+
ctx.kernelLoggingRootScope();
|
|
190
|
+
const scopePath = parentScope.scope ? `${parentScope.scope}.${alias}` : alias;
|
|
191
|
+
const importLogging = resource.logging
|
|
192
|
+
? (ctx.expandValue(resource.logging, {}) as LoggingManifestBlock)
|
|
193
|
+
: undefined;
|
|
194
|
+
(child as ModuleContext).setLoggingConfig({
|
|
195
|
+
...buildScopeConfig(importLogging, parentScope),
|
|
196
|
+
scope: scopePath,
|
|
197
|
+
module: targetModule,
|
|
198
|
+
secretValues: child.secretValues,
|
|
199
|
+
});
|
|
200
|
+
|
|
174
201
|
for (const manifest of manifests) {
|
|
175
202
|
child.registerManifest(manifest);
|
|
176
203
|
}
|
|
@@ -142,6 +142,7 @@ class ResourceDefinition implements ResourceInstance {
|
|
|
142
142
|
const loader = new ControllerLoader({
|
|
143
143
|
entryUrl: ctx.getEntryUrl(),
|
|
144
144
|
installRoot: ctx.getInstallRoot(),
|
|
145
|
+
log: ctx.log,
|
|
145
146
|
});
|
|
146
147
|
// Eager resolve — verify the controller is hostable now (so a broken
|
|
147
148
|
// `controllers:` candidate fails fast at boot), but defer the expensive
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { formatSpanCounter } from "./logging/span-id.js";
|
|
2
3
|
import {
|
|
3
4
|
getRefIdentity,
|
|
4
5
|
isCompiledValue,
|
|
@@ -164,6 +165,19 @@ export function buildResolvedProperties(
|
|
|
164
165
|
*/
|
|
165
166
|
const cancellationStore = new AsyncLocalStorage<InvokeContext>();
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The ambient dispatch context, for §7.2's automatic trace correlation: a record
|
|
170
|
+
* emitted inside an active span carries that span's ids without the controller
|
|
171
|
+
* passing anything.
|
|
172
|
+
*
|
|
173
|
+
* `AsyncLocalStorage.run()` is used throughout rather than `enterWith()`, which
|
|
174
|
+
* transitions for the remainder of the entire synchronous execution and leaks
|
|
175
|
+
* context into subsequent event handlers on the same tick (§9.1).
|
|
176
|
+
*/
|
|
177
|
+
export function ambientInvokeContext(): InvokeContext | undefined {
|
|
178
|
+
return cancellationStore.getStore();
|
|
179
|
+
}
|
|
180
|
+
|
|
167
181
|
type Walker = (ctx: Record<string, unknown>) => unknown;
|
|
168
182
|
|
|
169
183
|
/** Compile a manifest subtree into a tightly-bound walker closure. The returned
|
|
@@ -687,25 +701,83 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
687
701
|
* 2. Tear down own resource instances in reverse registration order,
|
|
688
702
|
* emitting a Teardown event for each via the injected emit callback.
|
|
689
703
|
*/
|
|
704
|
+
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
690
705
|
async teardownResources(): Promise<void> {
|
|
691
706
|
this.state = "Draining";
|
|
707
|
+
const failures: Array<{ resource: string; error: unknown }> = [];
|
|
708
|
+
|
|
692
709
|
for (const child of [...this.children].reverse()) {
|
|
693
|
-
|
|
710
|
+
try {
|
|
711
|
+
await child.teardownResources();
|
|
712
|
+
} catch (err) {
|
|
713
|
+
failures.push({ resource: "(child context)", error: err });
|
|
714
|
+
}
|
|
694
715
|
}
|
|
695
|
-
|
|
696
|
-
for (const [key, { resource, instance }] of
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
716
|
+
|
|
717
|
+
for (const [key, { resource, instance }] of this.teardownOrder()) {
|
|
718
|
+
const label = `${resource.kind}.${resource.metadata.name}`;
|
|
719
|
+
try {
|
|
720
|
+
if (instance.teardown) await instance.teardown();
|
|
721
|
+
} catch (err) {
|
|
722
|
+
// Aggregate rather than abort. A single throwing resource used to
|
|
723
|
+
// abandon every resource after it in the cascade — including the log
|
|
724
|
+
// sinks, which are pinned last precisely so they outlive everything that
|
|
725
|
+
// might log while shutting down. Losing the shutdown flush to an
|
|
726
|
+
// unrelated failure is exactly the silent loss §10.5 forbids.
|
|
727
|
+
failures.push({ resource: label, error: err });
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
await this.emit(`${label}.Teardown`, {
|
|
731
|
+
resource: {
|
|
732
|
+
kind: resource.kind,
|
|
733
|
+
name: resource.metadata.name,
|
|
734
|
+
id: this.resourceId(resource.kind, resource.metadata.name),
|
|
735
|
+
},
|
|
736
|
+
...(this.owner ? { owner: this.owner } : {}),
|
|
737
|
+
});
|
|
738
|
+
} catch (err) {
|
|
739
|
+
failures.push({ resource: `${label} (Teardown event)`, error: err });
|
|
740
|
+
}
|
|
706
741
|
this.resourceInstances.delete(key);
|
|
707
742
|
}
|
|
743
|
+
|
|
708
744
|
this.state = "Teardown";
|
|
745
|
+
|
|
746
|
+
if (failures.length > 0) {
|
|
747
|
+
throw new RuntimeError(
|
|
748
|
+
"ERR_TEARDOWN_FAILED",
|
|
749
|
+
`${failures.length} resource(s) failed during teardown`,
|
|
750
|
+
failures.map(({ resource, error }) => ({
|
|
751
|
+
severity: "error" as const,
|
|
752
|
+
message: error instanceof Error ? error.message : String(error),
|
|
753
|
+
resource,
|
|
754
|
+
})),
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Resource instances in teardown order: ascending `teardownPriority`, with the
|
|
761
|
+
* base reverse-insertion order preserved within each priority tier.
|
|
762
|
+
*
|
|
763
|
+
* The base order is reverse *insertion*, which is reverse init order in the
|
|
764
|
+
* happy path — but the init loop is a multi-pass retry, so a resource that
|
|
765
|
+
* failed its first pass lands later in the map than its topological rank
|
|
766
|
+
* implies. That makes the dependency graph an unreliable way to say "last".
|
|
767
|
+
* A resource that must reliably outlive the rest declares it directly via
|
|
768
|
+
* `teardownPriority` (log sinks set `TEARDOWN_LAST`), so the generic teardown
|
|
769
|
+
* path orders by a declared number rather than sniffing any one subsystem's
|
|
770
|
+
* instance shape.
|
|
771
|
+
*/
|
|
772
|
+
private teardownOrder(): Array<[string, { resource: any; instance: any }]> {
|
|
773
|
+
const entries = [...this.resourceInstances.entries()].reverse();
|
|
774
|
+
// Stable sort by priority (default 0); Array.prototype.sort is stable, so
|
|
775
|
+
// the reverse-insertion order survives within each tier.
|
|
776
|
+
return entries.sort(
|
|
777
|
+
([, a], [, b]) =>
|
|
778
|
+
((a.instance as { teardownPriority?: number })?.teardownPriority ?? 0) -
|
|
779
|
+
((b.instance as { teardownPriority?: number })?.teardownPriority ?? 0),
|
|
780
|
+
);
|
|
709
781
|
}
|
|
710
782
|
|
|
711
783
|
transientChild(context: Record<string, any>): EvaluationContext {
|
|
@@ -785,7 +857,9 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
785
857
|
* event *name* stays human-meaningful (`<name>.Invoked`) for bus subscribers;
|
|
786
858
|
* everything a debug consumer needs to rebuild the call tree rides here, so the
|
|
787
859
|
* consumer never parses the dotted name. `spanId`/`parentSpanId` are the
|
|
788
|
-
* tracer's `invocationId`/`parentInvocationId
|
|
860
|
+
* tracer's `invocationId`/`parentInvocationId`, rendered as 16 lowercase hex
|
|
861
|
+
* characters at this boundary (present only while tracing) so a span joins its
|
|
862
|
+
* log records by string equality — see kernel/specs/logging.md §7.1;
|
|
789
863
|
* `ref` carries the kind+name the name no longer encodes; `detail` is the
|
|
790
864
|
* per-capability data (inputs/outputs, error fields, cancellation reason).
|
|
791
865
|
*/
|
|
@@ -813,8 +887,12 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
813
887
|
): Record<string, unknown> {
|
|
814
888
|
return {
|
|
815
889
|
traceId,
|
|
816
|
-
|
|
817
|
-
|
|
890
|
+
// The counter stays a cheap integer internally; hex is a fixed-width
|
|
891
|
+
// render performed only here, at the encoding boundary, and only for
|
|
892
|
+
// spans actually being emitted (§7.1 forbids formatting eagerly at span
|
|
893
|
+
// creation). The salt keeps ids unique across processes in one trace.
|
|
894
|
+
spanId: spanId === undefined ? undefined : formatSpanCounter(spanId),
|
|
895
|
+
parentSpanId: parentSpanId === undefined ? undefined : formatSpanCounter(parentSpanId),
|
|
818
896
|
capability,
|
|
819
897
|
phase,
|
|
820
898
|
...(outcome !== undefined ? { outcome } : {}),
|
package/src/index.ts
CHANGED
|
@@ -40,3 +40,57 @@ export { ManifestRegistry as Registry } from "./registry.js";
|
|
|
40
40
|
export { ResourceURI } from "./resource-uri.js";
|
|
41
41
|
export type { RuntimeDiagnostic } from "@telorun/sdk";
|
|
42
42
|
|
|
43
|
+
// Structured logging — the runtime half of kernel/specs/logging.md. The record
|
|
44
|
+
// model, severity scale, and `Logger` surface live in `@telorun/sdk`; these are
|
|
45
|
+
// the pipeline, the encodings, the sinks, and the policies that gate emission.
|
|
46
|
+
export {
|
|
47
|
+
BOOTSTRAP_SINK_ID,
|
|
48
|
+
ConsoleSink,
|
|
49
|
+
DEBUG_WIRE_SINK_ID,
|
|
50
|
+
DebugWireSink,
|
|
51
|
+
DEFAULT_ATTRIBUTE_LIMITS,
|
|
52
|
+
DEFAULT_BUFFER_POLICY,
|
|
53
|
+
DEFAULT_CENSOR,
|
|
54
|
+
DropRegistry,
|
|
55
|
+
FileSink,
|
|
56
|
+
LoggingPipeline,
|
|
57
|
+
PIPELINE_SINK_ID,
|
|
58
|
+
RecordBuffer,
|
|
59
|
+
Sampler,
|
|
60
|
+
base64Bytes,
|
|
61
|
+
blockUnsupportedMessage,
|
|
62
|
+
compileRedactionPolicy,
|
|
63
|
+
createBootstrapWriter,
|
|
64
|
+
decideColor,
|
|
65
|
+
encodeJson,
|
|
66
|
+
encodeJsonLine,
|
|
67
|
+
encodePretty,
|
|
68
|
+
encodePrettyLine,
|
|
69
|
+
formatSpanCounter,
|
|
70
|
+
formatSpanId,
|
|
71
|
+
formatTraceParent,
|
|
72
|
+
normalizeAttributes,
|
|
73
|
+
normalizeSpanId,
|
|
74
|
+
normalizeTraceId,
|
|
75
|
+
parseTraceParent,
|
|
76
|
+
redactAttributes,
|
|
77
|
+
redactError,
|
|
78
|
+
saltSpanId,
|
|
79
|
+
toErrorValue,
|
|
80
|
+
toJsonProfile,
|
|
81
|
+
} from "./logging/index.js";
|
|
82
|
+
export type {
|
|
83
|
+
AttributeLimits,
|
|
84
|
+
ColorSetting,
|
|
85
|
+
DropCause,
|
|
86
|
+
DropReport,
|
|
87
|
+
LogSinkInstance,
|
|
88
|
+
OnFull,
|
|
89
|
+
RedactionPolicy,
|
|
90
|
+
SamplingConfig,
|
|
91
|
+
ScopeConfig,
|
|
92
|
+
SinkBufferPolicy,
|
|
93
|
+
TraceContext,
|
|
94
|
+
} from "./logging/index.js";
|
|
95
|
+
export { KernelLogging } from "./logging/kernel-logging.js";
|
|
96
|
+
export type { LoggingHost } from "./logging/logging-host.js";
|
package/src/internal-context.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResourceContext } from "@telorun/sdk";
|
|
2
|
+
import type { ScopeConfig } from "./logging/scope-config.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Context interface used by built-in kernel controllers (Telo.Application /
|
|
@@ -22,4 +23,7 @@ export interface BuiltinControllerContext extends ResourceContext {
|
|
|
22
23
|
* source-chain `resolveRelative`. Identical to what `loadGraph` used
|
|
23
24
|
* internally — so the produced URL agrees with the loader's caches. */
|
|
24
25
|
resolveImportUrl(fromSource: string, importSource: string): string;
|
|
26
|
+
/** The root Application's resolved logging configuration — the base an
|
|
27
|
+
* import's own `logging:` block cascades over (§12.2). */
|
|
28
|
+
kernelLoggingRootScope(): ScopeConfig;
|
|
25
29
|
}
|