@lunora/observability 1.0.0-alpha.4 → 1.0.0-alpha.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.mts +294 -55
  3. package/dist/index.d.ts +294 -55
  4. package/dist/index.mjs +1 -1
  5. package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-CeplWoRm.mjs +22 -0
  6. package/dist/packem_shared/DEFAULT_EXPLAIN_ISSUE_MODEL-BGGX6k5D.mjs +3 -0
  7. package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-ChlIe0vF.mjs +56 -0
  8. package/dist/packem_shared/ISSUE_SEVERITIES-gLC4VEPQ.mjs +15 -0
  9. package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-CidkqOQ1.mjs +1 -0
  10. package/dist/packem_shared/MetricBuffer-DGDoRAig.mjs +1 -0
  11. package/dist/packem_shared/REQUEST_LOG_TABLE-DMQIHLi_.mjs +1 -0
  12. package/dist/packem_shared/SpanBuffer-zjqCAuGp.mjs +1 -0
  13. package/dist/packem_shared/createDatabaseTally-DWEtTj-a.mjs +1 -0
  14. package/dist/packem_shared/createMetrics-z5hLfR2P.mjs +1 -0
  15. package/dist/packem_shared/findDanglingReferences-Dxl42LBT.mjs +1 -0
  16. package/dist/packem_shared/metric-buffer-QGsa8K4_.mjs +1 -0
  17. package/dist/packem_shared/prune-marker-B4An9Nuj.mjs +1 -0
  18. package/dist/packem_shared/readMetricHistory-dka8wnH-.mjs +31 -0
  19. package/dist/packem_shared/readQueryInsights-D37H7MII.mjs +32 -0
  20. package/dist/packem_shared/request-log-COB2xhns.mjs +22 -0
  21. package/dist/packem_shared/resolveTraceAnchor-DWxv_caY.mjs +1 -0
  22. package/dist/packem_shared/run-sql-0aPgJkIw.mjs +1 -0
  23. package/dist/packem_shared/{trace-context-DrdF960P.mjs → trace-context-CpFltS-q.mjs} +1 -1
  24. package/package.json +5 -5
  25. package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-C2V-DiGu.mjs +0 -22
  26. package/dist/packem_shared/DEFAULT_EXPLAIN_ISSUE_MODEL-xF11R4vX.mjs +0 -3
  27. package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-B1U7xWMo.mjs +0 -56
  28. package/dist/packem_shared/ISSUE_SEVERITIES-Js6lGUG5.mjs +0 -15
  29. package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-T-zuwRVR.mjs +0 -1
  30. package/dist/packem_shared/MetricBuffer-CYrLuEsz.mjs +0 -1
  31. package/dist/packem_shared/REQUEST_LOG_TABLE-Q_BYgAzY.mjs +0 -20
  32. package/dist/packem_shared/SpanBuffer-BR6Ff0M-.mjs +0 -1
  33. package/dist/packem_shared/createDatabaseTally-BTqRpC0j.mjs +0 -1
  34. package/dist/packem_shared/createMetrics-fpVig63l.mjs +0 -1
  35. package/dist/packem_shared/findDanglingReferences-D-x9LvY0.mjs +0 -1
  36. package/dist/packem_shared/log-fields-d5ouN1Pe.mjs +0 -1
  37. package/dist/packem_shared/metric-buffer-CdgXal7w.mjs +0 -1
  38. package/dist/packem_shared/readMetricHistory-DtnhufUq.mjs +0 -31
  39. package/dist/packem_shared/readQueryInsights-CwSKB0I1.mjs +0 -32
  40. package/dist/packem_shared/resolveTraceAnchor-IAN8riI0.mjs +0 -1
package/README.md CHANGED
@@ -27,7 +27,7 @@ Three details are Cloudflare's rather than this package's, so the host passes th
27
27
 
28
28
  - **AI model** — `explainIssue(binding, args, { defaultModel })`. `DEFAULT_EXPLAIN_ISSUE_MODEL` is a Workers AI id, exported so the Cloudflare host has a name to pass rather than a string literal.
29
29
  - **Query batch size** — `readIssueStates(sql, hashes, { hashQueryBatch })`. `DEFAULT_HASH_QUERY_BATCH` is 100, the Durable Object SQLite bound-parameter cap; a host with a different cap passes its own.
30
- - **Span projections** — `HostSpanLike`, `HostTracingLike`, `HostTracingResolver`, `resolveHostTracing`, `fuseHostSpans`, `applyHostSpanAttributes`. Named for the role, not the provider. Cloudflare's `enterSpan` callback argument is one shape that satisfies them.
30
+ - **Span projections** — the exported types `HostSpanLike`, `HostTracingLike` and `HostTracingResolver`. Named for the role, not the provider; Cloudflare's `enterSpan` callback argument is one shape that satisfies them. The host injects its tracer through the `TracerDeps` option fields `fuseHostSpans?: boolean` and `resolveHostTracing?: HostTracingResolver` — those are option names, not exported values, and the resolver itself lives in the host (`@lunora/do`'s `shard-do.ts` guards a dynamic `import("cloudflare:workers")`).
31
31
 
32
32
  ## Importing it
33
33
 
