@lunora/runtime 1.0.0-alpha.34 → 1.0.0-alpha.36

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
@@ -1,6 +1,6 @@
1
- import { R2SqlClient } from '@lunora/bindings/r2sql';
2
1
  import { RankDirection, RankPageRow, DatabaseWriterLike } from '@lunora/do';
3
2
  export type { RankDirection as RankPageDirection, RankPageRowKey as RankPageKey, RankPageRow, ShardRankPageResult } from '@lunora/do';
3
+ import { R2SqlClient } from '@lunora/bindings/r2sql';
4
4
  import { WorkflowsRestClient } from '@lunora/workflow';
5
5
  import { LunoraError as LunoraError$1, ErrorBody } from '@lunora/errors';
6
6
  /**
@@ -150,6 +150,24 @@ interface ExecutionContextLike {
150
150
  * receives a valid third argument.
151
151
  */
152
152
  declare const NOOP_EXECUTION_CONTEXT: ExecutionContextLike;
153
+ /**
154
+ * Trace-sampling configuration — the `sampling` block on the worker's
155
+ * observability options.
156
+ */
157
+ interface TraceSamplingConfig {
158
+ /**
159
+ * Always keep a whole trace that produced an error span (root or any child
160
+ * `ok: false`), regardless of the head decision — the tail bias that keeps
161
+ * failures observable under aggressive head sampling. Default `true`.
162
+ */
163
+ alwaysSampleErrors?: boolean;
164
+ /**
165
+ * Fraction of traces to keep by the deterministic head decision, in `[0, 1]`.
166
+ * `1` keeps every trace (the default), `0` drops every non-error trace, `0.1`
167
+ * keeps ~10%. Values outside the range are clamped by the decision helpers.
168
+ */
169
+ headRate?: number;
170
+ }
153
171
  /** A timestamp as better-auth stores it: epoch-ms, an ISO string, or absent. */
154
172
  type AuthTimestamp = null | number | string;
155
173
  /**
@@ -426,6 +444,67 @@ interface AuthAdmin {
426
444
  userId: string;
427
445
  }) => Promise<AuthUser>;
428
446
  }
447
+ /**
448
+ * Whether the recorded operation succeeded or failed (e.g. a rejected sign-in).
449
+ * Mirrors `@lunora/auth`'s `AuthAuditOutcome`.
450
+ */
451
+ type AuthAuditOutcome = "failure" | "success";
452
+ /**
453
+ * One recorded auth/security event, structurally mirroring `@lunora/auth`'s
454
+ * `AuthAuditEntry`. Duplicated here (like {@link import("./auth-admin-routes").AuthAdmin})
455
+ * so the runtime stays free of a hard `@lunora/auth` dependency — the host wires
456
+ * a structurally-compatible reader.
457
+ */
458
+ interface AuthAuditEntry {
459
+ /** The acting user's email, when known. Absent for anonymous/pre-auth events. */
460
+ actorEmail?: string;
461
+ /** The acting user's id, when known. */
462
+ actorId?: string;
463
+ /** JSON-decoded extra context, with secrets/PII redacted at write time; absent when none was recorded. */
464
+ detail?: Record<string, unknown>;
465
+ /** Auth event type, e.g. `sign-in` / `password-change`. */
466
+ event: string;
467
+ /** Client IP the event originated from, when resolvable. */
468
+ ip?: string;
469
+ /** Whether the operation succeeded or failed. */
470
+ outcome: AuthAuditOutcome;
471
+ /** Monotonic per-database cursor — strictly increasing, never reused. */
472
+ seq: number;
473
+ /** Wall-clock millis when the event was recorded. */
474
+ ts: number;
475
+ /** Client User-Agent, when present on the request. */
476
+ userAgent?: string;
477
+ }
478
+ /**
479
+ * Filter / paging options forwarded to the reader, structurally mirroring
480
+ * `@lunora/auth`'s `ReadAuthAuditOptions`. `limit` is clamped by the reader
481
+ * (`readAuthAuditLog` bounds it to `[1, 10000]`).
482
+ */
483
+ interface ReadAuthAuditQuery {
484
+ /** Return only events for this actor id. */
485
+ actorId?: string;
486
+ /** Return only events of this type. */
487
+ event?: string;
488
+ /** Max rows to return; clamped by the reader. */
489
+ limit?: number;
490
+ /** Return only events with `seq` strictly greater than this (forward paging). */
491
+ sinceSeq?: number;
492
+ }
493
+ /**
494
+ * The auth/security audit read plane backing the studio's "Security / audit"
495
+ * page. Unlike the shard-forwarded `__lunora_admin__:*` ops, the auth audit trail
496
+ * lives in the auth D1 database (via `@lunora/auth`'s `SqlExecutor`), so it is
497
+ * served at the worker. The host wires this — typically via `@lunora/auth`'s
498
+ * `createAuthAuditReader(d1Executor(env.DB))` — closing over that D1 binding; the
499
+ * runtime stays free of a hard `@lunora/auth` dependency. Omit the option and the
500
+ * RPC responds `AUTH_AUDIT_NOT_CONFIGURED`.
501
+ *
502
+ * The reader is a trusted server-side operator surface — the RPC gates it behind
503
+ * the worker's admin-bearer check before this is ever called.
504
+ */
505
+ interface AuthAuditReader {
506
+ read: (options: ReadAuthAuditQuery) => Promise<AuthAuditEntry[]>;
507
+ }
429
508
  /**
430
509
  * A compact, transport-safe description of one function argument — the runtime
431
510
  * read of a `v.*` validator's reflection tags (`kind` + `_meta`). The runtime
@@ -445,1177 +524,1628 @@ interface FunctionArgumentDescriptor {
445
524
  table?: string;
446
525
  }
447
526
  /**
448
- * Identity resolved from the inbound request by `WorkerOptions.resolveIdentity`.
449
- *
450
- * The `userId` field is specialit becomes `ctx.auth.userId` inside the
451
- * Durable Object. Any other keys (`email`, `name`, custom roles, etc.) are
452
- * forwarded verbatim as `ctx.auth.getIdentity()`'s return value.
453
- *
454
- * Return `null` to signal that the request is anonymous; the runtime will
455
- * skip both `x-lunora-userid` and `x-lunora-identity` headers, and
456
- * `ctx.auth.userId` will be `undefined` on the shard side.
527
+ * Cloudflare Durable Object jurisdictions restrict where a DO runs and persists
528
+ * data, for data-residency / compliance regimes (GDPR, FedRAMP, US data
529
+ * residency). The set is openCloudflare adds values over time — so this is a
530
+ * widening union rather than a closed enum.
531
+ * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
457
532
  */
