@telorun/sdk 0.48.0 → 0.50.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.
@@ -0,0 +1,71 @@
1
+ /** Raised when a request never reached the peer — DNS, connect, reset, or TLS
2
+ * trust. Distinct from a non-OK HTTP response, which is a reply the caller
3
+ * interprets itself. */
4
+ export declare const ERR_NETWORK_UNREACHABLE = "ERR_NETWORK_UNREACHABLE";
5
+ /** The facts a transport failure carries. Deliberately structured rather than
6
+ * pre-rendered: a controller reports *what happened*, and whoever displays the
7
+ * error turns it into a sentence. Prose baked into a controller would have to
8
+ * be re-typed identically by every language SDK (TS, Rust, Go, …) and would
9
+ * drift; `cause: "ENOTFOUND"` is the same symbol in every language.
10
+ *
11
+ * `message` on the thrown error is a reasonable default for today's renderers;
12
+ * a kernel-side renderer can format from these fields instead. */
13
+ export interface NetworkErrorData {
14
+ /** What was being attempted, e.g. `"Embedding model request"`. */
15
+ operation: string;
16
+ url: string;
17
+ host: string;
18
+ port?: number;
19
+ /** The OS/undici code — `ENOTFOUND`, `ECONNREFUSED`, `CERT_HAS_EXPIRED`, … */
20
+ cause: string;
21
+ /** The underlying error's own message, kept verbatim. Carries detail no code
22
+ * mapping has (`SSL alert number 80`, a resolver's remarks), so wrapping is
23
+ * never a downgrade on an unmapped code. */
24
+ detail?: string;
25
+ /** `metadata.name` of the resource whose configuration produced `url`, so the
26
+ * error names the actual instance rather than its kind. */
27
+ resource?: string;
28
+ /** The setting to change — a manifest field (`baseUrl`) or a CLI/env name
29
+ * (`--registry`). Structured rather than a pre-written sentence: this is the
30
+ * one genuinely actionable part, and prose here would be the thing every
31
+ * other language SDK has to retype and keep in sync. */
32
+ setting?: string;
33
+ }
34
+ /**
35
+ * Walk the cause chain for the first `code` — undici wraps the real error one or
36
+ * more levels down, which is exactly the detail lost when only `message` is
37
+ * reported.
38
+ *
39
+ * Exported because classifying a network failure by substring-matching the
40
+ * message does not work: `fetch` rejects with the literal text `"fetch failed"`
41
+ * for DNS, refusal, and TLS alike, so a `message.includes("enotfound")` test
42
+ * silently never matches and every failure collapses into whichever branch is
43
+ * last. Callers with their own error contract should classify on this code
44
+ * rather than on prose.
45
+ */
46
+ export declare function networkCauseCode(err: unknown): string | undefined;
47
+ /**
48
+ * `fetch` that turns a transport-level failure into an {@link InvokeError}
49
+ * carrying {@link NetworkErrorData}, instead of undici's opaque
50
+ * `TypeError: fetch failed` whose real cause sits unread on `error.cause`.
51
+ *
52
+ * Only *transport* failures are wrapped. A non-OK response is returned
53
+ * untouched, because a status code is a reply the caller interprets (and often
54
+ * renders from the provider's own error body) — so this drops into an existing
55
+ * call site without changing status handling. Cancellation is re-thrown as-is:
56
+ * an aborted request is the caller's intent, not a network fault.
57
+ *
58
+ * @param context.operation What is being attempted, for the message.
59
+ * @param context.resource `metadata.name` of the resource whose configuration
60
+ * produced the URL, so the error names the instance, not just its kind.
61
+ * @param context.setting The manifest field or CLI/env name to change. Passed
62
+ * as a bare identifier, never a sentence — the wording is composed here, in
63
+ * one place, so another language's SDK supplies the same two facts rather
64
+ * than retyping the same English.
65
+ */
66
+ export declare function fetchOrThrow(input: string | URL, init: RequestInit | undefined, context: {
67
+ operation: string;
68
+ resource?: string;
69
+ setting?: string;
70
+ }): Promise<Response>;
71
+ //# sourceMappingURL=network-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-fetch.d.ts","sourceRoot":"","sources":["../src/network-fetch.ts"],"names":[],"mappings":"AAGA;;yBAEyB;AACzB,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAEjE;;;;;;;mEAOmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd;;iDAE6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;gEAC4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;6DAGyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiDD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAQjE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE,OAAO,CAAC,QAAQ,CAAC,CA4CnB"}
@@ -0,0 +1,140 @@
1
+ import { isCancellationError } from "./cancellation.js";
2
+ import { InvokeError } from "./invoke-error.js";
3
+ /** Raised when a request never reached the peer — DNS, connect, reset, or TLS
4
+ * trust. Distinct from a non-OK HTTP response, which is a reply the caller
5
+ * interprets itself. */
6
+ export const ERR_NETWORK_UNREACHABLE = "ERR_NETWORK_UNREACHABLE";
7
+ /** Human explanation per transport failure code. Interpolates only the facts
8
+ * already in `NetworkErrorData`, so a renderer in another language can produce
9
+ * the same sentence from the same fields. */
10
+ function explain(code, host, port) {
11
+ const target = port ? `${host}:${port}` : host;
12
+ switch (code) {
13
+ case "ENOTFOUND":
14
+ return `DNS lookup failed for host '${host}' — the name does not resolve`;
15
+ case "EAI_AGAIN":
16
+ return `DNS is temporarily unavailable resolving '${host}' — a transient resolver failure`;
17
+ case "ECONNREFUSED":
18
+ return `nothing is listening on ${target}`;
19
+ case "ECONNRESET":
20
+ return `the connection to ${target} was reset by the peer`;
21
+ case "EHOSTUNREACH":
22
+ return `no network route to ${host}`;
23
+ case "ETIMEDOUT":
24
+ case "UND_ERR_CONNECT_TIMEOUT":
25
+ return `the connection to ${target} timed out`;
26
+ case "CERT_HAS_EXPIRED":
27
+ return `the TLS certificate presented by ${host} has expired`;
28
+ case "DEPTH_ZERO_SELF_SIGNED_CERT":
29
+ case "SELF_SIGNED_CERT_IN_CHAIN":
30
+ return `the TLS certificate presented by ${host} is self-signed and not trusted`;
31
+ case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
32
+ return `the TLS certificate chain presented by ${host} could not be verified`;
33
+ case "EPROTO":
34
+ return `the TLS handshake with ${host} failed`;
35
+ default:
36
+ return `the request to ${target} failed at the transport layer`;
37
+ }
38
+ }
39
+ /** The deepest `message` in the cause chain — the one carrying detail a code
40
+ * mapping cannot have. Skips undici's own `"fetch failed"`, which is the
41
+ * placeholder this whole module exists to replace. */
42
+ function causeDetail(err) {
43
+ let current = err;
44
+ let detail;
45
+ for (let depth = 0; current && depth < 5; depth++) {
46
+ const message = current.message;
47
+ if (typeof message === "string" && message && message !== "fetch failed")
48
+ detail = message;
49
+ current = current.cause;
50
+ }
51
+ return detail;
52
+ }
53
+ /**
54
+ * Walk the cause chain for the first `code` — undici wraps the real error one or
55
+ * more levels down, which is exactly the detail lost when only `message` is
56
+ * reported.
57
+ *
58
+ * Exported because classifying a network failure by substring-matching the
59
+ * message does not work: `fetch` rejects with the literal text `"fetch failed"`
60
+ * for DNS, refusal, and TLS alike, so a `message.includes("enotfound")` test
61
+ * silently never matches and every failure collapses into whichever branch is
62
+ * last. Callers with their own error contract should classify on this code
63
+ * rather than on prose.
64
+ */
65
+ export function networkCauseCode(err) {
66
+ let current = err;
67
+ for (let depth = 0; current && depth < 5; depth++) {
68
+ const code = current.code;
69
+ if (typeof code === "string")
70
+ return code;
71
+ current = current.cause;
72
+ }
73
+ return undefined;
74
+ }
75
+ /**
76
+ * `fetch` that turns a transport-level failure into an {@link InvokeError}
77
+ * carrying {@link NetworkErrorData}, instead of undici's opaque
78
+ * `TypeError: fetch failed` whose real cause sits unread on `error.cause`.
79
+ *
80
+ * Only *transport* failures are wrapped. A non-OK response is returned
81
+ * untouched, because a status code is a reply the caller interprets (and often
82
+ * renders from the provider's own error body) — so this drops into an existing
83
+ * call site without changing status handling. Cancellation is re-thrown as-is:
84
+ * an aborted request is the caller's intent, not a network fault.
85
+ *
86
+ * @param context.operation What is being attempted, for the message.
87
+ * @param context.resource `metadata.name` of the resource whose configuration
88
+ * produced the URL, so the error names the instance, not just its kind.
89
+ * @param context.setting The manifest field or CLI/env name to change. Passed
90
+ * as a bare identifier, never a sentence — the wording is composed here, in
91
+ * one place, so another language's SDK supplies the same two facts rather
92
+ * than retyping the same English.
93
+ */
94
+ export async function fetchOrThrow(input, init, context) {
95
+ const url = typeof input === "string" ? input : input.toString();
96
+ try {
97
+ return await fetch(input, init);
98
+ }
99
+ catch (err) {
100
+ if (isCancellationError(err))
101
+ throw err;
102
+ if (err instanceof DOMException && err.name === "AbortError")
103
+ throw err;
104
+ const code = networkCauseCode(err) ?? "UNKNOWN";
105
+ let host = url;
106
+ let port;
107
+ try {
108
+ const parsed = new URL(url);
109
+ host = parsed.hostname;
110
+ if (parsed.port)
111
+ port = Number(parsed.port);
112
+ }
113
+ catch {
114
+ // Non-absolute URL — fall back to the raw string as the host label.
115
+ }
116
+ const detail = causeDetail(err);
117
+ const data = { operation: context.operation, url, host, cause: code };
118
+ if (port !== undefined)
119
+ data.port = port;
120
+ if (detail)
121
+ data.detail = detail;
122
+ if (context.resource)
123
+ data.resource = context.resource;
124
+ if (context.setting)
125
+ data.setting = context.setting;
126
+ // `detail` is appended only when the code has no mapping of its own —
127
+ // otherwise the explanation already says it better. Without this, wrapping
128
+ // an unmapped code would *lose* information relative to the raw error.
129
+ const explained = explain(code, host, port);
130
+ const isMapped = !explained.startsWith("the request to");
131
+ const because = isMapped || !detail ? explained : `${explained} (${detail})`;
132
+ const fix = context.setting
133
+ ? ` Check \`${context.setting}\`${context.resource ? ` on resource '${context.resource}'` : ""}.`
134
+ : "";
135
+ const message = `${context.operation} failed: cannot reach ${url} — ${code}: ${because}.${fix}`;
136
+ // The wrapped error stays reachable as `cause`: the code mapping is a
137
+ // convenience, never a reason to destroy what was actually thrown.
138
+ throw new InvokeError(ERR_NETWORK_UNREACHABLE, message, data, { cause: err });
139
+ }
140
+ }
@@ -1,5 +1,7 @@
1
1
  import type { CancellationSource, InvokeContext, OpenSpan, OpenSpanOptions } from "./cancellation.js";