package/dist/index.d.mts CHANGED
@@ -64,9 +64,9 @@ declare const ensureAuthMetricsTables: (sql: SqlExec) => void;
64
64
  * `failures` advances only when `outcome === "fail"`. `since_ms` is set once on
65
65
  * the first attempt and never moved (so it stays a true first-seen marker).
66
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.
67
+ * Exactly two `INSERT … ON CONFLICT … DO UPDATE` statements, all keyed by
68
+ * primary key, plus a bounded `DELETE` that runs once per bucket window
69
+ * cheap enough to fire off the auth response path without blocking it.
70
70
  */
71
71
  declare const recordAuthEvent: (sql: SqlExec, input: RecordAuthEventInput) => void;
72
72
  /**
@@ -488,6 +488,15 @@ type HostTracingResolver = () => HostTracingLike | Promise<HostTracingLike | und
488
488
  interface TracerDeps {
489
489
  /** The trace this ctx's spans belong to. */
490
490
  anchor: TraceAnchor;
491
+ /**
492
+ * Whether to record a span body's error message (and a `recordException`
493
+ * stacktrace) verbatim rather than redacted. Mirrors the request log's
494
+ * `captureRaw`: `true` in dev (`isDevEnvironment`), `false` in production —
495
+ * the span pipeline is the one sink third-party collectors (Datadog/Axiom
496
+ * via `otlpSink`) receive, so it must not ship raw PII/internals by default
497
+ * the way the request log and function-metrics sinks already don't.
498
+ */
499
+ captureRaw?: boolean;
491
500
  /** Function path the spans are attributed to. */
492
501
  functionPath: string;
493
502
  /**
@@ -544,11 +553,17 @@ interface SpanCollector {
544
553
  * attributes become the canonical one-event-per-request summary. Sharing the
545
554
  * implementation is what makes those two feel like the same API instead of two
546
555
  * that happen to resemble each other.
556
+ *
557
+ * `captureRaw` (default `false`) gates `recordException`'s `exception.message`/
558
+ * `exception.stacktrace` the same way {@link createTracer} gates a span's own
559
+ * error message — a stack is file paths and internals by definition, the exact
560
+ * class `isInternalCode` redaction exists for, so it rides the same dev-only
561
+ * escape hatch rather than shipping to a third-party collector by default.
547
562
  */
548
563
  declare const createSpanCollector: (ids: {
549
564
  spanId: string;
550
565
  traceId: string;
551
- }) => SpanCollector;
566
+ }, captureRaw?: boolean) => SpanCollector;
552
567
  /**
553
568
  * Build the `ctx.trace` span factory for one dispatched function.
554
569
  *
@@ -590,7 +605,7 @@ declare const createSpanCollector: (ids: {
590
605
  *
591
606
  * **DO async-context caveat (EXPERIMENTAL, partially workerd-validated).**
592
607
  * `tracing.enterSpan` is now confirmed to EXIST and RUN inside a real Durable
593
- * Object under `@cloudflare/vitest-pool-workers` (see
608
+ * Object under `@cloudflare/vitest-plugin` (see
594
609
  * `__tests__/workerd/context-telemetry-cf-bridge.workerd.test.ts`): it resolves
595
610
  * from `cloudflare:workers`, its callback executes and returns the body value
596
611
  * without throwing, `span.isTraced` is a real boolean, and — the key additive
@@ -673,6 +688,14 @@ declare const createMetrics: (deps: MetricsDeps) => ContextMetrics;
673
688
  */
