@voltro/runtime 0.52.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -69,6 +69,7 @@ import { RetentionSource } from '@voltro/database';
69
69
  import { RetentionSpec } from '@voltro/database';
70
70
  import { retentionTtlMsFromEnv } from '@voltro/database';
71
71
  import { Row } from '@voltro/database';
72
+ import { RowPatch } from '@voltro/protocol';
72
73
  import { Rpc } from '@effect/rpc';
73
74
  import { RpcGroup } from '@effect/rpc';
74
75
  import { RpcInterceptor } from '@voltro/protocol';
@@ -97,6 +98,8 @@ import { TenantRowNotFound } from '@voltro/protocol';
97
98
  import { TenantScopeViolation } from '@voltro/protocol';
98
99
  import { Tracer } from 'effect';
99
100
  import { Unauthenticated } from '@voltro/protocol';
101
+ import { ValidationError } from '@voltro/protocol';
102
+ import { ValidationErrors } from '@voltro/protocol';
100
103
  import { WebSocketGatewayRoute } from '@voltro/protocol';
101
104
  import { WorkflowParentClosePolicy } from '@voltro/protocol';
102
105
  import { WorkflowRunHandle } from '@voltro/protocol';
@@ -151,6 +154,28 @@ export declare interface ActivitySignals {
151
154
  readonly activeWorkflowExecutions: () => number;
152
155
  }
153
156
 