2
2
  import { ControllerContext } from "./controller-context.js";
3
+ import type { Logger } from "./logger.js";
4
+ import type { LoggingHost } from "./log-sink.js";
3
5
  import { ControllerPolicy } from "./controller-policy.js";
4
6
  import { EvaluationContext } from "./evaluation-context.js";
5
7
  import { ModuleContext } from "./module-context.js";
@@ -119,6 +121,28 @@ export interface ResourceContext extends ControllerContext {
119
121
  * manifests. Use this when you need the full kind surface area visible from
120
122
  * the module. */
121
123
  loadManifests(url: string): Promise<ResourceManifest[]>;
124
+ /**
125
+ * The structured logger for this resource — `kernel/specs/logging.md` §13.2.
126
+ *
127
+ * Ambient rather than a resource (D3), because it must work before any
128
+ * resource initializes. Records are automatically stamped with this
129
+ * resource's identity, its module, its import-alias scope, and the active
130
+ * dispatch span's trace and span ids — a controller never passes those.
131
+ *
132
+ * A controller emits diagnostics **only** through this. Writing to
133
+ * stdout/stderr for diagnostic purposes is forbidden; writing to stdout as
134
+ * *data* (as the `Console` module does) is a separate, legitimate concern and
135
+ * is unaffected.
136
+ */
137
+ readonly log: Logger;
138
+ /**
139
+ * Sink attach/detach and drop accounting — the surface a `Telo.Sink`
140
+ * controller needs and nothing else. §10.2 keeps the sink set open to the
141
+ * ecosystem, so a third-party sink module reaches the pipeline through this
142
+ * rather than through a kernel-internal import. Ordinary controllers use
143
+ * {@link log}.
144
+ */
145
+ readonly logging: LoggingHost;
122
146
  readonly moduleContext: ModuleContext;
123
147
  readonly env: Record<string, string | undefined>;
124
148
  readonly stdin: NodeJS.ReadableStream;
@@ -1 +1 @@
1
- {"version":3,"file":"resource-context.d.ts","sourceRoot":"","sources":["../src/resource-context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resource-context.d.ts","sourceRoot":"","sources":["../src/resource-context.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,24 @@ export type ResourceInstance<TInput = Record<string, any>, TOutput = any> = Part
6
6
  init?(ctx?: ResourceContext): Promise<void>;
7
7
  teardown?(): void | Promise<void>;
8
8
  snapshot?(): Record<string, any> | Promise<Record<string, any>>;
9
+ /**
10
+ * Teardown ordering hint. Instances tear down in ascending priority — a
11
+ * higher number means *later*. Default `0`; within one priority the base
12
+ * order (reverse init) is preserved.
13
+ *
14
+ * This exists because the base order is reverse *insertion* order, which the
15
+ * multi-pass init retry can perturb, so a resource that must reliably outlive
16
+ * the rest at shutdown cannot express that through the dependency graph. Log
17
+ * sinks set {@link TEARDOWN_LAST} so they flush after every resource that
18
+ * might log while shutting down — a generic mechanism, not a logging-specific
19
+ * carve-out in the teardown path.
20
+ */
21
+ teardownPriority?: number;
9
22
  };
23
+ /** Teardown-last priority (see {@link ResourceInstance.teardownPriority}). Log
24
+ * sinks use it so anything logging during its own teardown still reaches a live
25
+ * destination. */
26
+ export declare const TEARDOWN_LAST = 1000;
10
27
  /** The kind+name an instance was resolved from. */
11
28
  export interface RefIdentity {
12
29
  kind: string;
@@ -1 +1 @@
1
- {"version":3,"file":"resource-instance.d.ts","sourceRoot":"","sources":["../src/resource-instance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,GAAG,IAAI,OAAO,CACjF,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3B,GACC,OAAO,CAAC,QAAQ,CAAC,GACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG;IAC3B,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;CACjE,CAAC;AAEJ,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,OAAO,MAAuC,CAAC;AAE1E;sFACsF;AACtF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CASnF;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAExE"}
1
+ {"version":3,"file":"resource-instance.d.ts","sourceRoot":"","sources":["../src/resource-instance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,GAAG,IAAI,OAAO,CACjF,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3B,GACC,OAAO,CAAC,QAAQ,CAAC,GACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG;IAC3B,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAChE;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEJ;;mBAEmB;AACnB,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,OAAO,MAAuC,CAAC;AAE1E;sFACsF;AACtF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CASnF;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAExE"}
@@ -1,3 +1,7 @@
1
+ /** Teardown-last priority (see {@link ResourceInstance.teardownPriority}). Log
2
+ * sinks use it so anything logging during its own teardown still reaches a live
3
+ * destination. */
4
+ export const TEARDOWN_LAST = 1000;
1
5
  /**
2
6
  * Non-enumerable identity tag the kernel stamps on a live instance when it
3
7
  * injects a resolved `!ref` into a slot. A consumer that holds only the bare
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/sdk",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "description": "Telo SDK - Public API for Telo module authors.",
5
5
  "keywords": [
6
6
  "telo",
package/src/index.ts CHANGED
@@ -17,6 +17,11 @@ export * from "./resource-context.js";
17
17
  export * from "./resource-instance.js";
18
18
  export * from "./resource-manifest.js";
19
19
  export * from "./invoke-error.js";
20
+ export * from "./log-record.js";
21
+ export * from "./log-sink.js";
22
+ export * from "./log-severity.js";
23
+ export * from "./logger.js";
24
+ export * from "./network-fetch.js";
20
25
  export * from "./runtime-error.js";
21
26
  export * from "./runtime-event.js";
22
27
  export * from "./runtime-resource.js";
@@ -11,7 +11,7 @@ export class InvokeError extends Error {
11
11
  readonly code: string;
12
12
  readonly data?: unknown;
13
13
 
14
- constructor(code: string, message: string, data?: unknown) {
14
+ constructor(code: string, message: string, data?: unknown, options?: { cause?: unknown }) {
15
15
  super(message);
16
16
  this.name = "InvokeError";
17
17
  this.code = code;
@@ -27,6 +27,19 @@ export class InvokeError extends Error {
27
27
  writable: false,
28
28
  configurable: false,
29
29
  });
30
+ // Preserve the error being wrapped. Defined rather than assigned so it is
31
+ // non-enumerable — matching how the Error constructor's own `cause` option
32
+ // behaves, and keeping it out of JSON serialisation / CEL property access
33
+ // for the same reason as the marker above. (`ErrorOptions` is not in this
34
+ // package's TS lib, hence the local option type.)
35
+ if (options && "cause" in options) {
36
+ Object.defineProperty(this, "cause", {
37
+ value: options.cause,
38
+ enumerable: false,
39
+ writable: true,
40
+ configurable: true,
41
+ });
42
+ }
30
43
  }
31
44
  }
32
45
 
@@ -0,0 +1,113 @@
1
+ import type { SeverityNumber } from "./log-severity.js";
2
+
3
+ /**
4
+ * The Telo log record model — `kernel/specs/logging.md` §4. Maps 1:1 onto an
5
+ * OpenTelemetry `LogRecord`; encodings (§11) determine spelling and MUST NOT add
6
+ * or remove semantics.
7
+ *
8
+ * The one deliberate deviation from OTel is {@link LogRecord.message}: OTel's
9
+ * `Body` is an `AnyValue` and may be structured, while Telo requires a string and
10
+ * routes structured data to `attributes`. That keeps the console encoding total —
11
+ * every record has a renderable headline — and matches slog, pino, and zap.
12
+ */
13
+
14
+ /** The attribute value type (§6.1). `null` is a valid value and is preserved. */
15
+ export type AnyValue =
16
+ | string
17
+ | boolean
18
+ | number
19
+ | bigint
20
+ | Uint8Array
21
+ | null
22
+ | AnyValue[]
23
+ | { [key: string]: AnyValue };
24
+
25
+ export type LogAttributes = Record<string, AnyValue>;
26
+
27
+ /** Structured error (§4.2). The `cause` chain is bounded per §6.3. */
28
+ export interface ErrorValue {
29
+ /** Error class or code, e.g. `ERR_INVOKE_CANCELLED`. */
30
+ type: string;
31
+ message: string;
32
+ /** Multi-line, unmodified. */
33
+ stack?: string;
34
+ cause?: ErrorValue;
35
+ }
36
+
37
+ /** The emitting Telo resource (§7.3). `id` is the full hierarchical id, which is
38
+ * what distinguishes two instances of the same templated kind. */
39
+ export interface ResourceRef {
40
+ kind: string;
41
+ name: string;
42
+ id?: string;
43
+ }
44
+
45
+ export interface LogRecord {
46
+ /** Nanoseconds since the Unix epoch, by the origin clock. */
47
+ timestamp: bigint;
48
+ /** When the runtime observed the event, when that differs from `timestamp`
49
+ * (a bridged third-party logger, §13.3). */
50
+ observedTimestamp?: bigint;
51
+ severityNumber: SeverityNumber;
52
+ /** Canonical short name, or the original source spelling when bridging. */
53
+ severityText: string;
54
+ /** May be empty; never absent. */
55
+ message: string;
56
+ attributes?: LogAttributes;
57
+ /** 32 lowercase hex chars. */
58
+ traceId?: string;
59
+ /** 16 lowercase hex chars. Never present without `traceId`. */
60
+ spanId?: string;
61
+ /** Bit 0 = sampled, bit 1 reserved (§7.5), bits 2–7 zero. */
62
+ traceFlags?: number;
63
+ resource?: ResourceRef;
64
+ /** Module name of the emitter. Not unique — see `scope`. */
65
+ module?: string;
66
+ /** Dotted import-alias path identifying which *instance* emitted the record
67
+ * (`Api.Domain.Db`). Absent for the root Application's own resources. */
68
+ scope?: string;
69
+ /** Identifies a class of event; max 256 chars. */
70
+ eventName?: string;
71
+ error?: ErrorValue;
72
+ /** Non-zero when §6.3 limits truncated attributes. */
73
+ droppedAttributesCount?: number;
74
+ }
75
+
76
+ // Written as `BigInt(...)` rather than as `1_000_000n` literals: this module is
77
+ // consumed from source by the browser-targeted editor, whose tsconfig targets
78
+ // below ES2020 and cannot parse the literal syntax.
79
+ const NANOS_PER_MS = BigInt(1_000_000);
80
+ const NANOS_PER_SECOND = BigInt(1_000_000_000);
81
+
82
+ /**
83
+ * Node has no true nanosecond wall clock: `Date` is millisecond-resolution and
84
+ * `hrtime.bigint()` is monotonic rather than epoch-anchored. The best available
85
+ * is the performance origin plus the monotonic offset, which yields microsecond
86
+ * resolution zero-padded to nine digits. Format-conformant with §11.1; the extra
87
+ * three digits are always zero.
88
+ *
89
+ * The origin is captured once as a bigint so the addition never routes a
90
+ * 16-significant-digit value through a float64 and loses the low microseconds.
91
+ */
92
+ const ORIGIN_NANOS = BigInt(Math.round(performance.timeOrigin * 1e6));
93
+
94
+ export function nowUnixNano(): bigint {
95
+ return ORIGIN_NANOS + BigInt(Math.round(performance.now() * 1e6));
96
+ }
97
+
98
+ /** Epoch nanoseconds for a millisecond-resolution instant — used when bridging a
99
+ * third-party record that carries a `Date` or epoch-millis timestamp. */
100
+ export function unixNanoFromMillis(epochMillis: number): bigint {
101
+ return BigInt(Math.round(epochMillis)) * NANOS_PER_MS;
102
+ }
103
+
104
+ /**
105
+ * RFC 3339, UTC, nanosecond precision, `Z` suffix — the `time` key of the `json`
106
+ * encoding (§11.1).
107
+ */
108
+ export function formatUnixNano(timestamp: bigint): string {
109
+ const seconds = timestamp / NANOS_PER_SECOND;
110
+ const nanos = timestamp - seconds * NANOS_PER_SECOND;
111
+ const isoSeconds = new Date(Number(seconds) * 1000).toISOString().slice(0, 19);
112
+ return `${isoSeconds}.${nanos.toString().padStart(9, "0")}Z`;
113
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The OpenTelemetry `SeverityNumber` scale (1–24, stable), which Telo adopts
3
+ * verbatim — see `kernel/specs/logging.md` §5.
4
+ *
5
+ * Higher is more severe. All comparison, filtering, and threshold logic uses the
6
+ * number; severity *text* is presentation only and MUST NOT be compared. The
7
+ * full 24-value range stays valid on the wire so records bridged from a
8
+ * third-party logger survive a round-trip with their original spelling intact.
9
+ */
10
+
11
+ /** An OTel SeverityNumber. `0` (UNSPECIFIED) is never emitted by a Telo runtime. */
12
+ export type SeverityNumber = number;
13
+
14
+ /** The six levels Telo names. Each is the floor of its four-value OTel range. */
15
+ export const SEVERITY = {
16
+ trace: 1,
17
+ debug: 5,
18
+ info: 9,
19
+ warn: 13,
20
+ error: 17,
21
+ fatal: 21,
22
+ } as const;
23
+
24
+ export type LevelName = keyof typeof SEVERITY;
25
+
26
+ export const LEVEL_NAMES: readonly LevelName[] = ["trace", "debug", "info", "warn", "error", "fatal"];
27
+
28
+ /** The severity at or above which a record describes an error (§5.1). This is
29
+ * the portable error predicate; runtimes expose it rather than re-deriving it. */
30
+ export const ERROR_SEVERITY_FLOOR = 17;
31
+
32
+ const FLOORS: readonly number[] = [1, 5, 9, 13, 17, 21];
33
+
34
+ const TEXT_BY_FLOOR: Readonly<Record<number, string>> = {
35
+ 1: "TRACE",
36
+ 5: "DEBUG",
37
+ 9: "INFO",
38
+ 13: "WARN",
39
+ 17: "ERROR",
40
+ 21: "FATAL",
41
+ };
42
+
43
+ /**
44
+ * The range floor for a severity number — the canonical level a value maps onto.
45
+ * Out-of-range values clamp into 1–24 rather than producing `0`, which §5.1
46
+ * forbids emitting.
47
+ */
48
+ export function severityFloor(severity: SeverityNumber): number {
49
+ const clamped = severity < 1 ? 1 : severity > 24 ? 24 : Math.trunc(severity);
50
+ let floor = FLOORS[0]!;
51
+ for (const candidate of FLOORS) {
52
+ if (candidate <= clamped) floor = candidate;
53
+ else break;
54
+ }
55
+ return floor;
56
+ }
57
+
58
+ /** Canonical short name (`TRACE`…`FATAL`) for a severity number. */
59
+ export function severityText(severity: SeverityNumber): string {
60
+ return TEXT_BY_FLOOR[severityFloor(severity)]!;
61
+ }
62
+
63
+ /** `true` when the record describes an error (§5.1). */
64
+ export function isErrorSeverity(severity: SeverityNumber): boolean {
65
+ return severity >= ERROR_SEVERITY_FLOOR;
66
+ }
67
+
68
+ /** Resolve a manifest `level:` name to its severity number. */
69
+ export function severityForLevel(level: LevelName): number {
70
+ return SEVERITY[level];
71
+ }
72
+
73
+ /**
74
+ * Map a level name of unknown provenance onto the scale. A name Telo does not
75
+ * recognize yields `undefined` so the caller can preserve the original spelling
76
+ * in `severity_text` while landing the number on a range floor (§5.1).
77
+ */
78
+ export function parseLevelName(name: string): number | undefined {
79
+ const key = name.trim().toLowerCase();
80
+ // Own-property check, not `in`: `in` also matches inherited members, so
81
+ // `parseLevelName("toString")` would otherwise return a Function and defeat
82
+ // the `?? fallback` at every call site. Written as `hasOwnProperty.call`
83
+ // rather than `Object.hasOwn` because this module is consumed from source by
84
+ // the browser-targeted editor, whose tsconfig targets below ES2022.
85
+ return Object.prototype.hasOwnProperty.call(SEVERITY, key)
86
+ ? SEVERITY[key as LevelName]
87
+ : undefined;
88
+ }
89
+
90
+ /**
91
+ * Go's `log/slog` documents that subtracting 9 from an OTel severity converts it
92
+ * to the slog range — an exact, officially sanctioned relation, so a Go runtime
93
+ * uses arithmetic rather than a table (§5.2). Exposed here so the conformance
94
+ * vectors can assert the relation from the Node side too.
95
+ */
96
+ export const SLOG_OFFSET = 9;
97
+
98
+ /**
99
+ * pino's scale is 10× and offset, with no arithmetic relation to OTel, so §5.2
100
+ * requires a table. Used by the Fastify logger replacement (§13.3).
101
+ */
102
+ const PINO_BY_SEVERITY: Readonly<Record<number, number>> = {
103
+ 1: 10,
104
+ 5: 20,
105
+ 9: 30,
106
+ 13: 40,
107
+ 17: 50,
108
+ 21: 60,
109
+ };
110
+
111
+ const SEVERITY_BY_PINO: Readonly<Record<number, number>> = {
112
+ 10: 1,
113
+ 20: 5,
114
+ 30: 9,
115
+ 40: 13,
116
+ 50: 17,
117
+ 60: 21,
118
+ };
119
+
120
+ export function pinoLevelForSeverity(severity: SeverityNumber): number {
121
+ return PINO_BY_SEVERITY[severityFloor(severity)]!;
122
+ }
123
+
124
+ /** `undefined` for a pino level Telo does not name, so the caller preserves the
125
+ * source spelling and falls back to the nearest floor. */
126
+ export function severityForPinoLevel(level: number): number | undefined {
127
+ return SEVERITY_BY_PINO[level];
128
+ }