458
- interface ResolvedIdentity {
459
- /** Arbitrary additional claims. Must be JSON-serialisable. */
460
- [key: string]: unknown;
533
+ type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
534
+ /**
535
+ * Structural projection of the bits of `DurableObjectNamespace` the runtime
536
+ * needs. Real workers-types defines a much wider surface; this lets us pass
537
+ * unit-test doubles without coupling to `@cloudflare/workers-types`.
538
+ */
539
+ interface ShardNamespaceLike {
540
+ get: (id: unknown) => {
541
+ fetch: (request: Request) => Promise<Response>;
542
+ };
461
543
  /**
462
- * JWT-standard expiry in epoch SECONDS. When present (and `expiresAtMs` is
463
- * absent), the runtime forwards it as the socket's credential expiry — the
464
- * DO drops the socket once it lapses. Used only on the WebSocket path.
544
+ * `getByName` is the friendlier API but isn't on every workers-types
545
+ * release yet. We prefer it when available and fall back to
546
+ * `idFromName` + `get` for compatibility.
465
547
  */
466
- exp?: number;
548
+ getByName?: (name: string) => {
549
+ fetch: (request: Request) => Promise<Response>;
550
+ };
551
+ idFromName: (name: string) => unknown;
467
552
  /**
468
- * Credential expiry in epoch MILLISECONDS. Preferred over `exp` when
469
- * both are present. Forwarded as the socket's expiry on the WebSocket path
470
- * so the DO drops the socket once it lapses; omit for non-expiring sessions.
553
+ * Derive a jurisdiction-restricted subnamespace. Every ID and stub created
554
+ * from the returned namespace is pinned to `jurisdiction`. Optional because
555
+ * older workers-types releases (and unit-test doubles) may not expose it;
556
+ * {@link applyJurisdiction} fails closed when a jurisdiction is requested
557
+ * but this method is absent.
471
558
  */
472
- expiresAtMs?: number;
473
- /** Stable user identifier (e.g. `"user_2k3..."` or `"u_42"`). */
474
- userId: string;
559
+ jurisdiction?: (jurisdiction: DurableObjectJurisdiction) => ShardNamespaceLike;
560
+ }
561
+ interface ResolvedShard {
562
+ fetch: (request: Request) => Promise<Response>;
475
563
  }
476
564
  /**
477
- * A verifier that turns an inbound request into a {@link ResolvedIdentity} (or
478
- * `null` for anonymous). Structurally identical to `WorkerOptions.resolveIdentity`,
479
- * so `.auth()`'s better-auth session resolver, a signed-preview-link verifier, a
480
- * per-tenant bearer check, an upstream-JWT reader, are all just `IdentityResolver`s
481
- * the identity layer is generic over every scheme, not coupled to any one.
565
+ * Return a jurisdiction-restricted view of `namespace`, or `namespace`
566
+ * unchanged when no jurisdiction is configured.
567
+ *
568
+ * Fail-closed: if a jurisdiction is requested but the binding does not expose
569
+ * `.jurisdiction()` (an older workers-types, or a misconfigured test double),
570
+ * this throws rather than silently routing to the un-pinned global namespace —
571
+ * silently dropping a residency constraint would let data land outside the
572
+ * compliance boundary the caller asked for.
482
573
  */
483
- type IdentityResolver = (request: Request, env: unknown) => Promise<ResolvedIdentity | null> | ResolvedIdentity | null;
484
- /** Error policy for {@link composeIdentityResolvers} when a participant resolver throws. */
485
- type ComposeIdentityResolversErrorMode = "fail-closed" | "skip";
486
- /** Options for {@link composeIdentityResolvers}. */
487
- interface ComposeIdentityResolversOptions {
488
- /**
489
- * What to do when a resolver throws. `"fail-closed"` (default, safe)
490
- * re-throws so a broken verifier fails the request rather than silently
491
- * falling through to a weaker one; `"skip"` swallows the error and tries the
492
- * next resolver (use only when a resolver's failure genuinely means "not my
493
- * scheme").
494
- */
495
- readonly onError?: ComposeIdentityResolversErrorMode;
574
+ declare const applyJurisdiction: (namespace: ShardNamespaceLike, jurisdiction?: DurableObjectJurisdiction) => ShardNamespaceLike;
575
+ /** Look up a shard stub by name, preferring `getByName` when present. */
576
+ declare const resolveShard: (namespace: ShardNamespaceLike, shardKey: string) => ResolvedShard;
577
+ /**
578
+ * Source of "which shard keys exist for a given table right now". Returning
579
+ * an empty array is valid — the coordinator will respond with the merge
580
+ * strategy's identity (empty array for `concat`, `0` for `sum`, etc.).
581
+ */
582
+ interface ShardRegistry {
583
+ listShardKeys: (table: string) => Promise<ReadonlyArray<string>> | ReadonlyArray<string>;
496
584
  }
497
585
  /**
498
- * Compose several {@link IdentityResolver}s into one, first-match-wins: each is
499
- * tried in order and the first that returns a non-null identity short-circuits.
500
- * Generic over every scheme — the better-auth session resolver (obtained via the
501
- * builder's `derived.resolveIdentity` escape hatch) is just one entry in the list,
502
- * so composition never means losing it.
503
- *
504
- * A resolver that throws is handled per {@link ComposeIdentityResolversOptions.onError}
505
- * (default `"fail-closed"`: the error propagates).
586
+ * Static-map implementation. Useful for tests and for small deployments
587
+ * where shard keys are known up front (e.g. a fixed set of channel IDs).
506
588
  */
507
- declare const composeIdentityResolvers: (resolvers: ReadonlyArray<IdentityResolver>, options?: ComposeIdentityResolversOptions) => IdentityResolver;
589
+ declare const createStaticShardRegistry: (table_to_keys: Readonly<Record<string, ReadonlyArray<string>>>) => ShardRegistry;
508
590
  /**
509
- * A thin, generic helper over {@link composeIdentityResolvers} for the per-route case:
510
- * pick a resolver by `new URL(request.url).pathname`. Keys are matched by longest
511
- * path prefix; `"*"` is the fallback. Still fully generic — a route→resolver map,
512
- * with no portal / preview / tenant concepts baked in (those live in the app's
513
- * own resolvers).
514
- * @example
515
- * routeIdentityResolvers({ "/admin": adminResolver, "/partner": partnerResolver, "*": sessionResolver })
591
+ * Wire-serializable merge strategy. `topK.by` is a field name on the row
592
+ * (the runtime looks it up with a string key), not a closure.
593
+ *
594
+ * Aggregate-friendly variants for cross-shard `count` / `aggregate` /
595
+ * `groupBy` fan-outs:
596
+ *
597
+ * - `sum` — `count(*)`, `aggregate({ op: "sum" })` (sums numeric per-shard payloads).
598
+ * - `max` — `aggregate({ op: "max" })`.
599
+ * - `min` — `aggregate({ op: "min" })`.
600
+ * - `groupBy` — per-shard `GroupByEntry[]` payloads, reduced into one
601
+ * entry per distinct key tuple. `op` controls how values combine across
602
+ * shards: `sum` (default — works for `COUNT(*)` and `SUM`), `max`, `min`.
603
+ *
604
+ * `avg` is intentionally absent in v1 — a correct cross-shard average
605
+ * requires shipping `(sum, count)` per shard, not the post-shard mean.
606
+ * Use two separate fan-outs (`sum` + `count`) and divide in the caller.
607
+ *
608
+ * `rank` — cross-shard `rank()` over a partition that spans shards (e.g. a
609
+ * global leaderboard `.shardBy("userId")` with `rankIndex(partitionBy: [])`).
610
+ * Each shard's `__lunora_admin__:rankBefore` returns `{before, total}` (its
611
+ * local rows strictly-before the explicit key, plus its local partition
612
+ * total); the merge sums them into `{position: Σbefore + 1, total: Σtotal}` —
613
+ * the 1-based global position and global partition size.
516
614
  */
517
- declare const routeIdentityResolvers: (routes: Record<string, IdentityResolver>) => IdentityResolver;
518
- /** The result of validating a candidate identity against an {@link IdentityContractLike}. */
519
- type IdentityValidation = {
520
- ok: true;
615
+ type MergeStrategy = {
616
+ kind: "concat";
521
617
  } | {
522
- error: string;
523
- ok: false;
618
+ by: string;
619
+ direction?: "asc" | "desc";
620
+ k: number;
621
+ kind: "topK";
622
+ } | {
623
+ kind: "first";
624
+ } | {
625
+ kind: "max";
626
+ } | {
627
+ kind: "min";
628
+ } | {
629
+ kind: "rank";
630
+ } | {
631
+ kind: "sum";
632
+ } | {
633
+ kind: "groupBy";
634
+ op?: "max" | "min" | "sum";
524
635
  };
525
636
  /**
526
- * Structural view of `@lunora/server`'s `IdentityContract` (from `defineIdentity`).
527
- * Kept structural so `@lunora/runtime` stays free of an `@lunora/server` dependency.
528
- * Keep the `onInvalid` union and `validate`/`IdentityValidation` shapes in sync with
529
- * `@lunora/server`'s `IdentityContract` — they are projected by hand, not imported.
530
- * The generated worker entry passes the app's `defineIdentity(...)` result here;
531
- * the worker validates every resolver's returned claims against it at the trust
532
- * boundary before they become `ctx.auth`.
637
+ * Convenience: build the right wire-serializable {@link MergeStrategy} for a
638
+ * given aggregate read. The reader doesn't know which op the caller chose, so
639
+ * a fan-out wrapper passes the user's op + by-keys through this to derive the
640
+ * merge.
641
+ *
642
+ * - `count` `sum`.
643
+ * - `aggregate({ op })` `sum`/`max`/`min` (or throws for `avg`).
644
+ * - `groupBy({ by, agg })` → `groupBy({ op })` (defaults to `sum` since
645
+ * `groupBy`'s default reducer is `count`).
646
+ * @returns the derived {@link MergeStrategy}.
533
647
  */
534
- interface IdentityContractLike {
535
- /** Reject policy applied when validation fails: downgrade to anonymous, or reject the request (401). */
536
- readonly onInvalid: "anonymous" | "reject";
537
- /** Validate resolver-returned claims against the declared contract. */
538
- validate: (identity: Record<string, unknown>) => IdentityValidation;
539
- }
540
- /** One KV namespace as the studio's KV browser surfaces it. */
541
- interface KvNamespaceSummary {
542
- /** The wrangler/env binding name, e.g. `"MY_KV"`. */
543
- binding: string;
544
- }
545
- /** One key entry as the KV admin browser surfaces it. */
546
- interface KvKeyEntry {
547
- /** Absolute expiration (Unix seconds), when set. */
548
- expiration?: number;
549
- /** Per-key metadata set at write time, or absent when none. */
550
- metadata?: unknown;
551
- /** The key name. */
552
- name: string;
553
- }
554
- /** A paginated page of KV keys as the admin browser returns it. */
555
- interface KvKeyListResult {
556
- /** Opaque cursor for the next page; absent when the listing is complete. */
557
- cursor?: string;
558
- /** The keys on this page. */
559
- keys: KvKeyEntry[];
560
- /** True when this is the final page. */
561
- listComplete: boolean;
562
- }
563
- /** A KV value together with its stored metadata. */
564
- interface KvValueResult {
565
- /** Per-key metadata, or `null` when none. */
566
- metadata: unknown;
567
- /** The stored value as a string, or `null` when the key is absent. */
568
- value: null | string;
648
+ declare const mergeStrategyForAggregate: (input: {
649
+ agg?: {
650
+ op?: "avg" | "count" | "max" | "min" | "sum";
651
+ };
652
+ kind: "groupBy";
653
+ } | {
654
+ kind: "count";
655
+ } | {
656
+ kind: "scalar";
657
+ op: "avg" | "count" | "max" | "min" | "sum";
658
+ }) => MergeStrategy;
659
+ interface FanOutSpec {
660
+ merge: MergeStrategy;
661
+ /** Table whose shard keys drive the fan-out. */
662
+ table: string;
569
663
  }
570
664
  /**
571
- * The introspector the worker wires for the studio's KV browser. Build it from
572
- * the env's bound KV namespaces. Omit it and the `/_lunora/admin/kv/*`
573
- * endpoints respond `KV_NOT_CONFIGURED`.
665
+ * Per-shard failure surfaced in the aggregate response's `errors` field. We
666
+ * never throw out of `fanOut` slow/failed shards are *data*, not an
667
+ * exception, so callers can decide whether to retry or surface a partial
668
+ * UI.
574
669
  */
575
- interface KvIntrospector {
576
- /** Delete a key from a namespace. No-op when the key is absent. */
577
- deleteKey: (options: {
578
- key: string;
579
- namespace: string;
580
- }) => Promise<void>;
581
- /** Read a value (as text) and its metadata from a namespace key. */
582
- getValue: (options: {
583
- key: string;
584
- namespace: string;
585
- }) => Promise<KvValueResult>;
586
- /** List keys in a namespace, optionally filtered by prefix and paginated. */
587
- listKeys: (options: {
588
- cursor?: string;
589
- limit?: number;
590
- namespace: string;
591
- prefix?: string;
592
- }) => Promise<KvKeyListResult>;
593
- /** List the registered KV namespaces (binding names). */
594
- listNamespaces: () => Promise<KvNamespaceSummary[]>;
595
- /** Write a value (as text) with optional absolute expiration / relative TTL and metadata. */
596
- putValue: (options: {
597
- expiration?: number;
598
- expirationTtl?: number;
599
- key: string;
600
- metadata?: unknown;
601
- namespace: string;
602
- value: string;
603
- }) => Promise<void>;
670
+ interface ShardError {
671
+ /** Human-readable; tests assert on `.includes("timeout")` and similar. */
672
+ message: string;
673
+ shardKey: string;
674
+ /** Set when the per-shard timeout fired. */
675
+ timedOut: boolean;
604
676
  }
605
- /**
606
- * Shared, bundler-inlined helpers for the structured `fields` a
607
- * `ctx.log.<level>(message, fields)` / `ctx.log.with(fields)` call carries.
608
- *
609
- * Inlined (like {@link file://./otlp.ts}) so `@lunora/do`, `@lunora/runtime`,
610
- * `@lunora/config`, and `@lunora/studio` — which sit on different tiers with no
611
- * acceptable runtime dependency edge between them — share ONE implementation of
612
- * field rendering/normalization instead of the byte-identical copies they would
613
- * otherwise hand-mirror. Keep this genuinely zero-dependency (only built-ins) so
614
- * inlining into each `dist` stays sound.
615
- */
616
- /** Structured, filterable key/value fields attached to a `ctx.log` line. */
617
- type LogFields = Record<string, unknown>;
618
- /**
619
- * Severity of a `ctx.log.*` call. The five console method names (`log` is the
620
- * default level, distinct from `info`) plus `trace`/`fatal`, so the logger spans
621
- * the full OpenTelemetry severity ramp (`trace`→`fatal`).
622
- */
623
- type ContextLogLevel = "debug" | "error" | "fatal" | "info" | "log" | "trace" | "warn";
624
- /**
625
- * Per-event context handed to a sink alongside the event: lets a sink register
626
- * background work (a telemetry POST, a durable pipeline send) with the request's
627
- * `waitUntil` so it survives isolate teardown after the response returns. Absent
628
- * `waitUntil` (no request context) means the sink falls back to fire-and-forget.
629
- */
630
- interface LogSinkContext {
631
- /** Keep a background promise alive past the response (the request's `waitUntil`). */
632
- waitUntil?: (promise: Promise<unknown>) => void;
677
+ interface FanOutResult<T = unknown> {
678
+ /** Merged value type depends on the merge strategy. */
679
+ data: T;
680
+ errors: ReadonlyArray<ShardError>;
681
+ /** Shards that failed or timed out. */
682
+ failed: number;
683
+ /** Shards that returned successfully. */
684
+ ok: number;
633
685
  }
634
- /**
635
- * One application log line emitted from a function handler via `ctx.log`.
636
- * Produced per `ctx.log.*` call (unlike a per-dispatch RPC summary).
637
- */
638
- interface LogEvent {
639
- /** Raw arguments passed to the `ctx.log.*` call, in order. */
640
- args: unknown[];
686
+ interface QueryCoordinatorOptions {
641
687
  /**
642
- * Structured fields the caller attached (`ctx.log.info(message, fields)` or a
643
- * bound `ctx.log.with(fields)` child), already normalized to a fresh bag of
644
- * JSON-safe primitives (see `shared/log-fields.ts`). Absent for a plain
645
- * console-style call.
688
+ * Maximum number of shard RPCs to issue in parallel. Defaults to 16 —
689
+ * keeps the 30-second Worker CPU budget healthy when fanning out to
690
+ * dozens of shards and avoids stampeding the DO namespace.
646
691
  */
647
- fields?: LogFields;
648
- /** Function path that emitted the line, e.g. `"messages:list"`. */
692
+ maxConcurrency?: number;
693
+ /**
694
+ * Hard per-shard timeout in milliseconds. Defaults to 5000; a slow
695
+ * shard surfaces in `errors[]` rather than stalling the aggregate.
696
+ */
697
+ perShardTimeoutMs?: number;
698
+ /** Required — drives which shards to fan out to. */
699
+ registry: ShardRegistry;
700
+ }
701
+ interface FanOutRequest {
702
+ args?: Record<string, unknown>;
703
+ fanOut: FanOutSpec;
649
704
  functionPath: string;
650
- /** Severity the line was logged at. */
651
- level: ContextLogLevel;
652
- /** Display string — the message, or the console-style args rendered and space-joined. */
653
- message: string;
654
- /** Shard key for single-shard calls; absent for the unnamed root DO. */
655
- shardKey?: string;
656
- /** Span id of the RPC this line was emitted under (trace correlation), or absent. */
657
- spanId?: string;
658
- /** Trace id this line belongs to (from the inbound `traceparent`), or absent. */
659
- traceId?: string;
660
- /** Wall-clock millis when the line was emitted. */
661
- ts: number;
662
- /** Acting userId, or absent when anonymous. */
663
- userId?: string;
705
+ /** Forwarded to each shard fetch (auth, cookies, bookmark). */
706
+ headers?: Record<string, string>;
664
707
  }
665
708
  /**
666
- * The written-column contract: every field `pipelineLogSink` emits, mapped to the
667
- * column it is stored under by default (the identity mapping). Also the source of
668
- * truth for the {@link PipelineLogField} union. Mirrors the record built in
669
- * `pipelineLogSink` the read side of the same contract.
670
- */
671
- declare const DEFAULT_COLUMNS: {
672
- readonly fields: "fields";
673
- readonly functionPath: "functionPath";
674
- readonly level: "level";
675
- readonly message: "message";
676
- readonly shardKey: "shardKey";
677
- readonly spanId: "spanId";
678
- readonly traceId: "traceId";
679
- readonly ts: "ts";
680
- readonly userId: "userId";
681
- };
682
- /**
683
- * The canonical field names of one persisted log record — the keys
684
- * `pipelineLogSink` writes. Used as the {@link PipelineLogColumnMap} keys and the
685
- * {@link PipelineLogRow} shape, so the reader stays decoupled from whatever
686
- * physical column names the operator's Iceberg table happens to use.
709
+ * Cross-shard migration request. Unlike {@link FanOutRequest} there is no merge
710
+ * strategy per-shard payloads are `MigrationRunResult`-shaped objects, not
711
+ * rows, so {@link QueryCoordinator.orchestrateMigration} rolls them up with the
712
+ * fixed semantics documented on {@link MigrationFanOutResult}.
713
+ *
714
+ * `functionPath` is the admin RPC to invoke on each shard
715
+ * (`__lunora_admin__:runMigration` or `:migrationStatus`); `headers` must carry
716
+ * the `Authorization` bearer header the shard's admin gate requires (the
717
+ * configured admin token), or every shard comes back as a 403 error.
687
718
  */
688
- type PipelineLogField = keyof typeof DEFAULT_COLUMNS;
719
+ interface MigrationFanOutRequest {
720
+ args?: Record<string, unknown>;
721
+ functionPath: string;
722
+ headers?: Record<string, string>;
723
+ /** Table whose live shard keys the migration runs across. */
724
+ table: string;
725
+ }
726
+ /** One shard's outcome: either the unwrapped admin `result` payload, or an error. */
727
+ interface ShardMigrationOutcome {
728
+ error?: {
729
+ message: string;
730
+ timedOut: boolean;
731
+ };
732
+ /** The shard's admin `result`, peeled out of the `{ result }` envelope. */
733
+ result?: unknown;
734
+ shardKey: string;
735
+ }
736
+ interface MigrationFanOutResult {
737
+ /** Summed `changed` across shards whose result carried a numeric count. */
738
+ changed: number;
739
+ /** Shards that errored or timed out. */
740
+ failed: number;
741
+ /** Shards that returned a 2xx result. */
742
+ ok: number;
743
+ /** Summed `processed` across shards whose result carried a numeric count. */
744
+ processed: number;
745
+ /** Per-shard outcomes, in registry order. */
746
+ shards: ReadonlyArray<ShardMigrationOutcome>;
747
+ /**
748
+ * Rolled-up status. `"failed"` if any shard's runner reported failure;
749
+ * `"in_progress"` if any shard is incomplete or unreachable (the run stays
750
+ * resumable); `"completed"` only when every shard finished cleanly.
751
+ */
752
+ status: "completed" | "failed" | "in_progress";
753
+ }
689
754
  /**
690
- * Field-to-column-name map. Defaults to the identity mapping (each field stored
691
- * under its own name, matching what `pipelineLogSink` writes). Override per-field
692
- * when the Iceberg schema renames a column; unspecified fields keep their
693
- * default. This is the single knob that lets one reader serve differently shaped
694
- * Data Catalog tables.
755
+ * Cross-shard rank request. Like {@link MigrationFanOutRequest} there is no
756
+ * caller-supplied merge per-shard payloads are `{before, total}` objects, so
757
+ * {@link QueryCoordinator.orchestrateRank} rolls them up with the fixed
758
+ * `{position: Σbefore + 1, total: Σtotal}` semantics {@link mergeRank} defines.
759
+ *
760
+ * The key tuple (`partitionKey`/`sortValues`/`rowId`) is built off the row doc
761
+ * via `@lunora/do`'s `rankKeyFromDoc(index, doc)` and forwarded verbatim to
762
+ * each shard's `__lunora_admin__:rankBefore` admin RPC; `headers` must carry
763
+ * the admin bearer the shard's admin gate requires.
695
764
  */
696
- type PipelineLogColumnMap = Partial<Record<PipelineLogField, string>>;
697
- /** An opaque keyset cursor: the `ts` of the row after the last one returned. */
698
- interface PipelineLogCursor {
699
- /** Epoch-millis boundary; the next page is every row strictly older than this. */
700
- ts: number;
765
+ interface RankFanOutRequest {
766
+ headers?: Record<string, string>;
767
+ /** Rank index name on `table`. */
768
+ index: string;
769
+ /** Canonical-JSON partition tuple — `encodePartitionKey(index.partitionBy, doc)`. */
770
+ partitionKey: string;
771
+ /** The `__id__` tiebreak value — `doc._id`. */
772
+ rowId: string;
773
+ /** Serialized sort-key values in `index.sortBy` order, as produced by `rankKeyFromDoc` (wire-safe + byte-matching the stored columns). */
774
+ sortValues: ReadonlyArray<unknown>;
775
+ /** Table whose live shard keys the rank fans out across. */
776
+ table: string;
701
777
  }
702
- /** Filters for one {@link PipelineLogReader} query. Every value is inlined safely (`lit`/`sql`). */
703
- interface PipelineLogQuery {
704
- /** Continue after a previous page (keyset on `ts DESC`). Combined with the other filters. */
705
- cursor?: PipelineLogCursor;
706
- /** Match only this exact function path's records. Prefer `functionPathPrefix` for a namespace sweep. */
707
- functionPath?: string;
708
- /** Match records whose `functionPath` starts with this string (rendered as a `LIKE 'prefix%'`). */
709
- functionPathPrefix?: string;
710
- /** Match only this exact severity. When set, `minLevel` is ignored for the same field. */
711
- level?: ContextLogLevel;
712
- /** Max rows to return. Clamped to `[1, 10000]`; defaults to {@link DEFAULT_LOG_LIMIT}. */
713
- limit?: number;
714
- /** Severity floor: keep every level at or above this in {@link LOG_LEVEL_ORDER} (e.g. `warn` keeps warn, error, fatal). */
715
- minLevel?: ContextLogLevel;
716
- /** Match only this shard key. */
717
- shardKey?: string;
718
- /** Lower time bound, inclusive (`ts` at or after this), epoch-millis. */
719
- sinceTs?: number;
720
- /** Match only this trace id. */
721
- traceId?: string;
722
- /** Upper time bound, inclusive (`ts` at or before this), epoch-millis. */
723
- untilTs?: number;
724
- /** Match only this acting user id. */
725
- userId?: string;
778
+ interface RankFanOutResult {
779
+ /** Shards that errored or timed out. */
780
+ failed: number;
781
+ /** Shards that returned a 2xx `{before, total}`. */
782
+ ok: number;
783
+ /** `true` when at least one shard failed/timed out, so `position`/`total` are under-counts (failed shards' rows missing). A caller needing an exact global rank should treat this as an error, not trust the numbers. */
784
+ partial: boolean;
785
+ /** 1-based global position within the partition (`Σbefore + 1`). */
786
+ position: number;
787
+ /** Per-shard outcomes, in registry order. */
788
+ shards: ReadonlyArray<ShardRankOutcome>;
789
+ /** Global partition total (`Σtotal`). */
790
+ total: number;
791
+ }
792
+ /** One shard's rank outcome: its `{before, total}` payload, or an error. */
793
+ interface ShardRankOutcome {
794
+ error?: {
795
+ message: string;
796
+ timedOut: boolean;
797
+ };
798
+ result?: {
799
+ before: number;
800
+ total: number;
801
+ };
802
+ shardKey: string;
726
803
  }
727
804
  /**
728
- * One decoded log record. Always keyed by the canonical {@link PipelineLogField}
729
- * names regardless of the physical columns (the reader remaps via `columnMap`),
730
- * so consumers never see the operator's storage names.
805
+ * Cross-shard ranked-pagination request. Like {@link RankFanOutRequest} there's
806
+ * no caller-supplied merge the merge is the fixed k-way merge by the rank-key
807
+ * tuple. `take` is the global page size; `cursor` is the opaque composite cursor
808
+ * from the prior page's `continueCursor` (absent → first page). `partitionKey`,
809
+ * when set, pins a single partition (`encodePartitionKey(index.partitionBy, where)`),
810
+ * forwarded so each shard scopes its local slice to that partition.
811
+ *
812
+ * `directions` is the per-sort-key direction list (`index.sortBy[i].direction`)
813
+ * the coordinator's comparator needs to break ties the same way each shard's
814
+ * `ORDER BY` does. `partitionKey` and the `__id__` tiebreak are always ascending
815
+ * (matching the shard companion's btree), so only the sort columns vary.
731
816
  */
732
- interface PipelineLogRow {
733
- /**
734
- * Structured fields, when the record carried them. A `serializeFields` sink
735
- * stores these as a JSON string, which the reader parses back to an object;
736
- * a plain string that is not valid JSON is returned verbatim.
737
- */
738
- fields?: unknown;
739
- /** Function path that emitted the line, e.g. `"messages:list"`. */
740
- functionPath: string;
741
- /** Severity the line was logged at. */
742
- level: ContextLogLevel;
743
- /** Rendered message. */
744
- message: string;
745
- /** Shard key for single-shard calls, when present. */
746
- shardKey?: string;
747
- /** Span id the line was emitted under, when present. */
748
- spanId?: string;
749
- /** Trace id the line belongs to, when present. */
750
- traceId?: string;
751
- /** Epoch-millis the line was emitted. */
752
- ts: number;
753
- /** Acting user id, when present. */
754
- userId?: string;
817
+ interface RankPageFanOutRequest {
818
+ /** Opaque composite cursor from the prior page's `continueCursor`. */
819
+ cursor?: null | string;
820
+ /** Per-sort-key directions, in `index.sortBy` order. Missing/short ascending. */
821
+ directions?: ReadonlyArray<RankDirection>;
822
+ headers?: Record<string, string>;
823
+ /** Rank index name on `table`. */
824
+ index: string;
825
+ /** Optional partition pin forwarded to each shard's local `rankPage`. */
826
+ partitionKey?: string;
827
+ /** Table whose live shard keys the page fans out across. */
828
+ table: string;
829
+ /** Global page size; defaults to 100, capped at 1000 (matching the shard-local `rankPage`). */
830
+ take?: number;
755
831
  }
756
- /** One page of {@link PipelineLogRow}s, newest first, plus the cursor for the next page (absent means last page). */
757
- interface PipelineLogPage {
758
- /** The cursor to pass as {@link PipelineLogQuery} `cursor` for the following page; absent when this is the last page. */
759
- nextCursor?: PipelineLogCursor;
760
- /** The rows, ordered `ts DESC` (newest first). At most `limit` of them. */
761
- rows: PipelineLogRow[];
832
+ /** One shard's `rankPage` outcome: its local ranked slice, or an error. */
833
+ interface ShardRankPageOutcome {
834
+ /** The directions the shard ordered by (`index.sortBy[i].direction`); authoritative for the merge. */
835
+ directions?: ReadonlyArray<RankDirection>;
836
+ error?: {
837
+ message: string;
838
+ timedOut: boolean;
839
+ };
840
+ hasMore?: boolean;
841
+ rows?: ReadonlyArray<RankPageRow>;
842
+ shardKey: string;
762
843
  }
763
- /** Options for {@link createPipelineLogReader}. */
764
- interface PipelineLogReaderOptions {
844
+ interface RankPageFanOutResult {
845
+ /** Opaque composite cursor for the next page, or `null` when the merge is exhausted. */
846
+ continueCursor: null | string;
847
+ /** Shards that errored or timed out. */
848
+ failed: number;
849
+ /** `true` when the global merge has no further rows. */
850
+ isDone: boolean;
851
+ /** Shards that returned a 2xx slice. */
852
+ ok: number;
853
+ /** The globally-ranked page of hydrated docs, in cross-shard rank order. */
854
+ page: ReadonlyArray<Record<string, unknown>>;
855
+ /** `true` when at least one shard failed/timed out, so the page may be missing that shard's rows. */
856
+ partial: boolean;
857
+ /** Per-shard outcomes, in registry order. */
858
+ shards: ReadonlyArray<ShardRankPageOutcome>;
859
+ }
860
+ interface QueryCoordinator {
861
+ fanOut: <T = unknown>(namespace: ShardNamespaceLike, request: FanOutRequest) => Promise<FanOutResult<T>>;
765
862
  /**
766
- * Override any physical column name that diverges from the default (identity)
767
- * mapping. Unspecified fields keep their {@link DEFAULT_LOG_COLUMNS} name.
863
+ * Fan the `__lunora_admin__:applyCdc` admin RPC out by forwarding each
864
+ * pre-bucketed per-shard batch of CDC changes, rolling up the applied/failed
865
+ * counts. The replay half of point-in-time recovery.
768
866
  */
769
- columnMap?: PipelineLogColumnMap;
867
+ orchestrateApplyCdc: (namespace: ShardNamespaceLike, request: ApplyCdcFanOutRequest) => Promise<ApplyCdcFanOutResult>;
868
+ /**
869
+ * Fan the `__lunora_admin__:cdcSync` admin RPC out to every live shard,
870
+ * each resumed from its own cursor in `request.cursors` (shardKey → seq).
871
+ * Returns the per-shard change pages plus their new cursors so the caller
872
+ * can checkpoint each shard independently — the streaming-export feed.
873
+ */
874
+ orchestrateCdcSync: (namespace: ShardNamespaceLike, request: CdcSyncFanOutRequest) => Promise<CdcSyncFanOutResult>;
875
+ /**
876
+ * Fan an export admin RPC out to every live shard, returning the
877
+ * per-shard `{rows}` payloads alongside any per-shard errors. Each shard
878
+ * returns a JSON envelope (not a streaming body) so this method is the
879
+ * collector — the worker assembles the NDJSON stream.
880
+ */
881
+ orchestrateExport: (namespace: ShardNamespaceLike, request: ExportFanOutRequest) => Promise<ExportFanOutResult>;
882
+ /**
883
+ * Fan an import admin RPC out by routing each row to its owning shard. The
884
+ * shard registry resolves which shards exist; rows whose table has a
885
+ * `shardBy(field)` are bucketed using that field's value as the shard key,
886
+ * other tables fall back to the runtime's default `__root__` shard.
887
+ */
888
+ orchestrateImport: (namespace: ShardNamespaceLike, request: ImportFanOutRequest) => Promise<ImportFanOutResult>;
889
+ /** Fan a migration admin RPC out to every live shard of a table and roll up the per-shard outcomes. */
890
+ orchestrateMigration: (namespace: ShardNamespaceLike, request: MigrationFanOutRequest) => Promise<MigrationFanOutResult>;
891
+ /**
892
+ * Fan the `__lunora_admin__:rankBefore` admin RPC out to every live shard of
893
+ * a table and roll up the per-shard `{before, total}` payloads into the
894
+ * global rank (`{position: Σbefore + 1, total: Σtotal}`). The cross-shard
895
+ * `rank()` path for a partition that spans shards.
896
+ */
897
+ orchestrateRank: (namespace: ShardNamespaceLike, request: RankFanOutRequest) => Promise<RankFanOutResult>;
898
+ /**
899
+ * Page a ranked query across every live shard of a `.shardBy(...)` table.
900
+ * Fans `__lunora_admin__:rankPage` out to each shard, gathers each shard's
901
+ * local ranked slice (rows tagged with their rank-key tuple), and k-way
902
+ * merges them by that tuple into one globally-ranked page of `take` rows.
903
+ * The opaque `continueCursor` is a composite of per-shard cursors so the
904
+ * next page resumes each shard strictly-after the last row the global page
905
+ * consumed from it — pages never drop or duplicate a row at a shard
906
+ * boundary. The cross-shard `rankPage()` path (PLAN5 §7.1 / PLAN2 #3).
907
+ */
908
+ orchestrateRankPage: (namespace: ShardNamespaceLike, request: RankPageFanOutRequest) => Promise<RankPageFanOutResult>;
909
+ /**
910
+ * Fan the `__lunora_admin__:getMetrics` admin RPC out to every live shard of
911
+ * a table and collect each shard's lifetime `requests` total into a per-shard
912
+ * `{ shardKey, requests }` distribution. The feed the studio's `hot_shard`
913
+ * advisor lint needs: a single shard's snapshot can't reveal cross-shard
914
+ * skew, so this fans the cheap metrics read out and returns the whole shard
915
+ * set's request volumes (a failed shard surfaces as `requests: 0`).
916
+ */
917
+ orchestrateShardTraffic: (namespace: ShardNamespaceLike, request: ShardTrafficFanOutRequest) => Promise<ShardTrafficFanOutResult>;
918
+ readonly registry: ShardRegistry;
919
+ }
920
+ /**
921
+ * Cross-shard export request. `tables` is the union of every table the caller
922
+ * wants exported (shard-local **or** global); `headers` carries the admin
923
+ * bearer the per-shard gate expects. Shard registries are queried for the
924
+ * complete set of live shards across all listed shard-local tables.
925
+ */
926
+ interface ExportFanOutRequest {
927
+ args?: Record<string, unknown>;
928
+ headers?: Record<string, string>;
770
929
  /**
771
- * The Iceberg namespace the `table` lives in (R2 Data Catalog database).
772
- * Combined as `namespace.table` in the `FROM` clause; omit when `table`
773
- * already carries its namespace.
930
+ * Tables driving the fan-out. Shards are derived from the union of each
931
+ * table's live shard keys so an export of `["users","messages"]` reaches
932
+ * every shard that holds either table. Globals are skipped here; the
933
+ * worker reads them from D1 directly.
774
934
  */
775
- namespace?: string;
776
- /** The Iceberg table name the Pipeline writes log records to (e.g. `"logs"`). */
777
- table: string;
935
+ tables: ReadonlyArray<string>;
778
936
  }
779
- /** The reader surface: a single keyset-paginated {@link PipelineLogPage} query. */
780
- interface PipelineLogReader {
781
- /** Run one filtered, keyset-paginated read and return a {@link PipelineLogPage}. */
782
- query: (query?: PipelineLogQuery) => Promise<PipelineLogPage>;
937
+ /** Per-shard export outcome. */
938
+ interface ShardExportOutcome {
939
+ error?: {
940
+ message: string;
941
+ timedOut: boolean;
942
+ };
943
+ /** Rows from this shard, or undefined when an error occurred. */
944
+ rows?: ReadonlyArray<{
945
+ doc: Record<string, unknown>;
946
+ table: string;
947
+ }>;
948
+ shardKey: string;
949
+ }
950
+ interface ExportFanOutResult {
951
+ failed: number;
952
+ ok: number;
953
+ shards: ReadonlyArray<ShardExportOutcome>;
783
954
  }
784
- /** The written-column contract exposed publicly: canonical field to default physical column name. */
785
- declare const DEFAULT_LOG_COLUMNS: Readonly<Record<PipelineLogField, string>>;
786
- /** Default page size when a query omits `limit`. */
787
- declare const DEFAULT_LOG_LIMIT: number;
788
955
  /**
789
- * Build a durable-log reader over one R2 Data Catalog (Iceberg) table.
790
- *
791
- * The returned {@link PipelineLogReader} compiles each call to a safe
792
- * `SELECT ... WHERE ... ORDER BY ts DESC LIMIT n` (over-fetching by one) and
793
- * decodes the rows back to the canonical {@link PipelineLogRow} shape. All value
794
- * filters are escaped through `@lunora/bindings/r2sql`'s `sql`/`lit`; column
795
- * names come from `options.columnMap` (operator config), spliced with `raw`.
796
- * @param client An {@link R2SqlClient} (`createR2Sql({ accountId, apiToken, bucket })`).
797
- * @param options The target `table` (plus optional `namespace`) and any `columnMap` overrides.
956
+ * Cross-shard change-data-capture request. `tables` drives shard discovery (the
957
+ * union of their live shard keys, like export); `cursors` maps each shard key
958
+ * to the `seq` it was last read through (absent → from the beginning). `limit`
959
+ * caps each shard's page.
798
960
  */
799
- declare const createPipelineLogReader: (client: R2SqlClient, options: PipelineLogReaderOptions) => PipelineLogReader;
961
+ interface CdcSyncFanOutRequest {
962
+ cursors?: Record<string, number>;
963
+ headers?: Record<string, string>;
964
+ limit?: number;
965
+ tables: ReadonlyArray<string>;
966
+ }
967
+ /** Per-shard CDC page: the changes plus the new cursor to resume this shard from. */
968
+ interface ShardCdcOutcome {
969
+ changes?: ReadonlyArray<Record<string, unknown>>;
970
+ /** New per-shard cursor; on error it echoes the shard's prior cursor so a retry resumes cleanly. */
971
+ cursor: number;
972
+ error?: {
973
+ message: string;
974
+ timedOut: boolean;
975
+ };
976
+ shardKey: string;
977
+ }
978
+ interface CdcSyncFanOutResult {
979
+ failed: number;
980
+ ok: number;
981
+ shards: ReadonlyArray<ShardCdcOutcome>;
982
+ }
800
983
  /**
801
- * Wire constants for the durable log archive, shared between the server route
802
- * (`@lunora/runtime`'s `log-archive-admin-routes`) and the Studio Archive feed
803
- * (`@lunora/studio`). Kept here not in `@lunora/runtime` because the studio
804
- * is a browser bundle that must not import a runtime *value* (which would drag
805
- * the DO/R2-SQL runtime into the browser). This file is dependency-free and
806
- * bundler-inlined into each consumer, so both sides share one source of truth
807
- * with no dependency edge.
984
+ * Cross-shard import request. Rows have already been bucketed by the runtime
985
+ * into one batch per shard key — the coordinator's job is to forward each
986
+ * batch and roll up the per-shard insert counts + errors.
808
987
  */
988
+ interface ImportFanOutRequest {
989
+ /**
990
+ * Per-shard batches keyed by shard key. Each entry will be POSTed as the
991
+ * `rows` arg of `__lunora_admin__:importShard`. The shard's
992
+ * starting-line-number for error attribution is carried in `startLine`.
993
+ */
994
+ batches: ReadonlyArray<{
995
+ rows: ReadonlyArray<{
996
+ doc: Record<string, unknown>;
997
+ table: string;
998
+ }>;
999
+ shardKey: string;
1000
+ startLine?: number;
1001
+ }>;
1002
+ headers?: Record<string, string>;
1003
+ }
1004
+ interface ShardImportOutcome {
1005
+ error?: {
1006
+ message: string;
1007
+ timedOut: boolean;
1008
+ };
1009
+ result?: {
1010
+ conflicts: number;
1011
+ errors: ReadonlyArray<{
1012
+ code: string;
1013
+ line: number;
1014
+ message: string;
1015
+ table: string;
1016
+ }>;
1017
+ inserted: Record<string, number>;
1018
+ };
1019
+ shardKey: string;
1020
+ }
1021
+ interface ImportFanOutResult {
1022
+ /** Total conflicts (skipped `_id`s) across shards. */
1023
+ conflicts: number;
1024
+ /** Errors merged across all per-shard outcomes. */
1025
+ errors: ReadonlyArray<{
1026
+ code: string;
1027
+ line: number;
1028
+ message: string;
1029
+ table: string;
1030
+ }>;
1031
+ failed: number;
1032
+ /** Per-table summed insert counts. */
1033
+ inserted: Record<string, number>;
1034
+ ok: number;
1035
+ shards: ReadonlyArray<ShardImportOutcome>;
1036
+ }
809
1037
  /**
810
- * The error `code` the archive route returns (400) when the operator has wired
811
- * no archive table (`logArchive`) or the `R2_SQL_*` credentials are missing. The
812
- * Studio keys its "not configured" empty state off this exact value.
1038
+ * Cross-shard CDC replay request (point-in-time recovery). Changes are
1039
+ * pre-bucketed by the runtime into one batch per shard key — the coordinator
1040
+ * forwards each batch to `__lunora_admin__:applyCdc` and rolls up the counts.
813
1041
  */
814
- declare const LOG_ARCHIVE_NOT_CONFIGURED = "LOG_ARCHIVE_NOT_CONFIGURED";
815
- /** The route the studio's `queryLogArchive` client method POSTs to. */
816
- declare const LOG_ARCHIVE_PATH = "/_lunora/admin/logs/archive";
1042
+ interface ApplyCdcFanOutRequest {
1043
+ batches: ReadonlyArray<{
1044
+ changes: ReadonlyArray<Record<string, unknown>>;
1045
+ shardKey: string;
1046
+ }>;
1047
+ headers?: Record<string, string>;
1048
+ }
1049
+ interface ApplyCdcFanOutResult {
1050
+ /** Total changes applied across shards. */
1051
+ applied: number;
1052
+ failed: number;
1053
+ ok: number;
1054
+ }
817
1055
  /**
818
- * The app-level archive config the worker passes through: which Data Catalog
819
- * table the Pipeline writes log records to (the read-side of `pipelineLogSink`),
820
- * plus optional namespace / physical-column overrides. The R2 SQL *credentials*
821
- * are NOT here they live on `env` (`R2_SQL_*`), read per request.
1056
+ * Cross-shard traffic request. Like {@link MigrationFanOutRequest} there is no
1057
+ * caller-supplied merge each shard's `__lunora_admin__:getMetrics` payload
1058
+ * carries its own lifetime `requests` total, and {@link rollUpShardTraffic}
1059
+ * collects them into one `{ shardKey, requests }` entry per shard. `headers`
1060
+ * must carry the admin bearer the per-shard `getMetrics` gate requires.
1061
+ *
1062
+ * `table` drives shard discovery: the registry's live shard keys for the table
1063
+ * are the shards fanned out to. This is the feed the studio's `hot_shard`
1064
+ * runtime advisor consumes to compute cross-shard skew — a single shard's
1065
+ * snapshot can't, so the panel fans this out on demand.
822
1066
  */
823
- interface LogArchiveConfig {
824
- /** Override any physical column name that diverges from the {@link createPipelineLogReader} default mapping. */
825
- columnMap?: PipelineLogColumnMap;
826
- /** The Iceberg namespace (R2 Data Catalog database) the table lives in; omit when `table` already carries it. */
827
- namespace?: string;
828
- /** The Iceberg table the Pipeline writes log records to (e.g. `"logs"`). */
1067
+ interface ShardTrafficFanOutRequest {
1068
+ headers?: Record<string, string>;
1069
+ /** Table whose live shard keys the traffic fan-out runs across. */
829
1070
  table: string;
830
1071
  }
1072
+ /** One shard's traffic total, mirroring the advisor's `AdvisorShardTraffic` (sans the optional `group`). */
1073
+ interface ShardTrafficEntry {
1074
+ /** Lifetime request count read off the shard's `getMetrics` snapshot; `0` for a shard that failed/timed out. */
1075
+ requests: number;
1076
+ /** The shard key (the DO id name); `""` for the unnamed root shard. */
1077
+ shardKey: string;
1078
+ }
1079
+ interface ShardTrafficFanOutResult {
1080
+ /** Shards that errored or timed out (their `requests` are reported as `0`). */
1081
+ failed: number;
1082
+ /** Shards that returned a 2xx `getMetrics` snapshot. */
1083
+ ok: number;
1084
+ /**
1085
+ * Per-shard request totals, in registry order. Shaped to plug straight into
1086
+ * the advisor's `LintContext.shardTraffic` so the `hot_shard` lint can
1087
+ * compute the cross-shard share. A failed shard still appears (with
1088
+ * `requests: 0`) so callers see the full shard set.
1089
+ */
1090
+ shards: ReadonlyArray<ShardTrafficEntry>;
1091
+ }
1092
+ declare const createQueryCoordinator: (options: QueryCoordinatorOptions) => QueryCoordinator;
831
1093
  /**
832
- * Build the {@link LogArchiveConfig} from `env` for the generated worker entry —
833
- * the zero-config seam the codegen `createWorker({ logArchive })` wiring uses,
834
- * mirroring how the R2 SQL *credentials* already come from `env` (`R2_SQL_*`).
835
- *
836
- * Returns `undefined` unless `LUNORA_LOG_ARCHIVE_TABLE` is set, so the studio
837
- * Archive feed stays "not configured" until the operator opts in by naming the
838
- * Data Catalog table (+ optional `LUNORA_LOG_ARCHIVE_NAMESPACE`). `columnMap`
839
- * overrides aren't env-expressible — a hand-written worker passes `logArchive`
840
- * to `createWorker` directly for those.
1094
+ * One change in the export stream a clean projection of the raw op-log CDC
1095
+ * record that preserves `seq` (for ordering / idempotency downstream) and the
1096
+ * post-image `doc`. A delete carries no `doc`; the primary key survives in `id`.
841
1097
  */
842
- declare const resolveLogArchiveFromEnv: (environment: unknown) => LogArchiveConfig | undefined;
1098
+ interface ExportChange {
1099
+ doc?: Record<string, unknown>;
1100
+ id?: string;
1101
+ op: "delete" | "insert" | "update" | "upsert";
1102
+ seq?: number;
1103
+ table: string;
1104
+ ts?: number;
1105
+ }
1106
+ /** One shard's batch handed to a sink. `cursor` is the new watermark this batch advances the shard to on ack. */
1107
+ interface ExportBatch {
1108
+ changes: ReadonlyArray<ExportChange>;
1109
+ cursor: number;
1110
+ shardKey: string;
1111
+ sink: string;
1112
+ }
843
1113
  /**
844
- * What kind of instrument produced a measurement, which decides how a collector
845
- * aggregates it:
846
- *
847
- * - `counter` a monotonic delta to add up (requests, retries, bytes sent).
848
- * - `gauge` — a point-in-time reading that replaces the last one (queue depth,
849
- * cache size).
850
- * - `histogram` — a value whose *distribution* matters (latency, payload size),
851
- * giving percentiles rather than just a mean.
1114
+ * An export sink. `deliver` MUST reject (throw) when the batch was not durably
1115
+ * accepted downstream — a resolved promise is treated as an acknowledgement and
1116
+ * advances the cursor. Build one with {@link defineExportSink}, or use the
1117
+ * built-in {@link webhookExportSink} / {@link r2Sink}.
852
1118
  */
853
- type MetricKind = "counter" | "gauge" | "histogram";
1119
+ interface ExportSink {
1120
+ deliver: (batch: ExportBatch) => Promise<void>;
1121
+ name: string;
1122
+ }
854
1123
  /**
855
- * One measurement recorded from a function handler.
856
- *
857
- * Each `ctx.metrics.*` call produces exactly one of these the runtime does no
858
- * pre-aggregation, so counters carry **delta** temporality and a collector sums
859
- * them. That keeps the sink model identical to logs and spans (one event, one
860
- * export) at the cost of chattiness in a hot loop, where the handler should sum
861
- * locally and record once.
1124
+ * Durable per-shard cursor store, keyed by sink name. Mirrors the
1125
+ * `__lunora_source_cursor` watermark from CDC-in: the last op-log `seq` each
1126
+ * shard was delivered through. Injected so the tap stays testable and workerd-safe
1127
+ * {@link createMemoryCursorStore} for tests, {@link createKvCursorStore} for a
1128
+ * deployment.
862
1129
  */
863
- interface MetricEvent {
864
- /**
865
- * Structured attributes the caller attached, normalized to a fresh bag of
866
- * JSON-safe primitives (see `shared/log-fields.ts`). These are the metric's
867
- * dimensions keep them low-cardinality; an id-valued attribute creates a
868
- * distinct time series per id.
869
- *
870
- * Caller-controlled, so they MAY contain user input and they DO egress to
871
- * whatever destination the sink ships to — the same caveat as a log line's
872
- * `fields` and a span's `error.message`. Scrub upstream if that matters.
873
- */
874
- attributes?: LogFields;
875
- /** Function path that recorded the measurement, e.g. `"orders:checkout"`. */
876
- functionPath: string;
877
- /** Instrument kind; see {@link MetricKind}. */
878
- kind: MetricKind;
879
- /** Instrument name, e.g. `"orders.placed"`. */
1130
+ interface ExportCursorStore {
1131
+ read: (sink: string) => Promise<Record<string, number>>;
1132
+ write: (sink: string, cursors: Record<string, number>) => Promise<void>;
1133
+ }
1134
+ /** A shard the tap could not drain this pass (sink failure or shard error); its cursor was left un-advanced for retry. */
1135
+ interface ExportTapFailure {
1136
+ error: string;
1137
+ shardKey: string;
1138
+ }
1139
+ /** Outcome of one drain pass. */
1140
+ interface ExportTapResult {
1141
+ /** The persisted per-shard cursor map after this pass. */
1142
+ cursors: Record<string, number>;
1143
+ /** Total changes acknowledged by the sink this pass. */
1144
+ delivered: number;
1145
+ /** Shards left un-advanced (retry pending). Their presence does not stall other shards or the shard's writes. */
1146
+ failures: ReadonlyArray<ExportTapFailure>;
1147
+ /** `true` when any shard returned a full page (more changes likely remain) or any shard failed — the caller should schedule another pass. */
1148
+ hasMore: boolean;
1149
+ /** Number of shards inspected this pass. */
1150
+ shards: number;
1151
+ }
1152
+ /** Options for one {@link runExportTap} drain pass. */
1153
+ interface RunExportTapOptions {
1154
+ /** Cross-shard coordinator providing the op-log change feed. */
1155
+ coordinator: QueryCoordinator;
1156
+ /** Durable cursor store (per-shard watermark). */
1157
+ cursorStore: ExportCursorStore;
1158
+ /** Headers forwarded to each shard (identity / admin bearer). */
1159
+ headers?: Record<string, string>;
1160
+ /** Base backoff in ms for the first retry (doubles each attempt, capped at `maxBackoffMs`). Defaults to `100`. */
1161
+ initialBackoffMs?: number;
1162
+ /** Per-shard page size. */
1163
+ limit?: number;
1164
+ /** Cap on the exponential backoff delay. Defaults to `5000`. */
1165
+ maxBackoffMs?: number;
1166
+ /** Retries after the first delivery attempt before a shard is left for the next pass. Defaults to `3`. */
1167
+ maxRetries?: number;
1168
+ /** The shard DO namespace to fan the feed across. */
1169
+ shardDO: ShardNamespaceLike;
1170
+ /** The sink to deliver to. */
1171
+ sink: ExportSink;
1172
+ /** Injected sleep (defaults to a real timer) so tests drive backoff deterministically. */
1173
+ sleep?: (ms: number) => Promise<void>;
1174
+ /** Tables driving shard discovery (union of their live shard keys). */
1175
+ tables: ReadonlyArray<string>;
1176
+ }
1177
+ /**
1178
+ * Project a raw op-log CDC record (`{ id, op, seq, table, ts, doc? }`) into a
1179
+ * clean {@link ExportChange}. Mirrors `./connector-cdc`'s `flattenCdcChange` but
1180
+ * PRESERVES `seq` / `id` / `ts` so a downstream warehouse can order and dedupe.
1181
+ */
1182
+ declare const sanitizeChange: (raw: Record<string, unknown>) => ExportChange;
1183
+ /**
1184
+ * Run one drain pass of the export tap for a single sink. Reads the durable
1185
+ * cursor, pulls the op-log change feed, delivers each shard's ordered batch (with
1186
+ * retry/backoff), advances only the cursors of shards the sink acknowledged, and
1187
+ * persists the merged cursor map. Idempotent to schedule repeatedly (cron / admin
1188
+ * poke); `hasMore` signals whether another pass is warranted immediately.
1189
+ */
1190
+ declare const runExportTap: (options: RunExportTapOptions) => Promise<ExportTapResult>;
1191
+ /**
1192
+ * Define a custom export sink. A thin identity wrapper that validates the shape
1193
+ * and gives call sites a named factory symmetric with `defineExportSink` in the
1194
+ * plan. The `deliver` contract: resolve on durable acceptance, reject otherwise.
1195
+ */
1196
+ declare const defineExportSink: (config: ExportSink) => ExportSink;
1197
+ /** `fetch`-like signature so the webhook sink is testable without a real network. */
1198
+ type FetchLike = (input: string, init: {
1199
+ body: string;
1200
+ headers: Record<string, string>;
1201
+ method: string;
1202
+ }) => Promise<{
1203
+ ok: boolean;
1204
+ status: number;
1205
+ }>;
1206
+ /**
1207
+ * Built-in webhook sink: POST the shard's changes as an NDJSON body to `url`. A
1208
+ * non-2xx response rejects, so the tap retries + applies backpressure. Idempotency
1209
+ * headers (`x-lunora-sink`, `x-lunora-shard`, `x-lunora-cursor`) let the receiver
1210
+ * dedupe an at-least-once replay.
1211
+ */
1212
+ declare const webhookExportSink: (config: {
1213
+ fetchImpl?: FetchLike;
1214
+ headers?: Record<string, string>;
880
1215
  name: string;
881
- /** Shard key for single-shard calls; absent for the unnamed root DO. */
882
- shardKey?: string;
883
- /** Wall-clock millis when the measurement was recorded. */
884
- ts: number;
885
- /**
886
- * The measured value: the increment for a `counter`, the current reading for
887
- * a `gauge`, the observed sample for a `histogram`.
888
- */
889
- value: number;
1216
+ url: string;
1217
+ }) => ExportSink;
1218
+ /** Minimal R2 bucket surface the sink needs (structurally compatible with an `R2Bucket` binding). */
1219
+ interface R2PutLike {
1220
+ put: (key: string, value: string, options?: {
1221
+ httpMetadata?: {
1222
+ contentType?: string;
1223
+ };
1224
+ }) => Promise<unknown>;
890
1225
  }
891
1226
  /**
892
- * One span produced by a `ctx.trace(name, fn)` call, or the synthetic root span
893
- * the shard records for the dispatch itself so a waterfall has a bar to hang
894
- * its children under.
895
- *
896
- * Ids are the same lowercase-hex form the OTLP encoders and `traceparent` use
897
- * (32-hex trace, 16-hex span), so a `SpanEvent` composes into an OTLP span with
898
- * no reformatting.
1227
+ * Built-in R2 sink: write each shard's changes as an NDJSON object under
1228
+ * `&lt;prefix>/&lt;shardKey>/&lt;cursor>.ndjson`. The cursor in the key makes each object
1229
+ * content-addressed by watermark, so an at-least-once replay overwrites the same
1230
+ * key rather than duplicating (idempotent at the object level). A `put` rejection
1231
+ * propagates so the tap retries.
899
1232
  */
900
- interface SpanEvent {
901
- /**
902
- * Structured attributes the caller attached, already normalized to a fresh
903
- * bag of JSON-safe primitives (see `shared/log-fields.ts`) exactly like a log
904
- * line's `fields`. Absent when the caller passed none.
905
- */
906
- attributes?: LogFields;
907
- /** Wall-clock duration of the span body, in milliseconds. */
908
- durationMs: number;
909
- /**
910
- * Populated when the span body threw. `type` is the error's constructor name
911
- * (or its `LunoraError` code); `message` is the human-readable string and may
912
- * include user input, so sinks shipping to third parties should scrub it.
913
- */
914
- error?: {
915
- message: string;
916
- type: string;
917
- };
918
- /**
919
- * Function path the span was created under, e.g. `"messages:list"`. A span
920
- * created inside a function invoked via `ctx.runQuery`/`runMutation`/
921
- * `runAction` carries the OUTER entrypoint's path, since the composed call
922
- * reuses its context — the same attribution rule `ctx.log` follows.
923
- */
924
- functionPath: string;
925
- /** Caller-supplied span name, e.g. `"stripe.charge"`. */
1233
+ declare const r2Sink: (config: {
1234
+ bucket: R2PutLike;
926
1235
  name: string;
927
- /** True when the span body returned without throwing. */
928
- ok: boolean;
929
- /**
930
- * Span id of the enclosing span — the parent `ctx.trace` when nested, else
931
- * the dispatch's own RPC span (from the inbound `traceparent`). A span with
932
- * no inbound trace context is parented to a locally-minted root, so this is
933
- * always set for a `ctx.trace` span; only the synthetic `dispatch` span below
934
- * carries `""`, meaning "nothing above me in this trace".
1236
+ prefix?: string;
1237
+ }) => ExportSink;
1238
+ /** In-memory cursor store for tests. `snapshot` exposes the persisted cursors for assertions. */
1239
+ declare const createMemoryCursorStore: () => ExportCursorStore & {
1240
+ snapshot: () => Record<string, Record<string, number>>;
1241
+ };
1242
+ /** Minimal KV surface the cursor store needs (structurally compatible with a `KVNamespace` binding). */
1243
+ interface KvLike {
1244
+ get: (key: string, type: "json") => Promise<unknown>;
1245
+ put: (key: string, value: string) => Promise<unknown>;
1246
+ }
1247
+ /**
1248
+ * KV-backed durable cursor store. The watermark key mirrors the CDC-in
1249
+ * convention (`__lunora_source_cursor`): `__lunora_source_cursor:export:&lt;sink>`.
1250
+ * A missing / malformed value reads as the empty map (drain from the beginning),
1251
+ * so a fresh sink or a corrupted key can never crash the pass.
1252
+ */
1253
+ declare const createKvCursorStore: (kv: KvLike, options?: {
1254
+ keyPrefix?: string;
1255
+ }) => ExportCursorStore;
1256
+ declare const HEALTH_PATH = "/_lunora/health";
1257
+ declare const HEALTH_READY_PATH = "/_lunora/health/ready";
1258
+ /** Which probe(s) a check participates in. `both` (the default) runs on the aggregate probe and the readiness gate. */
1259
+ type HealthProbeKind = "both" | "liveness" | "readiness";
1260
+ /** The verdict a single probe returns. `message` is runtime-authored and must never echo a secret, env value, or user binding name. */
1261
+ interface HealthProbeResult {
1262
+ healthy: boolean;
1263
+ /** Optional runtime-authored detail (e.g. "binding unreachable"). Included only in the `admin` posture. */
1264
+ message?: string;
1265
+ }
1266
+ /** One registered health check over a binding or subsystem. */
1267
+ interface HealthProbe {
1268
+ /**
1269
+ * The async probe. It must be cheap and self-contained; a thrown error is
1270
+ * treated as an unhealthy result (fail-closed) so a probe bug never 500s the
1271
+ * endpoint.
1272
+ */
1273
+ check: () => Promise<HealthProbeResult> | HealthProbeResult;
1274
+ /**
1275
+ * A critical dependency flips the aggregate `/_lunora/health` probe to `503`
1276
+ * when unhealthy. A non-critical one only degrades the reported status.
1277
+ */
1278
+ critical?: boolean;
1279
+ /** Which probe(s) this check runs on. Defaults to `"both"`. */
1280
+ kind?: HealthProbeKind;
1281
+ /** Stable check name surfaced in the report (e.g. `"durable-object"`, `"d1"`). Not a secret. */
1282
+ name: string;
1283
+ }
1284
+ /** Auth posture for the health endpoints. `"public"` (default) is unauthenticated + message-redacted; `"admin"` requires a valid admin bearer. */
1285
+ type HealthAuthPosture = "admin" | "public";
1286
+ /** One check's line in the response body. */
1287
+ interface HealthCheckReport {
1288
+ critical: boolean;
1289
+ /** Present only in the `admin` posture. */
1290
+ message?: string;
1291
+ name: string;
1292
+ status: "down" | "up";
1293
+ }
1294
+ /** The health response body. Deliberately minimal — status, per-check up/down, and static app metadata only. */
1295
+ interface HealthBody {
1296
+ appName: string;
1297
+ appVersion: string;
1298
+ checks: HealthCheckReport[];
1299
+ status: "degraded" | "healthy" | "unhealthy";
1300
+ timestamp: string;
1301
+ }
1302
+ /** Injected dependencies for the health routes. Probes are resolved per-request so they can read the invocation `env` (bindings only exist at request time). */
1303
+ interface HealthRouteDeps {
1304
+ /** Static application name surfaced in the body. Not a secret. */
1305
+ appName?: string;
1306
+ /** Static application version surfaced in the body. Not a secret. */
1307
+ appVersion?: string;
1308
+ /** Auth posture. Defaults to `"public"`. */
1309
+ auth?: HealthAuthPosture;
1310
+ /**
1311
+ * Cache the last computed report for this many ms so an orchestrator polling
1312
+ * every few seconds does not hammer the bindings. Defaults to `0` (no cache).
935
1313
  */
936
- parentSpanId: string;
1314
+ cacheTtlMs?: number;
1315
+ /** Admin-bearer predicate, consulted only when `auth === "admin"`. */
1316
+ isAdmin: (request: Request) => boolean;
937
1317
  /**
938
- * True for the synthetic span representing the **dispatch itself**, which the
939
- * shard records so a waterfall has a bar for the request to hang its
940
- * `ctx.trace` spans under.
941
- *
942
- * Named for what it is rather than "root": it is not the root of the
943
- * collector-side trace — the worker's own RPC span sits above it — and it is
944
- * never exported to a sink, because the runtime already emits that dispatch
945
- * via `onRpc` and a collector would otherwise show it twice. Locally it *is*
946
- * the outermost span, which is why the fold prefers it as a trace's anchor.
1318
+ * Resolve the probes for this invocation from its `env`. Called once per
1319
+ * request; the returned probes are registered on a fresh `HealthCheck`
1320
+ * registry so the report reflects the live bindings.
947
1321
  */
948
- dispatch?: boolean;
949
- /** Shard key for single-shard calls; absent for the unnamed root DO. */
950
- shardKey?: string;
951
- /** This span's own id (16-hex). */
952
- spanId: string;
953
- /** Wall-clock millis when the span started. */
954
- startTs: number;
955
- /** Trace this span belongs to (32-hex) — shared with the dispatch's logs. */
956
- traceId: string;
957
- /** Acting userId, or absent when anonymous. */
958
- userId?: string;
1322
+ resolveProbes: (env: unknown) => ReadonlyArray<HealthProbe>;
959
1323
  }
1324
+ /** Build the health + readiness route map merged into the worker's internal route table. */
1325
+ declare const buildHealthRoutes: (deps: HealthRouteDeps) => Record<string, (request: Request, env: unknown) => Promise<Response>>;
960
1326
  /**
961
- * Per-RPC dispatch event. Single-shard calls set `shardKey`; cross-shard
962
- * fan-outs set `fanOut` with the table being aggregated, shard count, and
963
- * per-shard failure count.
1327
+ * Structural probe of a Durable Object namespace's reachability: resolve the
1328
+ * default shard stub and issue a cheap request. ANY response (even a `404` for
1329
+ * an unknown path) proves the DO answered; only a thrown error means the object
1330
+ * is unreachable. Never inspects the response body, so it cannot leak state.
964
1331
  */
965
- interface ObservabilityEvent {
966
- /** Wall-clock duration of the dispatch, in milliseconds. */
967
- durationMs: number;
1332
+ declare const durableObjectProbe: (name: string, namespace: {
1333
+ get: (id: unknown) => {
1334
+ fetch: (request: Request) => Promise<Response>;
1335
+ };
1336
+ idFromName: (id: string) => unknown;
1337
+ }, shardKey: string) => HealthProbe;
1338
+ /**
1339
+ * Active probe of a D1 database: run `SELECT 1`. Healthy when it resolves. The
1340
+ * binding is passed structurally (only `.prepare().first()` is used) so the
1341
+ * runtime stays free of a hard `@cloudflare/workers-types` dependency.
1342
+ */
1343
+ declare const d1Probe: (name: string, database: {
1344
+ prepare: (sql: string) => {
1345
+ first: () => Promise<unknown>;
1346
+ };
1347
+ }) => HealthProbe;
1348
+ /**
1349
+ * Presence check for a binding whose remote health cannot be probed cheaply (R2,
1350
+ * queues, Hyperdrive). A bound, well-shaped value reports healthy; the check does
1351
+ * NOT perform a billable remote op. Non-critical by default: a presence gap
1352
+ * degrades the status without forcing a `503`.
1353
+ */
1354
+ declare const presenceProbe: (name: string, bound: boolean) => HealthProbe;
1355
+ /**
1356
+ * Identity resolved from the inbound request by `WorkerOptions.resolveIdentity`.
1357
+ *
1358
+ * The `userId` field is special — it becomes `ctx.auth.userId` inside the
1359
+ * Durable Object. Any other keys (`email`, `name`, custom roles, etc.) are
1360
+ * forwarded verbatim as `ctx.auth.getIdentity()`'s return value.
1361
+ *
1362
+ * Return `null` to signal that the request is anonymous; the runtime will
1363
+ * skip both `x-lunora-userid` and `x-lunora-identity` headers, and
1364
+ * `ctx.auth.userId` will be `undefined` on the shard side.
1365
+ */
1366
+ interface ResolvedIdentity {
1367
+ /** Arbitrary additional claims. Must be JSON-serialisable. */
1368
+ [key: string]: unknown;
968
1369
  /**
969
- * Populated on `ok === false`. `code`/`status` mirror the LunoraError
970
- * taxonomy; `message` is the human-readable string (may include user
971
- * input sinks that ship to third parties should scrub it).
1370
+ * JWT-standard expiry in epoch SECONDS. When present (and `expiresAtMs` is
1371
+ * absent), the runtime forwards it as the socket's credential expiry the
1372
+ * DO drops the socket once it lapses. Used only on the WebSocket path.
972
1373
  */
973
- error?: {
974
- code: string;
975
- message: string;
976
- status: number;
977
- };
1374
+ exp?: number;
978
1375
  /**
979
- * Populated for fan-out dispatches.
980
- * `shards` is the total fan-out cardinality; `failed` counts shards that
981
- * timed out or returned an error (the same `errors[]` the response body
982
- * carries to the caller).
1376
+ * Credential expiry in epoch MILLISECONDS. Preferred over `exp` when
1377
+ * both are present. Forwarded as the socket's expiry on the WebSocket path
1378
+ * so the DO drops the socket once it lapses; omit for non-expiring sessions.
983
1379
  */
984
- fanOut?: {
985
- failed: number;
986
- shards: number;
987
- table: string;
988
- };
989
- /** Function path being invoked, e.g. `"messages:list"`. */
990
- functionPath: string;
991
- /** True when the dispatch completed without throwing. */
992
- ok: boolean;
993
- /** Shard key for single-shard calls; absent for fan-outs. */
994
- shardKey?: string;
1380
+ expiresAtMs?: number;
1381
+ /** Stable user identifier (e.g. `"user_2k3..."` or `"u_42"`). */
1382
+ userId: string;
1383
+ }
1384
+ /**
1385
+ * A verifier that turns an inbound request into a {@link ResolvedIdentity} (or
1386
+ * `null` for anonymous). Structurally identical to `WorkerOptions.resolveIdentity`,
1387
+ * so `.auth()`'s better-auth session resolver, a signed-preview-link verifier, a
1388
+ * per-tenant bearer check, an upstream-JWT reader, … are all just `IdentityResolver`s
1389
+ * the identity layer is generic over every scheme, not coupled to any one.
1390
+ */
1391
+ type IdentityResolver = (request: Request, env: unknown) => Promise<ResolvedIdentity | null> | ResolvedIdentity | null;
1392
+ /** Error policy for {@link composeIdentityResolvers} when a participant resolver throws. */
1393
+ type ComposeIdentityResolversErrorMode = "fail-closed" | "skip";
1394
+ /** Options for {@link composeIdentityResolvers}. */
1395
+ interface ComposeIdentityResolversOptions {
995
1396
  /**
996
- * W3C trace context for this dispatch, generated once at dispatch entry (32-
997
- * and 16-hex). A sink (e.g. `otlpSink`) reuses these for the dispatch's span
998
- * instead of minting fresh ids, and the runtime propagates them to the shard
999
- * as a `traceparent` so a container the handler calls can stitch its spans
1000
- * under the same trace. Absent on paths that don't originate a trace (a sink
1001
- * falls back to random ids).
1397
+ * What to do when a resolver throws. `"fail-closed"` (default, safe)
1398
+ * re-throws so a broken verifier fails the request rather than silently
1399
+ * falling through to a weaker one; `"skip"` swallows the error and tries the
1400
+ * next resolver (use only when a resolver's failure genuinely means "not my
1401
+ * scheme").
1002
1402
  */
1003
- spanId?: string;
1004
- traceId?: string;
1403
+ readonly onError?: ComposeIdentityResolversErrorMode;
1005
1404
  }
1006
1405
  /**
1007
- * The `ctx.log` observability contract lives in `shared/` (inlined into each
1008
- * `dist`) so the DO that builds the events and the runtime sink that consumes
1009
- * them agree by construction rather than by hand-mirrored duplication. Re-exported
1010
- * here under the runtime's historical names.
1406
+ * Compose several {@link IdentityResolver}s into one, first-match-wins: each is
1407
+ * tried in order and the first that returns a non-null identity short-circuits.
1408
+ * Generic over every scheme the better-auth session resolver (obtained via the
1409
+ * builder's `derived.resolveIdentity` escape hatch) is just one entry in the list,
1410
+ * so composition never means losing it.
1011
1411
  *
1012
- * `LogLevel` is the canonical `ctx.log` severity union (the five console tiers
1013
- * plus `trace`/`fatal`); `ObservabilitySinkContext` is the shared per-event sink
1014
- * context (a `waitUntil` to keep a background send alive past the response).
1412
+ * A resolver that throws is handled per {@link ComposeIdentityResolversOptions.onError}
1413
+ * (default `"fail-closed"`: the error propagates).
1015
1414
  */
1016
- type LogLevel = ContextLogLevel;
1017
- type ObservabilitySinkContext = LogSinkContext;
1415
+ declare const composeIdentityResolvers: (resolvers: ReadonlyArray<IdentityResolver>, options?: ComposeIdentityResolversOptions) => IdentityResolver;
1018
1416
  /**
1019
- * The hook contract. Methods are optional so a sink can opt into only the
1020
- * events it cares about; the runtime no-ops the others.
1417
+ * A thin, generic helper over {@link composeIdentityResolvers} for the per-route case:
1418
+ * pick a resolver by `new URL(request.url).pathname`. Keys are matched by longest
1419
+ * path prefix; `"*"` is the fallback. Still fully generic — a route→resolver map,
1420
+ * with no portal / preview / tenant concepts baked in (those live in the app's
1421
+ * own resolvers).
1422
+ * @example
1423
+ * routeIdentityResolvers({ "/admin": adminResolver, "/partner": partnerResolver, "*": sessionResolver })
1021
1424
  */
1022
- interface ObservabilitySink {
1023
- /** Invoked once per `ctx.log.*` call from a function handler. */
1024
- onLog?: (event: LogEvent, context?: ObservabilitySinkContext) => void;
1025
- /**
1026
- * Invoked once per `ctx.metrics.*` measurement. No pre-aggregation happens
1027
- * upstream, so counter values are deltas for the destination to sum.
1028
- */
1029
- onMetric?: (event: MetricEvent, context?: ObservabilitySinkContext) => void;
1030
- /** Invoked once per dispatched RPC (single-shard or fan-out). */
1031
- onRpc?: (event: ObservabilityEvent, context?: ObservabilitySinkContext) => void;
1032
- /**
1033
- * Invoked once per `ctx.trace(name, fn)` span, when the span body settles.
1034
- * Distinct from `onRpc`: that is the one SERVER span per dispatch, this is the
1035
- * INTERNAL spans a handler creates beneath it.
1036
- */
1037
- onSpan?: (event: SpanEvent, context?: ObservabilitySinkContext) => void;
1425
+ declare const routeIdentityResolvers: (routes: Record<string, IdentityResolver>) => IdentityResolver;
1426
+ /** The result of validating a candidate identity against an {@link IdentityContractLike}. */
1427
+ type IdentityValidation = {
1428
+ ok: true;
1429
+ } | {
1430
+ error: string;
1431
+ ok: false;
1432
+ };
1433
+ /**
1434
+ * Structural view of `@lunora/server`'s `IdentityContract` (from `defineIdentity`).
1435
+ * Kept structural so `@lunora/runtime` stays free of an `@lunora/server` dependency.
1436
+ * Keep the `onInvalid` union and `validate`/`IdentityValidation` shapes in sync with
1437
+ * `@lunora/server`'s `IdentityContract` they are projected by hand, not imported.
1438
+ * The generated worker entry passes the app's `defineIdentity(...)` result here;
1439
+ * the worker validates every resolver's returned claims against it at the trust
1440
+ * boundary before they become `ctx.auth`.
1441
+ */
1442
+ interface IdentityContractLike {
1443
+ /** Reject policy applied when validation fails: downgrade to anonymous, or reject the request (401). */
1444
+ readonly onInvalid: "anonymous" | "reject";
1445
+ /** Validate resolver-returned claims against the declared contract. */
1446
+ validate: (identity: Record<string, unknown>) => IdentityValidation;
1447
+ }
1448
+ /** One KV namespace as the studio's KV browser surfaces it. */
1449
+ interface KvNamespaceSummary {
1450
+ /** The wrangler/env binding name, e.g. `"MY_KV"`. */
1451
+ binding: string;
1452
+ }
1453
+ /** One key entry as the KV admin browser surfaces it. */
1454
+ interface KvKeyEntry {
1455
+ /** Absolute expiration (Unix seconds), when set. */
1456
+ expiration?: number;
1457
+ /** Per-key metadata set at write time, or absent when none. */
1458
+ metadata?: unknown;
1459
+ /** The key name. */
1460
+ name: string;
1461
+ }
1462
+ /** A paginated page of KV keys as the admin browser returns it. */
1463
+ interface KvKeyListResult {
1464
+ /** Opaque cursor for the next page; absent when the listing is complete. */
1465
+ cursor?: string;
1466
+ /** The keys on this page. */
1467
+ keys: KvKeyEntry[];
1468
+ /** True when this is the final page. */
1469
+ listComplete: boolean;
1470
+ }
1471
+ /** A KV value together with its stored metadata. */
1472
+ interface KvValueResult {
1473
+ /** Per-key metadata, or `null` when none. */
1474
+ metadata: unknown;
1475
+ /** The stored value as a string, or `null` when the key is absent. */
1476
+ value: null | string;
1038
1477
  }
1039
1478
  /**
1040
- * Invoke `sink.onRpc` with the given event, swallowing any error the sink
1041
- * throws. Use at the dispatch boundary; the runtime should never see a
1042
- * sink-originating throw bubble up past this point. `context.waitUntil`, when
1043
- * supplied, lets a network sink keep its send alive past the response.
1479
+ * The introspector the worker wires for the studio's KV browser. Build it from
1480
+ * the env's bound KV namespaces. Omit it and the `/_lunora/admin/kv/*`
1481
+ * endpoints respond `KV_NOT_CONFIGURED`.
1044
1482
  */
1045
- declare const emitRpcEvent: (sink: ObservabilitySink | undefined, event: ObservabilityEvent, context?: ObservabilitySinkContext) => void;
1483
+ interface KvIntrospector {
1484
+ /** Delete a key from a namespace. No-op when the key is absent. */
1485
+ deleteKey: (options: {
1486
+ key: string;
1487
+ namespace: string;
1488
+ }) => Promise<void>;
1489
+ /** Read a value (as text) and its metadata from a namespace key. */
1490
+ getValue: (options: {
1491
+ key: string;
1492
+ namespace: string;
1493
+ }) => Promise<KvValueResult>;
1494
+ /** List keys in a namespace, optionally filtered by prefix and paginated. */
1495
+ listKeys: (options: {
1496
+ cursor?: string;
1497
+ limit?: number;
1498
+ namespace: string;
1499
+ prefix?: string;
1500
+ }) => Promise<KvKeyListResult>;
1501
+ /** List the registered KV namespaces (binding names). */
1502
+ listNamespaces: () => Promise<KvNamespaceSummary[]>;
1503
+ /** Write a value (as text) with optional absolute expiration / relative TTL and metadata. */
1504
+ putValue: (options: {
1505
+ expiration?: number;
1506
+ expirationTtl?: number;
1507
+ key: string;
1508
+ metadata?: unknown;
1509
+ namespace: string;
1510
+ value: string;
1511
+ }) => Promise<void>;
1512
+ }
1046
1513
  /**
1047
- * Invoke `sink.onLog` with the given log event, swallowing any error the sink
1048
- * throws. The same failure model as {@link emitRpcEvent}: a buggy log sink must
1049
- * never break the handler that emitted the line.
1514
+ * Shared, bundler-inlined helpers for the structured `fields` a
1515
+ * `ctx.log.<level>(message, fields)` / `ctx.log.with(fields)` call carries.
1516
+ *
1517
+ * Inlined (like {@link file://./otlp.ts}) so `@lunora/do`, `@lunora/runtime`,
1518
+ * `@lunora/config`, and `@lunora/studio` — which sit on different tiers with no
1519
+ * acceptable runtime dependency edge between them — share ONE implementation of
1520
+ * field rendering/normalization instead of the byte-identical copies they would
1521
+ * otherwise hand-mirror. Keep this genuinely zero-dependency (only built-ins) so
1522
+ * inlining into each `dist` stays sound.
1050
1523
  */
1051
- declare const emitLogEvent: (sink: ObservabilitySink | undefined, event: LogEvent, context?: ObservabilitySinkContext) => void;
1524
+ /** Structured, filterable key/value fields attached to a `ctx.log` line. */
1525
+ type LogFields = Record<string, unknown>;
1052
1526
  /**
1053
- * Cloudflare Durable Object jurisdictions restrict where a DO runs and persists
1054
- * data, for data-residency / compliance regimes (GDPR, FedRAMP, US data
1055
- * residency). The set is open — Cloudflare adds values over time — so this is a
1056
- * widening union rather than a closed enum.
1057
- * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
1527
+ * Severity of a `ctx.log.*` call. The five console method names (`log` is the
1528
+ * default level, distinct from `info`) plus `trace`/`fatal`, so the logger spans
1529
+ * the full OpenTelemetry severity ramp (`trace`→`fatal`).
1058
1530
  */
1059
- type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
1531
+ type ContextLogLevel = "debug" | "error" | "fatal" | "info" | "log" | "trace" | "warn";
1060
1532
  /**
1061
- * Structural projection of the bits of `DurableObjectNamespace` the runtime
1062
- * needs. Real workers-types defines a much wider surface; this lets us pass
1063
- * unit-test doubles without coupling to `@cloudflare/workers-types`.
1533
+ * Per-event context handed to a sink alongside the event: lets a sink register
1534
+ * background work (a telemetry POST, a durable pipeline send) with the request's
1535
+ * `waitUntil` so it survives isolate teardown after the response returns. Absent
1536
+ * `waitUntil` (no request context) means the sink falls back to fire-and-forget.
1064
1537
  */
1065
- interface ShardNamespaceLike {
1066
- get: (id: unknown) => {
1067
- fetch: (request: Request) => Promise<Response>;
1068
- };
1069
- /**
1070
- * `getByName` is the friendlier API but isn't on every workers-types
1071
- * release yet. We prefer it when available and fall back to
1072
- * `idFromName` + `get` for compatibility.
1073
- */
1074
- getByName?: (name: string) => {
1075
- fetch: (request: Request) => Promise<Response>;
1076
- };
1077
- idFromName: (name: string) => unknown;
1538
+ interface LogSinkContext {
1078
1539
  /**
1079
- * Derive a jurisdiction-restricted subnamespace. Every ID and stub created
1080
- * from the returned namespace is pinned to `jurisdiction`. Optional because
1081
- * older workers-types releases (and unit-test doubles) may not expose it;
1082
- * {@link applyJurisdiction} fails closed when a jurisdiction is requested
1083
- * but this method is absent.
1084
- */
1085
- jurisdiction?: (jurisdiction: DurableObjectJurisdiction) => ShardNamespaceLike;
1086
- }
1087
- interface ResolvedShard {
1088
- fetch: (request: Request) => Promise<Response>;
1540
+ * Resolves this request's detected OTLP resource attributes (`service.version`,
1541
+ * `cloud.region`, …) on demand, or absent when the host does not detect any.
1542
+ *
1543
+ * Deliberately a resolved, allowlisted bag behind a thunk rather than the raw
1544
+ * `env` and `Request` the host detected them from: this context is fanned out
1545
+ * to **every** registered sink, including user-authored ones, so anything
1546
+ * reachable here should be assumed to end up in someone's debug log — and raw
1547
+ * `env` is every secret binding, while a raw `Request` carries the caller's
1548
+ * `Authorization` and `Cookie`. The thunk keeps detection lazy (a sink that
1549
+ * does not want resource attributes pays nothing) and hosts are expected to
1550
+ * memoize it per request.
1551
+ */
1552
+ resourceAttributes?: () => Record<string, boolean | number | string>;
1553
+ /** Keep a background promise alive past the response (the request's `waitUntil`). */
1554
+ waitUntil?: (promise: Promise<unknown>) => void;
1089
1555
  }
1090
1556
  /**
1091
- * Return a jurisdiction-restricted view of `namespace`, or `namespace`
1092
- * unchanged when no jurisdiction is configured.
1093
- *
1094
- * Fail-closed: if a jurisdiction is requested but the binding does not expose
1095
- * `.jurisdiction()` (an older workers-types, or a misconfigured test double),
1096
- * this throws rather than silently routing to the un-pinned global namespace —
1097
- * silently dropping a residency constraint would let data land outside the
1098
- * compliance boundary the caller asked for.
1099
- */
1100
- declare const applyJurisdiction: (namespace: ShardNamespaceLike, jurisdiction?: DurableObjectJurisdiction) => ShardNamespaceLike;
1101
- /** Look up a shard stub by name, preferring `getByName` when present. */
1102
- declare const resolveShard: (namespace: ShardNamespaceLike, shardKey: string) => ResolvedShard;
1103
- /**
1104
- * Source of "which shard keys exist for a given table right now". Returning
1105
- * an empty array is valid — the coordinator will respond with the merge
1106
- * strategy's identity (empty array for `concat`, `0` for `sum`, etc.).
1557
+ * One application log line emitted from a function handler via `ctx.log`.
1558
+ * Produced per `ctx.log.*` call (unlike a per-dispatch RPC summary).
1107
1559
  */
1108
- interface ShardRegistry {
1109
- listShardKeys: (table: string) => Promise<ReadonlyArray<string>> | ReadonlyArray<string>;
1560
+ interface LogEvent {
1561
+ /** Raw arguments passed to the `ctx.log.*` call, in order. */
1562
+ args: unknown[];
1563
+ /**
1564
+ * Structured fields the caller attached (`ctx.log.info(message, fields)` or a
1565
+ * bound `ctx.log.with(fields)` child), already normalized to a fresh bag of
1566
+ * JSON-safe primitives (see `shared/log-fields.ts`). Absent for a plain
1567
+ * console-style call.
1568
+ */
1569
+ fields?: LogFields;
1570
+ /** Function path that emitted the line, e.g. `"messages:list"`. */
1571
+ functionPath: string;
1572
+ /** Severity the line was logged at. */
1573
+ level: ContextLogLevel;
1574
+ /** Display string — the message, or the console-style args rendered and space-joined. */
1575
+ message: string;
1576
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
1577
+ shardKey?: string;
1578
+ /** Span id of the RPC this line was emitted under (trace correlation), or absent. */
1579
+ spanId?: string;
1580
+ /** Trace id this line belongs to (from the inbound `traceparent`), or absent. */
1581
+ traceId?: string;
1582
+ /** Wall-clock millis when the line was emitted. */
1583
+ ts: number;
1584
+ /** Acting userId, or absent when anonymous. */
1585
+ userId?: string;
1110
1586
  }
1111
1587
  /**
1112
- * Static-map implementation. Useful for tests and for small deployments
1113
- * where shard keys are known up front (e.g. a fixed set of channel IDs).
1588
+ * The written-column contract: every field `pipelineLogSink` emits, mapped to the
1589
+ * column it is stored under by default (the identity mapping). Also the source of
1590
+ * truth for the {@link PipelineLogField} union. Mirrors the record built in
1591
+ * `pipelineLogSink` — the read side of the same contract.
1114
1592
  */
1115
- declare const createStaticShardRegistry: (table_to_keys: Readonly<Record<string, ReadonlyArray<string>>>) => ShardRegistry;
1593
+ declare const DEFAULT_COLUMNS: {
1594
+ readonly fields: "fields";
1595
+ readonly functionPath: "functionPath";
1596
+ readonly level: "level";
1597
+ readonly message: "message";
1598
+ readonly shardKey: "shardKey";
1599
+ readonly spanId: "spanId";
1600
+ readonly traceId: "traceId";
1601
+ readonly ts: "ts";
1602
+ readonly userId: "userId";
1603
+ };
1116
1604
  /**
1117
- * Wire-serializable merge strategy. `topK.by` is a field name on the row
1118
- * (the runtime looks it up with a string key), not a closure.
1119
- *
1120
- * Aggregate-friendly variants for cross-shard `count` / `aggregate` /
1121
- * `groupBy` fan-outs:
1122
- *
1123
- * - `sum` — `count(*)`, `aggregate({ op: "sum" })` (sums numeric per-shard payloads).
1124
- * - `max` — `aggregate({ op: "max" })`.
1125
- * - `min` — `aggregate({ op: "min" })`.
1126
- * - `groupBy` — per-shard `GroupByEntry[]` payloads, reduced into one
1127
- * entry per distinct key tuple. `op` controls how values combine across
1128
- * shards: `sum` (default — works for `COUNT(*)` and `SUM`), `max`, `min`.
1129
- *
1130
- * `avg` is intentionally absent in v1 — a correct cross-shard average
1131
- * requires shipping `(sum, count)` per shard, not the post-shard mean.
1132
- * Use two separate fan-outs (`sum` + `count`) and divide in the caller.
1133
- *
1134
- * `rank` — cross-shard `rank()` over a partition that spans shards (e.g. a
1135
- * global leaderboard `.shardBy("userId")` with `rankIndex(partitionBy: [])`).
1136
- * Each shard's `__lunora_admin__:rankBefore` returns `{before, total}` (its
1137
- * local rows strictly-before the explicit key, plus its local partition
1138
- * total); the merge sums them into `{position: Σbefore + 1, total: Σtotal}` —
1139
- * the 1-based global position and global partition size.
1605
+ * The canonical field names of one persisted log record the keys
1606
+ * `pipelineLogSink` writes. Used as the {@link PipelineLogColumnMap} keys and the
1607
+ * {@link PipelineLogRow} shape, so the reader stays decoupled from whatever
1608
+ * physical column names the operator's Iceberg table happens to use.
1140
1609
  */
1141
- type MergeStrategy = {
1142
- kind: "concat";
1143
- } | {
1144
- by: string;
1145
- direction?: "asc" | "desc";
1146
- k: number;
1147
- kind: "topK";
1148
- } | {
1149
- kind: "first";
1150
- } | {
1151
- kind: "max";
1152
- } | {
1153
- kind: "min";
1154
- } | {
1155
- kind: "rank";
1156
- } | {
1157
- kind: "sum";
1158
- } | {
1159
- kind: "groupBy";
1160
- op?: "max" | "min" | "sum";
1161
- };
1610
+ type PipelineLogField = keyof typeof DEFAULT_COLUMNS;
1162
1611
  /**
1163
- * Convenience: build the right wire-serializable {@link MergeStrategy} for a
1164
- * given aggregate read. The reader doesn't know which op the caller chose, so
1165
- * a fan-out wrapper passes the user's op + by-keys through this to derive the
1166
- * merge.
1167
- *
1168
- * - `count` → `sum`.
1169
- * - `aggregate({ op })` → `sum`/`max`/`min` (or throws for `avg`).
1170
- * - `groupBy({ by, agg })` → `groupBy({ op })` (defaults to `sum` since
1171
- * `groupBy`'s default reducer is `count`).
1172
- * @returns the derived {@link MergeStrategy}.
1612
+ * Field-to-column-name map. Defaults to the identity mapping (each field stored
1613
+ * under its own name, matching what `pipelineLogSink` writes). Override per-field
1614
+ * when the Iceberg schema renames a column; unspecified fields keep their
1615
+ * default. This is the single knob that lets one reader serve differently shaped
1616
+ * Data Catalog tables.
1173
1617
  */
1174
- declare const mergeStrategyForAggregate: (input: {
1175
- agg?: {
1176
- op?: "avg" | "count" | "max" | "min" | "sum";
1177
- };
1178
- kind: "groupBy";
1179
- } | {
1180
- kind: "count";
1181
- } | {
1182
- kind: "scalar";
1183
- op: "avg" | "count" | "max" | "min" | "sum";
1184
- }) => MergeStrategy;
1185
- interface FanOutSpec {
1186
- merge: MergeStrategy;
1187
- /** Table whose shard keys drive the fan-out. */
1188
- table: string;
1618
+ type PipelineLogColumnMap = Partial<Record<PipelineLogField, string>>;
1619
+ /** An opaque keyset cursor: the `ts` of the row after the last one returned. */
1620
+ interface PipelineLogCursor {
1621
+ /** Epoch-millis boundary; the next page is every row strictly older than this. */
1622
+ ts: number;
1623
+ }
1624
+ /** Filters for one {@link PipelineLogReader} query. Every value is inlined safely (`lit`/`sql`). */
1625
+ interface PipelineLogQuery {
1626
+ /** Continue after a previous page (keyset on `ts DESC`). Combined with the other filters. */
1627
+ cursor?: PipelineLogCursor;
1628
+ /** Match only this exact function path's records. Prefer `functionPathPrefix` for a namespace sweep. */
1629
+ functionPath?: string;
1630
+ /** Match records whose `functionPath` starts with this string (rendered as a `LIKE 'prefix%'`). */
1631
+ functionPathPrefix?: string;
1632
+ /** Match only this exact severity. When set, `minLevel` is ignored for the same field. */
1633
+ level?: ContextLogLevel;
1634
+ /** Max rows to return. Clamped to `[1, 10000]`; defaults to {@link DEFAULT_LOG_LIMIT}. */
1635
+ limit?: number;
1636
+ /** Severity floor: keep every level at or above this in {@link LOG_LEVEL_ORDER} (e.g. `warn` keeps warn, error, fatal). */
1637
+ minLevel?: ContextLogLevel;
1638
+ /** Match only this shard key. */
1639
+ shardKey?: string;
1640
+ /** Lower time bound, inclusive (`ts` at or after this), epoch-millis. */
1641
+ sinceTs?: number;
1642
+ /** Match only this trace id. */
1643
+ traceId?: string;
1644
+ /** Upper time bound, inclusive (`ts` at or before this), epoch-millis. */
1645
+ untilTs?: number;
1646
+ /** Match only this acting user id. */
1647
+ userId?: string;
1189
1648
  }
1190
1649
  /**
1191
- * Per-shard failure surfaced in the aggregate response's `errors` field. We
1192
- * never throw out of `fanOut` slow/failed shards are *data*, not an
1193
- * exception, so callers can decide whether to retry or surface a partial
1194
- * UI.
1650
+ * One decoded log record. Always keyed by the canonical {@link PipelineLogField}
1651
+ * names regardless of the physical columns (the reader remaps via `columnMap`),
1652
+ * so consumers never see the operator's storage names.
1195
1653
  */
1196
- interface ShardError {
1197
- /** Human-readable; tests assert on `.includes("timeout")` and similar. */
1654
+ interface PipelineLogRow {
1655
+ /**
1656
+ * Structured fields, when the record carried them. A `serializeFields` sink
1657
+ * stores these as a JSON string, which the reader parses back to an object;
1658
+ * a plain string that is not valid JSON is returned verbatim.
1659
+ */
1660
+ fields?: unknown;
1661
+ /** Function path that emitted the line, e.g. `"messages:list"`. */
1662
+ functionPath: string;
1663
+ /** Severity the line was logged at. */
1664
+ level: ContextLogLevel;
1665
+ /** Rendered message. */
1198
1666
  message: string;
1199
- shardKey: string;
1200
- /** Set when the per-shard timeout fired. */
1201
- timedOut: boolean;
1667
+ /** Shard key for single-shard calls, when present. */
1668
+ shardKey?: string;
1669
+ /** Span id the line was emitted under, when present. */
1670
+ spanId?: string;
1671
+ /** Trace id the line belongs to, when present. */
1672
+ traceId?: string;
1673
+ /** Epoch-millis the line was emitted. */
1674
+ ts: number;
1675
+ /** Acting user id, when present. */
1676
+ userId?: string;
1202
1677
  }
1203
- interface FanOutResult<T = unknown> {
1204
- /** Merged value — type depends on the merge strategy. */
1205
- data: T;
1206
- errors: ReadonlyArray<ShardError>;
1207
- /** Shards that failed or timed out. */
1208
- failed: number;
1209
- /** Shards that returned successfully. */
1210
- ok: number;
1678
+ /** One page of {@link PipelineLogRow}s, newest first, plus the cursor for the next page (absent means last page). */
1679
+ interface PipelineLogPage {
1680
+ /** The cursor to pass as {@link PipelineLogQuery} `cursor` for the following page; absent when this is the last page. */
1681
+ nextCursor?: PipelineLogCursor;
1682
+ /** The rows, ordered `ts DESC` (newest first). At most `limit` of them. */
1683
+ rows: PipelineLogRow[];
1211
1684
  }
1212
- interface QueryCoordinatorOptions {
1685
+ /** Options for {@link createPipelineLogReader}. */
1686
+ interface PipelineLogReaderOptions {
1213
1687
  /**
1214
- * Maximum number of shard RPCs to issue in parallel. Defaults to 16 —
1215
- * keeps the 30-second Worker CPU budget healthy when fanning out to
1216
- * dozens of shards and avoids stampeding the DO namespace.
1688
+ * Override any physical column name that diverges from the default (identity)
1689
+ * mapping. Unspecified fields keep their {@link DEFAULT_LOG_COLUMNS} name.
1217
1690
  */
1218
- maxConcurrency?: number;
1691
+ columnMap?: PipelineLogColumnMap;
1219
1692
  /**
1220
- * Hard per-shard timeout in milliseconds. Defaults to 5000; a slow
1221
- * shard surfaces in `errors[]` rather than stalling the aggregate.
1693
+ * The Iceberg namespace the `table` lives in (R2 Data Catalog database).
1694
+ * Combined as `namespace.table` in the `FROM` clause; omit when `table`
1695
+ * already carries its namespace.
1222
1696
  */
1223
- perShardTimeoutMs?: number;
1224
- /** Required drives which shards to fan out to. */
1225
- registry: ShardRegistry;
1697
+ namespace?: string;
1698
+ /** The Iceberg table name the Pipeline writes log records to (e.g. `"logs"`). */
1699
+ table: string;
1226
1700
  }
1227
- interface FanOutRequest {
1228
- args?: Record<string, unknown>;
1229
- fanOut: FanOutSpec;
1230
- functionPath: string;
1231
- /** Forwarded to each shard fetch (auth, cookies, bookmark). */
1232
- headers?: Record<string, string>;
1701
+ /** The reader surface: a single keyset-paginated {@link PipelineLogPage} query. */
1702
+ interface PipelineLogReader {
1703
+ /** Run one filtered, keyset-paginated read and return a {@link PipelineLogPage}. */
1704
+ query: (query?: PipelineLogQuery) => Promise<PipelineLogPage>;
1233
1705
  }
1706
+ /** The written-column contract exposed publicly: canonical field to default physical column name. */
1707
+ declare const DEFAULT_LOG_COLUMNS: Readonly<Record<PipelineLogField, string>>;
1708
+ /** Default page size when a query omits `limit`. */
1709
+ declare const DEFAULT_LOG_LIMIT: number;
1234
1710
  /**
1235
- * Cross-shard migration request. Unlike {@link FanOutRequest} there is no merge
1236
- * strategy — per-shard payloads are `MigrationRunResult`-shaped objects, not
1237
- * rows, so {@link QueryCoordinator.orchestrateMigration} rolls them up with the
1238
- * fixed semantics documented on {@link MigrationFanOutResult}.
1711
+ * Build a durable-log reader over one R2 Data Catalog (Iceberg) table.
1239
1712
  *
1240
- * `functionPath` is the admin RPC to invoke on each shard
1241
- * (`__lunora_admin__:runMigration` or `:migrationStatus`); `headers` must carry
1242
- * the `Authorization` bearer header the shard's admin gate requires (the
1243
- * configured admin token), or every shard comes back as a 403 error.
1713
+ * The returned {@link PipelineLogReader} compiles each call to a safe
1714
+ * `SELECT ... WHERE ... ORDER BY ts DESC LIMIT n` (over-fetching by one) and
1715
+ * decodes the rows back to the canonical {@link PipelineLogRow} shape. All value
1716
+ * filters are escaped through `@lunora/bindings/r2sql`'s `sql`/`lit`; column
1717
+ * names come from `options.columnMap` (operator config), spliced with `raw`.
1718
+ * @param client An {@link R2SqlClient} (`createR2Sql({ accountId, apiToken, bucket })`).
1719
+ * @param options The target `table` (plus optional `namespace`) and any `columnMap` overrides.
1244
1720
  */
1245
- interface MigrationFanOutRequest {
1246
- args?: Record<string, unknown>;
1247
- functionPath: string;
1248
- headers?: Record<string, string>;
1249
- /** Table whose live shard keys the migration runs across. */
1721
+ declare const createPipelineLogReader: (client: R2SqlClient, options: PipelineLogReaderOptions) => PipelineLogReader;
1722
+ /**
1723
+ * Wire constants for the durable log archive, shared between the server route
1724
+ * (`@lunora/runtime`'s `log-archive-admin-routes`) and the Studio Archive feed
1725
+ * (`@lunora/studio`). Kept here not in `@lunora/runtime` because the studio
1726
+ * is a browser bundle that must not import a runtime *value* (which would drag
1727
+ * the DO/R2-SQL runtime into the browser). This file is dependency-free and
1728
+ * bundler-inlined into each consumer, so both sides share one source of truth
1729
+ * with no dependency edge.
1730
+ */
1731
+ /**
1732
+ * The error `code` the archive route returns (400) when the operator has wired
1733
+ * no archive table (`logArchive`) or the `R2_SQL_*` credentials are missing. The
1734
+ * Studio keys its "not configured" empty state off this exact value.
1735
+ */
1736
+ declare const LOG_ARCHIVE_NOT_CONFIGURED = "LOG_ARCHIVE_NOT_CONFIGURED";
1737
+ /** The route the studio's `queryLogArchive` client method POSTs to. */
1738
+ declare const LOG_ARCHIVE_PATH = "/_lunora/admin/logs/archive";
1739
+ /**
1740
+ * The app-level archive config the worker passes through: which Data Catalog
1741
+ * table the Pipeline writes log records to (the read-side of `pipelineLogSink`),
1742
+ * plus optional namespace / physical-column overrides. The R2 SQL *credentials*
1743
+ * are NOT here — they live on `env` (`R2_SQL_*`), read per request.
1744
+ */
1745
+ interface LogArchiveConfig {
1746
+ /** Override any physical column name that diverges from the {@link createPipelineLogReader} default mapping. */
1747
+ columnMap?: PipelineLogColumnMap;
1748
+ /** The Iceberg namespace (R2 Data Catalog database) the table lives in; omit when `table` already carries it. */
1749
+ namespace?: string;
1750
+ /** The Iceberg table the Pipeline writes log records to (e.g. `"logs"`). */
1250
1751
  table: string;
1251
1752
  }
1252
- /** One shard's outcome: either the unwrapped admin `result` payload, or an error. */
1253
- interface ShardMigrationOutcome {
1254
- error?: {
1255
- message: string;
1256
- timedOut: boolean;
1257
- };
1258
- /** The shard's admin `result`, peeled out of the `{ result }` envelope. */
1259
- result?: unknown;
1260
- shardKey: string;
1261
- }
1262
- interface MigrationFanOutResult {
1263
- /** Summed `changed` across shards whose result carried a numeric count. */
1264
- changed: number;
1265
- /** Shards that errored or timed out. */
1266
- failed: number;
1267
- /** Shards that returned a 2xx result. */
1268
- ok: number;
1269
- /** Summed `processed` across shards whose result carried a numeric count. */
1270
- processed: number;
1271
- /** Per-shard outcomes, in registry order. */
1272
- shards: ReadonlyArray<ShardMigrationOutcome>;
1273
- /**
1274
- * Rolled-up status. `"failed"` if any shard's runner reported failure;
1275
- * `"in_progress"` if any shard is incomplete or unreachable (the run stays
1276
- * resumable); `"completed"` only when every shard finished cleanly.
1277
- */
1278
- status: "completed" | "failed" | "in_progress";
1279
- }
1280
1753
  /**
1281
- * Cross-shard rank request. Like {@link MigrationFanOutRequest} there is no
1282
- * caller-supplied merge per-shard payloads are `{before, total}` objects, so
1283
- * {@link QueryCoordinator.orchestrateRank} rolls them up with the fixed
1284
- * `{position: Σbefore + 1, total: Σtotal}` semantics {@link mergeRank} defines.
1754
+ * Build the {@link LogArchiveConfig} from `env` for the generated worker entry —
1755
+ * the zero-config seam the codegen `createWorker({ logArchive })` wiring uses,
1756
+ * mirroring how the R2 SQL *credentials* already come from `env` (`R2_SQL_*`).
1285
1757
  *
1286
- * The key tuple (`partitionKey`/`sortValues`/`rowId`) is built off the row doc
1287
- * via `@lunora/do`'s `rankKeyFromDoc(index, doc)` and forwarded verbatim to
1288
- * each shard's `__lunora_admin__:rankBefore` admin RPC; `headers` must carry
1289
- * the admin bearer the shard's admin gate requires.
1758
+ * Returns `undefined` unless `LUNORA_LOG_ARCHIVE_TABLE` is set, so the studio
1759
+ * Archive feed stays "not configured" until the operator opts in by naming the
1760
+ * Data Catalog table (+ optional `LUNORA_LOG_ARCHIVE_NAMESPACE`). `columnMap`
1761
+ * overrides aren't env-expressible a hand-written worker passes `logArchive`
1762
+ * to `createWorker` directly for those.
1290
1763
  */
1291
- interface RankFanOutRequest {
1292
- headers?: Record<string, string>;
1293
- /** Rank index name on `table`. */
1294
- index: string;
1295
- /** Canonical-JSON partition tuple — `encodePartitionKey(index.partitionBy, doc)`. */
1296
- partitionKey: string;
1297
- /** The `__id__` tiebreak value `doc._id`. */
1298
- rowId: string;
1299
- /** Serialized sort-key values in `index.sortBy` order, as produced by `rankKeyFromDoc` (wire-safe + byte-matching the stored columns). */
1300
- sortValues: ReadonlyArray<unknown>;
1301
- /** Table whose live shard keys the rank fans out across. */
1302
- table: string;
1303
- }
1304
- interface RankFanOutResult {
1305
- /** Shards that errored or timed out. */
1306
- failed: number;
1307
- /** Shards that returned a 2xx `{before, total}`. */
1308
- ok: number;
1309
- /** `true` when at least one shard failed/timed out, so `position`/`total` are under-counts (failed shards' rows missing). A caller needing an exact global rank should treat this as an error, not trust the numbers. */
1310
- partial: boolean;
1311
- /** 1-based global position within the partition (`Σbefore + 1`). */
1312
- position: number;
1313
- /** Per-shard outcomes, in registry order. */
1314
- shards: ReadonlyArray<ShardRankOutcome>;
1315
- /** Global partition total (`Σtotal`). */
1316
- total: number;
1317
- }
1318
- /** One shard's rank outcome: its `{before, total}` payload, or an error. */
1319
- interface ShardRankOutcome {
1320
- error?: {
1321
- message: string;
1322
- timedOut: boolean;
1323
- };
1324
- result?: {
1325
- before: number;
1326
- total: number;
1327
- };
1328
- shardKey: string;
1329
- }
1764
+ declare const resolveLogArchiveFromEnv: (environment: unknown) => LogArchiveConfig | undefined;
1765
+ /**
1766
+ * What kind of instrument produced a measurement, which decides how a collector
1767
+ * aggregates it:
1768
+ *
1769
+ * - `counter` — a monotonic delta to add up (requests, retries, bytes sent).
1770
+ * - `gauge` a point-in-time reading that replaces the last one (queue depth,
1771
+ * cache size).
1772
+ * - `histogram` a value whose *distribution* matters (latency, payload size),
1773
+ * giving percentiles rather than just a mean.
1774
+ */
1775
+ type MetricKind = "counter" | "gauge" | "histogram";
1330
1776
  /**
1331
- * Cross-shard ranked-pagination request. Like {@link RankFanOutRequest} there's
1332
- * no caller-supplied merge — the merge is the fixed k-way merge by the rank-key
1333
- * tuple. `take` is the global page size; `cursor` is the opaque composite cursor
1334
- * from the prior page's `continueCursor` (absent → first page). `partitionKey`,
1335
- * when set, pins a single partition (`encodePartitionKey(index.partitionBy, where)`),
1336
- * forwarded so each shard scopes its local slice to that partition.
1777
+ * One measurement recorded from a function handler.
1337
1778
  *
1338
- * `directions` is the per-sort-key direction list (`index.sortBy[i].direction`)
1339
- * the coordinator's comparator needs to break ties the same way each shard's
1340
- * `ORDER BY` does. `partitionKey` and the `__id__` tiebreak are always ascending
1341
- * (matching the shard companion's btree), so only the sort columns vary.
1779
+ * Each `ctx.metrics.*` call produces exactly one of these — the runtime does no
1780
+ * pre-aggregation, so counters carry **delta** temporality and a collector sums
1781
+ * them. That keeps the sink model identical to logs and spans (one event, one
1782
+ * export) at the cost of chattiness in a hot loop, where the handler should sum
1783
+ * locally and record once.
1342
1784
  */
1343
- interface RankPageFanOutRequest {
1344
- /** Opaque composite cursor from the prior page's `continueCursor`. */
1345
- cursor?: null | string;
1346
- /** Per-sort-key directions, in `index.sortBy` order. Missing/short ascending. */
1347
- directions?: ReadonlyArray<RankDirection>;
1348
- headers?: Record<string, string>;
1349
- /** Rank index name on `table`. */
1350
- index: string;
1351
- /** Optional partition pin forwarded to each shard's local `rankPage`. */
1352
- partitionKey?: string;
1353
- /** Table whose live shard keys the page fans out across. */
1354
- table: string;
1355
- /** Global page size; defaults to 100, capped at 1000 (matching the shard-local `rankPage`). */
1356
- take?: number;
1785
+ interface MetricEvent {
1786
+ /**
1787
+ * Structured attributes the caller attached, normalized to a fresh bag of
1788
+ * JSON-safe primitives (see `shared/log-fields.ts`). These are the metric's
1789
+ * dimensions — keep them low-cardinality; an id-valued attribute creates a
1790
+ * distinct time series per id.
1791
+ *
1792
+ * Caller-controlled, so they MAY contain user input and they DO egress to
1793
+ * whatever destination the sink ships to the same caveat as a log line's
1794
+ * `fields` and a span's `error.message`. Scrub upstream if that matters.
1795
+ */
1796
+ attributes?: LogFields;
1797
+ /** Function path that recorded the measurement, e.g. `"orders:checkout"`. */
1798
+ functionPath: string;
1799
+ /** Instrument kind; see {@link MetricKind}. */
1800
+ kind: MetricKind;
1801
+ /** Instrument name, e.g. `"orders.placed"`. */
1802
+ name: string;
1803
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
1804
+ shardKey?: string;
1805
+ /**
1806
+ * Trace id of the dispatch that recorded this measurement, when it ran inside
1807
+ * one — the measurement's **exemplar**, letting a consumer jump from a metric
1808
+ * point to a trace that produced it (OpenTelemetry's exemplar model). Stamped
1809
+ * by the shard from the current request's trace context, not by the caller.
1810
+ */
1811
+ traceId?: string;
1812
+ /** Wall-clock millis when the measurement was recorded. */
1813
+ ts: number;
1814
+ /**
1815
+ * The measured value: the increment for a `counter`, the current reading for
1816
+ * a `gauge`, the observed sample for a `histogram`.
1817
+ */
1818
+ value: number;
1357
1819
  }
1358
- /** One shard's `rankPage` outcome: its local ranked slice, or an error. */
1359
- interface ShardRankPageOutcome {
1360
- /** The directions the shard ordered by (`index.sortBy[i].direction`); authoritative for the merge. */
1361
- directions?: ReadonlyArray<RankDirection>;
1820
+ interface SpanEvent {
1821
+ /**
1822
+ * Structured attributes the caller attached, already normalized to a fresh
1823
+ * bag of JSON-safe primitives (see `shared/log-fields.ts`) exactly like a log
1824
+ * line's `fields`. Absent when the caller passed none.
1825
+ */
1826
+ attributes?: LogFields;
1827
+ /** Wall-clock duration of the span body, in milliseconds. */
1828
+ durationMs: number;
1829
+ /**
1830
+ * Populated when the span body threw. `type` is the error's constructor name
1831
+ * (or its `LunoraError` code); `message` is the human-readable string and may
1832
+ * include user input, so sinks shipping to third parties should scrub it.
1833
+ */
1362
1834
  error?: {
1363
1835
  message: string;
1364
- timedOut: boolean;
1836
+ type: string;
1365
1837
  };
1366
- hasMore?: boolean;
1367
- rows?: ReadonlyArray<RankPageRow>;
1368
- shardKey: string;
1369
- }
1370
- interface RankPageFanOutResult {
1371
- /** Opaque composite cursor for the next page, or `null` when the merge is exhausted. */
1372
- continueCursor: null | string;
1373
- /** Shards that errored or timed out. */
1374
- failed: number;
1375
- /** `true` when the global merge has no further rows. */
1376
- isDone: boolean;
1377
- /** Shards that returned a 2xx slice. */
1378
- ok: number;
1379
- /** The globally-ranked page of hydrated docs, in cross-shard rank order. */
1380
- page: ReadonlyArray<Record<string, unknown>>;
1381
- /** `true` when at least one shard failed/timed out, so the page may be missing that shard's rows. */
1382
- partial: boolean;
1383
- /** Per-shard outcomes, in registry order. */
1384
- shards: ReadonlyArray<ShardRankPageOutcome>;
1385
- }
1386
- interface QueryCoordinator {
1387
- fanOut: <T = unknown>(namespace: ShardNamespaceLike, request: FanOutRequest) => Promise<FanOutResult<T>>;
1388
1838
  /**
1389
- * Fan the `__lunora_admin__:applyCdc` admin RPC out by forwarding each
1390
- * pre-bucketed per-shard batch of CDC changes, rolling up the applied/failed
1391
- * counts. The replay half of point-in-time recovery.
1839
+ * Function path the span was created under, e.g. `"messages:list"`. A span
1840
+ * created inside a function invoked via `ctx.runQuery`/`runMutation`/
1841
+ * `runAction` carries the OUTER entrypoint's path, since the composed call
1842
+ * reuses its context — the same attribution rule `ctx.log` follows.
1392
1843
  */
1393
- orchestrateApplyCdc: (namespace: ShardNamespaceLike, request: ApplyCdcFanOutRequest) => Promise<ApplyCdcFanOutResult>;
1844
+ functionPath: string;
1845
+ /** Caller-supplied span name, e.g. `"stripe.charge"`. */
1846
+ name: string;
1847
+ /** True when the span body returned without throwing. */
1848
+ ok: boolean;
1394
1849
  /**
1395
- * Fan the `__lunora_admin__:cdcSync` admin RPC out to every live shard,
1396
- * each resumed from its own cursor in `request.cursors` (shardKey seq).
1397
- * Returns the per-shard change pages plus their new cursors so the caller
1398
- * can checkpoint each shard independently the streaming-export feed.
1850
+ * Span id of the enclosing span the parent `ctx.trace` when nested, else
1851
+ * the dispatch's own RPC span (from the inbound `traceparent`). A span with
1852
+ * no inbound trace context is parented to a locally-minted root, so this is
1853
+ * always set for a `ctx.trace` span; only the synthetic `dispatch` span below
1854
+ * carries `""`, meaning "nothing above me in this trace".
1399
1855
  */
1400
- orchestrateCdcSync: (namespace: ShardNamespaceLike, request: CdcSyncFanOutRequest) => Promise<CdcSyncFanOutResult>;
1856
+ parentSpanId: string;
1401
1857
  /**
1402
- * Fan an export admin RPC out to every live shard, returning the
1403
- * per-shard `{rows}` payloads alongside any per-shard errors. Each shard
1404
- * returns a JSON envelope (not a streaming body) so this method is the
1405
- * collector — the worker assembles the NDJSON stream.
1858
+ * True for the synthetic span representing the **dispatch itself**, which the
1859
+ * shard records so a waterfall has a bar for the request to hang its
1860
+ * `ctx.trace` spans under.
1861
+ *
1862
+ * Named for what it is rather than "root": it is not the root of the
1863
+ * collector-side trace — the worker's own RPC span sits above it — and it is
1864
+ * never exported to a sink, because the runtime already emits that dispatch
1865
+ * via `onRpc` and a collector would otherwise show it twice. Locally it *is*
1866
+ * the outermost span, which is why the fold prefers it as a trace's anchor.
1406
1867
  */
1407
- orchestrateExport: (namespace: ShardNamespaceLike, request: ExportFanOutRequest) => Promise<ExportFanOutResult>;
1868
+ dispatch?: boolean;
1869
+ /** Shard key for single-shard calls; absent for the unnamed root DO. */
1870
+ shardKey?: string;
1871
+ /** This span's own id (16-hex). */
1872
+ spanId: string;
1873
+ /** Wall-clock millis when the span started. */
1874
+ startTs: number;
1875
+ /** Trace this span belongs to (32-hex) — shared with the dispatch's logs. */
1876
+ traceId: string;
1877
+ /** Acting userId, or absent when anonymous. */
1878
+ userId?: string;
1879
+ }
1880
+ /**
1881
+ * Per-RPC dispatch event. Single-shard calls set `shardKey`; cross-shard
1882
+ * fan-outs set `fanOut` with the table being aggregated, shard count, and
1883
+ * per-shard failure count.
1884
+ */
1885
+ interface ObservabilityEvent {
1886
+ /** Wall-clock duration of the dispatch, in milliseconds. */
1887
+ durationMs: number;
1408
1888
  /**
1409
- * Fan an import admin RPC out by routing each row to its owning shard. The
1410
- * shard registry resolves which shards exist; rows whose table has a
1411
- * `shardBy(field)` are bucketed using that field's value as the shard key,
1412
- * other tables fall back to the runtime's default `__root__` shard.
1889
+ * Populated on `ok === false`. `code`/`status` mirror the LunoraError
1890
+ * taxonomy; `message` is the human-readable string (may include user
1891
+ * input sinks that ship to third parties should scrub it).
1413
1892
  */
1414
- orchestrateImport: (namespace: ShardNamespaceLike, request: ImportFanOutRequest) => Promise<ImportFanOutResult>;
1415
- /** Fan a migration admin RPC out to every live shard of a table and roll up the per-shard outcomes. */
1416
- orchestrateMigration: (namespace: ShardNamespaceLike, request: MigrationFanOutRequest) => Promise<MigrationFanOutResult>;
1893
+ error?: {
1894
+ code: string;
1895
+ message: string;
1896
+ status: number;
1897
+ };
1417
1898
  /**
1418
- * Fan the `__lunora_admin__:rankBefore` admin RPC out to every live shard of
1419
- * a table and roll up the per-shard `{before, total}` payloads into the
1420
- * global rank (`{position: Σbefore + 1, total: Σtotal}`). The cross-shard
1421
- * `rank()` path for a partition that spans shards.
1899
+ * Populated for fan-out dispatches.
1900
+ * `shards` is the total fan-out cardinality; `failed` counts shards that
1901
+ * timed out or returned an error (the same `errors[]` the response body
1902
+ * carries to the caller).
1422
1903
  */
1423
- orchestrateRank: (namespace: ShardNamespaceLike, request: RankFanOutRequest) => Promise<RankFanOutResult>;
1904
+ fanOut?: {
1905
+ failed: number;
1906
+ shards: number;
1907
+ table: string;
1908
+ };
1909
+ /** Function path being invoked, e.g. `"messages:list"`. */
1910
+ functionPath: string;
1911
+ /** Host of the inbound request (e.g. `"api.example.com"`). */
1912
+ host?: string;
1913
+ /** HTTP method of the inbound request (e.g. `"POST"`). */
1914
+ method?: string;
1915
+ /** True when the dispatch completed without throwing. */
1916
+ ok: boolean;
1424
1917
  /**
1425
- * Page a ranked query across every live shard of a `.shardBy(...)` table.
1426
- * Fans `__lunora_admin__:rankPage` out to each shard, gathers each shard's
1427
- * local ranked slice (rows tagged with their rank-key tuple), and k-way
1428
- * merges them by that tuple into one globally-ranked page of `take` rows.
1429
- * The opaque `continueCursor` is a composite of per-shard cursors so the
1430
- * next page resumes each shard strictly-after the last row the global page
1431
- * consumed from it — pages never drop or duplicate a row at a shard
1432
- * boundary. The cross-shard `rankPage()` path (PLAN5 §7.1 / PLAN2 #3).
1918
+ * Span id of the upstream caller extracted from the inbound `traceparent`,
1919
+ * when present. This becomes the OTLP `parentSpanId` for the dispatch span so
1920
+ * collector waterfalls show the worker span nested under the upstream caller.
1921
+ */
1922
+ parentSpanId?: string;
1923
+ /** URL path of the inbound request (e.g. `"/_lunora/rpc"`). */
1924
+ path?: string;
1925
+ /** Port of the inbound request, when available. */
1926
+ port?: number;
1927
+ /** URL scheme of the inbound request (e.g. `"https"`). */
1928
+ scheme?: string;
1929
+ /** Shard key for single-shard calls; absent for fan-outs. */
1930
+ shardKey?: string;
1931
+ /**
1932
+ * W3C trace context for this dispatch, generated once at dispatch entry (32-
1933
+ * and 16-hex). A sink (e.g. `otlpSink`) reuses these for the dispatch's span
1934
+ * instead of minting fresh ids, and the runtime propagates them to the shard
1935
+ * as a `traceparent` so a container the handler calls can stitch its spans
1936
+ * under the same trace. Absent on paths that don't originate a trace (a sink
1937
+ * falls back to random ids).
1433
1938
  */
1434
- orchestrateRankPage: (namespace: ShardNamespaceLike, request: RankPageFanOutRequest) => Promise<RankPageFanOutResult>;
1939
+ spanId?: string;
1435
1940
  /**
1436
- * Fan the `__lunora_admin__:getMetrics` admin RPC out to every live shard of
1437
- * a table and collect each shard's lifetime `requests` total into a per-shard
1438
- * `{ shardKey, requests }` distribution. The feed the studio's `hot_shard`
1439
- * advisor lint needs: a single shard's snapshot can't reveal cross-shard
1440
- * skew, so this fans the cheap metrics read out and returns the whole shard
1441
- * set's request volumes (a failed shard surfaces as `requests: 0`).
1941
+ * W3C trace flags for this dispatch (the sampled flag, bit 0). Carried from
1942
+ * the upstream `traceparent` or set by the runtime's head-sampling decision.
1442
1943
  */
1443
- orchestrateShardTraffic: (namespace: ShardNamespaceLike, request: ShardTrafficFanOutRequest) => Promise<ShardTrafficFanOutResult>;
1444
- readonly registry: ShardRegistry;
1944
+ traceFlags?: number;
1945
+ traceId?: string;
1946
+ /** Inbound `User-Agent` header, when available. */
1947
+ userAgent?: string;
1445
1948
  }
1446
1949
  /**
1447
- * Cross-shard export request. `tables` is the union of every table the caller
1448
- * wants exported (shard-local **or** global); `headers` carries the admin
1449
- * bearer the per-shard gate expects. Shard registries are queried for the
1450
- * complete set of live shards across all listed shard-local tables.
1950
+ * The `ctx.log` observability contract lives in `shared/` (inlined into each
1951
+ * `dist`) so the DO that builds the events and the runtime sink that consumes
1952
+ * them agree by construction rather than by hand-mirrored duplication. Re-exported
1953
+ * here under the runtime's historical names.
1954
+ *
1955
+ * `LogLevel` is the canonical `ctx.log` severity union (the five console tiers
1956
+ * plus `trace`/`fatal`); `ObservabilitySinkContext` is the shared per-event sink
1957
+ * context (a `waitUntil` to keep a background send alive past the response).
1451
1958
  */
1452
- interface ExportFanOutRequest {
1453
- args?: Record<string, unknown>;
1454
- headers?: Record<string, string>;
1959
+ type LogLevel = ContextLogLevel;
1960
+ type ObservabilitySinkContext = LogSinkContext;
1961
+ /**
1962
+ * The hook contract. Methods are optional so a sink can opt into only the
1963
+ * events it cares about; the runtime no-ops the others.
1964
+ */
1965
+ interface ObservabilitySink {
1966
+ /**
1967
+ * **Opt-in, EXPERIMENTAL, default `false`.** When `true`, each `ctx.trace`
1968
+ * span the Durable Object records is ALSO emitted as a Cloudflare **custom
1969
+ * span** (`tracing.enterSpan` from `cloudflare:workers`, GA 2026-06-16) so it
1970
+ * nests inside CF's native binding/fetch/handler trace tree on the hosted
1971
+ * path — a deeper waterfall in Cloudflare's own trace viewer.
1972
+ *
1973
+ * Capability-probed: a safe no-op off-Cloudflare, on a compat date predating
1974
+ * custom spans, or when the trace is unsampled. This ONLY ADDS a CF-side span;
1975
+ * it never replaces {@link ObservabilitySink.onSpan}, which stays the source
1976
+ * of truth and drives the local studio waterfall.
1977
+ *
1978
+ * **Workerd-validated (partial).** The `tracing.enterSpan` bridge is confirmed
1979
+ * available and side-effect-free inside a real Durable Object under
1980
+ * `@cloudflare/vitest-pool-workers` — the body runs without throwing,
1981
+ * `span.isTraced` is a real boolean, and `onSpan`'s recorded tree is byte-for-byte
1982
+ * identical with the flag on vs off. Still EXPERIMENTAL because the harness is
1983
+ * unsampled (`isTraced === false`), so CF's own EXPORTED parent-linking of the
1984
+ * custom span under the DO's ambient span is not yet observable there.
1985
+ *
1986
+ * **Double-export caveat.** Leave this off unless you understand the trade:
1987
+ * with it on, a deployment that also ships `onSpan` to a collector via
1988
+ * `otlpSink` AND lets Cloudflare export its trace tree will emit the same
1989
+ * logical span down two pipelines. Enable it only when you want the CF-native
1990
+ * nesting and have accounted for that overlap.
1991
+ *
1992
+ * Pass this on the SAME sink object you give both `createWorker` and
1993
+ * `createShardDO` — the DO reads the flag when building `ctx.trace`.
1994
+ */
1995
+ fuseCloudflareTraces?: boolean;
1996
+ /** Invoked once per `ctx.log.*` call from a function handler. */
1997
+ onLog?: (event: LogEvent, context?: ObservabilitySinkContext) => void;
1998
+ /**
1999
+ * Invoked once per `ctx.metrics.*` measurement. No pre-aggregation happens
2000
+ * upstream, so counter values are deltas for the destination to sum.
2001
+ */
2002
+ onMetric?: (event: MetricEvent, context?: ObservabilitySinkContext) => void;
2003
+ /** Invoked once per dispatched RPC (single-shard or fan-out). */
2004
+ onRpc?: (event: ObservabilityEvent, context?: ObservabilitySinkContext) => void;
1455
2005
  /**
1456
- * Tables driving the fan-out. Shards are derived from the union of each
1457
- * table's live shard keys so an export of `["users","messages"]` reaches
1458
- * every shard that holds either table. Globals are skipped here; the
1459
- * worker reads them from D1 directly.
2006
+ * Invoked once per `ctx.trace(name, fn)` span, when the span body settles.
2007
+ * Distinct from `onRpc`: that is the one SERVER span per dispatch, this is the
2008
+ * INTERNAL spans a handler creates beneath it.
1460
2009
  */
1461
- tables: ReadonlyArray<string>;
1462
- }
1463
- /** Per-shard export outcome. */
1464
- interface ShardExportOutcome {
1465
- error?: {
1466
- message: string;
1467
- timedOut: boolean;
1468
- };
1469
- /** Rows from this shard, or undefined when an error occurred. */
1470
- rows?: ReadonlyArray<{
1471
- doc: Record<string, unknown>;
1472
- table: string;
1473
- }>;
1474
- shardKey: string;
1475
- }
1476
- interface ExportFanOutResult {
1477
- failed: number;
1478
- ok: number;
1479
- shards: ReadonlyArray<ShardExportOutcome>;
2010
+ onSpan?: (event: SpanEvent, context?: ObservabilitySinkContext) => void;
1480
2011
  }
1481
2012
  /**
1482
- * Cross-shard change-data-capture request. `tables` drives shard discovery (the
1483
- * union of their live shard keys, like export); `cursors` maps each shard key
1484
- * to the `seq` it was last read through (absent → from the beginning). `limit`
1485
- * caps each shard's page.
2013
+ * Invoke `sink.onRpc` with the given event, swallowing any error the sink
2014
+ * throws. Use at the dispatch boundary; the runtime should never see a
2015
+ * sink-originating throw bubble up past this point. `context.waitUntil`, when
2016
+ * supplied, lets a network sink keep its send alive past the response.
2017
+ *
2018
+ * `sampling` applies the trace-sampling verdict to this dispatch's SERVER span:
2019
+ * the event is dropped unless the trace was head-sampled or (with errors
2020
+ * force-kept) this dispatch errored — the tail bias. A dispatch with no
2021
+ * `traceId` (a fan-out aggregation, which mints none) is always kept, and an
2022
+ * absent `sampling` keeps everything, so both are backward-compatible.
1486
2023
  */
1487
- interface CdcSyncFanOutRequest {
1488
- cursors?: Record<string, number>;
1489
- headers?: Record<string, string>;
1490
- limit?: number;
1491
- tables: ReadonlyArray<string>;
1492
- }
1493
- /** Per-shard CDC page: the changes plus the new cursor to resume this shard from. */
1494
- interface ShardCdcOutcome {
1495
- changes?: ReadonlyArray<Record<string, unknown>>;
1496
- /** New per-shard cursor; on error it echoes the shard's prior cursor so a retry resumes cleanly. */
1497
- cursor: number;
1498
- error?: {
1499
- message: string;
1500
- timedOut: boolean;
1501
- };
1502
- shardKey: string;
1503
- }
1504
- interface CdcSyncFanOutResult {
1505
- failed: number;
1506
- ok: number;
1507
- shards: ReadonlyArray<ShardCdcOutcome>;
1508
- }
2024
+ declare const emitRpcEvent: (sink: ObservabilitySink | undefined, event: ObservabilityEvent, context?: ObservabilitySinkContext, sampling?: TraceSamplingConfig) => void;
1509
2025
  /**
1510
- * Cross-shard import request. Rows have already been bucketed by the runtime
1511
- * into one batch per shard key the coordinator's job is to forward each
1512
- * batch and roll up the per-shard insert counts + errors.
2026
+ * Invoke `sink.onLog` with the given log event, swallowing any error the sink
2027
+ * throws. The same failure model as {@link emitRpcEvent}: a buggy log sink must
2028
+ * never break the handler that emitted the line.
1513
2029
  */
1514
- interface ImportFanOutRequest {
1515
- /**
1516
- * Per-shard batches keyed by shard key. Each entry will be POSTed as the
1517
- * `rows` arg of `__lunora_admin__:importShard`. The shard's
1518
- * starting-line-number for error attribution is carried in `startLine`.
1519
- */
1520
- batches: ReadonlyArray<{
1521
- rows: ReadonlyArray<{
1522
- doc: Record<string, unknown>;
1523
- table: string;
1524
- }>;
1525
- shardKey: string;
1526
- startLine?: number;
1527
- }>;
1528
- headers?: Record<string, string>;
2030
+ declare const emitLogEvent: (sink: ObservabilitySink | undefined, event: LogEvent, context?: ObservabilitySinkContext) => void;
2031
+ /** Procedure kinds that can be exposed over REST (`stream` cannot — it is a WebSocket surface). */
2032
+ type RestFunctionKind = "action" | "mutation" | "query";
2033
+ /**
2034
+ * The `.expose({ rest: true })` tag stamped onto a registered procedure (runtime,
2035
+ * as `fn.expose`) or discovered from its builder chain (codegen, onto the
2036
+ * `FunctionIR`). Presence of `rest === true` is the ONLY thing that opts a
2037
+ * procedure into the surface — everything is default-closed.
2038
+ */
2039
+ interface RestExposure {
2040
+ rest?: boolean;
1529
2041
  }
1530
- interface ShardImportOutcome {
1531
- error?: {
1532
- message: string;
1533
- timedOut: boolean;
1534
- };
1535
- result?: {
1536
- conflicts: number;
1537
- errors: ReadonlyArray<{
1538
- code: string;
1539
- line: number;
1540
- message: string;
1541
- table: string;
1542
- }>;
1543
- inserted: Record<string, number>;
1544
- };
1545
- shardKey: string;
2042
+ /** One resolved REST endpoint: the transport method + URL path a procedure is reachable at. */
2043
+ interface RestSurfaceEntry {
2044
+ functionPath: string;
2045
+ kind: RestFunctionKind;
2046
+ method: "GET" | "POST";
2047
+ name: string;
2048
+ namespace: string;
2049
+ path: string;
1546
2050
  }
1547
- interface ImportFanOutResult {
1548
- /** Total conflicts (skipped `_id`s) across shards. */
1549
- conflicts: number;
1550
- /** Errors merged across all per-shard outcomes. */
1551
- errors: ReadonlyArray<{
1552
- code: string;
1553
- line: number;
1554
- message: string;
1555
- table: string;
1556
- }>;
1557
- failed: number;
1558
- /** Per-table summed insert counts. */
1559
- inserted: Record<string, number>;
1560
- ok: number;
1561
- shards: ReadonlyArray<ShardImportOutcome>;
2051
+ /**
2052
+ * Resolve the full REST surface from a list of procedures, filtering to the ones
2053
+ * opted in via `.expose({ rest: true })`. The single source of truth both the
2054
+ * runtime router and the OpenAPI emitter derive from — a `stream` procedure or a
2055
+ * malformed path is skipped. Ordered by path for stable enumeration.
2056
+ */
2057
+ declare const describeRestSurface: (procedures: ReadonlyArray<{
2058
+ exposure?: RestExposure;
2059
+ functionPath: string;
2060
+ kind: "action" | "mutation" | "query" | "stream";
2061
+ }>) => RestSurfaceEntry[];
2062
+ /** The bits of a registered function the REST router reads: its kind and its `.expose` tag. */
2063
+ interface RestRegistryEntry {
2064
+ expose?: RestExposure;
2065
+ kind: "action" | "mutation" | "query" | "stream";
1562
2066
  }
2067
+ /** Registry map (structurally the generated `LUNORA_FUNCTIONS`, narrowed to what REST needs). */
2068
+ type RestRegistryLike = Record<string, RestRegistryEntry>;
2069
+ /** Dispatch one exposed procedure through the shared RPC path (auth + RLS + validators enforced at the shard). Returns the shard `Response`. */
2070
+ type RestInvoke = (parameters: {
2071
+ args: Record<string, unknown>;
2072
+ env: unknown;
2073
+ functionPath: string;
2074
+ request: Request;
2075
+ shardKey?: string;
2076
+ /** The request's `waitUntil`, so dispatch telemetry survives isolate teardown. */
2077
+ waitUntil?: (promise: Promise<unknown>) => void;
2078
+ }) => Promise<Response>;
1563
2079
  /**
1564
- * Cross-shard CDC replay request (point-in-time recovery). Changes are
1565
- * pre-bucketed by the runtime into one batch per shard key the coordinator
1566
- * forwards each batch to `__lunora_admin__:applyCdc` and rolls up the counts.
2080
+ * Optional per-request rate-limit gate for the public surface. Returns a `429`
2081
+ * `Response` when the request is limited (the router returns it verbatim), or
2082
+ * `undefined` to let the call through. Built in `create-worker` over
2083
+ * `@lunora/ratelimit`.
1567
2084
  */
1568
- interface ApplyCdcFanOutRequest {
1569
- batches: ReadonlyArray<{
1570
- changes: ReadonlyArray<Record<string, unknown>>;
1571
- shardKey: string;
2085
+ type RestRateLimit = (request: Request, functionPath: string) => Promise<Response | undefined> | Response | undefined;
2086
+ /**
2087
+ * A built REST route. Takes the same `(request, env, url, context)` shape as the
2088
+ * runtime's internal route table so it can be spread straight into it; `url` is
2089
+ * unused here (the route re-parses it) and `context` is read only for its
2090
+ * `waitUntil`, which keeps dispatch telemetry alive past the response.
2091
+ */
2092
+ type RestRoute = (request: Request, env: unknown, url?: URL, context?: {
2093
+ waitUntil?: (promise: Promise<unknown>) => void;
2094
+ }) => Promise<Response>;
2095
+ interface RestRouteDeps {
2096
+ /** The generated function registry — the source of which procedures are exposed. */
2097
+ functions: RestRegistryLike;
2098
+ /** The shared RPC dispatch (bound in `create-worker`). */
2099
+ invoke: RestInvoke;
2100
+ /** Optional rate-limit gate for the public surface. */
2101
+ rateLimit?: RestRateLimit;
2102
+ /** JSON body reader with the shared size cap. */
2103
+ readJsonBody: (request: Request) => Promise<Record<string, unknown>>;
2104
+ }
2105
+ /**
2106
+ * The resolved REST surface for a registry — the ordered list of exposed
2107
+ * `{ functionPath, method, path, kind }`. Exported so a contract test can assert
2108
+ * the runtime surface equals the published OpenAPI (both derive from the same
2109
+ * `shared/rest-surface` helper).
2110
+ */
2111
+ declare const restSurfaceFromRegistry: (functions: RestRegistryLike) => ReturnType<typeof describeRestSurface>;
2112
+ /** Read `shardKey` from `?shardKey=` or the `x-lunora-shard-key` header; `undefined` routes to the default shard. */
2113
+ declare const readShardKey: (url: URL, request: Request) => string | undefined;
2114
+ /**
2115
+ * Decode GET args from the query string. Each value is parsed as JSON when it
2116
+ * looks like a JSON scalar/array/object (so `?limit=10` → number, `?ids=[1,2]` →
2117
+ * array), else kept as a string. `shardKey` is reserved for routing and excluded.
2118
+ */
2119
+ declare const argsFromQuery: (url: URL) => Record<string, unknown>;
2120
+ /**
2121
+ * Build the REST route map merged into the worker's internal route table. One
2122
+ * exact-path entry per exposed procedure — so the surface is closed by
2123
+ * construction. A `query` handler accepts `GET` (args from the query string) and
2124
+ * `POST` (args from a JSON body); a `mutation` / `action` accepts `POST` only.
2125
+ */
2126
+ declare const buildRestRoutes: (deps: RestRouteDeps) => Record<string, RestRoute>;
2127
+ /** Structural view of a `@lunora/ratelimit` `RateLimiter` — only the `.limit()` call, so the runtime needs no hard dependency. */
2128
+ interface RateLimiterLike {
2129
+ limit: (name: string, args?: {
2130
+ key?: string;
2131
+ }) => Promise<{
2132
+ ok: boolean;
2133
+ retryAfter: number;
1572
2134
  }>;
1573
- headers?: Record<string, string>;
1574
- }
1575
- interface ApplyCdcFanOutResult {
1576
- /** Total changes applied across shards. */
1577
- applied: number;
1578
- failed: number;
1579
- ok: number;
1580
2135
  }
1581
2136
  /**
1582
- * Cross-shard traffic request. Like {@link MigrationFanOutRequest} there is no
1583
- * caller-supplied merge each shard's `__lunora_admin__:getMetrics` payload
1584
- * carries its own lifetime `requests` total, and {@link rollUpShardTraffic}
1585
- * collects them into one `{ shardKey, requests }` entry per shard. `headers`
1586
- * must carry the admin bearer the per-shard `getMetrics` gate requires.
1587
- *
1588
- * `table` drives shard discovery: the registry's live shard keys for the table
1589
- * are the shards fanned out to. This is the feed the studio's `hot_shard`
1590
- * runtime advisor consumes to compute cross-shard skew — a single shard's
1591
- * snapshot can't, so the panel fans this out on demand.
2137
+ * Adapt a `@lunora/ratelimit` limiter into a {@link RestRateLimit} gate for the
2138
+ * public REST surface (plan 167). Pass the limiter and the rate name to charge;
2139
+ * `key` isolates the limit per caller (IP / user / API key — defaults to the
2140
+ * `cf-connecting-ip` header, else a shared bucket). A denied request becomes a
2141
+ * `429` with a `Retry-After` header (seconds, ceil of the limiter's ms). The
2142
+ * runtime imports nothing from `@lunora/ratelimit` — build the limiter in the
2143
+ * worker entry and pass it here.
1592
2144
  */
1593
- interface ShardTrafficFanOutRequest {
1594
- headers?: Record<string, string>;
1595
- /** Table whose live shard keys the traffic fan-out runs across. */
1596
- table: string;
1597
- }
1598
- /** One shard's traffic total, mirroring the advisor's `AdvisorShardTraffic` (sans the optional `group`). */
1599
- interface ShardTrafficEntry {
1600
- /** Lifetime request count read off the shard's `getMetrics` snapshot; `0` for a shard that failed/timed out. */
1601
- requests: number;
1602
- /** The shard key (the DO id name); `""` for the unnamed root shard. */
1603
- shardKey: string;
1604
- }
1605
- interface ShardTrafficFanOutResult {
1606
- /** Shards that errored or timed out (their `requests` are reported as `0`). */
1607
- failed: number;
1608
- /** Shards that returned a 2xx `getMetrics` snapshot. */
1609
- ok: number;
1610
- /**
1611
- * Per-shard request totals, in registry order. Shaped to plug straight into
1612
- * the advisor's `LintContext.shardTraffic` so the `hot_shard` lint can
1613
- * compute the cross-shard share. A failed shard still appears (with
1614
- * `requests: 0`) so callers see the full shard set.
1615
- */
1616
- shards: ReadonlyArray<ShardTrafficEntry>;
1617
- }
1618
- declare const createQueryCoordinator: (options: QueryCoordinatorOptions) => QueryCoordinator;
2145
+ declare const createRestRateLimit: (limiter: RateLimiterLike, options: {
2146
+ key?: (request: Request, functionPath: string) => string | undefined;
2147
+ name: string;
2148
+ }) => RestRateLimit;
1619
2149
  /** Per-header overrides for {@link SecurityHeadersOptions}. `false` omits the header. */
1620
2150
  interface SecurityHeadersOptions {
1621
2151
  /**
@@ -1756,6 +2286,68 @@ declare const handleCorsPreflight: (request: Request, resolved: ResolvedSecurity
1756
2286
  * hibernation handshake.
1757
2287
  */
1758
2288
  declare const decorateResponse: (response: Response, request: Request, resolved: ResolvedSecurity) => Response;
2289
+ /**
2290
+ * Who is allowed to hand this worker a trace to join.
2291
+ *
2292
+ * Continuing an inbound W3C `traceparent` is what makes a distributed waterfall
2293
+ * stitch end to end, but the header is caller-supplied: on a public worker,
2294
+ * trusting it lets anyone choose which trace their spans and `ctx.log` lines land
2295
+ * in, and — because `shared/sampling` derives the head verdict from the trace id —
2296
+ * choose their own sampling outcome. Whether that matters is a *deployment*
2297
+ * question ("can an untrusted client reach this worker directly?"), which no
2298
+ * amount of request inspection can answer on its own.
2299
+ *
2300
+ * So rather than ask users to hand-roll a security predicate, this module ships
2301
+ * the answers that are actually sound, named:
2302
+ *
2303
+ * ```ts
2304
+ * createWorker({ trustInboundTraceContext: true }); // nothing untrusted can reach this worker
2305
+ * createWorker({ trustInboundTraceContext: "mtls" }); // only edge-verified client certs
2306
+ * createWorker({ trustInboundTraceContext: (request) => … }); // anything else
2307
+ * ```
2308
+ *
2309
+ * **Behind a gateway, mesh, or Cloudflare Access, `true` is the answer.** If the
2310
+ * worker is genuinely unreachable except through that front door, every caller
2311
+ * has already passed it and there is nothing left to discriminate on. Check that
2312
+ * it really is unreachable — a `*.workers.dev` route left enabled, or a hostname
2313
+ * outside the Access policy, is a second front door with no gate on it.
2314
+ *
2315
+ * There is deliberately no `"cloudflare-access"` signal. Recognising Access by its
2316
+ * `cf-access-jwt-assertion` header only tests that a header is present, which is
2317
+ * redundant when the worker is properly fronted (`true` already covers it) and
2318
+ * forgeable in one `curl` when it is not. Verifying the assertion for real means a
2319
+ * JWKS fetch and an audience check — `@lunora/cloudflare-access` does exactly
2320
+ * that, and it is async, so it belongs in `resolveIdentity` rather than on the
2321
+ * dispatch path. Pass a predicate if you want to wire it in yourself.
2322
+ *
2323
+ * Everything resolves to one predicate at worker construction, so the per-request
2324
+ * cost is a single call.
2325
+ *
2326
+ * The custom form receives only the `Request`, deliberately: handing user code the
2327
+ * Worker `env` would put every secret binding behind a telemetry callback, the
2328
+ * same boundary `LogSinkContext` was just narrowed to avoid. A predicate that
2329
+ * needs to compare against a binding should close over it — build the worker per
2330
+ * request from an options factory, the pattern `createLunoraHandler` already uses.
2331
+ */
2332
+ /**
2333
+ * A named trust signal — a per-request property that, on its own, establishes the
2334
+ * caller is one whose trace context may be adopted.
2335
+ *
2336
+ * - `"mtls"` — the caller presented a client certificate that **Cloudflare
2337
+ * verified at the edge**. `cf.tlsClientAuth` is platform-injected request
2338
+ * metadata, not a header, so a caller cannot write it: the check carries its own
2339
+ * proof and holds regardless of how the worker is exposed.
2340
+ *
2341
+ * Signals live here only when they meet that bar. A property a client can set for
2342
+ * itself is not a signal; see the module doc on why Cloudflare Access is absent.
2343
+ */
2344
+ type TraceTrustSignal = "mtls";
2345
+ /**
2346
+ * How much of the inbound trace context to trust. `false` (the default) ignores
2347
+ * it entirely; `true` trusts every caller, which is right when nothing untrusted
2348
+ * can reach the worker.
2349
+ */
2350
+ type TrustInboundTraceContext = boolean | TraceTrustSignal | ((request: Request) => boolean);
1759
2351
  /**
1760
2352
  * Wire-format RPC envelope. Posted to `POST /_lunora/rpc`.
1761
2353
  *
@@ -1917,6 +2509,17 @@ interface FunctionDescriptor {
1917
2509
  interface FunctionRegistryEntry {
1918
2510
  /** The function's `v.*` args validator map; read structurally for the signature view. */
1919
2511
  args?: unknown;
2512
+ /**
2513
+ * Opt-in public-surface tag set by the `.expose({ rest: true })` builder
2514
+ * modifier (plan 167). Present only on procedures deliberately published over
2515
+ * REST; the runtime builds a `/_lunora/rest/&lt;namespace>/&lt;fn>` route for each,
2516
+ * routing THROUGH the procedure so auth/RLS/validators are enforced. Rides
2517
+ * along on the registered function's identity (like `fn.x402` / `fn.rls`), so
2518
+ * reading it needs no change to the generated registry shape.
2519
+ */
2520
+ expose?: {
2521
+ readonly rest?: boolean;
2522
+ };
1920
2523
  /**
1921
2524
  * The generated registry carries `"stream"` alongside query/mutation/action;
1922
2525
  * the discovery endpoint surfaces the latter three only (a `stream` function
@@ -2206,6 +2809,81 @@ interface BackupManifest {
2206
2809
  scheduledTime: number;
2207
2810
  tables?: string;
2208
2811
  }
2812
+ /**
2813
+ * Health / readiness probe configuration (plan 177). Everything is optional; the
2814
+ * runtime always registers its default binding probes, so the endpoints work
2815
+ * with `health: {}` (or the field omitted). Nothing here is a secret — `appName`
2816
+ * / `appVersion` are the only strings echoed in the body, and per-check messages
2817
+ * are surfaced only under the `"admin"` posture.
2818
+ */
2819
+ interface HealthOptions {
2820
+ /** Application name surfaced in the health body. Defaults to `"lunora"`. */
2821
+ appName?: string;
2822
+ /** Application version surfaced in the health body. Defaults to `"0.0.0"`. */
2823
+ appVersion?: string;
2824
+ /**
2825
+ * Auth posture. `"public"` (default) serves the probe unauthenticated with
2826
+ * per-check messages redacted; `"admin"` requires a valid admin bearer and
2827
+ * includes the (runtime-authored) messages.
2828
+ */
2829
+ auth?: "admin" | "public";
2830
+ /** Cache the computed report for this many ms so a frequent poller does not re-run every probe. Defaults to `0`. */
2831
+ cacheTtlMs?: number;
2832
+ /** Skip the auto-registered D1 / R2 / queue / Hyperdrive binding probes (keep only the DO probe + `probes`). Defaults to `false`. */
2833
+ disableBindingProbes?: boolean;
2834
+ /** Extra bespoke probes appended to the auto-registered set (e.g. a downstream API reachability check). */
2835
+ probes?: ReadonlyArray<HealthProbe>;
2836
+ }
2837
+ /**
2838
+ * One registered device subscription as surfaced by the gated
2839
+ * `__lunora_admin__:listPushSubscriptions` admin RPC (backing the Studio
2840
+ * Notifications page). Structurally mirrors `@lunora/notify`'s
2841
+ * `PushSubscriptionDevice` — the runtime carries NO `@lunora/notify` dependency,
2842
+ * so the shape is declared here and matched by duck typing (the studio reuses the
2843
+ * canonical `@lunora/notify` type). Delivery secrets (Web Push `keys`, FCM
2844
+ * `token`) are never part of this shape.
2845
+ */
2846
+ interface NotifySubscriptionDevice {
2847
+ /** Unix-ms creation time. */
2848
+ createdAt: number;
2849
+ /** Web Push service endpoint URL (web-push only). */
2850
+ endpoint?: string;
2851
+ /** Stable identifier used as the store key. */
2852
+ id: string;
2853
+ /** The delivery channel this subscription targets (`"web-push"` / `"fcm"`). */
2854
+ kind: string;
2855
+ /** Last delivery error message, when `lastStatus` is `failed`/`expired`. */
2856
+ lastError?: string;
2857
+ /** Unix-ms time of the most recent register/send touch. */
2858
+ lastSeenAt: number;
2859
+ /** Last-known delivery outcome (`"ok"` / `"failed"` / `"expired"`). */
2860
+ lastStatus?: string;
2861
+ /** Arbitrary app metadata (device name, locale, topics, …). */
2862
+ metadata?: Record<string, unknown>;
2863
+ /** Owning user id, or `null`/absent when anonymous. */
2864
+ userId?: null | string;
2865
+ }
2866
+ /**
2867
+ * The minimal read surface the worker needs off an `@lunora/notify` subscription
2868
+ * store to serve `__lunora_admin__:listPushSubscriptions`: just `list`. Codegen
2869
+ * binds this from the app's `defineNotify({ store })` (`store(env)`), so the
2870
+ * worker reads registered devices through the very store the handlers write to.
2871
+ * Structural (not a `@lunora/notify` import) to keep the runtime dependency-free.
2872
+ */
2873
+ interface NotifySubscriptionStoreLike {
2874
+ /**
2875
+ * List every stored subscription. Declared with NO parameter so a concrete
2876
+ * `@lunora/notify` `SubscriptionStore` — whose `list(filter?)` narrows `kind`
2877
+ * to the `"web-push" | "fcm"` union — assigns cleanly under
2878
+ * `strictFunctionTypes` (an extra optional parameter on the source is fine).
2879
+ * The RPC handler applies the `{ kind, userId }` filter in-memory, so no typed
2880
+ * filter needs to cross this dependency-free structural boundary.
2881
+ */
2882
+ list: () => Promise<ReadonlyArray<NotifySubscriptionDevice & {
2883
+ keys?: unknown;
2884
+ token?: unknown;
2885
+ }>>;
2886
+ }
2209
2887
  interface WorkerOptions {
2210
2888
  /**
2211
2889
  * An additional, async authorization gate for the `/_lunora/admin/*` plane
@@ -2265,6 +2943,17 @@ interface WorkerOptions {
2265
2943
  * every `/auth/*` endpoint responds `AUTH_NOT_CONFIGURED`.
2266
2944
  */
2267
2945
  authAdmin?: AuthAdmin;
2946
+ /**
2947
+ * The auth/security audit read plane backing the studio's "Security / audit"
2948
+ * page (the `__lunora_admin__:getAuthAuditLog` admin RPC). The audit trail
2949
+ * lives in the auth D1 database (via `@lunora/auth`'s `SqlExecutor`), not in a
2950
+ * shard's DO SQLite, so — unlike the other `__lunora_admin__:*` ops — the RPC
2951
+ * is served here at the worker, admin-gated, through this reader. Wire it with
2952
+ * `@lunora/auth`'s `createAuthAuditReader(d1Executor(env.DB))`. Omit it and the
2953
+ * RPC responds `AUTH_AUDIT_NOT_CONFIGURED`; a caller without a valid admin
2954
+ * bearer always gets `ADMIN_FORBIDDEN` first (default-closed).
2955
+ */
2956
+ authAuditReader?: AuthAuditReader;
2268
2957
  /**
2269
2958
  * Base path the auth routes are mounted under (default `/api/auth`). Used
2270
2959
  * to classify which inbound paths are auth ATTEMPTS for the app-level
@@ -2377,11 +3066,27 @@ interface WorkerOptions {
2377
3066
  d1?: unknown;
2378
3067
  /** Default shard key used when an envelope omits one. */
2379
3068
  defaultShardKey?: string;
3069
+ /**
3070
+ * Durable per-shard cursor store for the continuous CDC export tap (plan 170),
3071
+ * mirroring the CDC-in `__lunora_source_cursor` watermark. Build a KV-backed
3072
+ * one with `createKvCursorStore(env.CDC_CURSORS)`. Required (alongside
3073
+ * {@link WorkerOptions.exportSinks}) for the `POST /_lunora/admin/export-tap/run`
3074
+ * drain route; absent → the route reports `EXPORT_TAP_NOT_CONFIGURED`.
3075
+ */
3076
+ exportCursorStore?: ExportCursorStore;
2380
3077
  /**
2381
3078
  * Stream `.global()` rows for the admin export endpoint. When omitted,
2382
3079
  * the export endpoint covers only shard-local tables.
2383
3080
  */
2384
3081
  exportGlobals?: GlobalExportFunction;
3082
+ /**
3083
+ * Named continuous-export sinks (plan 170) the CDC tap drains the op-log change
3084
+ * feed to. Build with `webhookSink({...})`, `r2Sink({...})`, or a custom
3085
+ * `defineExportSink({...})`. Paired with {@link WorkerOptions.exportCursorStore}
3086
+ * to enable the `POST /_lunora/admin/export-tap/run` drain route (at-least-once,
3087
+ * ordered per shard, resumable). Absent / empty → the route reports not-configured.
3088
+ */
3089
+ exportSinks?: Record<string, ExportSink>;
2385
3090
  /**
2386
3091
  * The generated `LUNORA_FUNCTIONS` map (from `_generated/functions.ts`). When
2387
3092
  * set, the worker exposes the admin-gated `GET /_lunora/admin/functions`
@@ -2398,6 +3103,17 @@ interface WorkerOptions {
2398
3103
  * respond `GLOBALS_NOT_CONFIGURED`.
2399
3104
  */
2400
3105
  globalIntrospector?: GlobalIntrospector;
3106
+ /**
3107
+ * Health / readiness probe configuration (plan 177). When present (or left as
3108
+ * the default — probes are always registered), the worker serves
3109
+ * `GET /_lunora/health` (aggregate; `503` when a critical dependency is down)
3110
+ * and `GET /_lunora/health/ready` (readiness gate). The runtime auto-registers
3111
+ * probes for the shard Durable Object (reachability, critical), any D1 binding
3112
+ * (`SELECT 1`, critical), and R2 / queue / Hyperdrive bindings (presence,
3113
+ * non-critical); `probes` adds bespoke checks. The body never leaks secrets —
3114
+ * see {@link HealthOptions}.
3115
+ */
3116
+ health?: HealthOptions;
2401
3117
  /**
2402
3118
  * Router for HTTP actions (`httpRouter()` from `@lunora/server`, a hono app).
2403
3119
  * Consulted for requests that miss the explicit {@link WorkerOptions.routes}
@@ -2462,6 +3178,17 @@ interface WorkerOptions {
2462
3178
  * overrides). Absent → the Archive feed reports "not configured".
2463
3179
  */
2464
3180
  logArchive?: LogArchiveConfig;
3181
+ /**
3182
+ * The `@lunora/notify` device-subscription store, bound from the request
3183
+ * `env` by codegen from the app's `lunora/notify.ts` `defineNotify({ store })`.
3184
+ * Backs the gated `__lunora_admin__:listPushSubscriptions` admin RPC (the
3185
+ * Studio Notifications page): the worker reads registered devices — endpoint /
3186
+ * kind / last-send status / delivery errors — through the SAME store the
3187
+ * handlers register into. Delivery secrets (Web Push keys, FCM token) are
3188
+ * stripped before the devices leave the worker. Absent (no store configured)
3189
+ * ⇒ the RPC returns an empty device list rather than erroring.
3190
+ */
3191
+ notifySubscriptionStore?: NotifySubscriptionStoreLike;
2465
3192
  /**
2466
3193
  * Optional telemetry sink. When supplied, the worker emits one
2467
3194
  * `onRpc` event per dispatched RPC (single-shard forward or fan-out)
@@ -2553,6 +3280,16 @@ interface WorkerOptions {
2553
3280
  * bucket rows; when omitted, every row routes to the default shard.
2554
3281
  */
2555
3282
  resolveTableSharding?: AdminTableResolver;
3283
+ /**
3284
+ * Optional per-request rate-limit gate for the opt-in public REST surface
3285
+ * (plan 167). Invoked with the inbound request + the target `functionPath`
3286
+ * BEFORE the procedure is dispatched; return a `429` `Response` to reject
3287
+ * (returned verbatim, `Retry-After` included) or `undefined` to allow. Build it
3288
+ * over `@lunora/ratelimit` in the worker entry — the runtime stays free of a
3289
+ * hard `@lunora/ratelimit` dependency. Only consulted for REST calls; typed RPC
3290
+ * is unaffected.
3291
+ */
3292
+ restRateLimit?: RestRateLimit;
2556
3293
  /**
2557
3294
  * Map of routes for custom HTTP handlers (auth callbacks etc.). Keys can
2558
3295
  * be either `"METHOD path"` (e.g. `"GET /healthz"`) or just `"path"`
@@ -2560,6 +3297,25 @@ interface WorkerOptions {
2560
3297
  * first.
2561
3298
  */
2562
3299
  routes?: Record<string, Route>;
3300
+ /**
3301
+ * Trace-sampling policy for the observability pipeline, mirroring Cloudflare
3302
+ * Workers' `head_sampling_rate`. Governs only trace spans (the per-dispatch
3303
+ * SERVER span and the `ctx.trace` INTERNAL spans beneath it) — never metrics
3304
+ * or `ctx.log` lines.
3305
+ *
3306
+ * The decision is deterministic per trace: a stable value derived from the
3307
+ * `traceId` is compared to `headRate`, so the same trace is kept or dropped
3308
+ * as a whole on the worker and on every shard/container it fans out to (no
3309
+ * half traces). The head decision is propagated to shards via the
3310
+ * `traceparent` sampled flag, so they drop the matching `ctx.trace` spans
3311
+ * coherently.
3312
+ *
3313
+ * With `alwaysSampleErrors` (default `true`), a trace that produced an error
3314
+ * span is kept whole regardless of the head decision — the tail bias, so
3315
+ * failures are never sampled away even at an aggressive `headRate`. Omit the
3316
+ * option (or leave `headRate` at its default `1`) to keep every trace.
3317
+ */
3318
+ sampling?: TraceSamplingConfig;
2563
3319
  /**
2564
3320
  * Namespace binding for the `SchedulerDO` (typically `env.SCHEDULER`). When
2565
3321
  * set, the worker exposes the admin-gated `/_lunora/admin/scheduled`
@@ -2627,6 +3383,39 @@ interface WorkerOptions {
2627
3383
  * endpoint. When omitted, the sync feed covers only shard-local tables.
2628
3384
  */
2629
3385
  syncGlobals?: GlobalCdcSyncFunction;
3386
+ /**
3387
+ * Who may hand this worker a trace to join. Controls whether an inbound W3C
3388
+ * `traceparent` is continued — adopting its trace id, parenting this
3389
+ * dispatch's span under the upstream span, and carrying its `tracestate` to
3390
+ * the shard. **Default: off.**
3391
+ *
3392
+ * ```ts
3393
+ * trustInboundTraceContext: true // nothing untrusted can reach this worker
3394
+ * trustInboundTraceContext: "mtls" // only edge-verified client certificates
3395
+ * trustInboundTraceContext: (request) => … // anything else
3396
+ * ```
3397
+ *
3398
+ * Off by default because the header is caller-supplied: on a worker an
3399
+ * untrusted client can reach directly, trusting it lets anyone choose which
3400
+ * trace their spans and `ctx.log` lines join — grafting entries into another
3401
+ * tenant's waterfall in a shared collector — and, because the head-sampling
3402
+ * verdict is derived from the trace id, choose their own sampling outcome.
3403
+ * (Error traces are unaffected either way: the tail bias is evaluated from
3404
+ * the worker's own decision, never the caller's.)
3405
+ *
3406
+ * Turn it on when something you control — a gateway, service mesh, or
3407
+ * Cloudflare Access — sets `traceparent` itself; a proxy that only _forwards_
3408
+ * the client's header is not such a thing. Behind a front door like that,
3409
+ * `true` is the answer, because every caller has already passed it. Confirm
3410
+ * the worker really is unreachable otherwise — a `*.workers.dev` route left
3411
+ * enabled is a second front door with no gate on it.
3412
+ *
3413
+ * Leaving this unset logs a one-time hint if an inbound trace is actually
3414
+ * dropped; setting it explicitly to `false` keeps the behaviour and silences
3415
+ * that.
3416
+ * @see {@link TrustInboundTraceContext} for what each signal proves.
3417
+ */
3418
+ trustInboundTraceContext?: TrustInboundTraceContext;
2630
3419
  /**
2631
3420
  * Read-only introspector for Vectorize indexes, backing the studio's vector
2632
3421
  * browser via `GET /_lunora/admin/vector/indexes` and
@@ -2711,9 +3500,12 @@ interface LunoraWorker {
2711
3500
  * @param options Call options mirroring the RPC envelope.
2712
3501
  * @param options.shardKey Routes to a specific shard (omitted → the worker's
2713
3502
  * `defaultShardKey`).
3503
+ * @param options.waitUntil The host's `waitUntil`, so dispatch telemetry
3504
+ * whose export is deferred (a gzipped OTLP body) survives isolate teardown.
2714
3505
  */
2715
3506
  serverQuery: (request: Request, env: unknown, reference: unknown, args?: Record<string, unknown>, options?: {
2716
3507
  shardKey?: string;
3508
+ waitUntil?: (promise: Promise<unknown>) => void;
2717
3509
  }) => Promise<Response>;
2718
3510
  }
2719
3511
  /**
@@ -2962,6 +3754,15 @@ declare class LunoraError extends LunoraError$1 {
2962
3754
  });
2963
3755
  toResponse(): Response;
2964
3756
  }
3757
+ /** A JS attribute value the encoder maps onto an OTLP `AnyValue`. */
3758
+ type OtlpAttributeValue = boolean | number | string;
3759
+ /**
3760
+ * A `Resource.attributes` bag — the process-level identity (`service.name`,
3761
+ * `service.version`, `cloud.region`, …) attached to every exported signal.
3762
+ * Lives here rather than in either exporter because both packages build one and
3763
+ * `wrapResource*` consumes it.
3764
+ */
3765
+ type OtlpResourceAttributes = Record<string, OtlpAttributeValue>;
2965
3766
  /** Shared shape for sinks that can be limited to error events only. */
2966
3767
  interface OnlyErrorsOption {
2967
3768
  /** When true, only events with `ok === false` are forwarded. */
@@ -3162,6 +3963,28 @@ interface PipelineLogSinkOptions {
3162
3963
  declare const pipelineLogSink: (options: PipelineLogSinkOptions) => ObservabilitySink;
3163
3964
  /** Options for {@link otlpSink}. */
3164
3965
  interface OtlpSinkOptions extends OnlyErrorsOption {
3966
+ /**
3967
+ * Value of the `deployment.environment` resource attribute (e.g.
3968
+ * `"production"`, `"staging"`, `"development"`).
3969
+ */
3970
+ deploymentEnvironment?: string;
3971
+ /**
3972
+ * When `true`, the sink attaches the resource attributes the **host** detected
3973
+ * for the current request, merged *under* any explicit option so those always
3974
+ * win on collision. In a Worker that is `service.version`,
3975
+ * `deployment.environment`, `cloud.provider`, and `cloud.region` (the colo).
3976
+ *
3977
+ * The sink never inspects `env` or the request itself — detection happens once
3978
+ * per request in the runtime and arrives pre-resolved on the sink context (see
3979
+ * `LogSinkContext.resourceAttributes`), so no sink is ever handed raw bindings.
3980
+ *
3981
+ * Events that originate inside a shard (`ctx.log`, `ctx.trace`, `ctx.metrics`)
3982
+ * carry no host-detected attributes today — the shard has no `env` of its own —
3983
+ * so they export with the explicit options only. Set the values you need
3984
+ * explicitly if you require them to match across worker and shard spans of the
3985
+ * same trace.
3986
+ */
3987
+ detectResources?: boolean;
3165
3988
  /**
3166
3989
  * The OTLP-over-HTTP collector base endpoint (e.g.
3167
3990
  * `https://collector.example.com`). Following the OTel base-endpoint
@@ -3176,11 +3999,29 @@ interface OtlpSinkOptions extends OnlyErrorsOption {
3176
3999
  * default and may be overridden here.
3177
4000
  */
3178
4001
  headers?: Record<string, string>;
4002
+ /**
4003
+ * Additional resource attributes to attach to every exported signal. These
4004
+ * ride alongside the built-in `service.name` and any convenience fields
4005
+ * (`serviceVersion`, `deploymentEnvironment`, etc.). A key that collides with
4006
+ * a built-in resource attribute wins; use this for custom dimensions like
4007
+ * `deployment.region`, `host.name`, or `service.instance.id`.
4008
+ */
4009
+ resourceAttributes?: OtlpResourceAttributes;
3179
4010
  /**
3180
4011
  * Value of the `service.name` resource attribute on every exported span and
3181
4012
  * log — the logical service the telemetry belongs to. Defaults to `lunora`.
3182
4013
  */
3183
4014
  serviceName?: string;
4015
+ /**
4016
+ * Value of the `service.namespace` resource attribute, useful when multiple
4017
+ * services share the same `service.name` under a tenant or team boundary.
4018
+ */
4019
+ serviceNamespace?: string;
4020
+ /**
4021
+ * Value of the `service.version` resource attribute (e.g. a git sha or
4022
+ * release tag).
4023
+ */
4024
+ serviceVersion?: string;
3184
4025
  /**
3185
4026
  * Convenience bearer token: when set, an `Authorization: Bearer` header
3186
4027
  * carrying it is added to every POST (overriding any authorization in
@@ -3225,4 +4066,4 @@ declare const otlpSink: (options: OtlpSinkOptions) => ObservabilitySink;
3225
4066
  */
3226
4067
  declare const combineSinks: (...sinks: ObservabilitySink[]) => ObservabilitySink;
3227
4068
  declare const VERSION: string;
3228
- export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_LOG_COLUMNS, DEFAULT_LOG_LIMIT, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, LOG_ARCHIVE_NOT_CONFIGURED, LOG_ARCHIVE_PATH, type ListAuthUsersOptions, type LogArchiveConfig, type LogEvent, type LogFields, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MetricEvent, type MetricKind, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type PipelineLike, type PipelineLogColumnMap, type PipelineLogCursor, type PipelineLogField, type PipelineLogPage, type PipelineLogQuery, type PipelineLogReader, type PipelineLogReaderOptions, type PipelineLogRow, type PipelineLogSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type SpanEvent, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createPipelineLogReader, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, pipelineLogSink, resolveLogArchiveFromEnv, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
4069
+ export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_LOG_COLUMNS, DEFAULT_LOG_LIMIT, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportBatch, type ExportChange, type ExportCursorStore, type ExportFanOutRequest, type ExportFanOutResult, type ExportSink, type ExportTapFailure, type ExportTapResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, HEALTH_PATH, HEALTH_READY_PATH, type HealthAuthPosture, type HealthBody, type HealthCheckReport, type HealthProbe, type HealthProbeKind, type HealthProbeResult, type HealthRouteDeps, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, LOG_ARCHIVE_NOT_CONFIGURED, LOG_ARCHIVE_PATH, type ListAuthUsersOptions, type LogArchiveConfig, type LogEvent, type LogFields, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MetricEvent, type MetricKind, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type NotifySubscriptionDevice, type NotifySubscriptionStoreLike, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpResourceAttributes, type OtlpSinkOptions, type PipelineLike, type PipelineLogColumnMap, type PipelineLogCursor, type PipelineLogField, type PipelineLogPage, type PipelineLogQuery, type PipelineLogReader, type PipelineLogReaderOptions, type PipelineLogRow, type PipelineLogSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type RateLimiterLike, type ResolvedSecurity, type ResolvedShard, type RestInvoke, type RestRateLimit, type RestRegistryEntry, type RestRegistryLike, type RestRoute, type RestRouteDeps, type Route, type RpcContext, type RpcEnvelope, type RunExportTapOptions, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type SpanEvent, type StorageListFunction as StorageListFn, type StorageObject, type TraceSamplingConfig, type TraceTrustSignal, type TrustInboundTraceContext, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, argsFromQuery, buildHealthRoutes, buildRestRoutes, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createKvCursorStore, createLunoraHandler, createMemoryCursorStore, createPipelineLogReader, createQueryCoordinator, createRestRateLimit, createStaticShardRegistry, createWorker, d1Probe, decorateResponse, defineExportSink, defineRpcEnvelope, durableObjectProbe, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, pipelineLogSink, presenceProbe, r2Sink, readShardKey, resolveLogArchiveFromEnv, resolveLunoraOptions, resolveSecurity, resolveShard, restSurfaceFromRegistry, routeIdentityResolvers, runExportTap, sanitizeChange, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookExportSink, webhookSink, withFrameworkWorker };