@telorun/kernel 0.49.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.
Files changed (186) hide show
  1. package/dist/controller-loader.d.ts +6 -1
  2. package/dist/controller-loader.d.ts.map +1 -1
  3. package/dist/controller-loader.js +2 -0
  4. package/dist/controller-loader.js.map +1 -1
  5. package/dist/controller-loaders/bundle-builder.d.ts +2 -1
  6. package/dist/controller-loaders/bundle-builder.d.ts.map +1 -1
  7. package/dist/controller-loaders/bundle-builder.js +14 -9
  8. package/dist/controller-loaders/bundle-builder.js.map +1 -1
  9. package/dist/controller-loaders/npm-loader.d.ts +8 -2
  10. package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
  11. package/dist/controller-loaders/npm-loader.js +19 -10
  12. package/dist/controller-loaders/npm-loader.js.map +1 -1
  13. package/dist/controllers/logging/console-sink-controller.d.ts +15 -0
  14. package/dist/controllers/logging/console-sink-controller.d.ts.map +1 -0
  15. package/dist/controllers/logging/console-sink-controller.js +40 -0
  16. package/dist/controllers/logging/console-sink-controller.js.map +1 -0
  17. package/dist/controllers/logging/file-sink-controller.d.ts +11 -0
  18. package/dist/controllers/logging/file-sink-controller.d.ts.map +1 -0
  19. package/dist/controllers/logging/file-sink-controller.js +33 -0
  20. package/dist/controllers/logging/file-sink-controller.js.map +1 -0
  21. package/dist/controllers/logging/sink-identity.d.ts +37 -0
  22. package/dist/controllers/logging/sink-identity.d.ts.map +1 -0
  23. package/dist/controllers/logging/sink-identity.js +40 -0
  24. package/dist/controllers/logging/sink-identity.js.map +1 -0
  25. package/dist/controllers/module/import-controller.d.ts.map +1 -1
  26. package/dist/controllers/module/import-controller.js +25 -0
  27. package/dist/controllers/module/import-controller.js.map +1 -1
  28. package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
  29. package/dist/controllers/resource-definition/resource-definition-controller.js +1 -0
  30. package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
  31. package/dist/evaluation-context.d.ts +27 -1
  32. package/dist/evaluation-context.d.ts.map +1 -1
  33. package/dist/evaluation-context.js +84 -16
  34. package/dist/evaluation-context.js.map +1 -1
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +5 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal-context.d.ts +4 -0
  40. package/dist/internal-context.d.ts.map +1 -1
  41. package/dist/kernel.d.ts +26 -0
  42. package/dist/kernel.d.ts.map +1 -1
  43. package/dist/kernel.js +78 -5
  44. package/dist/kernel.js.map +1 -1
  45. package/dist/logging/bootstrap-writer.d.ts +23 -0
  46. package/dist/logging/bootstrap-writer.d.ts.map +1 -0
  47. package/dist/logging/bootstrap-writer.js +31 -0
  48. package/dist/logging/bootstrap-writer.js.map +1 -0
  49. package/dist/logging/color-precedence.d.ts +27 -0
  50. package/dist/logging/color-precedence.d.ts.map +1 -0
  51. package/dist/logging/color-precedence.js +47 -0
  52. package/dist/logging/color-precedence.js.map +1 -0
  53. package/dist/logging/console-sink.d.ts +45 -0
  54. package/dist/logging/console-sink.d.ts.map +1 -0
  55. package/dist/logging/console-sink.js +56 -0
  56. package/dist/logging/console-sink.js.map +1 -0
  57. package/dist/logging/debug-wire-sink.d.ts +38 -0
  58. package/dist/logging/debug-wire-sink.d.ts.map +1 -0
  59. package/dist/logging/debug-wire-sink.js +85 -0
  60. package/dist/logging/debug-wire-sink.js.map +1 -0
  61. package/dist/logging/drop-accounting.d.ts +34 -0
  62. package/dist/logging/drop-accounting.d.ts.map +1 -0
  63. package/dist/logging/drop-accounting.js +98 -0
  64. package/dist/logging/drop-accounting.js.map +1 -0
  65. package/dist/logging/encode-json.d.ts +28 -0
  66. package/dist/logging/encode-json.d.ts.map +1 -0
  67. package/dist/logging/encode-json.js +86 -0
  68. package/dist/logging/encode-json.js.map +1 -0
  69. package/dist/logging/encode-pretty.d.ts +7 -0
  70. package/dist/logging/encode-pretty.d.ts.map +1 -0
  71. package/dist/logging/encode-pretty.js +104 -0
  72. package/dist/logging/encode-pretty.js.map +1 -0
  73. package/dist/logging/file-sink.d.ts +33 -0
  74. package/dist/logging/file-sink.d.ts.map +1 -0
  75. package/dist/logging/file-sink.js +72 -0
  76. package/dist/logging/file-sink.js.map +1 -0
  77. package/dist/logging/index.d.ts +39 -0
  78. package/dist/logging/index.d.ts.map +1 -0
  79. package/dist/logging/index.js +27 -0
  80. package/dist/logging/index.js.map +1 -0
  81. package/dist/logging/kernel-logging.d.ts +110 -0
  82. package/dist/logging/kernel-logging.d.ts.map +1 -0
  83. package/dist/logging/kernel-logging.js +199 -0
  84. package/dist/logging/kernel-logging.js.map +1 -0
  85. package/dist/logging/log-sink.d.ts +9 -0
  86. package/dist/logging/log-sink.d.ts.map +1 -0
  87. package/dist/logging/log-sink.js +8 -0
  88. package/dist/logging/log-sink.js.map +1 -0
  89. package/dist/logging/logging-host.d.ts +14 -0
  90. package/dist/logging/logging-host.d.ts.map +1 -0
  91. package/dist/logging/logging-host.js +27 -0
  92. package/dist/logging/logging-host.js.map +1 -0
  93. package/dist/logging/logging-pipeline.d.ts +89 -0
  94. package/dist/logging/logging-pipeline.d.ts.map +1 -0
  95. package/dist/logging/logging-pipeline.js +349 -0
  96. package/dist/logging/logging-pipeline.js.map +1 -0
  97. package/dist/logging/normalize-attributes.d.ts +48 -0
  98. package/dist/logging/normalize-attributes.d.ts.map +1 -0
  99. package/dist/logging/normalize-attributes.js +140 -0
  100. package/dist/logging/normalize-attributes.js.map +1 -0
  101. package/dist/logging/record-buffer.d.ts +24 -0
  102. package/dist/logging/record-buffer.d.ts.map +1 -0
  103. package/dist/logging/record-buffer.js +78 -0
  104. package/dist/logging/record-buffer.js.map +1 -0
  105. package/dist/logging/redact-attributes.d.ts +45 -0
  106. package/dist/logging/redact-attributes.d.ts.map +1 -0
  107. package/dist/logging/redact-attributes.js +152 -0
  108. package/dist/logging/redact-attributes.js.map +1 -0
  109. package/dist/logging/sampler.d.ts +34 -0
  110. package/dist/logging/sampler.d.ts.map +1 -0
  111. package/dist/logging/sampler.js +52 -0
  112. package/dist/logging/sampler.js.map +1 -0
  113. package/dist/logging/scope-config.d.ts +32 -0
  114. package/dist/logging/scope-config.d.ts.map +1 -0
  115. package/dist/logging/scope-config.js +2 -0
  116. package/dist/logging/scope-config.js.map +1 -0
  117. package/dist/logging/span-id.d.ts +24 -0
  118. package/dist/logging/span-id.d.ts.map +1 -0
  119. package/dist/logging/span-id.js +85 -0
  120. package/dist/logging/span-id.js.map +1 -0
  121. package/dist/logging/to-error-value.d.ts +3 -0
  122. package/dist/logging/to-error-value.d.ts.map +1 -0
  123. package/dist/logging/to-error-value.js +79 -0
  124. package/dist/logging/to-error-value.js.map +1 -0
  125. package/dist/logging/trace-parent.d.ts +54 -0
  126. package/dist/logging/trace-parent.d.ts.map +1 -0
  127. package/dist/logging/trace-parent.js +92 -0
  128. package/dist/logging/trace-parent.js.map +1 -0
  129. package/dist/manifest-schemas.d.ts +1 -1
  130. package/dist/manifest-schemas.d.ts.map +1 -1
  131. package/dist/manifest-schemas.js +12 -0
  132. package/dist/manifest-schemas.js.map +1 -1
  133. package/dist/module-context.d.ts +16 -0
  134. package/dist/module-context.d.ts.map +1 -1
  135. package/dist/module-context.js +6 -0
  136. package/dist/module-context.js.map +1 -1
  137. package/dist/resource-context.d.ts +15 -1
  138. package/dist/resource-context.d.ts.map +1 -1
  139. package/dist/resource-context.js +45 -0
  140. package/dist/resource-context.js.map +1 -1
  141. package/dist/schema-compiled-values.d.ts.map +1 -1
  142. package/dist/schema-compiled-values.js +7 -0
  143. package/dist/schema-compiled-values.js.map +1 -1
  144. package/dist/schema-validator.d.ts +8 -1
  145. package/dist/schema-validator.d.ts.map +1 -1
  146. package/dist/schema-validator.js +13 -3
  147. package/dist/schema-validator.js.map +1 -1
  148. package/package.json +3 -3
  149. package/src/controller-loader.ts +7 -1
  150. package/src/controller-loaders/bundle-builder.ts +18 -10
  151. package/src/controller-loaders/npm-loader.ts +32 -11
  152. package/src/controllers/logging/console-sink-controller.ts +46 -0
  153. package/src/controllers/logging/file-sink-controller.ts +40 -0
  154. package/src/controllers/logging/sink-identity.ts +53 -0
  155. package/src/controllers/module/import-controller.ts +27 -0
  156. package/src/controllers/resource-definition/resource-definition-controller.ts +1 -0
  157. package/src/evaluation-context.ts +93 -15
  158. package/src/index.ts +54 -0
  159. package/src/internal-context.ts +4 -0
  160. package/src/kernel.ts +98 -8
  161. package/src/logging/bootstrap-writer.ts +38 -0
  162. package/src/logging/color-precedence.ts +62 -0
  163. package/src/logging/console-sink.ts +86 -0
  164. package/src/logging/debug-wire-sink.ts +85 -0
  165. package/src/logging/drop-accounting.ts +108 -0
  166. package/src/logging/encode-json.ts +98 -0
  167. package/src/logging/encode-pretty.ts +117 -0
  168. package/src/logging/file-sink.ts +87 -0
  169. package/src/logging/index.ts +65 -0
  170. package/src/logging/kernel-logging.ts +266 -0
  171. package/src/logging/log-sink.ts +18 -0
  172. package/src/logging/logging-host.ts +33 -0
  173. package/src/logging/logging-pipeline.ts +465 -0
  174. package/src/logging/normalize-attributes.ts +228 -0
  175. package/src/logging/record-buffer.ts +72 -0
  176. package/src/logging/redact-attributes.ts +168 -0
  177. package/src/logging/sampler.ts +76 -0
  178. package/src/logging/scope-config.ts +32 -0
  179. package/src/logging/span-id.ts +84 -0
  180. package/src/logging/to-error-value.ts +89 -0
  181. package/src/logging/trace-parent.ts +110 -0
  182. package/src/manifest-schemas.ts +12 -0
  183. package/src/module-context.ts +23 -0
  184. package/src/resource-context.ts +48 -0
  185. package/src/schema-compiled-values.ts +6 -0
  186. package/src/schema-validator.ts +20 -5
