@lunora/observability 1.0.0-alpha.1 → 1.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -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
  *
@@ -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,7 +1189,7 @@ 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;
@@ -1261,6 +1323,17 @@ interface MetricHistorySeries {
1261
1323
  }
1262
1324
  /** {@link readMetricHistory} result: every tracked series with its buckets. */
1263
1325
  interface MetricHistoryResult {
1326
+ /**
1327
+ * True when the distinct-series cap has been reached — a **write-side**
1328
+ * signal ("this shard can no longer admit a brand-new series", see
1329
+ * `admitNewSeries`), not a read-side truncation flag. It is computed over
1330
+ * the whole table regardless of `options.sinceMs`/the row-count read
1331
+ * limit, so it can be `true` even when every series `readMetricHistory`
1332
+ * actually returned fits comfortably: the caller should read it as "a
1333
+ * flood of new series would currently be refused", the same thing
1334
+ * `readQueryInsights`'s `capped` already signals for query-metrics.
1335
+ */
1336
+ capped: boolean;
1264
1337
  series: MetricHistorySeries[];
1265
1338
  }
1266
1339
  /**
@@ -1269,7 +1342,7 @@ interface MetricHistoryResult {
1269
1342
  * omitted field keeps the historical behaviour.
1270
1343
  */
1271
1344
  interface MetricHistoryOptions {
1272
- /** Distinct series tracked before a brand-new one is dropped (default {@link METRIC_HISTORY_MAX_SERIES}). */
1345
+ /** Distinct series tracked before the least-recently-updated one is evicted to admit a new one (default {@link METRIC_HISTORY_MAX_SERIES}). */
1273
1346
  maxSeries?: number;
1274
1347
  /** Minute-buckets kept per series before older rows are trimmed (default {@link METRIC_HISTORY_BUCKET_RETENTION}). */
1275
1348
  retentionBuckets?: number;
@@ -1306,9 +1379,12 @@ declare const recordMetricHistory: (sql: SqlExec, event: MetricEvent, exemplarTr
1306
1379
  * trend line reads oldest→newest.
1307
1380
  *
1308
1381
  * `options.sinceMs`, when set, returns only buckets at or after this epoch-ms —
1309
- * the studio's time-window selector.
1382
+ * the studio's time-window selector. `options.maxSeries`, mirroring the write
1383
+ * side's tunable, is only used to compute `capped` — it does not affect which
1384
+ * rows are read.
1310
1385
  */
1311
1386
  declare const readMetricHistory: (sql: SqlExec, options?: {
1387
+ maxSeries?: number;
1312
1388
  sinceMs?: number;
1313
1389
  }) => MetricHistoryResult;
1314
1390
  /** One row of the `__lunora_metrics_queries` table, as returned by `readQueryMetrics`. */
@@ -1369,14 +1445,23 @@ interface QueryInsightsResult {
1369
1445
  */
1370
1446
  declare const readQueryInsights: (sql: SqlExec, rangeMs: number, now?: number) => QueryInsightsResult;
1371
1447
  /**
1372
- * Record one statement execution. Creates the table on first call. Silently
1448
+ * Record one statement's activity. Creates the table on first call. Silently
1373
1449
  * skips recording when the normalised statement is empty (shouldn't happen
1374
1450
  * in practice) or when the table is already at the
1375
1451
  * {@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.
1452
+ * tracked. See `admitStatement` for how the cap check avoids an unconditional
1453
+ * `COUNT(*)` on every execution.
1454
+ *
1455
+ * `execCount` (default 1) lets a caller fold several executions of the SAME
1456
+ * statement into one call — `shard-do.ts` does this per dispatch so a
1457
+ * query-in-a-loop handler pays one upsert here instead of one per raw
1458
+ * execution. `durationMs`/`rowsRead`/`rowsWritten` are then the SUM across
1459
+ * `execCount` executions, exactly as `exec_count`/`total_duration_ms` already
1460
+ * accumulate sums across separate calls — folding before calling is
1461
+ * indistinguishable, from this table's point of view, from `execCount`
1462
+ * separate calls with the same totals.
1378
1463
  */
1379
- declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number) => void;
1464
+ declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number, execCount?: number) => void;
1380
1465
  /**
1381
1466
  * Read all tracked statement aggregates, ordered by `total_duration_ms DESC`
1382
1467
  * (the leaderboard's default). Creates the table first so a read on a
@@ -1393,9 +1478,9 @@ interface RequestLogEntry {
1393
1478
  cacheHit?: boolean;
1394
1479
  /** Handler wall-clock duration in milliseconds (before the subscription write-flush, matching the per-function metrics). */
1395
1480
  durationMs: number;
1396
- /** Error message when `outcome === "error"`; absent on success. */
1481
+ /** Error message when `outcome === "error"`, redacted like args/identity; absent on success. */
1397
1482
  errorMessage?: string;
1398
- /** The `&lt;file>:&lt;function>` identifier dispatched, e.g. `messages:list`. */
1483
+ /** The `<file>:<function>` identifier dispatched, e.g. `messages:list`. */
1399
1484
  functionPath: string;
1400
1485
  /** 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
1486
  identity?: Record<string, unknown>;
@@ -1443,7 +1528,7 @@ interface RequestLogWriteOptions {
1443
1528
  }
1444
1529
  /** Filters for {@link readRequestLog}, all AND-combined; every value is a bound SQL parameter, so nothing here injects SQL. */
1445
1530
  interface ReadRequestLogOptions {
1446
- /** Functions whose path begins with this prefix (a `&lt;file>:` or `&lt;file>:&lt;fn>` correlation). */
1531
+ /** Functions whose path begins with this prefix (a `<file>:` or `<file>:<fn>` correlation). */
1447
1532
  functionPathPrefix?: string;
1448
1533
  /** Upper bound on returned rows, clamped to [1, 10000]. */
1449
1534
  limit?: number;
@@ -1473,15 +1558,20 @@ interface ErrorIssue {
1473
1558
  assignee?: string;
1474
1559
  /** Number of `error` rows folded into this Issue within the scanned window. */
1475
1560
  count: number;
1476
- /** The `&lt;file>:&lt;function>` (or `container:&lt;name>`) the errors came from. */
1561
+ /** The `<file>:<function>` (or `container:<name>`) the errors came from. */
1477
1562
  culprit: string;
1478
1563
  /** Wall-clock millis of the oldest folded row. */
1479
1564
  firstSeen: number;
1480
- /** Stable 16-char grouping hash over `functionPath :: bucket(message)`. */
1565
+ /**
1566
+ * Stable 16-char grouping hash over `functionPath :: bucket(message)`,
1567
+ * computed from the RAW (pre-redaction) message at write time and stored on
1568
+ * the row — see {@link appendRequestLogEntry} — so redacting `sampleMessage`
1569
+ * below can't change the grouping.
1570
+ */
1481
1571
  hash: string;
1482
1572
  /** Wall-clock millis of the newest folded row. */
1483
1573
  lastSeen: number;
1484
- /** A representative raw error message — taken from the most recent folded row. */
1574
+ /** A representative error message (redacted, like the durable row) — taken from the most recent folded row. */
1485
1575
  sampleMessage: string;
1486
1576
  /** Developer-tagged severity from the persisted triage state; absent when untriaged. */
1487
1577
  severity?: IssueSeverity;
@@ -1507,7 +1597,7 @@ interface IssuesResult {
1507
1597
  }
1508
1598
  /** Filters for {@link readErrorIssues}; forwarded to {@link readRequestLog} with `outcome` forced to `error`. */
1509
1599
  interface ReadIssuesOptions {
1510
- /** Functions whose path begins with this prefix (a `&lt;file>:` or `&lt;file>:&lt;fn>` correlation). */
1600
+ /** Functions whose path begins with this prefix (a `<file>:` or `<file>:<fn>` correlation). */
1511
1601
  functionPathPrefix?: string;
1512
1602
  /** Upper bound on error rows scanned before grouping, clamped to [1, 10000]. */
1513
1603
  limit?: number;
@@ -1518,21 +1608,73 @@ interface ReadIssuesOptions {
1518
1608
  /** Exact acting-userId match. */
1519
1609
  userId?: string;
1520
1610
  }
1611
+ /**
1612
+ * Redact the secrets / PII out of a value before it reaches the durable log or a
1613
+ * Logpush event, via `@visulima/redact`'s `standardRules`. Unlike a blunt
1614
+ * type-tag stamp this masks sensitive values by PATTERN (not just by key name)
1615
+ * while leaving benign values readable, so the studio's args/identity columns
1616
+ * stay useful. `null` / `undefined` pass through unchanged.
1617
+ *
1618
+ * What `standardRules` actually catches differs by shape, verified against its
1619
+ * real behavior rather than assumed from its name: on a KEYED object (`args`,
1620
+ * `identity`) it also matches by key name, so `{ password: "hunter2" }` and
1621
+ * `{ token: "…" }` ARE masked regardless of the value's shape. On a PLAIN
1622
+ * STRING — which is what `errorMessage`/log `fields`-as-rendered-text are —
1623
+ * only pattern-shaped matches apply: emails, long digit runs / structured
1624
+ * numeric IDs (credit-card, phone, SSN, AWS-access-key-style), and an explicit
1625
+ * `Bearer <token>` / `token=…`-shaped substring. A free-text `password=hunter2`
1626
+ * or a bare provider API key embedded in prose (e.g. `sk-live-…`) is NOT
1627
+ * caught on a plain string — there is no key to match against, and neither is
1628
+ * a recognized value pattern. So this is a PII-pattern net for rendered text,
1629
+ * not a general secrets scrubber; a handler that echoes a raw credential into
1630
+ * an error message or a log string can still leak it through here. Works on a
1631
+ * plain string too (`redact` traverses whatever value it's handed), which is
1632
+ * how {@link appendRequestLogEntry} and {@link emitRequestLogEvent} reuse this
1633
+ * for `errorMessage` — a validation error echoes the offending value, a
1634
+ * constraint error quotes the conflicting row, so the error message is at
1635
+ * least as PII-dense as args and gets the same treatment (with the free-text
1636
+ * caveat above).
1637
+ *
1638
+ * `captureRaw` is the development escape hatch: in a dev environment the dispatch
1639
+ * site (`isDevEnvironment`) passes `true` to skip redaction so a developer can
1640
+ * see real arg/identity/error values; production always redacts. The dev
1641
+ * decision is made at the call site from the deployment env, never inferred
1642
+ * here — so a real deploy that omits the env var stays redacted.
1643
+ */
1644
+ declare const redactArgs: (value: unknown, captureRaw?: boolean) => unknown;
1521
1645
  /**
1522
1646
  * Create the `__lunora_reqlog__` table. `seq` is an `AUTOINCREMENT` primary
1523
1647
  * key, giving each shard a monotonic cursor the Logs tab pages through; the
1524
1648
  * `args`/`identity`/`tables_read`/`tables_written` columns hold JSON and are
1525
1649
  * `NULL`/empty when none was recorded. Idempotent, so read and write paths can
1526
1650
  * call it defensively.
1651
+ *
1652
+ * `error_fingerprint` is the {@link fingerprintError} grouping hash captured
1653
+ * from the RAW `error_message` at write time, before {@link appendRequestLogEntry}
1654
+ * redacts it — see that function's docstring. It is added via a guarded
1655
+ * `ALTER TABLE` rather than baked into the `CREATE`, mirroring
1656
+ * `function-metrics.ts`'s `ensureFunctionMetricsTables`, so a shard whose
1657
+ * `__lunora_reqlog__` predates this column gains it on the next call without a
1658
+ * migration. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the duplicate-column
1659
+ * error from a re-run (or the freshly-created schema above) is swallowed.
1527
1660
  */
1528
1661
  declare const ensureRequestLogTable: (sql: SqlExec) => void;
1529
1662
  /**
1530
1663
  * Append one dispatch to the request log, then trim the log back to the most
1531
1664
  * 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.
1665
+ * table first so callers needn't. Args/identity/error message are redacted here
1666
+ * so a raw value never reaches the durable table — callers pass the unredacted
1667
+ * entry and rely on this, unless `captureRaw` (dev only) is set. `retention` is
1668
+ * the operator's `LUNORA_REQUEST_LOG_RETENTION` override, threaded in by the
1669
+ * dispatch site.
1670
+ *
1671
+ * The error-grouping fingerprint is computed from `entry.errorMessage` BEFORE
1672
+ * it's redacted below, and the resulting hash is persisted in
1673
+ * `error_fingerprint`. `readErrorIssues` groups off that stored hash instead of
1674
+ * recomputing `fingerprintError` from the (redacted) `error_message` column, so
1675
+ * masking a PII-bearing value — e.g. two different `<n>`-bucketed IDs that
1676
+ * redact to two different tag lengths (`<DL>` vs `<BANKACC>`) — can't split an
1677
+ * existing Issue or change its identity.
1536
1678
  */
1537
1679
  declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry, options?: RequestLogWriteOptions) => void;
1538
1680
  /**
@@ -1541,8 +1683,8 @@ declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry
1541
1683
  * NOT reimplement a transport: it produces a richer, lunora-attributed event and
1542
1684
  * lets CF's existing trace-log pipe ship it. The event mirrors the durable
1543
1685
  * `__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.
1686
+ * read/written, cache hit), with `args`, `identity`, AND `error` redacted
1687
+ * exactly like the durable write so no raw PII/secret reaches the log pipeline.
1546
1688
  *
1547
1689
  * An `error` outcome goes to `console.error` (surfacing at error level in the
1548
1690
  * trace so a SIEM can alert on it); everything else to `console.log`. The
@@ -1560,7 +1702,7 @@ declare const emitRequestLogEvent: (entry: AppendRequestLogEntry, options?: Requ
1560
1702
  */
1561
1703
  type LogEventInput = LogEvent;
1562
1704
  /**
1563
- * Split a `ctx.log.&lt;level>(...)` call's raw arguments into a display `message`
1705
+ * Split a `ctx.log.<level>(...)` call's raw arguments into a display `message`
1564
1706
  * and optional structured `fields`. The structured form — a message string plus
1565
1707
  * a plain-object fields bag — is matched only for exactly `(string, object)`;
1566
1708
  * every other shape is console-style and rendered whole (so existing
@@ -1591,11 +1733,19 @@ declare const parseLogArgs: (args: unknown[], boundFields?: LogFields) => {
1591
1733
  *
1592
1734
  * Structured `fields` (plus `traceId`/`spanId` for correlation) ARE emitted here
1593
1735
  * — 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;
1736
+ * Unlike `args`, `fields` IS redacted before it rides this console line — a
1737
+ * developer can attach anything to a fields bag (`ctx.log.info("charged",
1738
+ * { email, cardLast4 })`), and this is the one line that's told to a SIEM as
1739
+ * trustworthy, exactly like the request-log `args`/`identity`/`error` columns.
1740
+ * `options.captureRaw` (dev only) skips it, mirroring every other redaction
1741
+ * point in this module; the sole current caller (`ShardDO.recordUserLog`)
1742
+ * doesn't yet thread a dev flag through, so `fields` redacts unconditionally
1743
+ * there today — a conservative default, never a correctness gap. A field value
1744
+ * that can't be serialised (a circular object) would make `JSON.stringify`
1745
+ * throw and drop the whole line, so serialisation falls back to a fields-free
1746
+ * line rather than losing the event.
1747
+ */
1748
+ declare const emitLogEvent: (input: LogEventInput, options?: RequestLogWriteOptions) => void;
1599
1749
  /**
1600
1750
  * Read request-log entries newest-first, AND-combining the supplied filters
1601
1751
  * (function-path prefix, exact userId/shardKey/outcome, and a table-touched
@@ -1815,4 +1965,4 @@ declare const resolveTraceAnchor: (traceparent: string | undefined) => {
1815
1965
  sampled: boolean;
1816
1966
  traceId: string;
1817
1967
  };
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 };
1968
+ 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 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, 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 };