674
689
  declare const dispatchRootSpan: (input: {
675
690
  anchor: TraceAnchor;
691
+ /**
692
+ * Whether to record the failure message verbatim rather than redacted —
693
+ * the same dev-only escape hatch as {@link TracerDeps.captureRaw}. This
694
+ * synthetic root span carries the SAME error message the request log and
695
+ * function-metrics sinks already redact by default, so it must not be the
696
+ * one durable copy that ships it raw to a third-party collector.
697
+ */
698
+ captureRaw?: boolean;
676
699
  /**
677
700
  * What the handler attached to the dispatch through `ctx.span` — the **wide
678
701
  * event**. These are the attributes that would otherwise have been scattered
@@ -715,6 +738,14 @@ type DatabaseInstrumentation = "off" | "spans" | "summary";
715
738
  interface DatabaseTelemetryDeps {
716
739
  /** The trace produced spans belong to (`"spans"` mode only). */
717
740
  anchor: TraceAnchor;
741
+ /**
742
+ * Whether to record a failed call's error message verbatim rather than
743
+ * redacted (`"spans"` mode only) — the same dev-only escape hatch as
744
+ * `TracerDeps.captureRaw`. A constraint-error message quotes the
745
+ * conflicting row, so this CLIENT span gets the same default-redacted
746
+ * posture as the request log and function-metrics sinks.
747
+ */
748
+ captureRaw?: boolean;
718
749
  /** Function path spans and attributes are attributed to. */
719
750
  functionPath: string;
720
751
  /** Detail level; see {@link DatabaseInstrumentation}. */
@@ -786,13 +817,23 @@ declare const FUNCTION_METRICS_BUCKET_RETENTION = 1440;
786
817
  /**
787
818
  * Maximum distinct function `path`s tracked in the accumulator table. Mirrors
788
819
  * `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.
820
+ * same reason): the real bound is the app's own registered-function set plus
821
+ * deploy churn (a rename/removal leaves its old path's row in place, still
822
+ * counted against the cap, until an operator's own retention/cleanup
823
+ * process there is none built in today) a few thousand registered
824
+ * functions is already far beyond any real app. `shard-do.ts`'s dispatch
825
+ * handler explicitly does NOT record per-function metrics for an
826
+ * unregistered/`FUNCTION_NOT_FOUND` dispatch (see the guard next to its
827
+ * `FUNCTION_NOT_FOUND` check), so a caller cannot mint arbitrary `path`s here
828
+ * the way a raw caller-supplied SQL shape can in `query-metrics.ts`. Without a
829
+ * cap, deploy churn across the app's lifetime would still grow
830
+ * `__lunora_metrics` (and its bucket/scan satellites) without bound,
831
+ * eventually filling the shard's SQLite store shared with the app's real
832
+ * data. At the cap, a brand-new path is refused — protecting the incumbent
833
+ * leaderboard from a flood of one-off paths is the point, so admission is
834
+ * refused rather than evicting an existing path to make room; already-tracked
835
+ * paths keep accumulating past the cap. `readFunctionMetricsTotals`'s
836
+ * `capped` is the read-side signal for this.
796
837
  */
797
838
  declare const FUNCTION_METRICS_MAX_PATHS = 5e3;
798
839
  /**
@@ -814,6 +855,22 @@ interface FunctionMetricBucket {
814
855
  /** Subset of `calls` that threw. */
815
856
  errors: number;
816
857
  }
858
+ /** {@link readFunctionMetricBuckets} result: the time-series window plus whether the read limit cut it short. */
859
+ interface FunctionMetricBucketsResult {
860
+ buckets: (FunctionMetricBucket & {
861
+ path: string;
862
+ })[];
863
+ /**
864
+ * True when more rows existed than {@link FUNCTION_METRICS_READ_LIMIT} could
865
+ * return, so `buckets` is a partial (newest) window rather than the app's
866
+ * full retained history. Mirrors `readQueryInsights`'s `capped` and
867
+ * `foldTraces`'s `total`: a silently truncated read looks identical to a
868
+ * complete one to a caller that doesn't check for it — the Metrics chart's
869
+ * window would appear to shrink as the app grows, with a wrong leftmost
870
+ * bar, and nothing would say why.
871
+ */
872
+ truncated: boolean;
873
+ }
817
874
  /** One declared index a dispatch exercised (used to narrow a read). */
818
875
  interface IndexHit {
819
876
  /** The declared index name. */
@@ -854,7 +911,7 @@ interface RecordFunctionMetricInput {
854
911
  * dispatch used no declared index, keeping the hot path unchanged.
855
912
  */
856
913
  indexHits?: ReadonlyArray<IndexHit>;
857
- /** The `&lt;file>:&lt;function>` identifier. */
914
+ /** The `<file>:<function>` identifier. */
858
915
  path: string;
859
916
  /**
860
917
  * Distinct tables this dispatch full-scanned (read with no index / point
@@ -879,7 +936,9 @@ interface RecordFunctionMetricInput {
879
936
  * than baked into the `CREATE` so a shard whose `__lunora_metrics` predates the
880
937
  * causal-attribution feature gains the column on the next call without a
881
938
  * migration. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the duplicate-column
882
- * error from a re-run is swallowed.
939
+ * error from a re-run is swallowed. Both the `CREATE`s and the back-fill only
940
+ * run once per handle (see {@link ensuredHandles}) — a handle already marked
941
+ * ensured returns immediately.
883
942
  */
884
943
  declare const ensureFunctionMetricsTables: (sql: SqlExec) => void;
885
944
  /**
@@ -934,18 +993,21 @@ declare const readFunctionMetrics: (sql: SqlExec) => FunctionCallStat[];
934
993
  /**
935
994
  * Read the coarse time-series buckets for `path` (every path when omitted),
936
995
  * 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 `[]`.
996
+ * first so reads on a never-called shard return `{ buckets: [], truncated: false }`.
938
997
  */
939
- declare const readFunctionMetricBuckets: (sql: SqlExec, path?: string) => (FunctionMetricBucket & {
940
- path: string;
941
- })[];
998
+ declare const readFunctionMetricBuckets: (sql: SqlExec, path?: string) => FunctionMetricBucketsResult;
942
999
  /**
943
1000
  * 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.
1001
+ * health snapshot reports: total calls (`requests`), total `errors`. Returns
1002
+ * zeroes on a never-called shard.
1003
+ *
1004
+ * `capped` is true when the distinct-path cap ({@link FUNCTION_METRICS_MAX_PATHS})
1005
+ * has been reached — the write-side signal that a brand-new function path is
1006
+ * currently being refused (see `admitPath`), mirroring `readQueryInsights`'s
1007
+ * `capped` for query-metrics and `readMetricHistory`'s for metric-history.
947
1008
  */
948
1009
  declare const readFunctionMetricsTotals: (sql: SqlExec) => {
1010
+ capped: boolean;
949
1011
  errors: number;
950
1012
  requests: number;
951
1013
  };
@@ -978,7 +1040,7 @@ interface AiRunBinding {
978
1040
  }
979
1041
  /** Parsed `__lunora_admin__:explainIssue` payload: the folded Issue's identifying facts, plus an optional model override. */
980
1042
  interface ExplainIssueArgs {
981
- /** The Issue's culprit (`&lt;file>:&lt;function>` or `container:&lt;name>`), for grounding context. */
1043
+ /** The Issue's culprit (`<file>:<function>` or `container:<name>`), for grounding context. */
982
1044
  culprit?: string;
983
1045
  /** Per-request model-id override; falls back to the caller's `defaultModel`, then {@link DEFAULT_EXPLAIN_ISSUE_MODEL}. */
984
1046
  model?: string;
@@ -1127,13 +1189,15 @@ type LogLevel = ContextLogLevel;
1127
1189
  */
1128
1190
  interface LogEntry {
1129
1191
  exitCode?: number;
1130
- /** Structured fields from a `ctx.log.&lt;level>(message, fields)` / `ctx.log.with(fields)` call, when present. */
1192
+ /** Structured fields from a `ctx.log.<level>(message, fields)` / `ctx.log.with(fields)` call, when present. */
1131
1193
  fields?: Record<string, unknown>;
1132
1194
  functionPath?: string;
1133
1195
  instance?: string;
1134
1196
  level: LogLevel;
1135
1197
  message: string;
1136
1198
  timestamp: number;
1199
+ /** Trace the line was emitted under; absent outside a dispatch (container lifecycle, hibernation-path errors). What the Studio joins a log line to its waterfall on. */
1200
+ traceId?: string;
1137
1201
  }
1138
1202
  /**
1139
1203
  * A bounded, in-memory ring buffer of recent {@link LogEntry} records.
@@ -1261,6 +1325,17 @@ interface MetricHistorySeries {
1261
1325
  }
1262
1326
  /** {@link readMetricHistory} result: every tracked series with its buckets. */
1263
1327
  interface MetricHistoryResult {
1328
+ /**
1329
+ * True when the distinct-series cap has been reached — a **write-side**
1330
+ * signal ("this shard can no longer admit a brand-new series", see
1331
+ * `admitNewSeries`), not a read-side truncation flag. It is computed over
1332
+ * the whole table regardless of `options.sinceMs`/the row-count read
1333
+ * limit, so it can be `true` even when every series `readMetricHistory`
1334
+ * actually returned fits comfortably: the caller should read it as "a
1335
+ * flood of new series would currently be refused", the same thing
1336
+ * `readQueryInsights`'s `capped` already signals for query-metrics.
1337
+ */
1338
+ capped: boolean;
1264
1339
  series: MetricHistorySeries[];
1265
1340
  }
1266
1341
  /**
@@ -1269,7 +1344,7 @@ interface MetricHistoryResult {
1269
1344
  * omitted field keeps the historical behaviour.
1270
1345
  */
1271
1346
  interface MetricHistoryOptions {
1272
- /** Distinct series tracked before a brand-new one is dropped (default {@link METRIC_HISTORY_MAX_SERIES}). */
1347
+ /** Distinct series tracked before the least-recently-updated one is evicted to admit a new one (default {@link METRIC_HISTORY_MAX_SERIES}). */
1273
1348
  maxSeries?: number;
1274
1349
  /** Minute-buckets kept per series before older rows are trimmed (default {@link METRIC_HISTORY_BUCKET_RETENTION}). */
1275
1350
  retentionBuckets?: number;
@@ -1306,9 +1381,12 @@ declare const recordMetricHistory: (sql: SqlExec, event: MetricEvent, exemplarTr
1306
1381
  * trend line reads oldest→newest.
1307
1382
  *
1308
1383
  * `options.sinceMs`, when set, returns only buckets at or after this epoch-ms —
1309
- * the studio's time-window selector.
1384
+ * the studio's time-window selector. `options.maxSeries`, mirroring the write
1385
+ * side's tunable, is only used to compute `capped` — it does not affect which
1386
+ * rows are read.
1310
1387
  */
1311
1388
  declare const readMetricHistory: (sql: SqlExec, options?: {
1389
+ maxSeries?: number;
1312
1390
  sinceMs?: number;
1313
1391
  }) => MetricHistoryResult;
1314
1392
  /** One row of the `__lunora_metrics_queries` table, as returned by `readQueryMetrics`. */
@@ -1369,14 +1447,23 @@ interface QueryInsightsResult {
1369
1447
  */
1370
1448
  declare const readQueryInsights: (sql: SqlExec, rangeMs: number, now?: number) => QueryInsightsResult;
1371
1449
  /**
1372
- * Record one statement execution. Creates the table on first call. Silently
1450
+ * Record one statement's activity. Creates the table on first call. Silently
1373
1451
  * skips recording when the normalised statement is empty (shouldn't happen
1374
1452
  * in practice) or when the table is already at the
1375
1453
  * {@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.
1454
+ * tracked. See `admitStatement` for how the cap check avoids an unconditional
1455
+ * `COUNT(*)` on every execution.
1456
+ *
1457
+ * `execCount` (default 1) lets a caller fold several executions of the SAME
1458
+ * statement into one call — `shard-do.ts` does this per dispatch so a
1459
+ * query-in-a-loop handler pays one upsert here instead of one per raw
1460
+ * execution. `durationMs`/`rowsRead`/`rowsWritten` are then the SUM across
1461
+ * `execCount` executions, exactly as `exec_count`/`total_duration_ms` already
1462
+ * accumulate sums across separate calls — folding before calling is
1463
+ * indistinguishable, from this table's point of view, from `execCount`
1464
+ * separate calls with the same totals.
1378
1465
  */
1379
- declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number) => void;
1466
+ declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number, execCount?: number) => void;
1380
1467
  /**
1381
1468
  * Read all tracked statement aggregates, ordered by `total_duration_ms DESC`
1382
1469
  * (the leaderboard's default). Creates the table first so a read on a
@@ -1393,9 +1480,9 @@ interface RequestLogEntry {
1393
1480
  cacheHit?: boolean;
1394
1481
  /** Handler wall-clock duration in milliseconds (before the subscription write-flush, matching the per-function metrics). */
1395
1482
  durationMs: number;
1396
- /** Error message when `outcome === "error"`; absent on success. */
1483
+ /** Error message when `outcome === "error"`, redacted like args/identity; absent on success. */
1397
1484
  errorMessage?: string;
1398
- /** The `&lt;file>:&lt;function>` identifier dispatched, e.g. `messages:list`. */
1485
+ /** The `<file>:<function>` identifier dispatched, e.g. `messages:list`. */
1399
1486
  functionPath: string;
1400
1487
  /** 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
1488
  identity?: Record<string, unknown>;
@@ -1413,6 +1500,8 @@ interface RequestLogEntry {
1413
1500
  tablesRead: string[];
1414
1501
  /** Tables the handler wrote (from the change tracker); empty for a read-only dispatch. */
1415
1502
  tablesWritten: string[];
1503
+ /** W3C trace id (32-hex) of the dispatch; `undefined` on a row appended before this column existed. See the module docstring on the retention asymmetry. */
1504
+ traceId?: string;
1416
1505
  /** Wall-clock millis when the dispatch completed. */
1417
1506
  ts: number;
1418
1507
  /** Acting userId forwarded by the runtime, or `undefined` when anonymous. */
@@ -1431,6 +1520,7 @@ interface AppendRequestLogEntry {
1431
1520
  subscriptionsReRun?: number;
1432
1521
  tablesRead?: string[];
1433
1522
  tablesWritten?: string[];
1523
+ traceId?: string;
1434
1524
  ts: number;
1435
1525
  userId?: string;
1436
1526
  }
@@ -1443,7 +1533,7 @@ interface RequestLogWriteOptions {
1443
1533
  }
1444
1534
  /** Filters for {@link readRequestLog}, all AND-combined; every value is a bound SQL parameter, so nothing here injects SQL. */
1445
1535
  interface ReadRequestLogOptions {
1446
- /** Functions whose path begins with this prefix (a `&lt;file>:` or `&lt;file>:&lt;fn>` correlation). */
1536
+ /** Functions whose path begins with this prefix (a `<file>:` or `<file>:<fn>` correlation). */
1447
1537
  functionPathPrefix?: string;
1448
1538
  /** Upper bound on returned rows, clamped to [1, 10000]. */
1449
1539
  limit?: number;
@@ -1473,15 +1563,20 @@ interface ErrorIssue {
1473
1563
  assignee?: string;
1474
1564
  /** Number of `error` rows folded into this Issue within the scanned window. */
1475
1565
  count: number;
1476
- /** The `&lt;file>:&lt;function>` (or `container:&lt;name>`) the errors came from. */
1566
+ /** The `<file>:<function>` (or `container:<name>`) the errors came from. */
1477
1567
  culprit: string;
1478
1568
  /** Wall-clock millis of the oldest folded row. */
1479
1569
  firstSeen: number;
1480
- /** Stable 16-char grouping hash over `functionPath :: bucket(message)`. */
1570
+ /**
1571
+ * Stable 16-char grouping hash over `functionPath :: bucket(message)`,
1572
+ * computed from the RAW (pre-redaction) message at write time and stored on
1573
+ * the row — see {@link appendRequestLogEntry} — so redacting `sampleMessage`
1574
+ * below can't change the grouping.
1575
+ */
1481
1576
  hash: string;
1482
1577
  /** Wall-clock millis of the newest folded row. */
1483
1578
  lastSeen: number;
1484
- /** A representative raw error message — taken from the most recent folded row. */
1579
+ /** A representative error message (redacted, like the durable row) — taken from the most recent folded row. */
1485
1580
  sampleMessage: string;
1486
1581
  /** Developer-tagged severity from the persisted triage state; absent when untriaged. */
1487
1582
  severity?: IssueSeverity;
@@ -1507,7 +1602,7 @@ interface IssuesResult {
1507
1602
  }
1508
1603
  /** Filters for {@link readErrorIssues}; forwarded to {@link readRequestLog} with `outcome` forced to `error`. */
1509
1604
  interface ReadIssuesOptions {
1510
- /** Functions whose path begins with this prefix (a `&lt;file>:` or `&lt;file>:&lt;fn>` correlation). */
1605
+ /** Functions whose path begins with this prefix (a `<file>:` or `<file>:<fn>` correlation). */
1511
1606
  functionPathPrefix?: string;
1512
1607
  /** Upper bound on error rows scanned before grouping, clamped to [1, 10000]. */
1513
1608
  limit?: number;
@@ -1518,21 +1613,78 @@ interface ReadIssuesOptions {
1518
1613
  /** Exact acting-userId match. */
1519
1614
  userId?: string;
1520
1615
  }
1616
+ /**
1617
+ * Redact the secrets / PII out of a value before it reaches the durable log or a
1618
+ * Logpush event, via `@visulima/redact`'s `standardRules`. Unlike a blunt
1619
+ * type-tag stamp this masks sensitive values by PATTERN (not just by key name)
1620
+ * while leaving benign values readable, so the studio's args/identity columns
1621
+ * stay useful. `null` / `undefined` pass through unchanged.
1622
+ *
1623
+ * What `standardRules` actually catches differs by shape, verified against its
1624
+ * real behavior rather than assumed from its name: on a KEYED object (`args`,
1625
+ * `identity`) it also matches by key name, so `{ password: "hunter2" }` and
1626
+ * `{ token: "…" }` ARE masked regardless of the value's shape. On a PLAIN
1627
+ * STRING — which is what `errorMessage`/log `fields`-as-rendered-text are —
1628
+ * only pattern-shaped matches apply: emails, long digit runs / structured
1629
+ * numeric IDs (credit-card, phone, SSN, AWS-access-key-style), and an explicit
1630
+ * `Bearer <token>` / `token=…`-shaped substring. A free-text `password=hunter2`
1631
+ * or a bare provider API key embedded in prose (e.g. `sk-live-…`) is NOT
1632
+ * caught on a plain string — there is no key to match against, and neither is
1633
+ * a recognized value pattern. So this is a PII-pattern net for rendered text,
1634
+ * not a general secrets scrubber; a handler that echoes a raw credential into
1635
+ * an error message or a log string can still leak it through here. Works on a
1636
+ * plain string too (`redact` traverses whatever value it's handed), which is
1637
+ * how {@link appendRequestLogEntry} and {@link emitRequestLogEvent} reuse this
1638
+ * for `errorMessage` — a validation error echoes the offending value, a
1639
+ * constraint error quotes the conflicting row, so the error message is at
1640
+ * least as PII-dense as args and gets the same treatment (with the free-text
1641
+ * caveat above).
1642
+ *
1643
+ * `captureRaw` is the development escape hatch: in a dev environment the dispatch
1644
+ * site (`isDevEnvironment`) passes `true` to skip redaction so a developer can
1645
+ * see real arg/identity/error values; production always redacts. The dev
1646
+ * decision is made at the call site from the deployment env, never inferred
1647
+ * here — so a real deploy that omits the env var stays redacted.
1648
+ */
1649
+ declare const redactArgs: (value: unknown, captureRaw?: boolean) => unknown;
1521
1650
  /**
1522
1651
  * Create the `__lunora_reqlog__` table. `seq` is an `AUTOINCREMENT` primary
1523
1652
  * key, giving each shard a monotonic cursor the Logs tab pages through; the
1524
1653
  * `args`/`identity`/`tables_read`/`tables_written` columns hold JSON and are
1525
1654
  * `NULL`/empty when none was recorded. Idempotent, so read and write paths can
1526
1655
  * call it defensively.
1656
+ *
1657
+ * `error_fingerprint` is the {@link fingerprintError} grouping hash captured
1658
+ * from the RAW `error_message` at write time, before {@link appendRequestLogEntry}
1659
+ * redacts it — see that function's docstring. `trace_id` is the dispatch's W3C
1660
+ * trace id, the correlation key to the span ring and to whatever collector
1661
+ * `otlpSink` ships to.
1662
+ *
1663
+ * Both are also added via a guarded `ALTER TABLE`, mirroring
1664
+ * `function-metrics.ts`'s `ensureFunctionMetricsTables`, so a shard whose
1665
+ * `__lunora_reqlog__` predates a column gains it on the next call without a
1666
+ * migration. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the duplicate-column
1667
+ * error from a re-run (or from the freshly-created schema above) is swallowed
1668
+ * per column — the loop is what keeps one column's duplicate from skipping the
1669
+ * next column's add.
1527
1670
  */
1528
1671
  declare const ensureRequestLogTable: (sql: SqlExec) => void;
1529
1672
  /**
1530
1673
  * Append one dispatch to the request log, then trim the log back to the most
1531
1674
  * 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.
1675
+ * table first so callers needn't. Args/identity/error message are redacted here
1676
+ * so a raw value never reaches the durable table — callers pass the unredacted
1677
+ * entry and rely on this, unless `captureRaw` (dev only) is set. `retention` is
1678
+ * the operator's `LUNORA_REQUEST_LOG_RETENTION` override, threaded in by the
1679
+ * dispatch site.
1680
+ *
1681
+ * The error-grouping fingerprint is computed from `entry.errorMessage` BEFORE
1682
+ * it's redacted below, and the resulting hash is persisted in
1683
+ * `error_fingerprint`. `readErrorIssues` groups off that stored hash instead of
1684
+ * recomputing `fingerprintError` from the (redacted) `error_message` column, so
1685
+ * masking a PII-bearing value — e.g. two different `<n>`-bucketed IDs that
1686
+ * redact to two different tag lengths (`<DL>` vs `<BANKACC>`) — can't split an
1687
+ * existing Issue or change its identity.
1536
1688
  */
1537
1689
  declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry, options?: RequestLogWriteOptions) => void;
1538
1690
  /**
@@ -1541,8 +1693,8 @@ declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry
1541
1693
  * NOT reimplement a transport: it produces a richer, lunora-attributed event and
1542
1694
  * lets CF's existing trace-log pipe ship it. The event mirrors the durable
1543
1695
  * `__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.
1696
+ * read/written, cache hit), with `args`, `identity`, AND `error` redacted
1697
+ * exactly like the durable write so no raw PII/secret reaches the log pipeline.
1546
1698
  *
1547
1699
  * An `error` outcome goes to `console.error` (surfacing at error level in the
1548
1700
  * trace so a SIEM can alert on it); everything else to `console.log`. The
@@ -1560,7 +1712,7 @@ declare const emitRequestLogEvent: (entry: AppendRequestLogEntry, options?: Requ
1560
1712
  */
1561
1713
  type LogEventInput = LogEvent;
1562
1714
  /**
1563
- * Split a `ctx.log.&lt;level>(...)` call's raw arguments into a display `message`
1715
+ * Split a `ctx.log.<level>(...)` call's raw arguments into a display `message`
1564
1716
  * and optional structured `fields`. The structured form — a message string plus
1565
1717
  * a plain-object fields bag — is matched only for exactly `(string, object)`;
1566
1718
  * every other shape is console-style and rendered whole (so existing
@@ -1591,11 +1743,19 @@ declare const parseLogArgs: (args: unknown[], boundFields?: LogFields) => {
1591
1743
  *
1592
1744
  * Structured `fields` (plus `traceId`/`spanId` for correlation) ARE emitted here
1593
1745
  * — 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;
1746
+ * Unlike `args`, `fields` IS redacted before it rides this console line — a
1747
+ * developer can attach anything to a fields bag (`ctx.log.info("charged",
1748
+ * { email, cardLast4 })`), and this is the one line that's told to a SIEM as
1749
+ * trustworthy, exactly like the request-log `args`/`identity`/`error` columns.
1750
+ * `options.captureRaw` (dev only) skips it, mirroring every other redaction
1751
+ * point in this module; the sole current caller (`ShardDO.recordUserLog`)
1752
+ * doesn't yet thread a dev flag through, so `fields` redacts unconditionally
1753
+ * there today — a conservative default, never a correctness gap. A field value
1754
+ * that can't be serialised (a circular object) would make `JSON.stringify`
1755
+ * throw and drop the whole line, so serialisation falls back to a fields-free
1756
+ * line rather than losing the event.
1757
+ */
1758
+ declare const emitLogEvent: (input: LogEventInput, options?: RequestLogWriteOptions) => void;
1599
1759
  /**
1600
1760
  * Read request-log entries newest-first, AND-combining the supplied filters
1601
1761
  * (function-path prefix, exact userId/shardKey/outcome, and a table-touched
@@ -1692,6 +1852,15 @@ interface TraceSpan {
1692
1852
  message: string;
1693
1853
  type: string;
1694
1854
  };
1855
+ /**
1856
+ * Timestamped occurrences inside the span — `span.addEvent(...)` and
1857
+ * `span.recordException(...)`. Carried through so a handled retry or a
1858
+ * swallowed exception is visible on the span it happened in, which is the
1859
+ * only place it is interpretable. Absent when the body recorded none.
1860
+ */
1861
+ events?: SpanEventPoint[];
1862
+ /** OTel `SpanKind`; absent means `"internal"`. */
1863
+ kind?: OtlpSpanKind;
1695
1864
  name: string;
1696
1865
  /** Start of this span relative to the trace's start, in ms. */
1697
1866
  offsetMs: number;
@@ -1710,12 +1879,25 @@ interface TraceSummary {
1710
1879
  rootName: string;
1711
1880
  shardKey?: string;
1712
1881
  /**
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.
1882
+ * Spans in **pre-order**, ready to render as waterfall rows: every span is
1883
+ * immediately followed by its own subtree, siblings in start order. So
1884
+ * indenting each row by its `depth` yields the real nesting the row above
1885
+ * a `depth`-`n+1` row is genuinely its parent.
1886
+ *
1887
+ * This is a tree walk, not a sort, because no ordering on `(offsetMs,
1888
+ * depth)` can produce it. Given a parent, its child `a`, `a`'s child `a1`
1889
+ * and `a`'s sibling `b` all at offset 0, that comparator yields
1890
+ * `parent, a, b, a1` — which renders `a1` indented beneath `b`, under a
1891
+ * parent it does not belong to.
1892
+ *
1893
+ * **Why they tie is not millisecond resolution.** On the Workers runtime
1894
+ * `Date.now()` is pinned to the time of the last I/O — a Spectre mitigation,
1895
+ * not a bug — so it does not advance at all across pure computation. Every
1896
+ * duration in this package is `Date.now() - startTs`, which means a span
1897
+ * wrapping CPU-only work reports `0`, and a parent whose child performed no
1898
+ * I/O shares its exact start and end. See `docs/concepts/observability`
1899
+ * ("Span durations on Workers"); the practical consequence is that a `0 ms`
1900
+ * span means "no I/O happened here", not "this was fast".
1719
1901
  */
1720
1902
  spans: TraceSpan[];
1721
1903
  startTs: number;
@@ -1815,4 +1997,61 @@ declare const resolveTraceAnchor: (traceparent: string | undefined) => {
1815
1997
  sampled: boolean;
1816
1998
  traceId: string;
1817
1999
  };
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 };
2000
+ export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry,
2001
+ /**
2002
+ * `@lunora/observability` — host-neutral telemetry storage and read models.
2003
+ *
2004
+ * Logs, traces, metrics, issue grouping and the security-audit view: the data
2005
+ * behind the Studio's observability pages. None of it is Cloudflare-specific —
2006
+ * it reads and writes through the SQL handle the engine hands it, and it lived
2007
+ * inside `@lunora/do` only because that is where the Durable Object it
2008
+ * instruments happens to live.
2009
+ *
2010
+ * The dependency runs `@lunora/do` → here → `@lunora/shard-engine`, never the
2011
+ * reverse, which is what makes this reusable by a second host rather than
2012
+ * merely relocated.
2013
+ *
2014
+ * This barrel is deliberately curated rather than `export *`. A star export
2015
+ * would promote every module internal to permanent public API — the exact
2016
+ * mistake `@lunora/do`'s own barrel documents having made once with the search
2017
+ * core. What is exported here is what a consumer actually needs.
2018
+ */
2019
+ type AuthMetrics,
2020
+ /**
2021
+ * `@lunora/observability` — host-neutral telemetry storage and read models.
2022
+ *
2023
+ * Logs, traces, metrics, issue grouping and the security-audit view: the data
2024
+ * behind the Studio's observability pages. None of it is Cloudflare-specific —
2025
+ * it reads and writes through the SQL handle the engine hands it, and it lived
2026
+ * inside `@lunora/do` only because that is where the Durable Object it
2027
+ * instruments happens to live.
2028
+ *
2029
+ * The dependency runs `@lunora/do` → here → `@lunora/shard-engine`, never the
2030
+ * reverse, which is what makes this reusable by a second host rather than
2031
+ * merely relocated.
2032
+ *
2033
+ * This barrel is deliberately curated rather than `export *`. A star export
2034
+ * would promote every module internal to permanent public API — the exact
2035
+ * mistake `@lunora/do`'s own barrel documents having made once with the search
2036
+ * core. What is exported here is what a consumer actually needs.
2037
+ */
2038
+ type AuthMetricsBucket, type ContextFetch, type ContextLogLevel, type ContextMetrics, type ContextTracer, DEFAULT_EXPLAIN_ISSUE_MODEL, type DanglingReference, type DanglingReferenceResult, type DatabaseInstrumentation, type DatabaseTally, type DatabaseTelemetryDeps, type ErrorIssue, 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 FoldedTraces, type FunctionMetricBucket, type FunctionMetricBucketsResult, type FunctionMetricIndexHit, type HostSpanLike, type HostTracingLike, type HostTracingResolver, 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 MetricEvent, type MetricHistoryOptions, type MetricHistoryPoint, type MetricHistoryResult, type MetricHistorySeries, type MetricKind, type MetricSeries, type MetricsDeps, type QueryInsightBucket, type QueryInsightEntry, type QueryInsightsResult, type QueryStatEntry, REQUEST_LOG_TABLE, type ReadIssuesOptions, type ReadRequestLogOptions,
2039
+ /**
2040
+ * `@lunora/observability` — host-neutral telemetry storage and read models.
2041
+ *
2042
+ * Logs, traces, metrics, issue grouping and the security-audit view: the data
2043
+ * behind the Studio's observability pages. None of it is Cloudflare-specific —
2044
+ * it reads and writes through the SQL handle the engine hands it, and it lived
2045
+ * inside `@lunora/do` only because that is where the Durable Object it
2046
+ * instruments happens to live.
2047
+ *
2048
+ * The dependency runs `@lunora/do` → here → `@lunora/shard-engine`, never the
2049
+ * reverse, which is what makes this reusable by a second host rather than
2050
+ * merely relocated.
2051
+ *
2052
+ * This barrel is deliberately curated rather than `export *`. A star export
2053
+ * would promote every module internal to permanent public API — the exact
2054
+ * mistake `@lunora/do`'s own barrel documents having made once with the search
2055
+ * core. What is exported here is what a consumer actually needs.
2056
+ */
2057
+ type RecordAuthEventInput, type RecordFunctionMetricInput, type RequestLogEntry, type RequestLogResult, type RequestLogWriteOptions, type RequestOutcome, type SecurityAuditResult, type SecurityFinding, type SecurityFindingKind, type SecurityFindingLevel, SpanBuffer, type SpanCollection, type SpanCollector, type SpanEvent, type SpanEventPoint, type SpanHandle, type OtlpSpanKind as SpanKind, type SpanLink, type SpanOptions, type TraceAnchor, type TraceSpan, type TraceSummary, type TracedFetchDeps, 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, redactArgs, resolveTraceAnchor, upsertIssueState };