157
+ /**
158
+ * Adopt a detached subscription for a resuming client at `fromRevision`.
159
+ * Undefined — identity unknown, entry dead (an error/revocation while
160
+ * offline), or the revision does not chain — means: subscribe fresh, send a
161
+ * snapshot. A failed adoption EXPIRES the entry: its ring cannot serve this
162
+ * client and keeping a half-dead entry around only risks a second caller
163
+ * adopting state the first one invalidated.
164
+ */
165
+ export declare const adoptDetached: (identity: ResumeIdentity, fromRevision: number, now?: number, tunables?: ResumeTunables) => AdoptedSubscription | undefined;
166
+
167
+ export declare interface AdoptedSubscription {
168
+ /** The deltas the client missed, in order — replay before going live. */
169
+ readonly deltas: ReadonlyArray<{
170
+ readonly revision: number;
171
+ readonly emittedAt: number;
172
+ readonly patch: RowPatch;
173
+ }>;
174
+ /** Redirect this to the new stream to go live on the SAME revision line. */
175
+ readonly sink: ResumeSink;
176
+ readonly unsubscribe: () => void;
177
+ }
178
+
154
179
  export declare interface AggregateContext {
155
180
  /** Read-side store for source queries. Note: the build function
156
181
  * runs OUTSIDE any per-request subject, so the auto-stamping a
@@ -808,6 +833,15 @@ export declare interface AnalyticsTopEntry {
808
833
  readonly value: number;
809
834
  }
810
835
 
836
+ /**
837
+ * Answer a `resumeFrom` for this identity. `fromRevision` is the last
838
+ * revision the CLIENT materialised. Only revisions inside
839
+ * [baseRevision, headRevision] chain; everything else — unknown key, aged
840
+ * ring, a revision the ring no longer covers, a revision AHEAD of the head
841
+ * (a client from a different life) — is a snapshot.
842
+ */
843
+ export declare const answerResume: (identity: ResumeIdentity, fromRevision: number, now?: number, tunables?: ResumeTunables) => ResumeAnswer;
844
+
811
845
  declare type AnyRow = Record<string, unknown>;
812
846
 
813
847
  declare type AnyRow_2 = Record<string, unknown>;
@@ -990,6 +1024,10 @@ export declare interface AppContext {
990
1024
  * `ctx.access.has(scope)` / `yield* ctx.access.require(scope)` instead of
991
1025
  * reaching into `ctx.request.subject.scopes`. Always present. */
992
1026
  readonly access: AppAccess;
1027
+ /** Typed server-side field validation — `yield* ctx.validation.fail('email',
1028
+ * 'validation.emailTaken')` routes to the form field that submitted it.
1029
+ * Always present. */
1030
+ readonly validation: AppValidation;
993
1031
  /** Cache — always present (memory backend by default). Use
994
1032
  * `ctx.cache.wrap(key, { ttlMs, tags }, () => expensive())` to cache
995
1033
  * derived work; tagged entries auto-invalidate when a mutation writes a
@@ -1074,6 +1112,28 @@ export declare interface AppContext {
1074
1112
  */
1075
1113
  export declare const applyConnectionCredential: (clientId: number, headers: Record<string, string | undefined>) => Record<string, string | undefined>;
1076
1114
 
1115
+ /**
1116
+ * Reconcile a mutation target's declared junction `relations:` against the
1117
+ * input — INSIDE the mutation's transaction, through `ctx.store`, so the
1118
+ * links ride the same undo capture, rule evaluation and rollback as the
1119
+ * mutation's own writes.
1120
+ *
1121
+ * Semantics that must not drift:
1122
+ * - an ABSENT input field touches nothing (absent ≠ empty);
1123
+ * - an empty array is the explicit clear;
1124
+ * - a non-array value is a refusal naming the field — silently ignoring it
1125
+ * would read as "saved" while the links stayed stale;
1126
+ * - the row id is `output.id`, else `input.id`; with neither the declaration
1127
+ * is unusable and says so.
1128
+ */
1129
+ export declare const applyDeclaredRelations: (params: {
1130
+ readonly store: RelationLinksStore;
1131
+ readonly target: MutationLike["descriptor"]["target"];
1132
+ readonly procedure: string;
1133
+ readonly input: unknown;
1134
+ readonly output: unknown;
1135
+ }) => Promise<void>;
1136
+
1077
1137
  /**
1078
1138
  * Pure helper backing `ctx.store.applyDefined`. Picks the listed keys from
1079
1139
  * `input` whose value is not `undefined`, so a PATCH sets exactly the fields
@@ -1220,6 +1280,35 @@ export declare interface AppSupervisor {
1220
1280
  stop: () => Promise<void>;
1221
1281
  }
1222
1282
 
1283
+ /** Typed server-side FIELD validation — the executor half of form error
1284
+ * routing. Each helper fails the procedure with a wire `ValidationError`
1285
+ * (auto-merged into every mutation's and action's error union), which the
1286
+ * form binding routes to `errors[field]` instead of `submitError`. */
1287
+ export declare interface AppValidation {
1288
+ /** Fail with ONE field error. `message` is a message id
1289
+ * (`'validation.emailTaken'`) resolved by the client's validation catalog.
1290
+ *
1291
+ * if (await emailTaken(input.email)) {
1292
+ * return yield* ctx.validation.fail('email', 'validation.emailTaken')
1293
+ * }
1294
+ */
1295
+ readonly fail: (field: string, message: string, params?: Record<string, unknown>) => Effect.Effect<never, ValidationError>;
1296
+ /** Fail with several field errors in one round trip. Refuses an empty
1297
+ * list — a failure with nothing to show is a bug at the call site. */
1298
+ readonly failAll: (issues: ReadonlyArray<{
1299
+ readonly field: string;
1300
+ readonly message: string;
1301
+ readonly params?: Record<string, unknown>;
1302
+ }>) => Effect.Effect<never, ValidationErrors>;
1303
+ /** Guard form: succeed when `condition` holds, else fail the field. */
1304
+ readonly require: (condition: boolean, field: string, message: string, params?: Record<string, unknown>) => Effect.Effect<void, ValidationError>;
1305
+ }
1306
+
1307
+ /** The ONE `ctx.validation` value. Pure and subject-free, so a single shared
1308
+ * instance serves every context — request, schedule, workflow, test — and
1309
+ * the builders cannot each assemble a drifting copy. */
1310
+ export declare const appValidation: AppValidation;
1311
+
1223
1312
  /** Effect-style guard for handlers — throws the typed {@link AccessDenied} on a
1224
1313
  * deny so it surfaces to the client typed. */
1225
1314
  export declare const assertCan: (subject: RebacSubject, action: string, resource: RebacResource, deps: CanDeps) => void;
@@ -1623,7 +1712,14 @@ reauthorize?: (subject: Subject) => () => Promise<unknown>,
1623
1712
  */
1624
1713
  refilter?: (subject: Subject) => () => Promise<RowFilterScope>) => Stream.Stream<SubscriptionEvent<T>, never, SubjectService | ConnectionInfo>;
1625
1714
 
1626
- export declare const bindSubscriptionUntyped: (descriptor: QueryDescriptor, dispatcher: Dispatcher, context: RuntimeContext, indexHint: MatcherIndexHint | undefined, label: string, cacheBinding: SnapshotCacheBinding | undefined, reauthorize: (() => Promise<unknown>) | undefined, refilter?: (() => Promise<RowFilterScope>) | undefined) => Stream.Stream<SubscriptionEvent<ReadonlyArray<Row>>, never, never>;
1715
+ export declare const bindSubscriptionUntyped: (descriptor: QueryDescriptor, dispatcher: Dispatcher, context: RuntimeContext, indexHint: MatcherIndexHint | undefined, label: string, cacheBinding: SnapshotCacheBinding | undefined, reauthorize: (() => Promise<unknown>) | undefined, refilter?: (() => Promise<RowFilterScope>) | undefined, backpressure?: {
1716
+ readonly maxBufferedBytes: number;
1717
+ readonly overrunAfterMs: number;
1718
+ readonly oversizedEventBytes: number;
1719
+ }, resume?: {
1720
+ readonly input: unknown;
1721
+ readonly fromRevision?: number;
1722
+ }) => Stream.Stream<SubscriptionEvent<ReadonlyArray<Row>>, never, never>;
1627
1723
 
1628
1724
  export declare interface BrandedScheduleDefinition extends ScheduleDefinition {
1629
1725
  readonly [SCHEDULE_BRAND]: true;
@@ -1760,6 +1856,8 @@ export declare interface CandidateShape {
1760
1856
  readonly windowed?: boolean;
1761
1857
  }
1762
1858
 
1859
+ export declare const canonicalInputKey: (input: unknown) => string;
1860
+
1763
1861
  /* Excluded from this release type: canonicalize */
1764
1862
 
1765
1863
  /**
@@ -2542,6 +2640,8 @@ export declare const counter: (name: string, description?: string) => Metric.Met
2542
2640
  */
2543
2641
  export declare const countRunningWorkflows: (store: DataStore) => Promise<number>;
2544
2642
 
2643
+ export declare const crdtCompactMaxBytes: () => number;
2644
+
2545
2645
  /**
2546
2646
  * Every firing instant of `def` in `(from, to]`, oldest first, bounded by
2547
2647
  * `cap + 1` entries.
@@ -2819,6 +2919,8 @@ export declare const DEFAULT_CLAIM_BUCKET_MS = 60000;
2819
2919
 
2820
2920
  export declare const DEFAULT_COMPRESSION_MIN_BYTES = 1024;
2821
2921
 
2922
+ export declare const DEFAULT_CRDT_COMPACT_MAX_BYTES: number;
2923
+
2822
2924
  /**
2823
2925
  * How many subscribers one change event is delivered to CONCURRENTLY.
2824
2926
  *
@@ -2857,6 +2959,10 @@ export declare const DEFAULT_RAW_READ_TRACKING_LIMIT = 32;
2857
2959
 
2858
2960
  export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
2859
2961
 
2962
+ export declare const DEFAULT_RESUME_MAX_DELTAS = 256;
2963
+
2964
+ export declare const DEFAULT_RESUME_WINDOW_MS = 60000;
2965
+
2860
2966
  /**
2861
2967
  * Retry policy applied to `load` when a filter does not specify one: three
2862
2968
  * attempts total (the initial call plus two retries), backing off exponentially
@@ -2869,6 +2975,14 @@ export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
2869
2975
  */
2870
2976
  export declare const DEFAULT_ROW_FILTER_RETRY: Schedule.Schedule<unknown, unknown>;
2871
2977
 
2978
+ /** Defaults, overridable via `app.config.ts` `reactive.socket.*` (+ env) —
2979
+ * resolved by the boot paths' tunables resolver. */
2980
+ export declare const DEFAULT_SOCKET_MAX_BUFFERED_BYTES = 1048576;
2981
+
2982
+ export declare const DEFAULT_SOCKET_OVERRUN_AFTER_MS = 10000;
2983
+
2984
+ export declare const DEFAULT_SOCKET_OVERSIZED_EVENT_BYTES = 262144;
2985
+
2872
2986
  /**
2873
2987
  * The message of an UNDECLARED throw, bounded, with nothing else attached.
2874
2988
  *
@@ -3074,6 +3188,17 @@ export declare const deliveredRowCount: (value: unknown) => number;
3074
3188
  */
3075
3189
  export declare const deriveKey: (passphrase: string) => Buffer;
3076
3190
 
3191
+ /**
3192
+ * Detach a live subscription for possible resume. The sink is redirected to
3193
+ * ring-recording only; after the window the subscription is truly
3194
+ * unsubscribed. If an entry already sits under this identity (an older
3195
+ * incarnation), it is expired first — one identity, one detached entry.
3196
+ */
3197
+ export declare const detachForResume: (identity: ResumeIdentity, handle: {
3198
+ readonly sink: ResumeSink;
3199
+ readonly unsubscribe: () => void;
3200
+ }, tunables?: ResumeTunables, now?: () => number) => void;
3201
+
3077
3202
  export { DialectReplicationAdapter }
3078
3203
 
3079
3204
  /** Field-level diff for one change. Insert → every field `undefined→value`;
@@ -3180,7 +3305,12 @@ export declare class Dispatcher {
3180
3305
  /** Re-run the query's `guards:` before every delivery — see
3181
3306
  * `ActiveSubscription.reauthorize`. Omitted for unguarded queries. */
3182
3307
  reauthorize?: () => Promise<unknown>,
3183
- /** Re-resolve row visibility per delivery — see `ActiveSubscription.refilter`. */
3308
+ /** Re-resolve row visibility per delivery — see `ActiveSubscription.refilter`.
3309
+ *
3310
+ * Optional ONLY in the sense that an app with no registered row filter has
3311
+ * nothing to resolve; it is not the caller's choice. Omitting it while a
3312
+ * filter IS registered throws below, because the read would then ignore the
3313
+ * filter entirely. Build it with `makeDefaultRefilter(subject)`. */
3184
3314
  refilter?: () => Promise<RowFilterScope>): Promise<() => void>;
3185
3315
  /**
3186
3316
  * Tear a subscription down because it can no longer be served CORRECTLY
@@ -3254,6 +3384,10 @@ export declare class Dispatcher {
3254
3384
 
3255
3385
  export declare interface DispatcherDependencies {
3256
3386
  readonly store: DataStore;
3387
+ /** CRDT columns per table (schemaRegistry.crdtColumns). Feeds the
3388
+ * downstream lane: crdt cells diff as incremental mergeCells ops.
3389
+ * Absent → no crdt tables (tests, embedders) — plain full-value diffs. */
3390
+ readonly crdtColumnsOf?: (table: string) => ReadonlySet<string>;
3257
3391
  /** Optional sink for per-delivery timing. `voltro dev` wires this into
3258
3392
  * the trace buffer so the dashboards show each data transfer's latency.
3259
3393
  * No-op when absent (tests, embedders that don't trace). */
@@ -3489,6 +3623,8 @@ export declare const evaluateTouchedRules: (params: {
3489
3623
  readonly subject: unknown;
3490
3624
  }) => Promise<void>;
3491
3625
 
3626
+ declare type Event_2 = SubscriptionEvent<ReadonlyArray<Row>>;
3627
+
3492
3628
  /**
3493
3629
  * How many deliveries one client may fall behind before the oldest are dropped.
3494
3630
  *
@@ -4411,6 +4547,14 @@ export declare interface FluentStore extends Omit<MutationStore, 'update' | 'del
4411
4547
  * await ctx.store.links('post_tags', { postId: post.id }).set(tagIds)
4412
4548
  */
4413
4549
  links(junctionTable: string, anchor: Readonly<Record<string, string>>): JunctionLinks;
4550
+ /**
4551
+ * `links(...)` with the anchor COLUMN derived from the anchored TABLE: the
4552
+ * junction's reference column that points at `anchorTable` becomes the
4553
+ * source. Exactly one column may qualify — a self-junction (both columns
4554
+ * referencing the same table) is refused with the fix, never guessed.
4555
+ * This is what a mutation target's declared `relations:` resolves through.
4556
+ */
4557
+ relationLinks(junctionTable: string, anchorTable: string, anchorId: string): JunctionLinks;
4414
4558
  /**
4415
4559
  * Execute a query descriptor and return the matching rows — TYPED.
4416
4560
  *
@@ -5417,6 +5561,25 @@ export declare const makeCoordinatedScheduler: (deps: CoordinatedScheduleDeps) =
5417
5561
  */
5418
5562
  export declare const makeDataLoader: (deps: LoaderDeps) => DataLoader;
5419
5563
 
5564
+ /**
5565
+ * The per-delivery refilter EVERY subscribing transport must hand the
5566
+ * dispatcher — the one that re-resolves this subject's visibility before each
5567
+ * delivery instead of freezing it at subscribe.
5568
+ *
5569
+ * It lives here, beside the registration it reads, because it was a private
5570
+ * helper of the WebSocket entrypoint and the transports that did not import it
5571
+ * did not filter at all. `dispatcher.subscribe` resolves visibility itself and
5572
+ * treats an absent refilter as "this app has no filter", so a transport that
5573
+ * simply omitted the argument read the UNFILTERED descriptor — on the initial
5574
+ * snapshot and on every delta. The SSE and gRPC projections omitted it.
5575
+ *
5576
+ * Returning `undefined` when no filter is registered is the fast path, not an
5577
+ * opt-out: it is what keeps a filterless app free of a per-delivery await, and
5578
+ * `dispatcher.subscribe` refuses the ambiguous case (nothing passed WHILE a
5579
+ * filter is registered) rather than reading it as this one.
5580
+ */
5581
+ export declare const makeDefaultRefilter: (subject: Subject, onError?: (error: unknown) => void) => (() => Promise<RowFilterScope>) | undefined;
5582
+
5420
5583
  /** `__voltro.connections.disconnect` — forget the caller's credential. */
5421
5584
  export declare const makeDisconnectConnectionExecutor: (deps: ConnectionBuiltinDeps) => (input: {
5422
5585
  readonly connectionId: string;
@@ -5626,6 +5789,8 @@ export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (
5626
5789
  readonly ok: boolean;
5627
5790
  }>;
5628
5791
 
5792
+ export declare const makeSubscriptionOutbox: (options: SubscriptionBackpressureOptions) => SubscriptionOutbox;
5793
+
5629
5794
  /**
5630
5795
  * The factory the serve entrypoints inject into `makeMutationRunner`'s
5631
5796
  * `undoCapture` dep. Collects changes from the wrapped tx; `persist` writes ONE
@@ -5633,7 +5798,7 @@ export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (
5633
5798
  * mutation's transaction. The changes are JSON-serialized into a TEXT column —
5634
5799
  * dialect-uniform, no json codec on the path.
5635
5800
  */
5636
- export declare const makeUndoCapture: (rawTx: DataStore) => {
5801
+ export declare const makeUndoCapture: (rawTx: DataStore, crdtColumnsOf?: (table: string) => ReadonlySet<string>) => {
5637
5802
  tx: DataStore;
5638
5803
  persist: (store: unknown, meta: {
5639
5804
  readonly tag: string;
@@ -5922,8 +6087,10 @@ export declare interface MutationLike {
5922
6087
  readonly requiresApproval?: AnyApprovalPolicy | undefined;
5923
6088
  readonly target?: {
5924
6089
  readonly table: string;
6090
+ readonly relations?: Readonly<Record<string, string>> | undefined;
5925
6091
  } | ReadonlyArray<{
5926
6092
  readonly table: string;
6093
+ readonly relations?: Readonly<Record<string, string>> | undefined;
5927
6094
  }> | undefined;
5928
6095
  };
5929
6096
  executor(input: unknown, ctx: unknown): unknown;
@@ -6629,6 +6796,16 @@ export declare const publishEvent: <Name extends string, Key extends Schema.Sche
6629
6796
  readonly deferred: boolean;
6630
6797
  }, EventPublishError>;
6631
6798
 
6799
+ /**
6800
+ * A Layer that publishes whatever tracer is in scope when it is built.
6801
+ *
6802
+ * Provided immediately INSIDE the tracer layer at boot, so it captures the
6803
+ * configured tracer rather than the default one. With tracing off it captures
6804
+ * Effect's no-op tracer, which is the correct answer — detached work then
6805
+ * behaves exactly as in-request work does.
6806
+ */
6807
+ export declare const publishServerTracerLayer: Layer.Layer<never, never, never>;
6808
+
6632
6809
  /**
6633
6810
  * The two scopes, composed once, for every path that finalises a descriptor.
6634
6811
  *
@@ -6690,9 +6867,23 @@ export declare interface QueryProducerDeps<D> {
6690
6867
  }
6691
6868
 
6692
6869
  export declare interface QuerySubscriberDeps<D> extends QueryProducerDeps<D> {
6693
- /** Open a dispatcher subscription for a finalized descriptor. */
6694
- readonly subscribeDescriptor: (descriptor: D, emit: (event: unknown) => void, context: ServeRequestContext) => Promise<() => void>;
6695
- /** Open a dispatcher subscription for a COMPUTED query (re-runs on source change). */
6870
+ /**
6871
+ * Open a dispatcher subscription for a finalized descriptor.
6872
+ *
6873
+ * `reauthorize` and `refilter` are REQUIRED parameters of this callback, not
6874
+ * optional extras, and the requirement is the guard: they used to be
6875
+ * `dispatcher.subscribe`'s trailing optional arguments, this callback simply
6876
+ * did not pass them, and the dispatcher reads their absence as "this query is
6877
+ * unguarded and this app has no row filter". So the SSE and gRPC projections
6878
+ * re-ran no guard and read the UNFILTERED descriptor — initial snapshot and
6879
+ * every delta — while the WebSocket path (which goes through
6880
+ * `bindSubscription`) did both. Threading them through the signature makes the
6881
+ * next transport unable to repeat that by omission.
6882
+ */
6883
+ readonly subscribeDescriptor: (descriptor: D, emit: (event: unknown) => void, context: ServeRequestContext, reauthorize: (() => Promise<unknown>) | undefined, refilter: (() => Promise<RowFilterScope>) | undefined) => Promise<() => void>;
6884
+ /** Open a dispatcher subscription for a COMPUTED query (re-runs on source change).
6885
+ * A computed query re-runs its HANDLER per change, so guards and the row
6886
+ * filter are re-applied by that re-run — it needs no separate pair. */
6696
6887
  readonly subscribeComputed: (computed: ComputedQuery, emit: (event: unknown) => void, context: ServeRequestContext) => Promise<() => void>;
6697
6888
  }
6698
6889
 
@@ -6869,6 +7060,25 @@ export declare interface ReactiveConfigInput {
6869
7060
  * warning to name a later one.
6870
7061
  */
6871
7062
  readonly rawReadTrackingLimit?: number;
7063
+ /**
7064
+ * Subscription socket backpressure (plan 01 P1). A slow or dead consumer no
7065
+ * longer retains every event: updates COALESCE onto the newest state while
7066
+ * the socket is blocked, and a consumer persistently over the buffer
7067
+ * threshold is closed with a typed `SubscriptionOverrun` (it re-subscribes
7068
+ * for a fresh snapshot). Resolved by the CLI's `reactiveTunables.ts`; env
7069
+ * overrides win: `VOLTRO_REACTIVE_MAX_BUFFERED_BYTES` /
7070
+ * `VOLTRO_REACTIVE_OVERRUN_AFTER_MS` / `VOLTRO_REACTIVE_OVERSIZED_EVENT_BYTES`.
7071
+ */
7072
+ readonly socket?: {
7073
+ /** Estimated pending bytes per subscription above which a persistently
7074
+ * blocked consumer is overrun-closed. Default 1 MiB. */
7075
+ readonly maxBufferedBytes?: number;
7076
+ /** How long the pending state must stay over the threshold before the
7077
+ * terminal close. Default 10_000 ms. */
7078
+ readonly overrunAfterMs?: number;
7079
+ /** Per-event WARN threshold (telemetry, NOT a cap). Default 256 KiB. */
7080
+ readonly oversizedEventBytes?: number;
7081
+ };
6872
7082
  }
6873
7083
 
6874
7084
  export declare interface ReactiveEnv {
@@ -6884,6 +7094,16 @@ export declare type ReactiveReturn<D extends ExecutorDescriptor> = D extends {
6884
7094
  readonly descriptor: QueryDescriptor;
6885
7095
  } : never;
6886
7096
 
7097
+ export declare interface ReactiveSocketTunables {
7098
+ readonly maxBufferedBytes: number;
7099
+ readonly overrunAfterMs: number;
7100
+ readonly oversizedEventBytes: number;
7101
+ }
7102
+
7103
+ /** The resolved tunables, or the defaults when no boot path has set any
7104
+ * (unit tests, a bare library use). */
7105
+ export declare const reactiveSocketTunables: () => ReactiveSocketTunables;
7106
+
6887
7107
  export declare interface ReadClassification {
6888
7108
  /** Tables whose ROWS can reach the caller: the queried table and its joins. */
6889
7109
  readonly composed: ReadonlySet<string>;
@@ -6970,6 +7190,8 @@ export declare interface RebacSubject {
6970
7190
  readonly scopes?: ReadonlyArray<string>;
6971
7191
  }
6972
7192
 
7193
+ export declare const recordCoalesced: (label: string, collapsed: number) => void;
7194
+
6973
7195
  export declare const recordCredentialExpiry: (clientId: number, exp: number | undefined) => void;
6974
7196
 
6975
7197
  /** What the framework emit-seams (servePipeline / rpcServer / plugin wrappers)
@@ -6996,6 +7218,15 @@ export declare const recordEventPublished: (event: string) => void;
6996
7218
  /** Subscriber attached (+1) or detached (-1). */
6997
7219
  export declare const recordEventSubscribers: (event: string, delta: number) => void;
6998
7220
 
7221
+ /**
7222
+ * Record one emitted event into the identity's ring. Snapshots RESET the ring
7223
+ * (the snapshot's revision becomes the new replay base — a client holding an
7224
+ * older revision is outside the chain); deltas append. An error event drops
7225
+ * the ring: whatever state the client holds, the safe answer after an error
7226
+ * is a fresh snapshot.
7227
+ */
7228
+ export declare const recordForResume: (identity: ResumeIdentity, event: SubscriptionEvent<unknown>, now?: number, tunables?: ResumeTunables) => void;
7229
+
6999
7230
  /**
7000
7231
  * A store that notes every read and changes nothing else.
7001
7232
  *
@@ -7006,6 +7237,10 @@ export declare const recordEventSubscribers: (event: string, delta: number) => v
7006
7237
  */
7007
7238
  export declare const recordingStore: (store: DataStore) => DataStore;
7008
7239
 
7240
+ export declare const recordOverrun: (label: string) => void;
7241
+
7242
+ export declare const recordOversized: (label: string) => void;
7243
+
7009
7244
  /**
7010
7245
  * Record a raw read, opening a scope for the rest of this execution context if
7011
7246
  * none is open yet.
@@ -7296,6 +7531,13 @@ export declare interface RegistryTableLike {
7296
7531
  * request was. */
7297
7532
  export declare const rehydrateGuards: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<AnyGuardSpec>;
7298
7533
 
7534
+ /** The store slice `applyDeclaredRelations` needs. */
7535
+ declare interface RelationLinksStore {
7536
+ relationLinks(junctionTable: string, anchorTable: string, anchorId: string): {
7537
+ set(targetIds: ReadonlyArray<string>): Promise<unknown>;
7538
+ };
7539
+ }
7540
+
7299
7541
  /** A relationship tuple — `subjectId` has `relation` on `<resourceType>:<resourceId>`.
7300
7542
  * In practice these are rows in a relation table; the engine takes them as data. */
7301
7543
  export declare interface RelationTuple {
@@ -7481,15 +7723,24 @@ export declare const resetComputedQueryCacheWarnings: () => void;
7481
7723
  /** Test/dev-only — clear ALL per-connection state. Don't call from app code. */
7482
7724
  export declare const _resetConnectionCredentialsForTest: () => void;
7483
7725
 
7726
+ /** Test seam — expire everything immediately. */
7727
+ export declare const resetDetachedForTest: () => void;
7728
+
7484
7729
  /** Test seams for the tag-cache guard — the cache is module-private otherwise. */
7485
7730
  export declare const resetEventMetricTagCacheForTests: () => void;
7486
7731
 
7487
7732
  /** Drop everything recorded. For tests; not called by the runtime. */
7488
7733
  export declare const resetObservedGraph: () => void;
7489
7734
 
7735
+ /** Test seam. */
7736
+ export declare const resetResumeRingsForTest: () => void;
7737
+
7490
7738
  /** Reset to the env default (tests). */
7491
7739
  export declare const resetSecretsBackend: () => void;
7492
7740
 
7741
+ /** Test seam. */
7742
+ export declare const resetSubscriptionSocketMetrics: () => void;
7743
+
7493
7744
  /**
7494
7745
  * The client address to rate-limit, geo-block and audit by.
7495
7746
  *
@@ -7808,6 +8059,46 @@ export declare const restrictingReadsEffect: <A, E, R>(effect: Effect.Effect<A,
7808
8059
  /** The full ordered list of result variants for a definition, holdout last. */
7809
8060
  export declare const resultVariantNames: (def: ExperimentDefinition) => ReadonlyArray<string>;
7810
8061
 
8062
+ export declare type ResumeAnswer =
8063
+ /** The client's revision chains: replay these deltas in order. */
8064
+ {
8065
+ readonly kind: 'deltas';
8066
+ readonly deltas: ReadonlyArray<RingDelta>;
8067
+ }
8068
+ /** Outside the window / unknown identity / gap — send a snapshot,
8069
+ * `resumed: false`. */
8070
+ | {
8071
+ readonly kind: 'snapshot';
8072
+ };
8073
+
8074
+ /**
8075
+ * The identity a ring is recorded under — and the identity a resume must
8076
+ * present to read it back. `subjectId`/`tenantId` are part of the KEY, so a
8077
+ * login/logout/tenant-switch between disconnect and resume simply misses the
8078
+ * ring and falls back to a snapshot (`resumed: false`) — the blank-on-auth
8079
+ * doctrine, enforced structurally rather than checked.
8080
+ */
8081
+ export declare interface ResumeIdentity {
8082
+ readonly label: string;
8083
+ /** Canonicalised query input (JSON with sorted keys). */
8084
+ readonly inputKey: string;
8085
+ readonly subjectId: string;
8086
+ readonly tenantId: string;
8087
+ }
8088
+
8089
+ export declare const resumeKeyOf: (identity: ResumeIdentity) => string;
8090
+
8091
+ export declare interface ResumeSink {
8092
+ current: (event: SubscriptionEvent<unknown>) => void;
8093
+ }
8094
+
8095
+ export declare interface ResumeTunables {
8096
+ readonly windowMs: number;
8097
+ readonly maxDeltas: number;
8098
+ }
8099
+
8100
+ export declare const resumeTunables: () => ResumeTunables;
8101
+
7811
8102
  export { RetentionConflict }
7812
8103
 
7813
8104
  export { RetentionSource }
@@ -7882,6 +8173,12 @@ export declare const revokedIds: (before: ReadonlyArray<{
7882
8173
  readonly id: string;
7883
8174
  }>) => ReadonlyArray<string>;
7884
8175
 
8176
+ declare interface RingDelta {
8177
+ readonly revision: number;
8178
+ readonly emittedAt: number;
8179
+ readonly patch: RowPatch;
8180
+ }
8181
+
7885
8182
  /**
7886
8183
  * Live traffic the router fronts — read by idle detection (the orchestrator
7887
8184
  * can't see the app's HTTP/WS load directly when it's scaled to zero, but
@@ -7974,8 +8271,71 @@ export declare interface RowFilter<Ctx = unknown> {
7974
8271
  * the handler still carries the check the filter was meant to replace.
7975
8272
  */
7976
8273
  readonly onLoadError?: 'fail' | 'deny';
8274
+ /**
8275
+ * The ONLY tables this filter may narrow. Optional; declaring it buys
8276
+ * delta-resume back for every subscription whose source is not in the set.
8277
+ *
8278
+ * WHY IT EXISTS. A subscription whose row set is re-resolved per delivery
8279
+ * must not replay deltas on reconnect — the answer can change while the
8280
+ * socket is down (a membership ends) and a wrong replay leaks rows. But the
8281
+ * question the framework could ask was only "is a filter registered at
8282
+ * all?", so ONE registration disabled delta-resume for the whole process. A
8283
+ * deployment measured a filter narrowing 4 tables costing the feature on all
8284
+ * 173 of their query descriptors, 55 of whose source tables it never touches.
8285
+ *
8286
+ * WHY A DECLARATION RATHER THAN A PROBE. Resolving the scope at subscribe
8287
+ * and treating `predicate(ctx, source) === undefined` as safe is cheaper and
8288
+ * unsound: the predicate is a function of freshly loaded context, so a table
8289
+ * it does not narrow now may be narrowed on the next delivery — which is the
8290
+ * entire reason the refilter is per-delivery. A static list is a promise
8291
+ * about every future resolution.
8292
+ *
8293
+ * IT IS VERIFIED, not trusted. Returning a predicate for a table outside
8294
+ * this set raises {@link RowFilterDeclarationViolated} at the read that did
8295
+ * it — the request fails and a subscription is revoked, rather than serving
8296
+ * rows under a resume grant the declaration no longer earns. An undeclared
8297
+ * filter (this field absent) keeps the conservative behaviour: no resume
8298
+ * anywhere, no verification.
8299
+ *
8300
+ * setRowFilter({
8301
+ * load, predicate,
8302
+ * tables: ['bookmarks', 'recentSearches', 'todoSchedules', 'todoTags'],
8303
+ * })
8304
+ */
8305
+ readonly tables?: ReadonlyArray<string>;
8306
+ }
8307
+
8308
+ /**
8309
+ * A row filter narrowed a table its `tables:` declaration does not list.
8310
+ *
8311
+ * Fail-closed by construction: the declaration is what the framework hands
8312
+ * delta-resume, so a filter that quietly narrows beyond it would have rings
8313
+ * recorded for a table whose visibility CAN change — the leak the exclusion
8314
+ * exists to prevent. Raised at the read that violated it, naming the table and
8315
+ * the fix, rather than degrading to an unfiltered or empty answer.
8316
+ */
8317
+ export declare class RowFilterDeclarationViolated extends RowFilterDeclarationViolated_base {
7977
8318
  }
7978
8319
 
8320
+ declare const RowFilterDeclarationViolated_base: Schema.TaggedErrorClass<RowFilterDeclarationViolated, "RowFilterDeclarationViolated", {
8321
+ readonly _tag: Schema.tag<"RowFilterDeclarationViolated">;
8322
+ } & {
8323
+ /** The table the predicate narrowed. */
8324
+ table: typeof Schema.String;
8325
+ /** The declared set, for the message. */
8326
+ declared: Schema.Array$<typeof Schema.String>;
8327
+ }>;
8328
+
8329
+ /**
8330
+ * Could the registered filter narrow ANY of `tables`?
8331
+ *
8332
+ * The question delta-resume asks before recording a ring. Answers `true`
8333
+ * whenever it cannot prove otherwise — no declaration means the framework does
8334
+ * not know which tables the predicate may reach, and "unknown" must read as
8335
+ * "yes" or the exclusion stops protecting anything.
8336
+ */
8337
+ export declare const rowFilterMayNarrow: (tables: ReadonlyArray<string>) => boolean;
8338
+
7979
8339
  /**
7980
8340
  * Is a row filter registered right now — asked by a path about to serve an
7981
8341
  * UNFILTERED read.
@@ -8097,9 +8457,16 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
8097
8457
  * http app, so it fires on EVERY request — inspect, webhooks, AND the
8098
8458
  * rpc websocket upgrade — before auth/routing. A non-null return
8099
8459
  * short-circuits with that response (e.g. a rate-limit 429 / geo-block
8100
- * 451). Absent → no wrapper. Fail-OPEN: a throwing interceptor is
8101
- * swallowed and the request continues, so a buggy shield can't take
8102
- * the whole listener down.
8460
+ * 451). Absent → no wrapper.
8461
+ *
8462
+ * FAIL-CLOSED: a throwing interceptor answers 500 and logs, it does NOT
8463
+ * let the request through. The composed chain is where security gates
8464
+ * live (an IP shield, a tenant fence — anything a plugin mounts
8465
+ * pre-auth), and a gate that crashes open has silently stopped
8466
+ * guarding while still reading as installed. An interceptor that must
8467
+ * not take the listener down with its own dependency owns that
8468
+ * decision itself — plugin-ratelimit's httpShield catches its store
8469
+ * failure and degrades to "unlimited, loudly" rather than throwing.
8103
8470
  */
8104
8471
  readonly pluginHttpInterceptor?: HttpRequestInterceptor;
8105
8472
  /**
@@ -8968,6 +9335,12 @@ export declare interface ServeRequestContext {
8968
9335
  readonly subject: unknown;
8969
9336
  readonly traceId: string;
8970
9337
  readonly spanId?: string;
9338
+ /**
9339
+ * Caller-side cancellation (the gRPC deadline / client-cancel path).
9340
+ * Honoured at the ONE place the executor effect runs to a promise, so an
9341
+ * exceeded deadline INTERRUPTS the server work — not merely the response.
9342
+ */
9343
+ readonly signal?: AbortSignal;
8971
9344
  /**
8972
9345
  * Row-level visibility for this request, resolved by `withRowFilter` and read
8973
9346
  * by the context builder when it wraps the store.
@@ -9025,13 +9398,26 @@ export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown,
9025
9398
  readonly target?: ReadonlyArray<string>;
9026
9399
  }) => Effect.Effect<unknown, unknown, never>;
9027
9400
 
9401
+ /** The published tracer, or `undefined` when tracing is off / no server booted. */
9402
+ export declare const serverTracer: () => Tracer.Tracer | undefined;
9403
+
9404
+ export declare const setBufferedBytes: (label: string, bytes: number) => void;
9405
+
9028
9406
  /** Register the process-wide connection resolver (or clear with `undefined`).
9029
9407
  * Called by the CLI at boot; tests call it directly. */
9030
9408
  export declare const setConnectionResolver: (resolver: ConnectionResolver | undefined) => void;
9031
9409
 
9410
+ /** Boot/test override — the slot `wireCrdtTunables` writes. `null` restores
9411
+ * env/default resolution. */
9412
+ export declare const setCrdtCompactMaxBytes: (maxBytes: number | null) => void;
9413
+
9032
9414
  /** Register the field cipher (or clear with `undefined`). */
9033
9415
  export declare const setFieldCipher: (cipher: FieldCipher | undefined) => void;
9034
9416
 
9417
+ export declare const setReactiveSocketTunables: (tunables: ReactiveSocketTunables | null) => void;
9418
+
9419
+ export declare const setResumeTunables: (tunables: ResumeTunables | null) => void;
9420
+
9035
9421
  /**
9036
9422
  * Boot-path wiring. `null` clears (shutdown). Registering `null` vs never
9037
9423
  * registering are deliberately the SAME state — a call before boot wiring and
@@ -9048,6 +9434,9 @@ export declare const setRowFilter: <Ctx>(filter: RowFilter<Ctx> | undefined) =>
9048
9434
  /** Install the process-wide secrets backend (called once at boot). */
9049
9435
  export declare const setSecretsBackend: (backend: SecretsBackend) => void;
9050
9436
 
9437
+ /** Publish the server's tracer. Called once, from inside the provided scope. */
9438
+ export declare const setServerTracer: (tracer: Tracer.Tracer | undefined) => void;
9439
+
9051
9440
  /** Install the finding sink. `undefined` turns recording back off. */
9052
9441
  export declare const setSourceGapSink: (fn: ((finding: string) => void) | undefined) => void;
9053
9442
 
@@ -9160,7 +9549,10 @@ export declare interface SnapshotCacheBinding {
9160
9549
  readonly swrMs: number | undefined;
9161
9550
  }
9162
9551
 
9163
- /** Snapshot the whole registry, normalized to `MetricSample[]`. */
9552
+ /** Snapshot the whole registry, normalized to `MetricSample[]` — plus the
9553
+ * socket-backpressure counters (`subscriptionSocketMetrics.ts`), appended
9554
+ * HERE so every reader (Prometheus exporter, inspect Metrics panel) sees
9555
+ * one derivation and the two cannot diverge. */
9164
9556
  export declare const snapshotMetrics: Effect.Effect<ReadonlyArray<MetricSample>>;
9165
9557
 
9166
9558
  /** Eager snapshot for sync callers (the inspect endpoint). */
@@ -9425,6 +9817,22 @@ export declare interface SubscriberDefinition {
9425
9817
  readonly handler: SubscribeHandler;
9426
9818
  }
9427
9819
 
9820
+ export declare interface SubscriptionBackpressureOptions {
9821
+ /** Estimated pending bytes above which a persistently-blocked consumer is
9822
+ * overrun-closed. `reactive.socket.maxBufferedBytes` / env override. */
9823
+ readonly maxBufferedBytes: number;
9824
+ /** How long the pending state must stay over the threshold (with the pump
9825
+ * blocked) before the terminal close. */
9826
+ readonly overrunAfterMs: number;
9827
+ /** Per-event WARN threshold — telemetry for oversized snapshots/deltas.
9828
+ * Deliberately NOT a cap: measurability is the goal. */
9829
+ readonly oversizedEventBytes: number;
9830
+ /** Query tag for logs + metrics. */
9831
+ readonly label: string;
9832
+ readonly now?: () => number;
9833
+ readonly warn?: (message: string, fields: Record<string, unknown>) => void;
9834
+ }
9835
+
9428
9836
  /**
9429
9837
  * Timing of ONE subscription delivery (the initial snapshot, or a delta
9430
9838
  * pushed on a change). The duration is the real produce→push latency:
@@ -9460,6 +9868,29 @@ export declare interface SubscriptionDeliveryRecord {
9460
9868
  readonly rowCount: number;
9461
9869
  }
9462
9870
 
9871
+ export declare interface SubscriptionOutbox {
9872
+ /** SYNC push from the dispatcher's emit callback. */
9873
+ readonly push: (event: Event_2) => void;
9874
+ /** Pump side: resolves with the next event to hand into the stream —
9875
+ * coalesced when more than one push landed since the last take — or null
9876
+ * once the outbox is closed and drained. */
9877
+ readonly take: () => Promise<Event_2 | null>;
9878
+ /** True once the overrun policy fired (the error event has been queued;
9879
+ * the stream ends after it drains). */
9880
+ readonly overrun: () => boolean;
9881
+ readonly close: () => void;
9882
+ }
9883
+
9884
+ /** The wire shape of the terminal overrun error — documented in
9885
+ * wire-protocol.md. The client surfaces it as the entry's `error` and
9886
+ * re-subscribes for a fresh snapshot. */
9887
+ export declare interface SubscriptionOverrunError {
9888
+ readonly _tag: 'SubscriptionOverrun';
9889
+ readonly message: string;
9890
+ readonly bufferedBytes: number;
9891
+ readonly maxBufferedBytes: number;
9892
+ }
9893
+
9463
9894
  export declare type SubscriptionRecord = {
9464
9895
  readonly id: string;
9465
9896
  readonly table: string;
@@ -9520,6 +9951,35 @@ export declare interface SubscriptionSnapshot {
9520
9951
  readonly subscriberCount: number;
9521
9952
  }
9522
9953
 
9954
+ /**
9955
+ * The counters as `MetricSample`s — appended by `snapshotMetrics`
9956
+ * (metrics.ts), which is the ONE derivation both the Prometheus exporter and
9957
+ * the inspect Metrics panel read. Same input → the two cannot diverge.
9958
+ */
9959
+ export declare const subscriptionSocketMetricSamples: () => ReadonlyArray< MetricSample>;
9960
+
9961
+ export declare interface SubscriptionSocketMetricsSnapshot {
9962
+ /** Sum + per-label current pending bytes across live subscriptions. */
9963
+ readonly bufferedBytes: {
9964
+ readonly total: number;
9965
+ readonly byLabel: Readonly<Record<string, number>>;
9966
+ };
9967
+ readonly coalescedTotal: {
9968
+ readonly total: number;
9969
+ readonly byLabel: Readonly<Record<string, number>>;
9970
+ };
9971
+ readonly overrunTotal: {
9972
+ readonly total: number;
9973
+ readonly byLabel: Readonly<Record<string, number>>;
9974
+ };
9975
+ readonly oversizedTotal: {
9976
+ readonly total: number;
9977
+ readonly byLabel: Readonly<Record<string, number>>;
9978
+ };
9979
+ }
9980
+
9981
+ export declare const subscriptionSocketMetricsSnapshot: () => SubscriptionSocketMetricsSnapshot;
9982
+
9523
9983
  /** Drop handshake grants past their TTL. Wired into the boot retention sweep;
9524
9984
  * a grant is worthless the moment it expires and keeping them turns a hot
9525
9985
  * table into an audit liability. */
@@ -10390,24 +10850,6 @@ export declare const webhookVerificationOf: (handle: unknown) => WebhookVerifica
10390
10850
  */
10391
10851
  export declare type WhereOp = '=' | '!=' | '<>' | '>' | '>=' | '<' | '<=' | 'in' | 'contains' | 'startsWith' | 'fts';
10392
10852
 
10393
- /**
10394
- * Resolve this request's row-filter scope. A no-op (same object back) when the
10395
- * app registered no filter, so an app that uses none pays nothing.
10396
- *
10397
- * Every runner calls this before building a context. A read path that skipped
10398
- * it would have no row-level security and would look exactly like one that
10399
- * does — which is why it lives in the shared spine rather than in each
10400
- * entrypoint.
10401
- *
10402
- * REJECTS with the raw `RowFilterUnavailable` (via `runProvidedEffect`, not
10403
- * `Effect.runPromise`) when the filter's `onLoadError` is the default `'fail'`
10404
- * and `load` failed every retry. Raw, so the rpc encoder matches it against the
10405
- * descriptor's `error:` schema and the client gets the tag instead of an opaque
10406
- * defect — the same treatment `enforceGuards` gives a `ScopeError`. Proceeding
10407
- * with an unresolved filter is not an option here: the context builder would
10408
- * receive `rowFilter: undefined`, which means "this app registered no filter"
10409
- * and would silently serve the request UNFILTERED.
10410
- */
10411
10853
  export declare const withRowFilter: <R extends ServeRequestContext>(request: R) => Promise<R>;
10412
10854
 
10413
10855
  /**
@@ -10432,6 +10874,16 @@ export declare const withRowFilter: <R extends ServeRequestContext>(request: R)
10432
10874
  */
10433
10875
  export declare const withScopedRequest: <R extends ServeRequestContext, A>(requestContext: R, body: (scoped: R) => A | Effect.Effect<A, unknown, never>) => A | Effect.Effect<A, unknown, never>;
10434
10876
 
10877
+ /**
10878
+ * Run detached work under the server's tracer, so a span it opens is exported
10879
+ * rather than created and dropped.
10880
+ *
10881
+ * A no-op when nothing is published (a unit test, an embedder, a process with no
10882
+ * rpc server) — never a second tracer, never a throw. The alternative to this
10883
+ * function is not "a different tracer", it is silence.
10884
+ */
10885
+ export declare const withServerTracer: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
10886
+
10435
10887
  /**
10436
10888
  * What the gate decided. `passthrough` is the common case — no controls
10437
10889
  * declared — and is distinct from `start` so the facade can skip the commit
@@ -10937,7 +11389,7 @@ export declare interface WorkflowWaitOptions {
10937
11389
  * calls (incl. multi-table within one mutation) are. A mutation that writes via
10938
11390
  * a bulk helper simply produces no undo entry (documented).
10939
11391
  */
10940
- export declare const wrapCaptureStore: (tx: DataStore, collect: (c: ForwardChange) => void) => DataStore;
11392
+ export declare const wrapCaptureStore: (tx: DataStore, collect: (c: ForwardChange) => void, crdtColumnsOf?: (table: string) => ReadonlySet<string>) => DataStore;
10941
11393
 
10942
11394
  /**
10943
11395
  * Wrap a raw `DataStore` with the storage codec only — `.encrypted()` columns