@@ -0,0 +1,266 @@
1
+ import {
2
+ parseLevelName,
3
+ SEVERITY,
4
+ type LevelName,
5
+ type LogAttributes,
6
+ type Logger,
7
+ type ResourceRef,
8
+ } from "@telorun/sdk";
9
+ import { BOOTSTRAP_SINK_ID, createBootstrapWriter } from "./bootstrap-writer.js";
10
+ import { ConsoleSink } from "./console-sink.js";
11
+ import { compileRedactionPolicy, EMPTY_REDACTION_POLICY } from "./redact-attributes.js";
12
+ import type { DropCause, LogSinkInstance } from "./log-sink.js";
13
+ import { createLoggingHost, type LoggingHost } from "./logging-host.js";
14
+ import { LoggingPipeline, type ScopeConfig, type TraceContextProvider } from "./logging-pipeline.js";
15
+ import type { SamplingConfig } from "./sampler.js";
16
+
17
+ /**
18
+ * The kernel's ownership of the logging pipeline — `kernel/specs/logging.md`
19
+ * §12.
20
+ *
21
+ * Sequencing is the whole job here. An internal console writer covers process
22
+ * start through manifest validation, because declared sinks cannot instantiate
23
+ * before the block that declares them has been validated. Once the manifest
24
+ * resolves, the declared configuration takes over: eager sinks attach, buffered
25
+ * records replay, and the bootstrap writer steps aside.
26
+ */
27
+
28
+ /** The raw `logging:` block as authored, after CEL evaluation. */
29
+ export interface LoggingManifestBlock {
30
+ level?: string;
31
+ attributes?: LogAttributes;
32
+ redact?: { paths?: string[]; censor?: string; remove?: boolean };
33
+ sampling?: { first?: number; thereafter?: number; tick?: string; sampleErrors?: boolean };
34
+ sinks?: unknown[];
35
+ }
36
+
37
+ const DEFAULT_SAMPLING_TICK_MS = 1000;
38
+
39
+ /** The module-context tree as far as logging is concerned — just enough to walk
40
+ * children and read each one's resolved scope config, declared structurally so
41
+ * this file stays independent of the concrete `ModuleContext`. */
42
+ export interface LoggingContextNode {
43
+ readonly children?: readonly LoggingContextNode[];
44
+ getLoggingConfig?(): ScopeConfig | undefined;
45
+ }
46
+
47
+ /** How many sinks the root Application declared. Zero means the runtime behaves
48
+ * exactly as if a single `Telo.ConsoleSink` were declared (§12.1), which is why
49
+ * the bootstrap writer is replaced by a threshold-aware one rather than left as
50
+ * the fixed-`info` writer. */
51
+ function declaredSinkCount(manifests: readonly { kind?: string }[]): number {
52
+ const root = manifests.find((m) => m.kind === "Telo.Application") as
53
+ | { logging?: { sinks?: unknown[] } }
54
+ | undefined;
55
+ return root?.logging?.sinks?.length ?? 0;
56
+ }
57
+
58
+ export class KernelLogging {
59
+ readonly pipeline: LoggingPipeline;
60
+ readonly host: LoggingHost;
61
+
62
+ #rootScope: ScopeConfig;
63
+ #bootstrap: LogSinkInstance | undefined;
64
+ #sealed = false;
65
+ /** Kept so the zero-config console sink can be rebuilt at the resolved
66
+ * threshold once the manifest is known (see {@link sealBootstrap}). */
67
+ readonly #streams: {
68
+ env: Record<string, string | undefined>;
69
+ stdout: NodeJS.WritableStream;
70
+ stderr: NodeJS.WritableStream;
71
+ };
72
+
73
+ constructor(options: {
74
+ env: Record<string, string | undefined>;
75
+ stdout: NodeJS.WritableStream;
76
+ stderr: NodeJS.WritableStream;
77
+ /** The process's real stderr — §8.4's fallback diagnostic stream. Kept
78
+ * distinct from the kernel's injectable `stderr` so a captured stream can
79
+ * never swallow the report that the capture itself failed. */
80
+ fallbackStream?: { write(chunk: string): unknown };
81
+ }) {
82
+ this.#streams = { env: options.env, stdout: options.stdout, stderr: options.stderr };
83
+ this.pipeline = new LoggingPipeline({
84
+ fallbackStream: options.fallbackStream ?? {
85
+ write: (chunk: string) => process.stderr.write(chunk),
86
+ },
87
+ });
88
+
89
+ // §12.3: a fixed default of `info` until the manifest resolves. This phase
90
+ // is the only one not manifest-governed, and it is deliberately not
91
+ // configurable by any other means.
92
+ this.#rootScope = {
93
+ threshold: SEVERITY.info,
94
+ redaction: EMPTY_REDACTION_POLICY,
95
+ };
96
+
97
+ this.#bootstrap = createBootstrapWriter(options);
98
+ this.pipeline.attach(this.#bootstrap);
99
+
100
+ this.host = createLoggingHost(
101
+ this.pipeline,
102
+ () => this.#rootScope.threshold,
103
+ (sinkId: string, cause: DropCause, count?: number) => this.recordDrop(sinkId, cause, count),
104
+ );
105
+ }
106
+
107
+ get rootScope(): ScopeConfig {
108
+ return this.#rootScope;
109
+ }
110
+
111
+ setTraceContextProvider(provider: TraceContextProvider | undefined): void {
112
+ this.pipeline.setTraceContextProvider(provider);
113
+ }
114
+
115
+ recordDrop(sinkId: string, cause: DropCause, count = 1): void {
116
+ // Routed through the pipeline so the recovery warning §10.4 requires is
117
+ // emitted as an ordinary record and reaches every sink.
118
+ this.pipeline.recordDrop(sinkId, cause, count);
119
+ }
120
+
121
+ /**
122
+ * Adopt the manifest's declared configuration. Called as soon as the
123
+ * `logging:` block has been validated — after that point the bootstrap default
124
+ * is no longer in force.
125
+ */
126
+ applyRootConfig(block: LoggingManifestBlock | undefined, secretValues?: ReadonlySet<string>): void {
127
+ this.#rootScope = buildScopeConfig(block, {
128
+ threshold: SEVERITY.info,
129
+ redaction: EMPTY_REDACTION_POLICY,
130
+ secretValues,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Detach the bootstrap writer and stop holding records for replay. Called once
136
+ * every declared sink has attached: the buffer exists to cover the pre-attach
137
+ * window, and a consumer connecting later (the debug wire) wants the live
138
+ * stream rather than the whole process history.
139
+ *
140
+ * When the manifest declares no sinks at all, the runtime behaves exactly as
141
+ * if a single `Telo.ConsoleSink` were declared — "pretty logs on stderr in a
142
+ * terminal, JSON when piped", with no imports.
143
+ *
144
+ * That equivalence is why the bootstrap writer is *replaced* here rather than
145
+ * promoted in place. The bootstrap writer is pinned at `info` (§12.3), but a
146
+ * declared `Telo.ConsoleSink` with no explicit `level:` takes the resolved
147
+ * scope threshold — so promoting the fixed-`info` writer would silently ignore
148
+ * a root `logging.level`, in both directions (a `debug` never lowers the gate,
149
+ * a `warn` never raises it). The fresh sink is built at the resolved threshold,
150
+ * so `logging: { level: debug }` with no `sinks:` behaves as documented.
151
+ */
152
+ sealBootstrap(manifests: readonly { kind?: string }[]): void {
153
+ if (this.#sealed) return;
154
+ this.#sealed = true;
155
+
156
+ if (this.#bootstrap) {
157
+ this.pipeline.detach(this.#bootstrap);
158
+ this.#bootstrap = undefined;
159
+ }
160
+ // Drop the replay buffer *before* attaching the zero-config sink, so the
161
+ // records the bootstrap writer already wrote live are not replayed into it.
162
+ this.pipeline.sealBootstrap();
163
+
164
+ if (declaredSinkCount(manifests) === 0) {
165
+ this.pipeline.attach(
166
+ new ConsoleSink({
167
+ sinkId: BOOTSTRAP_SINK_ID,
168
+ level: this.#rootScope.threshold,
169
+ destination: "stderr",
170
+ encoding: "auto",
171
+ color: "auto",
172
+ env: this.#streams.env,
173
+ stdout: this.#streams.stdout,
174
+ stderr: this.#streams.stderr,
175
+ }),
176
+ );
177
+ }
178
+ }
179
+
180
+ /** A logger scoped to a module context, stamped with a resource identity. */
181
+ createLogger(scope: ScopeConfig | undefined, resource?: ResourceRef): Logger {
182
+ return this.pipeline.createLogger(scope ?? this.#rootScope, resource);
183
+ }
184
+
185
+ /**
186
+ * Every module context's resolved logging configuration, keyed by its dotted
187
+ * import-alias path (`Api.Domain.Db`); the root is keyed `""`.
188
+ *
189
+ * This closes §12.2's loop between reading and configuring: the `scope` on a
190
+ * log line is exactly the path you write in the manifest to change that
191
+ * instance's level, and this answers "what level did that path actually
192
+ * resolve to" without re-deriving the cascade by hand. The tree walk is
193
+ * logging logic, so it lives here rather than on the orchestrator.
194
+ */
195
+ scopesFrom(rootContext: LoggingContextNode | undefined): Map<string, ScopeConfig> {
196
+ const scopes = new Map<string, ScopeConfig>();
197
+ scopes.set("", this.#rootScope);
198
+ const visit = (context: LoggingContextNode): void => {
199
+ for (const child of context.children ?? []) {
200
+ const config = child.getLoggingConfig?.();
201
+ if (config?.scope) scopes.set(config.scope, config);
202
+ visit(child);
203
+ }
204
+ };
205
+ if (rootContext) visit(rootContext);
206
+ return scopes;
207
+ }
208
+
209
+ /** The kernel's own logger — used for diagnostics the kernel emits about
210
+ * itself, which §13.1 requires to go through this pipeline rather than to
211
+ * `process.stderr` or `console.*`. */
212
+ kernelLogger(): Logger {
213
+ return this.pipeline.createLogger(this.#rootScope, undefined);
214
+ }
215
+
216
+ async shutdown(): Promise<void> {
217
+ await this.pipeline.close();
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Merge a `logging:` block over an inherited configuration. Config cascades and
223
+ * may be narrowed at each hop, which is what makes a dependency you do not own
224
+ * diagnosable — raising `Api`'s level lifts everything beneath it without
225
+ * editing `Api`'s manifest (§12.2).
226
+ */
227
+ export function buildScopeConfig(
228
+ block: LoggingManifestBlock | undefined,
229
+ inherited: ScopeConfig,
230
+ ): ScopeConfig {
231
+ if (!block) return inherited;
232
+
233
+ const level = block.level ? parseLevelName(block.level as LevelName) : undefined;
234
+
235
+ return {
236
+ threshold: level ?? inherited.threshold,
237
+ redaction: block.redact
238
+ ? compileRedactionPolicy({
239
+ paths: block.redact.paths,
240
+ censor: block.redact.censor,
241
+ remove: block.redact.remove,
242
+ })
243
+ : inherited.redaction,
244
+ sampling: block.sampling ? toSamplingConfig(block.sampling) : inherited.sampling,
245
+ secretValues: inherited.secretValues,
246
+ scope: inherited.scope,
247
+ module: inherited.module,
248
+ attributes: block.attributes ?? inherited.attributes,
249
+ };
250
+ }
251
+
252
+ function toSamplingConfig(sampling: NonNullable<LoggingManifestBlock["sampling"]>): SamplingConfig {
253
+ return {
254
+ first: sampling.first ?? 0,
255
+ thereafter: sampling.thereafter ?? 0,
256
+ tickMs: sampling.tick ? parseTickMs(sampling.tick) : DEFAULT_SAMPLING_TICK_MS,
257
+ sampleErrors: sampling.sampleErrors ?? false,
258
+ };
259
+ }
260
+
261
+ function parseTickMs(tick: string): number {
262
+ const match = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h)\s*$/.exec(tick);
263
+ if (!match) return DEFAULT_SAMPLING_TICK_MS;
264
+ const unit = { ms: 1, s: 1000, m: 60_000, h: 3_600_000 }[match[2] as "ms" | "s" | "m" | "h"];
265
+ return Number(match[1]) * unit;
266
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The sink contract now lives in `@telorun/sdk` — §10.2 keeps the sink set open
3
+ * to the ecosystem, so a third-party sink module implements it as an ordinary
4
+ * module author rather than importing a kernel-internal type. Re-exported here
5
+ * so the kernel's own imports keep one spelling.
6
+ */
7
+ export {
8
+ BLOCK_UNSUPPORTED,
9
+ blockUnsupportedMessage,
10
+ DEFAULT_BUFFER_POLICY,
11
+ } from "@telorun/sdk";
12
+ export type {
13
+ DropCause,
14
+ LoggingHost,
15
+ LogSinkInstance,
16
+ OnFull,
17
+ SinkBufferPolicy,
18
+ } from "@telorun/sdk";
@@ -0,0 +1,33 @@
1
+ import { parseLevelName, type DropCause, type LevelName, type LoggingHost } from "@telorun/sdk";
2
+
3
+ export type { LoggingHost } from "@telorun/sdk";
4
+ import type { LoggingPipeline } from "./logging-pipeline.js";
5
+
6
+ /**
7
+ * The narrow facade a sink controller sees — `kernel/specs/logging.md` §12.1.
8
+ *
9
+ * A sink is a resource, so its controller runs under an ordinary
10
+ * `ResourceContext`; this is the one extra surface it needs. It is deliberately
11
+ * small: attach, detach, resolve a level, count a drop. Everything else about
12
+ * the pipeline stays private to the kernel, so a third-party sink shipped as a
13
+ * module depends on this contract and nothing deeper.
14
+ */
15
+ export function createLoggingHost(
16
+ pipeline: LoggingPipeline,
17
+ scopeThreshold: () => number,
18
+ recordDrop: (sinkId: string, cause: DropCause, count?: number) => void,
19
+ ): LoggingHost {
20
+ return {
21
+ attach: (sink) => pipeline.attach(sink),
22
+ detach: (sink) => pipeline.detach(sink),
23
+ levelFor: (level) => {
24
+ if (!level) return scopeThreshold();
25
+ const severity = parseLevelName(level as LevelName);
26
+ // The schema constrains `level:` to the six named levels, so an unknown
27
+ // value here means the manifest bypassed validation; fall back rather than
28
+ // throw, because a logging misconfiguration must not break boot.
29
+ return severity ?? scopeThreshold();
30
+ },
31
+ recordDrop,
32
+ };
33
+ }