@lunora/observability 0.0.0 → 1.0.0-alpha.1

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,1818 @@
1
+ import { SqlExec, FunctionScanAttribution, FunctionCallStat } from '@lunora/shard-engine';
2
+ /** Reserved single-row auth accumulator table. Auto-hidden from the data browser by the `__lunora` prefix. */
3
+ declare const AUTH_METRICS_TABLE = "__lunora_auth_metrics";
4
+ /** Reserved coarse time-series table: app-wide auth attempt/failure counts bucketed by a fixed window. */
5
+ declare const AUTH_METRICS_BUCKETS_TABLE = "__lunora_auth_metrics_buckets";
6
+ /**
7
+ * Width of one history bucket, in milliseconds. 60s gives a minute-resolution
8
+ * time series — fine-grained enough to chart a burst of failed sign-ins on the
9
+ * studio, coarse enough that auth emits at most one row per minute. Mirrors
10
+ * `FUNCTION_METRICS_BUCKET_MS`.
11
+ */
12
+ declare const AUTH_METRICS_BUCKET_MS = 6e4;
13
+ /**
14
+ * Most recent buckets kept; older rows are trimmed after each write so the time
15
+ * series can't grow unbounded. 1440 minute-buckets ≈ 24h of auth history.
16
+ */
17
+ declare const AUTH_METRICS_BUCKET_RETENTION = 1440;
18
+ /** One coarse time-series sample: auth attempt/failure counts within `[bucketMs, bucketMs + AUTH_METRICS_BUCKET_MS)`. */
19
+ interface AuthMetricsBucket {
20
+ /** Auth attempts recorded in this window. */
21
+ attempts: number;
22
+ /** Epoch-ms floor of the bucket window. */
23
+ bucketMs: number;
24
+ /** Subset of `attempts` that failed (HTTP ≥ 400). */
25
+ failures: number;
26
+ }
27
+ /**
28
+ * Lifetime auth health for the app, served by `__lunora_admin__:getAuthMetrics`
29
+ * and consumed by the studio SLO panel. `failureRate` is the derived
30
+ * `failures / attempts` (0 when there have been no attempts), surfaced so the
31
+ * panel needn't recompute it; `sinceMs` is the epoch-ms the first attempt was
32
+ * recorded (a best-effort "since" marker); `history` is the minute-bucketed
33
+ * series for the sparkline, oldest bucket first.
34
+ */
35
+ interface AuthMetrics {
36
+ /** Total auth attempts (sign-in / sign-up / callback) recorded. */
37
+ attempts: number;
38
+ /** Derived `attempts === 0 ? 0 : failures / attempts`. */
39
+ failureRate: number;
40
+ /** Subset of `attempts` that failed (the auth route answered HTTP ≥ 400). */
41
+ failures: number;
42
+ /** Minute-bucketed attempt/failure series for the sparkline, oldest bucket first. */
43
+ history: AuthMetricsBucket[];
44
+ /** Epoch-ms the first attempt was recorded, or `0` on a never-seen app. */
45
+ sinceMs: number;
46
+ }
47
+ /** Fields recorded for one auth attempt. `outcome === "fail"` advances the failure counters. */
48
+ interface RecordAuthEventInput {
49
+ /** `"ok"` for a 2xx/3xx auth response, `"fail"` for an HTTP ≥ 400 response. */
50
+ outcome: "fail" | "ok";
51
+ /** Epoch-ms the auth attempt completed. */
52
+ ts: number;
53
+ }
54
+ /**
55
+ * Create the two reserved auth-metrics tables. Idempotent, so the read and write
56
+ * paths can call it defensively. The accumulator is a single keyed row; the
57
+ * bucket table is keyed by `bucket_ms` (one row per minute window).
58
+ */
59
+ declare const ensureAuthMetricsTables: (sql: SqlExec) => void;
60
+ /**
61
+ * Persist one auth attempt: a single upsert into the accumulator row and one
62
+ * upsert into the current time bucket, then a bounded trim of old buckets.
63
+ * Creates the tables first so callers needn't. `attempts` always advances;
64
+ * `failures` advances only when `outcome === "fail"`. `since_ms` is set once on
65
+ * the first attempt and never moved (so it stays a true first-seen marker).
66
+ *
67
+ * Exactly two `INSERT … ON CONFLICT … DO UPDATE` statements plus a bounded
68
+ * `DELETE`, all keyed by primary key — cheap enough to fire off the auth
69
+ * response path without blocking it.
70
+ */
71
+ declare const recordAuthEvent: (sql: SqlExec, input: RecordAuthEventInput) => void;
72
+ /**
73
+ * Read the durable auth metrics as the {@link AuthMetrics} wire shape the
74
+ * studio SLO panel consumes. Creates the tables first so a read on a
75
+ * never-authenticated app returns an all-zero shape (empty `history`) instead of
76
+ * throwing. `failureRate` is derived here so the consumer needn't recompute it.
77
+ */
78
+ declare const readAuthMetrics: (sql: SqlExec) => AuthMetrics;
79
+ /**
80
+ * Shared, bundler-inlined helpers for the structured `fields` a
81
+ * `ctx.log.<level>(message, fields)` / `ctx.log.with(fields)` call carries.
82
+ *
83
+ * Inlined (like {@link file://./otlp.ts}) so `@lunora/do`, `@lunora/runtime`,
84
+ * `@lunora/config`, and `@lunora/studio` — which sit on different tiers with no
85
+ * acceptable runtime dependency edge between them — share ONE implementation of
86
+ * field rendering/normalization instead of the byte-identical copies they would
87
+ * otherwise hand-mirror. Keep this genuinely zero-dependency (only built-ins) so
88
+ * inlining into each `dist` stays sound.
89
+ */
90
+ /** Structured, filterable key/value fields attached to a `ctx.log` line. */
91
+ type LogFields = Record<string, unknown>;
92
+ /**
93
+ * What kind of instrument produced a measurement, which decides how a collector
94
+ * aggregates it:
95
+ *
96
+ * - `counter` — a monotonic delta to add up (requests, retries, bytes sent).
97
+ * - `gauge` — a point-in-time reading that replaces the last one (queue depth,
98
+ * cache size).
99
+ * - `histogram` — a value whose *distribution* matters (latency, payload size),
100
+ * giving percentiles rather than just a mean.
101
+ */
102
+ type MetricKind = "counter" | "gauge" | "histogram";
103
+ /**
104
+ * One measurement recorded from a function handler.
105
+ *
106
+ * Each `ctx.metrics.*` call produces exactly one of these — the runtime does no
107
+ * pre-aggregation, so counters carry **delta** temporality and a collector sums
108
+ * them. That keeps the sink model identical to logs and spans (one event, one
109
+ * export) at the cost of chattiness in a hot loop, where the handler should sum
110
+ * locally and record once.
111
+ */
112
+ interface MetricEvent {
113
+ /**
114
+ * Structured attributes the caller attached, normalized to a fresh bag of
115
+ * JSON-safe primitives (see `shared/log-fields.ts`). These are the metric's
116
+ * dimensions — keep them low-cardinality; an id-valued attribute creates a
117
+ * distinct time series per id.
118
+ *
119
+ * Caller-controlled, so they MAY contain user input and they DO egress to
120
+ * whatever destination the sink ships to — the same caveat as a log line's
121
+ * `fields` and a span's `error.message`. Scrub upstream if that matters.
122
+ */
123
+ attributes?: LogFields;
124
+ /** Function path that recorded the measurement, e.g. `"orders:checkout"`. */
125
+ functionPath: string;
126
+ /** Instrument kind; see {@link MetricKind}. */
127
+ kind: MetricKind;
128
+ /** Instrument name, e.g. `"orders.placed"`. */
129
+ name: string;
130
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
131
+ shardKey?: string;
132
+ /**
133
+ * Trace id of the dispatch that recorded this measurement, when it ran inside
134
+ * one — the measurement's **exemplar**, letting a consumer jump from a metric
135
+ * point to a trace that produced it (OpenTelemetry's exemplar model). Stamped
136
+ * by the shard from the current request's trace context, not by the caller.
137
+ */
138
+ traceId?: string;
139
+ /** Wall-clock millis when the measurement was recorded. */
140
+ ts: number;
141
+ /**
142
+ * The measured value: the increment for a `counter`, the current reading for
143
+ * a `gauge`, the observed sample for a `histogram`.
144
+ */
145
+ value: number;
146
+ }
147
+ /** One eval verdict to turn into `gen_ai.evaluation.*` attributes. */
148
+ interface EvaluationInput {
149
+ /**
150
+ * Optional categorical label (e.g. `"pass"` / `"fail"` / a rubric bucket),
151
+ * emitted as the `.label` attribute. Omitted → no label attribute.
152
+ */
153
+ label?: string;
154
+ /**
155
+ * The scorer/evaluation name — becomes the key's name segment. Any character
156
+ * outside `[A-Za-z0-9._-]` is replaced with `_` so a scorer name carrying a
157
+ * colon (e.g. `"contains:shipped"`) still yields a well-formed attribute key.
158
+ */
159
+ name: string;
160
+ /** The numeric score (typically `[0, 1]`), emitted as the `.score` attribute. */
161
+ score: number;
162
+ }
163
+ /**
164
+ * Severity of a `ctx.log.*` call. The five console method names (`log` is the
165
+ * default level, distinct from `info`) plus `trace`/`fatal`, so the logger spans
166
+ * the full OpenTelemetry severity ramp (`trace`→`fatal`).
167
+ */
168
+ type ContextLogLevel = "debug" | "error" | "fatal" | "info" | "log" | "trace" | "warn";
169
+ /**
170
+ * One application log line emitted from a function handler via `ctx.log`.
171
+ * Produced per `ctx.log.*` call (unlike a per-dispatch RPC summary).
172
+ */
173
+ interface LogEvent {
174
+ /** Raw arguments passed to the `ctx.log.*` call, in order. */
175
+ args: unknown[];
176
+ /**
177
+ * OTel `LogRecord.eventName` — set when the line was emitted as a **structured
178
+ * event** via `ctx.log.event(name, fields)` rather than as a human-readable
179
+ * log line.
180
+ *
181
+ * The distinction is the whole point of the Events API: a log line's payload
182
+ * is its `message` (prose, for a human, unstable), while an event's payload is
183
+ * its `fields` (a named schema, for a query, stable). A collector that knows
184
+ * `eventName` can index and aggregate the latter; without it, "how many
185
+ * checkouts failed" degrades into a substring search over prose.
186
+ *
187
+ * Absent for ordinary `ctx.log.*` calls.
188
+ */
189
+ eventName?: string;
190
+ /**
191
+ * Structured fields the caller attached (`ctx.log.info(message, fields)` or a
192
+ * bound `ctx.log.with(fields)` child), already normalized to a fresh bag of
193
+ * JSON-safe primitives (see `shared/log-fields.ts`). Absent for a plain
194
+ * console-style call.
195
+ */
196
+ fields?: LogFields;
197
+ /** Function path that emitted the line, e.g. `"messages:list"`. */
198
+ functionPath: string;
199
+ /** Severity the line was logged at. */
200
+ level: ContextLogLevel;
201
+ /** Display string — the message, or the console-style args rendered and space-joined. */
202
+ message: string;
203
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
204
+ shardKey?: string;
205
+ /** Span id of the RPC this line was emitted under (trace correlation), or absent. */
206
+ spanId?: string;
207
+ /** Trace id this line belongs to (from the inbound `traceparent`), or absent. */
208
+ traceId?: string;
209
+ /** Wall-clock millis when the line was emitted. */
210
+ ts: number;
211
+ /** Acting userId, or absent when anonymous. */
212
+ userId?: string;
213
+ }
214
+ /**
215
+ * The OTel `SpanKind` union, in the spec's own words rather than its wire
216
+ * numbers, so a call site reads `{ kind: "client" }` instead of `{ kind: 3 }`.
217
+ *
218
+ * Kind is not cosmetic: a service map is built from it. A CLIENT span with no
219
+ * matching SERVER span on the other side is a dropped hop; PRODUCER/CONSUMER is
220
+ * what makes a queue render as an async edge rather than a synchronous call.
221
+ * Getting it wrong is why "everything is INTERNAL" traces produce no topology.
222
+ */
223
+ type OtlpSpanKind = "client" | "consumer" | "internal" | "producer" | "server";
224
+ /**
225
+ * One timestamped occurrence inside a span — OTel's `Span.events`.
226
+ *
227
+ * The right shape for something that has a moment but no duration: a retry, a
228
+ * cache miss, a validation failure, a thrown exception. Modelling those as
229
+ * near-zero-width child spans clutters the waterfall, and modelling them as
230
+ * separate log lines loses the "which span was I in" correlation that makes them
231
+ * useful in the first place.
232
+ */
233
+ interface SpanEventPoint {
234
+ /** Structured attributes, normalized like a span's own. */
235
+ attributes?: LogFields;
236
+ /** Event name, e.g. `"exception"` or `"cache.miss"`. */
237
+ name: string;
238
+ /** Wall-clock millis when it happened. */
239
+ ts: number;
240
+ }
241
+ /**
242
+ * A causal reference to a span in ANOTHER trace — OTel's `Span.links`.
243
+ *
244
+ * The standard answer to fan-in: a queue consumer processing a batch of 100
245
+ * messages links to the 100 producing spans rather than parenting to one of them
246
+ * (arbitrary) or all of them (impossible). The traces stay separately navigable
247
+ * and the causal edge survives.
248
+ */
249
+ interface SpanLink {
250
+ /** Attributes describing the relationship, e.g. `{ "link.kind": "enqueued_by" }`. */
251
+ attributes?: LogFields;
252
+ /** Linked span id (16-hex). */
253
+ spanId: string;
254
+ /** Linked trace id (32-hex). */
255
+ traceId: string;
256
+ }
257
+ /**
258
+ * One span produced by a `ctx.trace(name, fn)` call, or the synthetic root span
259
+ * the shard records for the dispatch itself so a waterfall has a bar to hang
260
+ * its children under.
261
+ *
262
+ * Ids are the same lowercase-hex form the OTLP encoders and `traceparent` use
263
+ * (32-hex trace, 16-hex span), so a `SpanEvent` composes into an OTLP span with
264
+ * no reformatting.
265
+ */
266
+ /**
267
+ * Handle the enclosing `ctx.trace` span hands its body, so the body can attach
268
+ * attributes only known *after* it resolves — an AI call's token usage or dollar
269
+ * cost, a downstream response's status, a computed row count. The start
270
+ * attributes passed to `ctx.trace(name, fn, attributes)` are snapshotted before
271
+ * the body runs (so a mid-span mutation can't rewrite them); anything set through
272
+ * this handle is merged over that snapshot at record time, with the post-hoc
273
+ * value winning on a key clash.
274
+ */
275
+ interface SpanHandle {
276
+ /**
277
+ * Record a timestamped {@link SpanEventPoint} on the enclosing span — a retry,
278
+ * a cache miss, a state transition. Prefer this over an extra `ctx.log` line
279
+ * for anything that only makes sense *relative to this span*: it rides the
280
+ * span's own export, so it costs no additional log record and can never be
281
+ * separated from its context.
282
+ */
283
+ addEvent: (name: string, attributes?: LogFields) => void;
284
+ /**
285
+ * Link this span to one in another trace (see {@link SpanLink}) — how a batch
286
+ * consumer points back at the requests that enqueued its items without
287
+ * collapsing every producer into one giant trace.
288
+ */
289
+ addLink: (link: SpanLink) => void;
290
+ /**
291
+ * Attach an AI **evaluation** verdict to this (generation) span as the
292
+ * `gen_ai.evaluation.<name>.score` / `.label` OpenTelemetry attributes, so a
293
+ * scorer's grade rides the same trace as the generation it graded and the
294
+ * collector reads it straight off the span. Convenience over
295
+ * {@link SpanHandle.setAttributes} that owns the key format; privacy-safe —
296
+ * only the name, score, and optional label are emitted, never the graded
297
+ * prompt or completion. Throws on an empty name or a non-finite score.
298
+ */
299
+ recordEvaluation: (evaluation: EvaluationInput) => void;
300
+ /**
301
+ * Record a caught exception as the OTel-conventional `exception` span event
302
+ * (`exception.type` / `exception.message` / `exception.stacktrace`).
303
+ *
304
+ * Distinct from letting the error propagate: this is for an error you
305
+ * **handled** — a retried request, a fallback that worked — which should be
306
+ * visible in the trace without marking the span failed. An error that escapes
307
+ * the span body is recorded automatically and *does* set the error status.
308
+ */
309
+ recordException: (error: unknown) => void;
310
+ /** Set one attribute on the enclosing span (merged at record time; post-hoc wins on key clash). */
311
+ setAttribute: (key: string, value: LogFields[string]) => void;
312
+ /** Merge attributes onto the enclosing span (post-hoc wins on key clash). */
313
+ setAttributes: (fields: LogFields) => void;
314
+ /**
315
+ * The W3C ids of the span this handle refers to.
316
+ *
317
+ * A handle that cannot say WHICH span it is forces every consumer that needs
318
+ * the identity — a `traceparent` for a hand-rolled outbound call, a trace id
319
+ * echoed in an error response so a user can quote it in a bug report, an
320
+ * `@opentelemetry/api` bridge parenting a third-party library's spans — to
321
+ * reach around the API for it.
322
+ */
323
+ spanContext: () => {
324
+ spanId: string;
325
+ traceId: string;
326
+ };
327
+ }
328
+ /** Options accepted by `ctx.trace(name, fn, options)` beyond the plain attribute bag. */
329
+ interface SpanOptions {
330
+ /** Start attributes, snapshotted before the body runs. */
331
+ attributes?: LogFields;
332
+ /**
333
+ * OTel `SpanKind`, default `"internal"`. Set `"client"` for a call OUT to
334
+ * another service, `"producer"`/`"consumer"` for queue hops — this is what a
335
+ * collector builds its service map from, so leaving everything `"internal"`
336
+ * yields a trace with no topology.
337
+ */
338
+ kind?: OtlpSpanKind;
339
+ /** Links to spans in other traces, known at start (see {@link SpanLink}). */
340
+ links?: SpanLink[];
341
+ }
342
+ interface SpanEvent {
343
+ /**
344
+ * Structured attributes the caller attached, already normalized to a fresh
345
+ * bag of JSON-safe primitives (see `shared/log-fields.ts`) exactly like a log
346
+ * line's `fields`. Absent when the caller passed none.
347
+ */
348
+ attributes?: LogFields;
349
+ /** Wall-clock duration of the span body, in milliseconds. */
350
+ durationMs: number;
351
+ /**
352
+ * Timestamped occurrences inside the span (see {@link SpanEventPoint}) —
353
+ * `ctx.trace`'s `span.addEvent(...)` / `span.recordException(...)`. Absent
354
+ * when the body recorded none.
355
+ */
356
+ events?: SpanEventPoint[];
357
+ /**
358
+ * Populated when the span body threw. `type` is the error's constructor name
359
+ * (or its `LunoraError` code); `message` is the human-readable string and may
360
+ * include user input, so sinks shipping to third parties should scrub it.
361
+ */
362
+ error?: {
363
+ message: string;
364
+ type: string;
365
+ };
366
+ /**
367
+ * Function path the span was created under, e.g. `"messages:list"`. A span
368
+ * created inside a function invoked via `ctx.runQuery`/`runMutation`/
369
+ * `runAction` carries the OUTER entrypoint's path, since the composed call
370
+ * reuses its context — the same attribution rule `ctx.log` follows.
371
+ */
372
+ functionPath: string;
373
+ /**
374
+ * OTel `SpanKind`. Absent means `"internal"` — the overwhelming majority of
375
+ * `ctx.trace` spans — so the common case costs no bytes on the wire and every
376
+ * pre-existing recorded span stays valid.
377
+ */
378
+ kind?: OtlpSpanKind;
379
+ /** Causal references to spans in other traces (see {@link SpanLink}). Absent when none. */
380
+ links?: SpanLink[];
381
+ /** Caller-supplied span name, e.g. `"stripe.charge"`. */
382
+ name: string;
383
+ /** True when the span body returned without throwing. */
384
+ ok: boolean;
385
+ /**
386
+ * Span id of the enclosing span — the parent `ctx.trace` when nested, else
387
+ * the dispatch's own RPC span (from the inbound `traceparent`). A span with
388
+ * no inbound trace context is parented to a locally-minted root, so this is
389
+ * always set for a `ctx.trace` span; only the synthetic `dispatch` span below
390
+ * carries `""`, meaning "nothing above me in this trace".
391
+ */
392
+ parentSpanId: string;
393
+ /**
394
+ * True for the synthetic span representing the **dispatch itself**, which the
395
+ * shard records so a waterfall has a bar for the request to hang its
396
+ * `ctx.trace` spans under.
397
+ *
398
+ * Named for what it is rather than "root": it is not the root of the
399
+ * collector-side trace — the worker's own RPC span sits above it — and it is
400
+ * never exported to a sink, because the runtime already emits that dispatch
401
+ * via `onRpc` and a collector would otherwise show it twice. Locally it *is*
402
+ * the outermost span, which is why the fold prefers it as a trace's anchor.
403
+ */
404
+ dispatch?: boolean;
405
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
406
+ shardKey?: string;
407
+ /** This span's own id (16-hex). */
408
+ spanId: string;
409
+ /** Wall-clock millis when the span started. */
410
+ startTs: number;
411
+ /** Trace this span belongs to (32-hex) — shared with the dispatch's logs. */
412
+ traceId: string;
413
+ /** Acting userId, or absent when anonymous. */
414
+ userId?: string;
415
+ }
416
+ /**
417
+ * Structural shape of the `ctx.trace` span factory (see the server
418
+ * `LunoraTracer`). Declared here rather than imported so `@lunora/do` takes no
419
+ * dependency on `@lunora/server`; a cross-package assignability guard in
420
+ * `@lunora/testing` fails the build if the two drift apart.
421
+ *
422
+ * The body's second argument is the enclosing span's {@link SpanHandle}, through
423
+ * which it can attach attributes only known *after* it resolves (post-hoc). It is
424
+ * a trailing parameter, so a `(trace) => …` body that ignores it still conforms.
425
+ */
426
+ type ContextTracer = <T>(name: string, function_: (trace: ContextTracer, span: SpanHandle) => Promise<T> | T, options?: LogFields | SpanOptions) => Promise<T>;
427
+ /** Structural shape of the `ctx.metrics` recorder (see the server `LunoraMetrics`). */
428
+ interface ContextMetrics {
429
+ count: (name: string, value?: number, attributes?: LogFields) => void;
430
+ gauge: (name: string, value: number, attributes?: LogFields) => void;
431
+ record: (name: string, value: number, attributes?: LogFields) => void;
432
+ }
433
+ /** The trace a ctx's spans hang off: the shared id, and the span they parent to. */
434
+ interface TraceAnchor {
435
+ rootSpanId: string;
436
+ /**
437
+ * The W3C `sampled` verdict for this trace, inherited from the inbound
438
+ * `traceparent` when there was one. Carried on the anchor rather than
439
+ * re-derived per outbound call so every `ctx.fetch` of one dispatch
440
+ * propagates the same answer.
441
+ */
442
+ sampled?: boolean;
443
+ traceId: string;
444
+ }
445
+ /**
446
+ * Minimal structural shape of one **host-native custom span** — the object a
447
+ * `tracing.enterSpan(name, (span) => …)` callback receives (GA 2026-06-16). Only
448
+ * the surface the bridge touches is declared, so `@lunora/do` needs no runtime
449
+ * dependency on `cloudflare:workers`; the real platform span is structurally
450
+ * assignable.
451
+ */
452
+ /**
453
+ * A host-supplied span, structurally.
454
+ *
455
+ * Named for the role rather than the provider: this is whatever the runtime's
456
+ * own tracer hands back, and Cloudflare's `enterSpan` callback argument is one
457
+ * shape that satisfies it. Kept structural so no provider type is imported —
458
+ * the repo's documented `*Like` pattern.
459
+ */
460
+ interface HostSpanLike {
461
+ /**
462
+ * Whether this span is actually being recorded by the runtime's sampler.
463
+ * `false` off the traced path (unsampled) — the bridge skips its
464
+ * `setAttribute` work in that case rather than building attribute strings for
465
+ * a span nobody will read.
466
+ */
467
+ readonly isTraced: boolean;
468
+ /** Attach one primitive attribute to the CF span. */
469
+ setAttribute: (key: string, value: boolean | number | string | undefined) => void;
470
+ }
471
+ /**
472
+ * Minimal structural shape of the `tracing` namespace exported by
473
+ * `cloudflare:workers`. `enterSpan` opens a custom span that auto-nests under the
474
+ * runtime's ambient span and ends when `callback` settles.
475
+ */
476
+ interface HostTracingLike {
477
+ enterSpan: <T>(name: string, callback: (span: HostSpanLike) => T) => T;
478
+ }
479
+ /**
480
+ * Resolves CF's `tracing` namespace, or `undefined` when it is unavailable —
481
+ * on a host with no native tracer, on a Cloudflare compat date predating custom spans, or when
482
+ * `tracing.enterSpan` is not a function. **Injected, never imported here**, so the
483
+ * tracer stays pure and unit-testable without `cloudflare:workers`; the shard
484
+ * supplies the real resolver, tests a fake or `undefined`.
485
+ */
486
+ type HostTracingResolver = () => HostTracingLike | Promise<HostTracingLike | undefined> | undefined;
487
+ /** What {@link createTracer} needs from the shard to build a span. */
488
+ interface TracerDeps {
489
+ /** The trace this ctx's spans belong to. */
490
+ anchor: TraceAnchor;
491
+ /** Function path the spans are attributed to. */
492
+ functionPath: string;
493
+ /**
494
+ * **Opt-in, EXPERIMENTAL, default off.** When `true` *and*
495
+ * {@link TracerDeps.resolveHostTracing} yields a working
496
+ * `tracing.enterSpan`, each `ctx.trace` span is ALSO emitted as a host-native
497
+ * **custom span**, so it nests inside CF's native binding/fetch/handler trace
498
+ * tree on the hosted path. This only ADDS a CF-side span — the recorded
499
+ * {@link SpanEvent} (our `SpanBuffer`/`otlpSink`) is untouched and remains the
500
+ * source of truth plus the local studio waterfall. The `enterSpan` call itself
501
+ * is now workerd-validated as available and side-effect-free inside a Durable
502
+ * Object; CF's exported parent-linking under sampling remains unverified. See
503
+ * {@link createTracer} for the double-export and Durable-Object async-context
504
+ * caveats.
505
+ */
506
+ fuseHostSpans?: boolean;
507
+ /** Hand a finished span to the buffer + sink. */
508
+ record: (span: SpanEvent) => void;
509
+ /**
510
+ * Injected resolver for CF's `tracing` namespace (see
511
+ * {@link HostTracingResolver}). Only consulted when
512
+ * {@link TracerDeps.fuseHostSpans} is `true`, so the default path never
513
+ * calls it.
514
+ */
515
+ resolveHostTracing?: HostTracingResolver;
516
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
517
+ shardKey: string | undefined;
518
+ /** Read lazily — the acting user is resolved per span, not per ctx. */
519
+ userId: () => string | undefined;
520
+ }
521
+ /** What {@link createMetrics} needs from the shard to build a measurement. */
522
+ interface MetricsDeps {
523
+ functionPath: string;
524
+ record: (event: MetricEvent) => void;
525
+ shardKey: string | undefined;
526
+ }
527
+ /** Everything a {@link SpanHandle}'s body attached, ready to merge into the recorded span. */
528
+ interface SpanCollection {
529
+ attributes: Record<string, LogFields[string]>;
530
+ events: SpanEventPoint[];
531
+ links: SpanLink[];
532
+ }
533
+ /** A {@link SpanHandle} plus read access to what it has collected so far. */
534
+ interface SpanCollector {
535
+ collected: SpanCollection;
536
+ handle: SpanHandle;
537
+ }
538
+ /**
539
+ * Build a span's post-hoc collection surface: the {@link SpanHandle} handed to a
540
+ * `ctx.trace` body, and the bag it writes into.
541
+ *
542
+ * Factored out because the same surface backs two things — every `ctx.trace`
543
+ * span, and the per-dispatch **wide event** (`ctx.span`), where the accumulated
544
+ * attributes become the canonical one-event-per-request summary. Sharing the
545
+ * implementation is what makes those two feel like the same API instead of two
546
+ * that happen to resemble each other.
547
+ */
548
+ declare const createSpanCollector: (ids: {
549
+ spanId: string;
550
+ traceId: string;
551
+ }) => SpanCollector;
552
+ /**
553
+ * Build the `ctx.trace` span factory for one dispatched function.
554
+ *
555
+ * **Nesting is explicit, not ambient.** Each span's body receives a tracer bound
556
+ * to that span; calling it is what makes a child. An earlier design kept an
557
+ * ambient stack of "the currently open span" and parented to its top, which
558
+ * reads nicer but is unfixably wrong under concurrency: in
559
+ * `Promise.all([trace("a", …), trace("b", …)])`, `b` starts while `a` is on the
560
+ * stack and is recorded as a *child* of `a` rather than its sibling — and
561
+ * parallel fan-out is one of the main things people reach for a tracer to
562
+ * measure. Distinguishing "called inside a's body" from "called concurrently
563
+ * with a" needs `AsyncLocalStorage`, which this package deliberately avoids (see
564
+ * `dependency-tracker.ts` — shard DOs run under a slimmer compat profile than
565
+ * `nodejs_compat`). So the parent is threaded, exactly like the dependency
566
+ * tracker and the subscription identity: correct in every case, and visible at
567
+ * the call site.
568
+ *
569
+ * The anchor is passed in for the same reason. `ShardDO.currentRequestTrace` is
570
+ * cleared in the dispatch `finally`, and a subscription re-run builds its ctx
571
+ * during* the writing mutation's flush — so reading that shared field at span
572
+ * time would file the re-run's spans under the mutation's trace.
573
+ *
574
+ * **Cloudflare custom-spans bridge (opt-in, EXPERIMENTAL).** When
575
+ * `deps.fuseHostSpans` is `true` and `deps.resolveHostTracing` yields
576
+ * a working `tracing.enterSpan` (`cloudflare:workers`, GA 2026-06-16), each span
577
+ * body runs inside a CF custom span so our span nests under CF's native
578
+ * binding/fetch/handler trace tree on the hosted path, and the finished span's
579
+ * key attributes are mirrored onto it (gated on `span.isTraced`). Two deliberate
580
+ * boundaries hold.
581
+ *
582
+ * **No double-export by default, and never a replacement.** The bridge only ADDS a
583
+ * CF-side span; the recorded {@link SpanEvent} handed to `record` (our
584
+ * `SpanBuffer`/`otlpSink`) is byte-for-byte the same as without the bridge and
585
+ * stays the source of truth. It is off unless explicitly enabled precisely
586
+ * because, once on, a deployment that ALSO ships our `otlpSink` to a collector
587
+ * AND lets CF export its trace tree emits the same logical span down two
588
+ * pipelines — an intentional, documented trade the operator opts into, not a
589
+ * default.
590
+ *
591
+ * **DO async-context caveat (EXPERIMENTAL, partially workerd-validated).**
592
+ * `tracing.enterSpan` is now confirmed to EXIST and RUN inside a real Durable
593
+ * Object under `@cloudflare/vitest-pool-workers` (see
594
+ * `__tests__/workerd/context-telemetry-cf-bridge.workerd.test.ts`): it resolves
595
+ * from `cloudflare:workers`, its callback executes and returns the body value
596
+ * without throwing, `span.isTraced` is a real boolean, and — the key additive
597
+ * guarantee — our recorded {@link SpanEvent} tree (parent/child via the threaded
598
+ * `parentSpanId`) is byte-for-byte identical with the bridge on vs off. What that
599
+ * harness CANNOT prove is CF's own *exported* parent-linking: with no trace head
600
+ * attached the run is unsampled (`isTraced === false`), so CF records nothing and
601
+ * its span tree is not introspectable. So `enterSpan`'s ambient-span parent-linking
602
+ * inside a DO stays unverified upstream, and this remains capability-probed: an
603
+ * absent/undefined `tracing`, a missing `enterSpan`, or an off-CF/unsampled run all
604
+ * resolve to `undefined`/no-op — exact prior behavior. If CF's ambient-span linkage
605
+ * misbehaves in a DO, the worst case is a mis-parented CF span; our own recorded
606
+ * waterfall is unaffected.
607
+ */
608
+ declare const createTracer: (deps: TracerDeps) => ContextTracer;
609
+ /** What {@link createTracedFetch} needs from the shard. */
610
+ interface TracedFetchDeps {
611
+ /** The trace the CLIENT spans belong to. */
612
+ anchor: TraceAnchor;
613
+ /** Function path the spans are attributed to. */
614
+ functionPath: string;
615
+ /**
616
+ * Whether to inject `traceparent` into the outbound request — and, with a
617
+ * predicate, to which destinations. Default `true`.
618
+ */
619
+ propagate?: ((url: URL) => boolean) | boolean;
620
+ /** Hand a finished span to the buffer + sink. */
621
+ record: (span: SpanEvent) => void;
622
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
623
+ shardKey: string | undefined;
624
+ /** Read lazily — the acting user is resolved per span. */
625
+ userId: () => string | undefined;
626
+ }
627
+ /** The `fetch` shape `ctx.fetch` exposes — the platform global's, narrowed to what we wrap. */
628
+ type ContextFetch = (input: Request | string | URL, init?: RequestInit) => Promise<Response>;
629
+ /**
630
+ * Build `ctx.fetch`: the platform `fetch`, wrapped so every outbound call
631
+ * becomes a **CLIENT span** and carries W3C trace context to the callee.
632
+ *
633
+ * Two gaps close here. First, an uninstrumented `fetch` makes the single most
634
+ * common source of latency — waiting on somebody else's service — invisible: a
635
+ * handler that spends 900ms in Stripe shows one opaque 900ms bar. Second,
636
+ * without an outbound `traceparent` the callee starts a brand-new trace, so the
637
+ * two halves of one logical request can never be stitched together, which is the
638
+ * entire premise of distributed tracing.
639
+ *
640
+ * The span id is minted BEFORE the request is sent, precisely so the header
641
+ * announces the id the span will actually be recorded under. Deriving it
642
+ * afterwards (or reusing the parent's) would produce a `traceparent` naming a
643
+ * span that never existed, and a callee parented to nothing.
644
+ *
645
+ * Kind is `client` rather than `internal` — that is what lets a collector draw
646
+ * the edge to the downstream service in a service map.
647
+ *
648
+ * Failures are recorded and re-thrown untouched, and a non-2xx response is
649
+ * recorded as an ERROR span (it is a failed call from the caller's point of
650
+ * view) while still being returned normally — instrumentation, never flow
651
+ * control.
652
+ */
653
+ declare const createTracedFetch: (deps: TracedFetchDeps, base: ContextFetch) => ContextFetch;
654
+ /**
655
+ * Build the `ctx.metrics` recorder for one dispatched function.
656
+ *
657
+ * Deliberately stateless: each call emits one measurement rather than
658
+ * accumulating into a per-dispatch map. Pre-aggregating here would have to pick a
659
+ * flush point and a merge rule per instrument kind (sum a counter, last-wins a
660
+ * gauge, and a histogram cannot be merged at all without losing the
661
+ * distribution) — so the runtime stays a transport and the collector, which is
662
+ * built for exactly this, does the aggregation.
663
+ */
664
+ declare const createMetrics: (deps: MetricsDeps) => ContextMetrics;
665
+ /**
666
+ * Build the synthetic root span for a finished dispatch — the bar the studio's
667
+ * waterfall hangs a request's `ctx.trace` spans under.
668
+ *
669
+ * Pure: the caller decides whether to record it (only when the dispatch actually
670
+ * produced spans) and where to put it. It is never routed to `sink.onSpan`,
671
+ * because the runtime already emits the dispatch to `onRpc` and a collector would
672
+ * otherwise show it twice.
673
+ */
674
+ declare const dispatchRootSpan: (input: {
675
+ anchor: TraceAnchor;
676
+ /**
677
+ * What the handler attached to the dispatch through `ctx.span` — the **wide
678
+ * event**. These are the attributes that would otherwise have been scattered
679
+ * across a dozen `ctx.log` lines; carrying them on the one span that already
680
+ * exists per request is the OTel-native way to get a wide event without
681
+ * multiplying log records.
682
+ */
683
+ collected?: SpanCollection;
684
+ durationMs: number;
685
+ failure: {
686
+ thrown: unknown;
687
+ } | undefined;
688
+ functionPath: string;
689
+ shardKey: string | undefined;
690
+ startTs: number;
691
+ userId: string | undefined;
692
+ }) => SpanEvent;
693
+ /** Running totals for `"summary"` mode; created by the caller, read once at the dispatch boundary. */
694
+ interface DatabaseTally {
695
+ calls: number;
696
+ durationMs: number;
697
+ errors: number;
698
+ perOperation: Record<string, number>;
699
+ spansEmitted: number;
700
+ spansTruncated: boolean;
701
+ }
702
+ /**
703
+ * How much detail `ctx.db` auto-instrumentation produces.
704
+ *
705
+ * `"summary"` (default) — aggregate counters on the dispatch's wide event: no
706
+ * extra spans, no extra log records, and a cost that does not grow with call count.
707
+ *
708
+ * `"spans"` — one span per database call. The full waterfall, at the price of a
709
+ * span per call; right when diagnosing, noisy as a permanent default.
710
+ *
711
+ * `"off"` — no database telemetry at all.
712
+ */
713
+ type DatabaseInstrumentation = "off" | "spans" | "summary";
714
+ /** What {@link instrumentDatabase} needs to record what it observes. */
715
+ interface DatabaseTelemetryDeps {
716
+ /** The trace produced spans belong to (`"spans"` mode only). */
717
+ anchor: TraceAnchor;
718
+ /** Function path spans and attributes are attributed to. */
719
+ functionPath: string;
720
+ /** Detail level; see {@link DatabaseInstrumentation}. */
721
+ mode: DatabaseInstrumentation;
722
+ /** Hand a finished span to the buffer + sink (`"spans"` mode only). */
723
+ record: (span: SpanEvent) => void;
724
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
725
+ shardKey: string | undefined;
726
+ /**
727
+ * Caller-supplied accumulator for `"summary"` mode. The instrumenter only ever
728
+ * increments numbers on it; the shard reads it ONCE at the dispatch boundary
729
+ * and formats it with {@link formatTally}.
730
+ *
731
+ * Two properties fall out of that split. Per-call cost stays at a few integer
732
+ * increments — no object allocation, no lookup — which matters because this is
733
+ * on the path of every query. And because nothing is written through the
734
+ * dispatch's `SpanHandle`, the wide-event collector is never materialized, so
735
+ * a handler that instrumented nothing still doesn't trip the root-span gate.
736
+ */
737
+ tally: DatabaseTally;
738
+ /** Read lazily — the acting user is resolved per span. */
739
+ userId: () => string | undefined;
740
+ }
741
+ /**
742
+ * Wrap a `ctx.db` writer so its storage-touching methods are instrumented.
743
+ *
744
+ * Returns the database unchanged when `mode` is `"off"`, so the default-disabled
745
+ * path costs nothing — not even a proxy indirection.
746
+ *
747
+ * Implemented as a `Proxy` rather than by enumerating and rebinding methods:
748
+ * `DatabaseWriterLike` has optional members that a given backend may or may not
749
+ * implement, plus properties (`system`) and builder factories (`query`) that
750
+ * must pass through untouched. A proxy instruments exactly what it is asked to
751
+ * and is transparently correct for everything else, including members added
752
+ * later — an enumeration would silently stop covering them.
753
+ */
754
+ declare const instrumentDatabase: <T extends object>(database: T, deps: DatabaseTelemetryDeps) => T;
755
+ /** A zero'd tally for one dispatch. */
756
+ declare const createDatabaseTally: () => DatabaseTally;
757
+ /**
758
+ * Render the running tally as span attributes.
759
+ *
760
+ * Called ONCE per dispatch, from the shard's root-span recorder — not per query.
761
+ * Building this object on every call was pure waste on a hot path. It is a handful of keys, so the per-call cost is a small object
762
+ * assignment — the property that makes `"summary"` mode scale to any call count.
763
+ */
764
+ declare const formatTally: (tally: DatabaseTally) => LogFields;
765
+ /** Reserved per-function accumulator table. Auto-hidden from the data browser by the `__lunora` prefix. */
766
+ declare const FUNCTION_METRICS_TABLE = "__lunora_metrics";
767
+ /** Reserved coarse time-series table: per-function call/error counts bucketed by a fixed window. */
768
+ declare const FUNCTION_METRICS_BUCKETS_TABLE = "__lunora_metrics_buckets";
769
+ /** Reserved causal full-scan attribution table: per-(function, table) full-scan counts. */
770
+ declare const FUNCTION_METRICS_SCANS_TABLE = "__lunora_metrics_scans";
771
+ /** Reserved per-(table, index) hit-counter table backing the advisor dead-index lint. */
772
+ declare const FUNCTION_METRICS_INDEX_TABLE = "__lunora_metrics_index";
773
+ /**
774
+ * Width of one history bucket, in milliseconds. 60s gives a minute-resolution
775
+ * time series — fine-grained enough to chart bursts on the studio, coarse
776
+ * enough that a single function emits at most one row per minute. Exported so
777
+ * consumers (and tests) can align timestamps to the same grid.
778
+ */
779
+ declare const FUNCTION_METRICS_BUCKET_MS = 6e4;
780
+ /**
781
+ * Most recent buckets kept per function; older rows are trimmed after each
782
+ * write so the time series can't grow unbounded. 1440 minute-buckets ≈ 24h of
783
+ * history per function.
784
+ */
785
+ declare const FUNCTION_METRICS_BUCKET_RETENTION = 1440;
786
+ /**
787
+ * Maximum distinct function `path`s tracked in the accumulator table. Mirrors
788
+ * `query-metrics.ts`'s `QUERY_METRICS_MAX_STATEMENTS` cap (and exists for the
789
+ * same reason): the `path` is attacker-reachable — an unregistered/`FUNCTION_NOT_FOUND`
790
+ * dispatch still records a row keyed by the caller-supplied `functionPath` — so
791
+ * without a cap a flood of distinct random paths would grow `__lunora_metrics`
792
+ * (and its bucket/scan satellites) without bound, eventually filling the shard's
793
+ * SQLite store shared with the app's real data. A few thousand registered
794
+ * functions is already far beyond any real app, so a new path past this cap is
795
+ * dropped while already-tracked paths keep accumulating.
796
+ */
797
+ declare const FUNCTION_METRICS_MAX_PATHS = 5e3;
798
+ /**
799
+ * Upper bound on rows the admin reads materialize into DO memory at once. Even
800
+ * with the write-side `FUNCTION_METRICS_MAX_PATHS` cap in place, an existing
801
+ * shard could already hold a bloated accumulator (rows written before the cap
802
+ * landed), so the read path also clamps — a `SELECT *` with no LIMIT would
803
+ * otherwise load every row via `.toArray()` and risk OOMing the ~128MB DO when
804
+ * the studio Function Stats panel opens. Ordered reads keep the busiest/most
805
+ * recent rows; the tail past this limit is simply not returned to the panel.
806
+ */
807
+ declare const FUNCTION_METRICS_READ_LIMIT = 1e3;
808
+ /** One coarse time-series sample for a function: call/error counts within `[bucketMs, bucketMs + FUNCTION_METRICS_BUCKET_MS)`. */
809
+ interface FunctionMetricBucket {
810
+ /** Epoch-ms floor of the bucket window. */
811
+ bucketMs: number;
812
+ /** Dispatches recorded in this window. */
813
+ calls: number;
814
+ /** Subset of `calls` that threw. */
815
+ errors: number;
816
+ }
817
+ /** One declared index a dispatch exercised (used to narrow a read). */
818
+ interface IndexHit {
819
+ /** The declared index name. */
820
+ index: string;
821
+ /** The table the index is declared on. */
822
+ table: string;
823
+ }
824
+ /** One declared index's cumulative recorded read count (durable, non-decaying) — the advisor dead-index lint input. */
825
+ interface FunctionMetricIndexHit {
826
+ /** The declared index name. */
827
+ index: string;
828
+ /** Recorded reads that used this index to narrow. */
829
+ reads: number;
830
+ /** The table the index is declared on. */
831
+ table: string;
832
+ }
833
+ /** Fields recorded for one completed dispatch. `errored` advances the failure counters. */
834
+ interface RecordFunctionMetricInput {
835
+ /**
836
+ * Whether the dispatch failed on an optimistic-concurrency (OCC) write
837
+ * conflict — a compare-and-swap that lost to a concurrent commit. Advances
838
+ * the durable `conflicts` counter behind the write-contention advisor. A
839
+ * conflicted dispatch also `errored`, so conflicts are a subset of errors.
840
+ * Omitted/false on the common path, keeping the hot path unchanged.
841
+ */
842
+ conflicted?: boolean;
843
+ /** Wall-clock millis the handler took. */
844
+ durationMs: number;
845
+ /** Whether the dispatch threw. */
846
+ errored: boolean;
847
+ /** Most recent failure message, recorded only when `errored`. */
848
+ errorMessage?: string;
849
+ /**
850
+ * Distinct declared indexes this dispatch exercised (used to narrow a read),
851
+ * collected from the `onIndexUse` signal. Each entry bumps the
852
+ * per-`(table, index)` hit counter in `__lunora_metrics_index`, the durable
853
+ * producer behind the advisor dead-index lint. Omitted/empty when the
854
+ * dispatch used no declared index, keeping the hot path unchanged.
855
+ */
856
+ indexHits?: ReadonlyArray<IndexHit>;
857
+ /** The `&lt;file>:&lt;function>` identifier. */
858
+ path: string;
859
+ /**
860
+ * Distinct tables this dispatch full-scanned (read with no index / point
861
+ * lookup), collected from the `SCAN_DEP` reads. Each entry bumps the
862
+ * aggregate `scans` counter and the per-`(path, table)` attribution row.
863
+ * Omitted/empty when the dispatch didn't full-scan anything (the common
864
+ * indexed case), keeping the hot path to the same two upserts as before.
865
+ */
866
+ scannedTables?: ReadonlyArray<string>;
867
+ /** Epoch-ms the dispatch completed. */
868
+ ts: number;
869
+ }
870
+ /**
871
+ * Create the four reserved metrics tables. Idempotent, so the read and write
872
+ * paths can call it defensively. The accumulator table is keyed by `path` (one
873
+ * row per function); the bucket table by `(path, bucketMs)` (one row per
874
+ * function per window); the scans table by `(path, table)` (one row per
875
+ * function per full-scanned table); the index table by `(table, index)` (one
876
+ * row per declared index the app exercises).
877
+ *
878
+ * The accumulator's `scans` column is added via a guarded `ALTER TABLE` rather
879
+ * than baked into the `CREATE` so a shard whose `__lunora_metrics` predates the
880
+ * causal-attribution feature gains the column on the next call without a
881
+ * migration. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the duplicate-column
882
+ * error from a re-run is swallowed.
883
+ */
884
+ declare const ensureFunctionMetricsTables: (sql: SqlExec) => void;
885
+ /**
886
+ * Persist one completed dispatch: a single upsert into the accumulator row and
887
+ * one upsert into the current time bucket, then a bounded trim of old buckets
888
+ * for that path. Creates the tables first so callers needn't. This is the hot
889
+ * path — exactly two `INSERT … ON CONFLICT … DO UPDATE` statements plus a
890
+ * bounded `DELETE`, all keyed by primary key, so it stays cheap.
891
+ *
892
+ * When the dispatch full-scanned one or more tables (`scannedTables`), the
893
+ * aggregate `scans` counter on the accumulator row advances by the distinct
894
+ * table count and one extra `(path, table)` upsert fires per scanned table.
895
+ * Indexed dispatches (the common case) skip all of that and pay nothing.
896
+ */
897
+ declare const recordFunctionMetric: (sql: SqlExec, input: RecordFunctionMetricInput) => void;
898
+ /**
899
+ * Read the per-function full-scan attribution, grouped by function path. The
900
+ * returned map keys are `path`; each value is the function's full-scanned
901
+ * tables ordered by scan count (busiest scan first), so the causal "slow
902
+ * BECAUSE it scanned X" read can lead with the dominant table. Creates the
903
+ * table first so reads on a never-called shard return an empty map.
904
+ */
905
+ declare const readFunctionMetricScans: (sql: SqlExec) => Map<string, FunctionScanAttribution[]>;
906
+ /**
907
+ * Read the per-`(table, index)` hit counts — the advisor dead-index lint input.
908
+ * Each entry is a declared index and how many recorded reads used it to narrow
909
+ * (a cumulative, non-decaying count); the lint reconciles this against the schema
910
+ * to flag a declared index that appears with zero reads (or not at all) as dead. Ordered
911
+ * by table then index for stable output. Creates the table first so a read on a
912
+ * never-exercised shard returns `[]`.
913
+ */
914
+ declare const readFunctionMetricIndexHits: (sql: SqlExec) => FunctionMetricIndexHit[];
915
+ /**
916
+ * Fold a dispatch's distinct full-scanned tables into an in-memory attribution
917
+ * list, mirroring the per-`(path, table)` upsert {@link recordFunctionMetric}
918
+ * applies to the durable `__lunora_metrics_scans` table. Kept here, beside its
919
+ * SQL twin, so the one rule (one occurrence = +1 scan for that table, list
920
+ * re-sorted busiest-first) lives in a single module — the in-memory copy exists
921
+ * only for the warm-instance fallback when the durable read is unavailable.
922
+ * Mutates and returns `into`.
923
+ */
924
+ declare const mergeScanAttribution: (into: FunctionScanAttribution[], scanned: ReadonlyArray<string>) => FunctionScanAttribution[];
925
+ /**
926
+ * Read the persisted per-function accumulators as {@link FunctionCallStat}s,
927
+ * newest-called first. Creates the table first so reads on a never-called shard
928
+ * return `[]` instead of throwing. The shape is a superset of the legacy
929
+ * in-memory `getFunctionStats` rows — the additive `scans` total and
930
+ * `scannedTables` causal attribution are folded in here so a single read backs
931
+ * the Insights "missing index" / "full scan" signal.
932
+ */
933
+ declare const readFunctionMetrics: (sql: SqlExec) => FunctionCallStat[];
934
+ /**
935
+ * Read the coarse time-series buckets for `path` (every path when omitted),
936
+ * oldest-bucket first so a chart can plot them left-to-right. Creates the table
937
+ * first so reads on a never-called shard return `[]`.
938
+ */
939
+ declare const readFunctionMetricBuckets: (sql: SqlExec, path?: string) => (FunctionMetricBucket & {
940
+ path: string;
941
+ })[];
942
+ /**
943
+ * Aggregate the persisted accumulators into the lifetime totals the metrics
944
+ * health snapshot reports: total calls (`requests`), total `errors`, and the
945
+ * earliest `last_called_at` seen — a best-effort "since" marker for durable
946
+ * data. Returns zeroes on a never-called shard.
947
+ */
948
+ declare const readFunctionMetricsTotals: (sql: SqlExec) => {
949
+ errors: number;
950
+ requests: number;
951
+ };
952
+ /**
953
+ * The Workers AI text model the Issue explainer uses when the caller does not
954
+ * override it. The fp8-fast instruct model the rest of the repo defaults to —
955
+ * the explainer is a short, grounded rewrite (not a reasoning task), so a
956
+ * latency-optimized build beats a larger one. Deliberately not the retired
957
+ * `@cf/meta/llama-3.1-8b-instruct`: a deprecated model-id makes `binding.run`
958
+ * throw, which would silently degrade every explain to `"ai-error"`.
959
+ */
960
+ /**
961
+ * Fallback model id when neither the caller nor the request names one.
962
+ *
963
+ * A Workers AI id, which makes it the *host's* default rather than this
964
+ * package's — a second host runs different models and must be able to say so.
965
+ * `explainIssue` therefore takes `defaultModel`, and this constant is only what
966
+ * the Cloudflare host happens to pass. Kept exported so that host has a name to
967
+ * pass rather than a string literal.
968
+ */
969
+ declare const DEFAULT_EXPLAIN_ISSUE_MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
970
+ /**
971
+ * Structural projection of the Workers `AI` binding's `run` method — declared
972
+ * locally so `@lunora/do` needs no dependency edge on `@lunora/ai` (nor on
973
+ * `@cloudflare/workers-types`) to reach `env.AI`. Mirrors `AiBindingLike` in
974
+ * `@lunora/ai`.
975
+ */
976
+ interface AiRunBinding {
977
+ run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
978
+ }
979
+ /** Parsed `__lunora_admin__:explainIssue` payload: the folded Issue's identifying facts, plus an optional model override. */
980
+ interface ExplainIssueArgs {
981
+ /** The Issue's culprit (`&lt;file>:&lt;function>` or `container:&lt;name>`), for grounding context. */
982
+ culprit?: string;
983
+ /** Per-request model-id override; falls back to the caller's `defaultModel`, then {@link DEFAULT_EXPLAIN_ISSUE_MODEL}. */
984
+ model?: string;
985
+ /** A representative raw error message for the Issue — the fact the explanation is grounded in. Required. */
986
+ sampleMessage: string;
987
+ /** The Issue's human-readable title (first line of the sample message), for grounding context. */
988
+ title?: string;
989
+ }
990
+ /**
991
+ * Why the explainer fell back to the grounded hint alone. A closed union rather
992
+ * than a bare `string` so `degraded(reason)` rejects a typo'd sentinel at compile
993
+ * time instead of letting it fall through to the client's generic error copy.
994
+ * Mirrored by `ExplainIssueResult["reason"]` in `@lunora/studio`'s `lib/admin.ts`.
995
+ */
996
+ type ExplainIssueDegradedReason = "ai-error" | "empty-response" | "no-ai-binding";
997
+ /**
998
+ * The grounding facts both `__lunora_admin__:explainIssue` outcomes carry. Present
999
+ * whenever {@link findIssueSolution} recognized the message — offline,
1000
+ * deterministic, and independent of whether the AI path ran at all.
1001
+ *
1002
+ * The hint BODY is deliberately not on the wire: the client derives it from the
1003
+ * same catalog offline (that is the whole point of the grounded layer), so
1004
+ * shipping it would be payload nothing reads.
1005
+ */
1006
+ interface ExplainIssueGrounding {
1007
+ /**
1008
+ * The id of the matched catalog/platform solution the prompt was grounded in,
1009
+ * absent when nothing recognized the message. The client renders a caveat on
1010
+ * absence — an ungrounded explanation is a free-form model guess, not a
1011
+ * catalog-backed one, and must not be presented as the latter.
1012
+ */
1013
+ groundedId?: string;
1014
+ }
1015
+ /**
1016
+ * The `__lunora_admin__:explainIssue` result — a discriminated union on `degraded`
1017
+ * rather than a bag of optionals, so each outcome's guaranteed fields are
1018
+ * guaranteed in the type too. The AI `explanation` is best-effort: the degraded
1019
+ * arm is returned when no `env.AI` binding is configured or the inference call
1020
+ * failed, and the client falls back to its own grounded hint alone.
1021
+ *
1022
+ * Modelling this as one flat interface let a `degraded` result type-check without a
1023
+ * `reason`, which the studio silently renders as the generic AI-error copy.
1024
+ */
1025
+ /** The arm returned when no inference happened, or it failed. */
1026
+ interface ExplainIssueDegraded extends ExplainIssueGrounding {
1027
+ /** The AI path was unavailable or failed — render the grounded hint instead. */
1028
+ degraded: true;
1029
+ /** Why the AI path degraded, for the client to surface. */
1030
+ reason: ExplainIssueDegradedReason;
1031
+ }
1032
+ /** The arm returned when the model ran and produced text. */
1033
+ interface ExplainIssueSuccess extends ExplainIssueGrounding {
1034
+ /** The AI path ran and produced text. */
1035
+ degraded: false;
1036
+ /** The AI-generated plain-language explanation. */
1037
+ explanation: string;
1038
+ /** The Workers AI model-id that produced {@link ExplainIssueSuccess.explanation}. */
1039
+ model: string;
1040
+ }
1041
+ type ExplainIssueResult = ExplainIssueDegraded | ExplainIssueSuccess;
1042
+ /**
1043
+ * Validate the `__lunora_admin__:explainIssue` payload. Requires a non-empty
1044
+ * `sampleMessage` (the grounding fact); `title`, `culprit`, and `model` are
1045
+ * optional context/overrides. Throws a 400 `LunoraError` on a bad shape.
1046
+ *
1047
+ * Every caller-supplied field that reaches the prompt is capped here — capping
1048
+ * `sampleMessage` alone left `title`/`culprit` as an open door onto the same
1049
+ * prompt budget.
1050
+ */
1051
+ declare const parseExplainIssueArgs: (args: Record<string, unknown>) => ExplainIssueArgs;
1052
+ /**
1053
+ * Run the full explain flow for one Issue: validate the payload, ground it in the
1054
+ * catalog, and — when `binding` is a usable Workers AI binding — ask the model for
1055
+ * a plain-language rewrite. Never throws for an AI-side failure; every such path
1056
+ * returns the `degraded: true` arm carrying the grounded hint, so the caller
1057
+ * always has something to render. Only a malformed payload throws (a 400).
1058
+ *
1059
+ * `binding` is `unknown` so the caller can hand over `env.AI` untyped — the shape
1060
+ * check lives here rather than at each call site.
1061
+ */
1062
+ declare const explainIssue: (binding: unknown, args: Record<string, unknown>, options?: {
1063
+ defaultModel?: string;
1064
+ }) => Promise<ExplainIssueResult>;
1065
+ /** Reserved table holding one triage-state row per Issue fingerprint. Auto-hidden by the `__lunora` prefix. */
1066
+ declare const ISSUE_STATE_TABLE = "__lunora_issue_state__";
1067
+ /**
1068
+ * Triage status of an Issue. `open` is the implicit default (no state row); a
1069
+ * developer moves it to `resolved` (fixed, but a *new* matching error re-opens
1070
+ * it — see {@link readIssueStates}'s consumer) or `ignored` (deliberately muted,
1071
+ * and stays muted regardless of new occurrences).
1072
+ */
1073
+ type IssueStatus = "ignored" | "open" | "resolved";
1074
+ /** Ordered severity a developer can tag an Issue with; drives the Studio badge palette. */
1075
+ type IssueSeverity = "critical" | "high" | "low" | "medium";
1076
+ /** The persisted triage state for one Issue fingerprint. */
1077
+ interface IssueState {
1078
+ /** Free-form assignee (a userId or a name); absent when unassigned. */
1079
+ assignee?: string;
1080
+ /** Stable 16-char fingerprint hash — the same key `readErrorIssues` folds on. */
1081
+ hash: string;
1082
+ /** Developer-tagged severity; absent when untriaged. */
1083
+ severity?: IssueSeverity;
1084
+ /** Current triage status. */
1085
+ status: IssueStatus;
1086
+ /** Wall-clock millis the state was last changed — compared against an Issue's `lastSeen` to detect a regression. */
1087
+ updatedAt: number;
1088
+ /** Acting userId that last changed the state, when known. */
1089
+ updatedBy?: string;
1090
+ }
1091
+ /** Patch applied by an admin write; every field is optional so a caller can change one facet at a time. */
1092
+ interface IssueStatePatch {
1093
+ assignee?: null | string;
1094
+ severity?: IssueSeverity | null;
1095
+ status?: IssueStatus;
1096
+ }
1097
+ /** The valid {@link IssueStatus} values, for arg validation at the admin boundary. */
1098
+ declare const ISSUE_STATUSES: ReadonlyArray<IssueStatus>;
1099
+ /** The valid {@link IssueSeverity} values, for arg validation at the admin boundary. */
1100
+ declare const ISSUE_SEVERITIES: ReadonlyArray<IssueSeverity>;
1101
+ /**
1102
+ * Apply a triage patch to one Issue, upserting its state row. A `null` in the
1103
+ * patch clears the field (unassign, untag severity); an omitted field is left
1104
+ * unchanged. Returns the resulting {@link IssueState} so the caller can echo it.
1105
+ *
1106
+ * Uses `ON CONFLICT(hash) DO UPDATE` with `COALESCE(?, column)` per optional
1107
+ * field so a partial patch touches only what it names — except the explicit
1108
+ * `null` sentinels for assignee/severity, which are threaded separately so a
1109
+ * clear can win over `COALESCE`.
1110
+ */
1111
+ declare const upsertIssueState: (sql: SqlExec, hash: string, patch: IssueStatePatch, updatedAt: number, updatedBy?: string) => IssueState;
1112
+ /**
1113
+ * Severity of a buffered log entry — the full seven-tier `ctx.log` ramp
1114
+ * (`trace`→`fatal`), not a console-shaped subset. The buffer used to fold the
1115
+ * ramp onto four tiers, which made `trace` and `fatal` lines indistinguishable
1116
+ * from `debug` and `error` in the Studio Logs panel; it now stores the level the
1117
+ * caller actually logged at. Container-lifecycle entries only ever use
1118
+ * `info`/`error`, which remain part of the union.
1119
+ */
1120
+ type LogLevel = ContextLogLevel;
1121
+ /**
1122
+ * One buffered log line. `functionPath` is the RPC that produced it (when the
1123
+ * entry came from the RPC dispatch site); `timestamp` is `Date.now()` at the
1124
+ * moment it was pushed. `instance`/`exitCode` are populated for container
1125
+ * lifecycle entries: `instance` correlates the per-instance Durable Object id,
1126
+ * `exitCode` carries the process exit code parsed out of a `stop` event.
1127
+ */
1128
+ interface LogEntry {
1129
+ exitCode?: number;
1130
+ /** Structured fields from a `ctx.log.&lt;level>(message, fields)` / `ctx.log.with(fields)` call, when present. */
1131
+ fields?: Record<string, unknown>;
1132
+ functionPath?: string;
1133
+ instance?: string;
1134
+ level: LogLevel;
1135
+ message: string;
1136
+ timestamp: number;
1137
+ }
1138
+ /**
1139
+ * A bounded, in-memory ring buffer of recent {@link LogEntry} records.
1140
+ *
1141
+ * In-memory only: like the metrics counters on `ShardDO`, the buffer is a field
1142
+ * on the live Durable Object instance and so resets whenever the DO hibernates
1143
+ * or restarts. It is a "recent activity on this instance" readout (for the
1144
+ * studio's live log panel), NOT a durable log store or a transport.
1145
+ * Production log shipping is the platform's job, not ours: use Cloudflare
1146
+ * **Workers Logs** (retained, queryable in the studio), **Logpush** (stream
1147
+ * to R2 / a SIEM / a log service), or a **Tail Worker** for programmatic
1148
+ * capture. Lunora deliberately does not reimplement any of those — this buffer
1149
+ * stays a tiny dev/ops readout. Capacity is fixed at construction; once full,
1150
+ * the oldest entry is evicted to make room (FIFO), so memory stays bounded
1151
+ * regardless of traffic.
1152
+ */
1153
+ declare class LogBuffer {
1154
+ /** Backing store, kept in insertion order (oldest first). */
1155
+ private readonly buffer;
1156
+ private readonly capacity;
1157
+ constructor(capacity?: number);
1158
+ /** Number of entries currently buffered. */
1159
+ get size(): number;
1160
+ /** Drop every buffered entry. */
1161
+ clear(): void;
1162
+ /**
1163
+ * Snapshot of the buffered entries, **newest first** so the panel renders
1164
+ * the most recent activity at the top without re-sorting. Returns a fresh
1165
+ * array each call; the caller may mutate it freely.
1166
+ */
1167
+ entries(): LogEntry[];
1168
+ /**
1169
+ * Append an entry, evicting the oldest when at capacity so the buffer never
1170
+ * grows past its bound.
1171
+ */
1172
+ push(entry: LogEntry): void;
1173
+ }
1174
+ /**
1175
+ * One aggregated metric series: every measurement sharing a `(name, kind,
1176
+ * attributes)` identity folded into a single running summary.
1177
+ *
1178
+ * All fields are maintained for every {@link MetricKind} — the fold is uniform
1179
+ * and O(1), and letting the panel choose the meaningful projection per kind
1180
+ * (counter → `sum`, gauge → `last`, histogram → `sum`/`count` for the mean, plus
1181
+ * `min`/`max`) is cheaper and clearer than branching on kind at record time.
1182
+ */
1183
+ interface MetricSeries {
1184
+ /** The series' dimensions, if any — the attributes that made it distinct. */
1185
+ attributes?: LogFields;
1186
+ /** Number of measurements folded into this series. */
1187
+ count: number;
1188
+ /** Trace id of the most recent measurement that carried one — the series' exemplar, for linking to a trace. */
1189
+ exemplarTraceId?: string;
1190
+ /** Wall-clock millis of the first measurement folded in. */
1191
+ firstTs: number;
1192
+ /** Function path that recorded the series' most recent measurement. */
1193
+ functionPath: string;
1194
+ /** Instrument kind; decides which projection the panel shows. */
1195
+ kind: MetricKind;
1196
+ /** Most recent measured value — the current reading for a `gauge`. */
1197
+ last: number;
1198
+ /** Wall-clock millis of the most recent measurement. */
1199
+ lastTs: number;
1200
+ /** Largest measured value seen. */
1201
+ max: number;
1202
+ /** Smallest measured value seen. */
1203
+ min: number;
1204
+ /** Instrument name, e.g. `"orders.placed"`. */
1205
+ name: string;
1206
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
1207
+ shardKey?: string;
1208
+ /** Sum of measured values — a `counter`'s total, a `histogram`'s sum. */
1209
+ sum: number;
1210
+ }
1211
+ /**
1212
+ * A bounded map of running metric aggregates, keyed by series identity. Eviction
1213
+ * is least-recently-*updated*: a re-recorded series moves back to the tail (Map
1214
+ * insertion order), so the capacity bound sheds cold, high-cardinality series and
1215
+ * keeps the ones still receiving traffic — the opposite of a raw ring, which
1216
+ * would evict a hot counter's own history.
1217
+ */
1218
+ declare class MetricBuffer {
1219
+ private readonly capacity;
1220
+ private readonly series;
1221
+ constructor(capacity?: number);
1222
+ /** Number of distinct series currently aggregated. */
1223
+ get size(): number;
1224
+ /** Drop every aggregated series. */
1225
+ clear(): void;
1226
+ /**
1227
+ * Snapshot of the aggregated series, most-recently-updated first, each a fresh
1228
+ * copy so a caller can't mutate the live aggregate. `series` is kept in
1229
+ * update order (tail = newest), so one reverse yields newest-first.
1230
+ */
1231
+ entries(): MetricSeries[];
1232
+ /** Fold one measurement into its series, creating or updating the aggregate. */
1233
+ push(event: MetricEvent): void;
1234
+ }
1235
+ /** One time-bucket sample of a series: the aggregate over `[bucketMs, bucketMs + METRIC_HISTORY_BUCKET_MS)`. */
1236
+ interface MetricHistoryPoint {
1237
+ /** Epoch-ms floor of the bucket window. */
1238
+ bucketMs: number;
1239
+ /** Measurements folded into this bucket. */
1240
+ count: number;
1241
+ /** Sample `traceId` of a measurement in this bucket, if one carried trace context — the exemplar. */
1242
+ exemplarTraceId?: string;
1243
+ /** Last measured value in the bucket — a gauge's reading at the window's end. */
1244
+ last: number;
1245
+ /** Largest value in the bucket. */
1246
+ max: number;
1247
+ /** Smallest value in the bucket. */
1248
+ min: number;
1249
+ /** Sum of values in the bucket — a counter's increment total, a histogram's sum. */
1250
+ sum: number;
1251
+ }
1252
+ /** One series' durable history: its identity plus its time-ordered buckets, oldest first. */
1253
+ interface MetricHistorySeries {
1254
+ attributes?: LogFields;
1255
+ functionPath: string;
1256
+ kind: MetricKind;
1257
+ name: string;
1258
+ /** Buckets in ascending `bucketMs` order, ready to chart as a line. */
1259
+ points: MetricHistoryPoint[];
1260
+ shardKey?: string;
1261
+ }
1262
+ /** {@link readMetricHistory} result: every tracked series with its buckets. */
1263
+ interface MetricHistoryResult {
1264
+ series: MetricHistorySeries[];
1265
+ }
1266
+ /**
1267
+ * Tunable caps for {@link recordMetricHistory}, threaded from the sink's
1268
+ * `metricHistory` option. Each falls back to its module-constant default, so an
1269
+ * omitted field keeps the historical behaviour.
1270
+ */
1271
+ interface MetricHistoryOptions {
1272
+ /** Distinct series tracked before a brand-new one is dropped (default {@link METRIC_HISTORY_MAX_SERIES}). */
1273
+ maxSeries?: number;
1274
+ /** Minute-buckets kept per series before older rows are trimmed (default {@link METRIC_HISTORY_BUCKET_RETENTION}). */
1275
+ retentionBuckets?: number;
1276
+ }
1277
+ /**
1278
+ * Fold one measurement into its `(series, minute)` bucket. Runs per
1279
+ * `ctx.metrics.*` call (unlike `function-metrics.ts`, which runs once per
1280
+ * dispatch), so it's tuned for the in-minute repeat: a bucket this instance has
1281
+ * already written is a single upsert (no reads), a bucket only in the DB costs one
1282
+ * PK point-lookup + upsert, and only a genuinely new bucket also pays the
1283
+ * distinct-series cap scan + retention trim. Still a durable SQLite write per
1284
+ * measurement, so a hot loop recording thousands of points a second should
1285
+ * pre-aggregate and record once (see `shared/metric-event.ts`). Creates the table
1286
+ * first so callers needn't.
1287
+ *
1288
+ * `exemplarTraceId` (the recording dispatch's trace, when it had one) is stored on
1289
+ * the bucket so the studio can link a chart point back to a trace. Latest wins:
1290
+ * a later sample carrying a trace replaces an earlier bucket's exemplar.
1291
+ *
1292
+ * `options` tunes the distinct-series cap and retention window from the sink's
1293
+ * `metricHistory` flag (see {@link MetricHistoryOptions}); each defaults to its
1294
+ * module constant.
1295
+ */
1296
+ declare const recordMetricHistory: (sql: SqlExec, event: MetricEvent, exemplarTraceId?: string, options?: MetricHistoryOptions) => void;
1297
+ /**
1298
+ * Read the durable history, grouped into one {@link MetricHistorySeries} per
1299
+ * series with its buckets in ascending time order.
1300
+ *
1301
+ * Rows are fetched most-recent-first (`bucket_ms DESC`) under the row cap, NOT
1302
+ * `series_key`-ordered: all active series write the same recent minutes, so this
1303
+ * windows every series to a recent slice fairly, instead of handing the
1304
+ * alphabetically-first series its full 1440-bucket history and starving the rest
1305
+ * once the cap is hit. Each series' points are re-sorted ascending below, since a
1306
+ * trend line reads oldest→newest.
1307
+ *
1308
+ * `options.sinceMs`, when set, returns only buckets at or after this epoch-ms —
1309
+ * the studio's time-window selector.
1310
+ */
1311
+ declare const readMetricHistory: (sql: SqlExec, options?: {
1312
+ sinceMs?: number;
1313
+ }) => MetricHistoryResult;
1314
+ /** One row of the `__lunora_metrics_queries` table, as returned by `readQueryMetrics`. */
1315
+ interface QueryStatEntry {
1316
+ /** Total number of times this statement was executed. */
1317
+ execCount: number;
1318
+ /** Normalised SQL text (literals stripped, truncated). */
1319
+ normalizedSql: string;
1320
+ /** Total rows read across all executions (SELECT result sizes). */
1321
+ rowsRead: number;
1322
+ /** Total rows written across all executions. */
1323
+ rowsWritten: number;
1324
+ /** Total wall-clock milliseconds across all executions. */
1325
+ totalDurationMs: number;
1326
+ }
1327
+ /** One statement's activity within a chosen time range. */
1328
+ interface QueryInsightEntry {
1329
+ /** Mean milliseconds per execution across the range. */
1330
+ avgDurationMs: number;
1331
+ execCount: number;
1332
+ normalizedSql: string;
1333
+ /** Interpolated 50th percentile latency, in milliseconds. */
1334
+ p50DurationMs: number;
1335
+ /** Interpolated 95th percentile latency, in milliseconds. */
1336
+ p95DurationMs: number;
1337
+ rowsRead: number;
1338
+ rowsWritten: number;
1339
+ totalDurationMs: number;
1340
+ }
1341
+ /** One point on the throughput/latency charts. */
1342
+ interface QueryInsightBucket {
1343
+ /** Mean milliseconds per execution in this window, across all statements. */
1344
+ avgDurationMs: number;
1345
+ /** Bucket start, epoch millis. */
1346
+ bucketMs: number;
1347
+ execCount: number;
1348
+ }
1349
+ /** What `getQueryInsights` returns. */
1350
+ interface QueryInsightsResult {
1351
+ /** Time series across the whole range, all statements combined. */
1352
+ buckets: QueryInsightBucket[];
1353
+ /**
1354
+ * True when the tracked-statement cap has been reached, so the caller can say
1355
+ * "showing N of a capped set" rather than implying totality. Silent
1356
+ * truncation reads as complete coverage when it is not.
1357
+ */
1358
+ capped: boolean;
1359
+ entries: QueryInsightEntry[];
1360
+ /** How many distinct statements the lifetime table is tracking. */
1361
+ trackedStatements: number;
1362
+ }
1363
+ /**
1364
+ * Per-statement activity within `rangeMs` of `now`, plus a combined time series.
1365
+ *
1366
+ * Reads the bucket table (not the lifetime one) so the numbers answer "what is
1367
+ * hot right now"; the statement TEXT is joined back from the lifetime table,
1368
+ * which is the only place it is stored.
1369
+ */
1370
+ declare const readQueryInsights: (sql: SqlExec, rangeMs: number, now?: number) => QueryInsightsResult;
1371
+ /**
1372
+ * Record one statement execution. Creates the table on first call. Silently
1373
+ * skips recording when the normalised statement is empty (shouldn't happen
1374
+ * in practice) or when the table is already at the
1375
+ * {@link QUERY_METRICS_MAX_STATEMENTS} cap and the statement is not yet
1376
+ * tracked. The cap check is a single cheap `COUNT(*)` on the primary-key
1377
+ * index, so the hot-path cost is minimal.
1378
+ */
1379
+ declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number) => void;
1380
+ /**
1381
+ * Read all tracked statement aggregates, ordered by `total_duration_ms DESC`
1382
+ * (the leaderboard's default). Creates the table first so a read on a
1383
+ * never-called shard returns `[]`.
1384
+ */
1385
+ declare const readQueryMetrics: (sql: SqlExec) => QueryStatEntry[];
1386
+ /** Reserved append-only table backing the studio Logs tab. Auto-hidden from the data browser by the `__lunora` prefix. */
1387
+ declare const REQUEST_LOG_TABLE = "__lunora_reqlog__";
1388
+ /** Outcome of one dispatch — `ok` for a returned result, `error` for a thrown handler. */
1389
+ type RequestOutcome = "error" | "ok";
1390
+ /** One recorded `/rpc` dispatch, in monotonic `seq` order. */
1391
+ interface RequestLogEntry {
1392
+ /** Whether the result was served from the reactive cache; `undefined` when the cache is disabled or the path isn't cached (a write/action). */
1393
+ cacheHit?: boolean;
1394
+ /** Handler wall-clock duration in milliseconds (before the subscription write-flush, matching the per-function metrics). */
1395
+ durationMs: number;
1396
+ /** Error message when `outcome === "error"`; absent on success. */
1397
+ errorMessage?: string;
1398
+ /** The `&lt;file>:&lt;function>` identifier dispatched, e.g. `messages:list`. */
1399
+ functionPath: string;
1400
+ /** Identity-claim envelope forwarded by the runtime, JSON-decoded; leaf values are redacted (the claims are PII), so only the shape survives. Absent for anonymous requests. Correlate on `userId` instead. */
1401
+ identity?: Record<string, unknown>;
1402
+ /** `ok` for a returned result, `error` for a thrown handler. */
1403
+ outcome: RequestOutcome;
1404
+ /** Call args with leaf values redacted by default (keys/shape preserved); absent when no args were sent. */
1405
+ redactedArgs?: unknown;
1406
+ /** Monotonic per-shard cursor — strictly increasing, never reused. */
1407
+ seq: number;
1408
+ /** Shard key (the DO id name), or `undefined` for the unnamed `__root__` DO. */
1409
+ shardKey?: string;
1410
+ /** Count of subscriptions re-run by the write this dispatch triggered; `0` when none (or not measured at the dispatch site). */
1411
+ subscriptionsReRun: number;
1412
+ /** Tables the handler read (from the dependency tracker); empty when the reactive cache is off or the path read nothing. */
1413
+ tablesRead: string[];
1414
+ /** Tables the handler wrote (from the change tracker); empty for a read-only dispatch. */
1415
+ tablesWritten: string[];
1416
+ /** Wall-clock millis when the dispatch completed. */
1417
+ ts: number;
1418
+ /** Acting userId forwarded by the runtime, or `undefined` when anonymous. */
1419
+ userId?: string;
1420
+ }
1421
+ /** Fields accepted when appending one request-log entry; `seq` is assigned by the table. */
1422
+ interface AppendRequestLogEntry {
1423
+ cacheHit?: boolean;
1424
+ durationMs: number;
1425
+ errorMessage?: string;
1426
+ functionPath: string;
1427
+ identity?: Record<string, unknown>;
1428
+ outcome: RequestOutcome;
1429
+ redactedArgs?: unknown;
1430
+ shardKey?: string;
1431
+ subscriptionsReRun?: number;
1432
+ tablesRead?: string[];
1433
+ tablesWritten?: string[];
1434
+ ts: number;
1435
+ userId?: string;
1436
+ }
1437
+ /** Knobs the dispatch site threads into a request-log write. */
1438
+ interface RequestLogWriteOptions {
1439
+ /** When `true` (development only), skip args/identity redaction so a developer sees raw values. Defaults to `false` (production-safe). */
1440
+ captureRaw?: boolean;
1441
+ /** Rows to keep after the append-time trim; defaults to {@link REQUEST_LOG_RETENTION}. The operator's `LUNORA_REQUEST_LOG_RETENTION` override. */
1442
+ retention?: number;
1443
+ }
1444
+ /** Filters for {@link readRequestLog}, all AND-combined; every value is a bound SQL parameter, so nothing here injects SQL. */
1445
+ interface ReadRequestLogOptions {
1446
+ /** Functions whose path begins with this prefix (a `&lt;file>:` or `&lt;file>:&lt;fn>` correlation). */
1447
+ functionPathPrefix?: string;
1448
+ /** Upper bound on returned rows, clamped to [1, 10000]. */
1449
+ limit?: number;
1450
+ /** Keep only `ok` / `error` outcomes. */
1451
+ outcome?: RequestOutcome;
1452
+ /** Exact shard-key match. */
1453
+ shardKey?: string;
1454
+ /** Only entries strictly after this cursor (forward paging). */
1455
+ sinceSeq?: number;
1456
+ /** Keep only entries whose read OR written table set contains this table. */
1457
+ tableTouched?: string;
1458
+ /** Exact acting-userId match. */
1459
+ userId?: string;
1460
+ }
1461
+ /** Payload of a `__lunora_admin__:getRequestLog` call: the recorded entries, newest first. */
1462
+ interface RequestLogResult {
1463
+ entries: RequestLogEntry[];
1464
+ }
1465
+ /**
1466
+ * One grouped error **Issue**: many `error`-outcome request-log rows that share a
1467
+ * fingerprint folded into a single triage row. The `hash` is the same stable key
1468
+ * a cloud Incident groups on, so a local Issue and a cloud Incident are the same
1469
+ * object.
1470
+ */
1471
+ interface ErrorIssue {
1472
+ /** Assignee (a userId or a name) from the persisted triage state; absent when unassigned. */
1473
+ assignee?: string;
1474
+ /** Number of `error` rows folded into this Issue within the scanned window. */
1475
+ count: number;
1476
+ /** The `&lt;file>:&lt;function>` (or `container:&lt;name>`) the errors came from. */
1477
+ culprit: string;
1478
+ /** Wall-clock millis of the oldest folded row. */
1479
+ firstSeen: number;
1480
+ /** Stable 16-char grouping hash over `functionPath :: bucket(message)`. */
1481
+ hash: string;
1482
+ /** Wall-clock millis of the newest folded row. */
1483
+ lastSeen: number;
1484
+ /** A representative raw error message — taken from the most recent folded row. */
1485
+ sampleMessage: string;
1486
+ /** Developer-tagged severity from the persisted triage state; absent when untriaged. */
1487
+ severity?: IssueSeverity;
1488
+ /**
1489
+ * Wall-clock millis the persisted triage state was last changed; absent when
1490
+ * the Issue has never been triaged. Compared against `lastSeen` to detect a
1491
+ * regression (a new error after a resolve).
1492
+ */
1493
+ stateUpdatedAt?: number;
1494
+ /**
1495
+ * Triage status folded in from the persisted state (`open` by default). A
1496
+ * `resolved` Issue whose `lastSeen` is newer than `stateUpdatedAt` is
1497
+ * auto-reopened to `open` here (a regression), so a fresh occurrence never
1498
+ * hides behind a stale resolution; `ignored` stays sticky by design.
1499
+ */
1500
+ status: IssueStatus;
1501
+ /** Human-readable title (first line of the sample message, capped). */
1502
+ title: string;
1503
+ }
1504
+ /** Payload of a `__lunora_admin__:getIssues` call: grouped error Issues, most-recently-active first. */
1505
+ interface IssuesResult {
1506
+ issues: ErrorIssue[];
1507
+ }
1508
+ /** Filters for {@link readErrorIssues}; forwarded to {@link readRequestLog} with `outcome` forced to `error`. */
1509
+ interface ReadIssuesOptions {
1510
+ /** Functions whose path begins with this prefix (a `&lt;file>:` or `&lt;file>:&lt;fn>` correlation). */
1511
+ functionPathPrefix?: string;
1512
+ /** Upper bound on error rows scanned before grouping, clamped to [1, 10000]. */
1513
+ limit?: number;
1514
+ /** Exact shard-key match. */
1515
+ shardKey?: string;
1516
+ /** Keep only Issues in this triage status, applied AFTER the persisted-state fold + auto-reopen. */
1517
+ status?: IssueStatus;
1518
+ /** Exact acting-userId match. */
1519
+ userId?: string;
1520
+ }
1521
+ /**
1522
+ * Create the `__lunora_reqlog__` table. `seq` is an `AUTOINCREMENT` primary
1523
+ * key, giving each shard a monotonic cursor the Logs tab pages through; the
1524
+ * `args`/`identity`/`tables_read`/`tables_written` columns hold JSON and are
1525
+ * `NULL`/empty when none was recorded. Idempotent, so read and write paths can
1526
+ * call it defensively.
1527
+ */
1528
+ declare const ensureRequestLogTable: (sql: SqlExec) => void;
1529
+ /**
1530
+ * Append one dispatch to the request log, then trim the log back to the most
1531
+ * recent `retention` rows (default {@link REQUEST_LOG_RETENTION}). Creates the
1532
+ * table first so callers needn't. Args/identity are redacted here so a raw value
1533
+ * never reaches the durable table — callers pass the unredacted entry and rely on
1534
+ * this, unless `captureRaw` (dev only) is set. `retention` is the operator's
1535
+ * `LUNORA_REQUEST_LOG_RETENTION` override, threaded in by the dispatch site.
1536
+ */
1537
+ declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry, options?: RequestLogWriteOptions) => void;
1538
+ /**
1539
+ * Emit one structured request event to `console` so Cloudflare's Workers Logs /
1540
+ * Logpush pipeline carries it to external sinks (SIEMs) — PLAN3 §3.3. This does
1541
+ * NOT reimplement a transport: it produces a richer, lunora-attributed event and
1542
+ * lets CF's existing trace-log pipe ship it. The event mirrors the durable
1543
+ * `__lunora_reqlog__` row (function path, shard, user, outcome, duration, tables
1544
+ * read/written, cache hit), with `args` AND `identity` redacted exactly like the
1545
+ * durable write so no raw PII/secret reaches the log pipeline.
1546
+ *
1547
+ * An `error` outcome goes to `console.error` (surfacing at error level in the
1548
+ * trace so a SIEM can alert on it); everything else to `console.log`. The
1549
+ * `source: "lunora"` / `type: "request"` envelope lets a consumer filter these
1550
+ * events out of the raw Workers-trace firehose. `captureRaw` (dev only) skips
1551
+ * redaction, mirroring the durable write. Best-effort by contract — the caller
1552
+ * wraps it so a serialization hiccup can never fail the served request.
1553
+ */
1554
+ declare const emitRequestLogEvent: (entry: AppendRequestLogEntry, options?: RequestLogWriteOptions) => void;
1555
+ /**
1556
+ * The `ctx.log` event contract (shape + severity union) lives in
1557
+ * `shared/log-event.ts` (inlined into each `dist`) so the DO that builds these
1558
+ * events and the `@lunora/runtime` sink that consumes them agree by construction.
1559
+ * `LogEventInput` is the DO's historical name for the shared `LogEvent`.
1560
+ */
1561
+ type LogEventInput = LogEvent;
1562
+ /**
1563
+ * Split a `ctx.log.&lt;level>(...)` call's raw arguments into a display `message`
1564
+ * and optional structured `fields`. The structured form — a message string plus
1565
+ * a plain-object fields bag — is matched only for exactly `(string, object)`;
1566
+ * every other shape is console-style and rendered whole (so existing
1567
+ * `console`-shaped calls are unchanged). Bound `.with(...)` fields merge under
1568
+ * the per-call fields (per-call wins); the result is normalized to a fresh bag
1569
+ * of JSON-safe primitives, or `undefined` when empty (see `normalizeLogFields`).
1570
+ */
1571
+ declare const parseLogArgs: (args: unknown[], boundFields?: LogFields) => {
1572
+ fields?: LogFields;
1573
+ message: string;
1574
+ };
1575
+ /**
1576
+ * Emit one application-log event from a `ctx.log.*` call to `console`, tagged
1577
+ * `{ source: "lunora", type: "log" }` so the CLI / Vite formatter can pretty-print
1578
+ * it in the dev terminal and a Logpush/SIEM consumer can filter it out of the
1579
+ * raw Workers-trace firehose.
1580
+ *
1581
+ * Only the rendered `message` is emitted here, NOT the structured `args` array:
1582
+ * the console event rides CF Workers Logs / Logpush to prod, and shipping raw,
1583
+ * un-redacted arg objects on a `source: "lunora"` line a SIEM is told to trust
1584
+ * would be a surprising PII/secret surface. The raw `args` stay on the in-process
1585
+ * `sink.onLog` path (`recordUserLog`), which the operator opts into and controls.
1586
+ * `message` already carries the developer's rendered values, exactly like a raw
1587
+ * `console.log` line.
1588
+ *
1589
+ * `error`/`fatal` go to `console.error`, `warn` to `console.warn` (so they
1590
+ * surface at the right level in the trace); every other level to `console.log`.
1591
+ *
1592
+ * Structured `fields` (plus `traceId`/`spanId` for correlation) ARE emitted here
1593
+ * — they are intentional metadata a log pipeline filters on, unlike raw `args`.
1594
+ * A field value that can't be serialised (a circular object) would make
1595
+ * `JSON.stringify` throw and drop the whole line, so serialisation falls back to
1596
+ * a fields-free line rather than losing the event.
1597
+ */
1598
+ declare const emitLogEvent: (input: LogEventInput) => void;
1599
+ /**
1600
+ * Read request-log entries newest-first, AND-combining the supplied filters
1601
+ * (function-path prefix, exact userId/shardKey/outcome, and a table-touched
1602
+ * match against the read OR written table sets), up to `limit` (clamped to
1603
+ * [1, 10000]). Each value is a bound parameter, so no filter can inject SQL.
1604
+ * Creates the table first so reads on a never-logged shard return `[]` instead
1605
+ * of throwing. Mirrors `readAuditLog`/`readCdcChanges`.
1606
+ */
1607
+ declare const readRequestLog: (sql: SqlExec, options?: ReadRequestLogOptions) => RequestLogEntry[];
1608
+ declare const readErrorIssues: (sql: SqlExec, options?: ReadIssuesOptions) => ErrorIssue[];
1609
+ /**
1610
+ * Ordering/visual weight of a security finding — mirrors the studio's insight
1611
+ * severities so the Security Advisor and the Performance Advisor (Insights) share
1612
+ * one badge vocabulary. `error` sorts worst-first, then `warning`, then `info`.
1613
+ */
1614
+ type SecurityFindingLevel = "error" | "info" | "warning";
1615
+ /**
1616
+ * Which security heuristic fired. The detection stays free of presentation
1617
+ * strings — the studio maps each kind to a localized title, explanation, and
1618
+ * remediation hint — so the rule set is trivially unit-testable and the wire
1619
+ * payload is tiny.
1620
+ *
1621
+ * `admin-token-weak`: `LUNORA_ADMIN_TOKEN` is set but short enough to be brute-forceable. (An *unset* token disables admin introspection entirely, so this audit — itself admin-gated — only ever runs with a token present.)
1622
+ *
1623
+ * `ws-gate-open`: admin HTTP RPCs require the bearer, but `LUNORA_WS_BEARER` is unset so the WebSocket upgrade gate defaults open — live admin subscriptions (Logs, Metrics, …) are reachable without a credential.
1624
+ *
1625
+ * `dev-args-unredacted`: the worker reports a development environment, so the durable request log captures raw, un-redacted args and identity (PII). A production deploy mislabeled as dev would persist sensitive payloads.
1626
+ *
1627
+ * `auth-secret-weak`: `AUTH_SECRET` / `BETTER_AUTH_SECRET` is set but shorter than 32 chars — too little entropy to sign session tokens safely. Pairs with `admin-token-weak`.
1628
+ *
1629
+ * `cors-wildcard-credentials`: `LUNORA_ALLOWED_ORIGINS` includes a `*` wildcard while `LUNORA_CORS_ALLOW_CREDENTIALS` is on — browsers reject the combination and it defeats the allowlist, so credentialed cross-origin requests are effectively unguarded.
1630
+ *
1631
+ * `security-headers-disabled`: the deployment set `LUNORA_SECURITY_HEADERS` off, so HSTS / CSP / nosniff / frame-options are not applied — a real exposure on a production worker.
1632
+ *
1633
+ * `csrf-disabled`: the deployment set `LUNORA_SECURITY_CSRF` off, so the cross-origin state-change guard is down — cookie-authenticated mutations are forgeable on a production worker.
1634
+ *
1635
+ * `cookies-insecure`: `BETTER_AUTH_URL` is a plaintext `http://` origin on a non-dev worker, so session cookies cannot carry the `Secure` attribute and ride in cleartext.
1636
+ */
1637
+ type SecurityFindingKind = "admin-token-weak" | "auth-secret-weak" | "cookies-insecure" | "cors-wildcard-credentials" | "csrf-disabled" | "dev-args-unredacted" | "security-headers-disabled" | "ws-gate-open";
1638
+ /**
1639
+ * One detected security issue. `detail` carries kind-specific context the studio
1640
+ * may interpolate into the localized copy (e.g. the offending token length);
1641
+ * absent when the kind needs none.
1642
+ */
1643
+ interface SecurityFinding {
1644
+ detail?: Record<string, unknown>;
1645
+ kind: SecurityFindingKind;
1646
+ level: SecurityFindingLevel;
1647
+ }
1648
+ /** Payload of a `__lunora_admin__:getSecurityAudit` call: every detected finding, worst-first. */
1649
+ interface SecurityAuditResult {
1650
+ findings: SecurityFinding[];
1651
+ }
1652
+ /**
1653
+ * Minimum `LUNORA_ADMIN_TOKEN` length considered safe against brute force. A
1654
+ * short token gates the studio's destructive admin ops (writeRow, clearTable,
1655
+ * pitrRestore, …), so a guessable one is a real exposure. 24 chars ≈ 128 bits
1656
+ * for a random base64-ish token.
1657
+ */
1658
+ declare const MIN_ADMIN_TOKEN_LENGTH = 24;
1659
+ /**
1660
+ * Minimum `AUTH_SECRET` / `BETTER_AUTH_SECRET` length. better-auth signs session
1661
+ * tokens with this secret; 32 chars (≈ `openssl rand -hex 32` → 32 bytes hex, or
1662
+ * 192 bits of base64) is the floor below which the signing key is brute-forceable.
1663
+ */
1664
+ declare const MIN_AUTH_SECRET_LENGTH = 32;
1665
+ /**
1666
+ * Audit the Worker `env` for deployment-level security misconfigurations the
1667
+ * Durable Object can observe directly. Pure and side-effect-free — same `env`,
1668
+ * same findings — so the rules unit-test without a live shard.
1669
+ *
1670
+ * This is the server half of the studio's **Security Advisor**: CF's dashboard
1671
+ * is infra-level and can't reason about lunora's admin/WS gates or its
1672
+ * request-log redaction policy, so these are signals only lunora can surface.
1673
+ * The audit is served behind the same admin gate as every other introspection
1674
+ * RPC, so it only runs once a `LUNORA_ADMIN_TOKEN` is configured — which is why
1675
+ * a *missing* token is never itself a finding here (introspection is simply off).
1676
+ */
1677
+ declare const buildSecurityAudit: (rawEnv: unknown, options: {
1678
+ dev: boolean;
1679
+ }) => SecurityAuditResult;
1680
+ /**
1681
+ * One span in a folded trace, flattened for rendering: `depth` is its nesting
1682
+ * level under the root and `offsetMs` its start relative to the trace start, so
1683
+ * a waterfall row is a pure function of the record (indent by `depth`, bar from
1684
+ * `offsetMs` to `offsetMs + durationMs`) with no client-side tree math.
1685
+ */
1686
+ interface TraceSpan {
1687
+ attributes?: LogFields;
1688
+ /** Nesting level; the root span is 0. */
1689
+ depth: number;
1690
+ durationMs: number;
1691
+ error?: {
1692
+ message: string;
1693
+ type: string;
1694
+ };
1695
+ name: string;
1696
+ /** Start of this span relative to the trace's start, in ms. */
1697
+ offsetMs: number;
1698
+ ok: boolean;
1699
+ parentSpanId: string;
1700
+ spanId: string;
1701
+ }
1702
+ /** One folded trace: the dispatch plus every span recorded beneath it. */
1703
+ interface TraceSummary {
1704
+ /** Wall-clock span of the whole trace (root start → last span end). */
1705
+ durationMs: number;
1706
+ functionPath: string;
1707
+ /** False when the root or any descendant span errored. */
1708
+ ok: boolean;
1709
+ /** Display name of the trace — the root span's name. */
1710
+ rootName: string;
1711
+ shardKey?: string;
1712
+ /**
1713
+ * Spans ordered by `(offsetMs, depth)`, ready to render as waterfall rows.
1714
+ * Start time alone is not enough to order them: spans are recorded on
1715
+ * completion and `startTs` has millisecond resolution, so a parent and its
1716
+ * child routinely tie. Breaking that tie by depth makes the sequence a valid
1717
+ * pre-order traversal of the span tree, so indenting each row by its `depth`
1718
+ * yields the nesting without a separate tree walk.
1719
+ */
1720
+ spans: TraceSpan[];
1721
+ startTs: number;
1722
+ traceId: string;
1723
+ }
1724
+ /**
1725
+ * A bounded, in-memory ring of recent {@link SpanEvent}s (oldest evicted first),
1726
+ * mirroring `LogBuffer`. Spans arrive in *completion* order — a parent settles
1727
+ * after its children — so ordering is imposed by {@link foldTraces} at read
1728
+ * time rather than assumed here.
1729
+ */
1730
+ declare class SpanBuffer {
1731
+ private readonly buffer;
1732
+ private readonly capacity;
1733
+ constructor(capacity?: number);
1734
+ /** Number of spans currently buffered. */
1735
+ get size(): number;
1736
+ /** Drop every buffered span. */
1737
+ clear(): void;
1738
+ /** Snapshot of the buffered spans in insertion order. Fresh array per call. */
1739
+ entries(): SpanEvent[];
1740
+ /**
1741
+ * Whether any buffered span belongs to `traceId`. A membership test rather
1742
+ * than `entries().some(...)` so the per-dispatch check that decides whether
1743
+ * to record a root span doesn't copy the whole ring on every request.
1744
+ */
1745
+ hasTrace(traceId: string): boolean;
1746
+ /** Append a span, evicting the oldest when at capacity. */
1747
+ push(span: SpanEvent): void;
1748
+ }
1749
+ /** {@link foldTraces} result: the folded waterfalls plus the total distinct traces available before the `limit`. */
1750
+ interface FoldedTraces {
1751
+ /**
1752
+ * Distinct traces present in the buffer — the denominator for a "showing N of
1753
+ * M" affordance. `traces.length` is `min(total, limit)`, so `total > traces.length`
1754
+ * means older traces are held in the ring but not returned.
1755
+ */
1756
+ total: number;
1757
+ /** The newest `limit` traces, folded into waterfalls. */
1758
+ traces: TraceSummary[];
1759
+ }
1760
+ /**
1761
+ * Group a flat span list into per-trace waterfalls, newest trace first, plus the
1762
+ * total number of distinct traces available (so a caller can report truncation).
1763
+ * @param spans Buffered spans, in arrival order.
1764
+ * @param limit Maximum number of traces to return, newest first.
1765
+ */
1766
+ declare const foldTraces: (spans: ReadonlyArray<SpanEvent>, limit?: number) => FoldedTraces;
1767
+ /** One record field whose `v.storage()` value points at an object key absent from the bucket. */
1768
+ interface DanglingReference {
1769
+ /** The `v.storage()` column the dangling key was found in. */
1770
+ column: string;
1771
+ /** Primary key (`id`) of the owning row. */
1772
+ id: string;
1773
+ /** The object key the row references but which does not exist in the bucket. */
1774
+ key: string;
1775
+ /** The table the owning row lives in. */
1776
+ table: string;
1777
+ }
1778
+ /**
1779
+ * Result of {@link findDanglingReferences}: the dangling references discovered
1780
+ * (record fields pointing at a missing object), plus `truncated` — `true` when a
1781
+ * scan/result cap clipped the set, so the studio can log/surface that the view is
1782
+ * partial. `scanned` is the total number of non-empty storage-field values
1783
+ * examined, so the studio can show "checked N references".
1784
+ */
1785
+ interface DanglingReferenceResult {
1786
+ references: DanglingReference[];
1787
+ scanned: number;
1788
+ truncated: boolean;
1789
+ }
1790
+ /**
1791
+ * Find every record storage-field value that points at an object key NOT present
1792
+ * in `liveKeys` — a dangling reference (the record references a file the bucket no
1793
+ * longer has). `storageColumns` is the schema-derived `{ table: [field, …] }` map
1794
+ * the codegen subclass supplies (empty for the base, schema-free DO); `liveKeys`
1795
+ * is the set of object keys that actually exist in the bucket (the caller passes
1796
+ * the enumerated bucket listing). Scans only the declared storage columns — never
1797
+ * the whole shard — and resolves each column to its physical/`__doc__` expression
1798
+ * with the same injection-safe, bound-parameter discipline as `readTablePage`.
1799
+ *
1800
+ * Bounded: at most {@link DANGLING_SCAN_CAP} rows per column are examined and at
1801
+ * most {@link DANGLING_RESULT_CAP} references returned; `truncated` flags either
1802
+ * cap firing. An empty `storageColumns` (an app that models no storage refs)
1803
+ * yields an empty, non-truncated result.
1804
+ */
1805
+ declare const findDanglingReferences: (sql: SqlExec, storageColumns: Record<string, string[]>, liveKeys: Iterable<string>) => DanglingReferenceResult;
1806
+ /**
1807
+ * The trace ids a dispatch's spans hang off: taken from the inbound
1808
+ * `traceparent` when the runtime forwarded one (so the shard's spans join the
1809
+ * worker's trace and any container's beneath it), else freshly minted so a
1810
+ * dispatch with no inbound context — a subscription re-run, a server-initiated
1811
+ * call — still produces a coherent, self-contained local trace.
1812
+ */
1813
+ declare const resolveTraceAnchor: (traceparent: string | undefined) => {
1814
+ rootSpanId: string;
1815
+ sampled: boolean;
1816
+ traceId: string;
1817
+ };
1818
+ export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry, type AuthMetrics, type AuthMetricsBucket, type ContextFetch, type ContextLogLevel, type ContextMetrics, type ContextTracer, DEFAULT_EXPLAIN_ISSUE_MODEL, type DatabaseInstrumentation, type DatabaseTally, type ExplainIssueArgs, type ExplainIssueDegradedReason, type ExplainIssueGrounding, type ExplainIssueResult, FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_MAX_PATHS, FUNCTION_METRICS_READ_LIMIT, FUNCTION_METRICS_SCANS_TABLE, FUNCTION_METRICS_TABLE, type FunctionMetricBucket, type FunctionMetricIndexHit, type HostTracingLike, ISSUE_SEVERITIES, ISSUE_STATE_TABLE, ISSUE_STATUSES, type IndexHit, type IssueSeverity, type IssueState, type IssueStatePatch, type IssueStatus, type IssuesResult, LogBuffer, type LogEntry, type LogEventInput, type LogLevel, MIN_ADMIN_TOKEN_LENGTH, MIN_AUTH_SECRET_LENGTH, MetricBuffer, type MetricHistoryOptions, type MetricHistoryPoint, type MetricHistorySeries, type MetricSeries, type MetricsDeps, type QueryStatEntry, REQUEST_LOG_TABLE, type RecordAuthEventInput, type RecordFunctionMetricInput, type RequestLogResult, type RequestLogWriteOptions, type SecurityAuditResult, type SecurityFinding, type SecurityFindingKind, type SecurityFindingLevel, SpanBuffer, type SpanCollection, type SpanCollector, type SpanHandle, type TraceAnchor, type TraceSpan, type TraceSummary, type TracerDeps, appendRequestLogEntry, buildSecurityAudit, createDatabaseTally, createMetrics, createSpanCollector, createTracedFetch, createTracer, dispatchRootSpan, emitLogEvent, emitRequestLogEvent, ensureAuthMetricsTables, ensureFunctionMetricsTables, ensureRequestLogTable, explainIssue, findDanglingReferences, foldTraces, formatTally, instrumentDatabase, mergeScanAttribution, parseExplainIssueArgs, parseLogArgs, readAuthMetrics, readErrorIssues, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetricScans, readFunctionMetrics, readFunctionMetricsTotals, readMetricHistory, readQueryInsights, readQueryMetrics, readRequestLog, recordAuthEvent, recordFunctionMetric, recordMetricHistory, recordQueryMetric, resolveTraceAnchor, upsertIssueState };