@voltro/runtime 0.63.0 → 0.65.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
@@ -33,10 +33,12 @@ import { EventResumePoint } from '@voltro/protocol';
33
33
  import { EventStreamEvent } from '@voltro/protocol';
34
34
  import { extractDbCause } from '@voltro/database';
35
35
  import { Fiber } from 'effect';
36
+ import { FieldBinding } from '@voltro/database';
36
37
  import { FieldCipher } from '@voltro/database';
37
38
  import * as http from 'node:http';
38
39
  import { HttpClient } from '@effect/platform';
39
40
  import { HttpRequestInterceptor } from '@voltro/protocol';
41
+ import { HttpRouteObserver } from '@voltro/protocol';
40
42
  import { IdempotencyStore } from '@voltro/protocol';
41
43
  import { IdScheme } from '@voltro/database';
42
44
  import { InMemoryMetricExporter } from '@opentelemetry/sdk-metrics';
@@ -55,6 +57,7 @@ import { listRetentions } from '@voltro/database';
55
57
  import { Metric } from 'effect';
56
58
  import { MetricBoundaries } from 'effect';
57
59
  import { MetricReader } from '@opentelemetry/sdk-metrics';
60
+ import { NoRowFound } from '@voltro/protocol';
58
61
  import { PendingApproval } from '@voltro/protocol';
59
62
  import { PluginHttpRoute } from '@voltro/protocol';
60
63
  import { PluginRefOrphanPolicy } from '@voltro/database';
@@ -102,6 +105,7 @@ import { Tracer } from 'effect';
102
105
  import { Unauthenticated } from '@voltro/protocol';
103
106
  import { ValidationError } from '@voltro/protocol';
104
107
  import { ValidationErrors } from '@voltro/protocol';
108
+ import { VersionConflict } from '@voltro/database';
105
109
  import { WebSocketGatewayRoute } from '@voltro/protocol';
106
110
  import { WorkflowParentClosePolicy } from '@voltro/protocol';
107
111
  import { WorkflowRunHandle } from '@voltro/protocol';
@@ -1568,6 +1572,15 @@ export declare interface BeginOAuthResult {
1568
1572
  readonly state: string;
1569
1573
  }
1570
1574
 
1575
+ /** Exported for the backpressure suite — the production caller is `bindSubscription`. */
1576
+ export declare const bindComputedUntyped: (computed: ComputedQuery, dispatcher: Dispatcher, context: RuntimeContext, label: string,
1577
+ /** Tunables override (tests); production reads the process-wide slot. */
1578
+ backpressure?: {
1579
+ readonly maxBufferedBytes: number;
1580
+ readonly overrunAfterMs: number;
1581
+ readonly oversizedEventBytes: number;
1582
+ }) => Stream.Stream<SubscriptionEvent<unknown>, never, never>;
1583
+
1571
1584
  /**
1572
1585
  * Present a new credential on an ALREADY-OPEN connection.
1573
1586
  *
@@ -2070,6 +2083,27 @@ export declare const collectPluginRefRules: (tables: ReadonlyArray<{
2070
2083
  }>;
2071
2084
  }>, registered: ReadonlySet<string>) => ReadonlyArray<PluginRefRule>;
2072
2085
 
2086
+ /**
2087
+ * The soft-delete rules an app's OWN `reference(…, { onSoftDelete })` columns
2088
+ * declare — app→app, where the hard delete is a real foreign key and only the
2089
+ * soft delete needs the runtime.
2090
+ *
2091
+ * Refuses at boot, naming the column, when `'cascade'` points from a table
2092
+ * that cannot be soft-deleted: the engine would `store.delete` the child, the
2093
+ * middleware would HARD-delete it (no `softDelete()` to turn it into an
2094
+ * update), and a restore of the parent would bring back a parent whose
2095
+ * children are gone for good.
2096
+ */
2097
+ export declare const collectReferenceSoftDeleteRules: (tables: ReadonlyArray<{
2098
+ readonly name: string;
2099
+ readonly softDeletable?: boolean;
2100
+ readonly fields?: Readonly<Record<string, {
2101
+ readonly type?: string;
2102
+ readonly references?: () => unknown;
2103
+ readonly refOnSoftDelete?: "cascade" | "setNull";
2104
+ }>>;
2105
+ }>) => ReadonlyArray<PluginRefRule>;
2106
+
2073
2107
  /** Parse + return the compiled `Cron` for a definition. Throws the
2074
2108
  * same way `defineSchedule` validated — callers that already hold a
2075
2109
  * branded definition can trust this won't throw. */
@@ -2916,7 +2950,7 @@ export declare const decideApprovalInvocation: (input: {
2916
2950
  * the old form is already on production disks, and a fix that needs the data
2917
2951
  * rewritten before the app works is an outage with a migration attached.
2918
2952
  */
2919
- export declare const decryptField: (value: string) => string;
2953
+ export declare const decryptField: (value: string, binding?: FieldBinding) => string;
2920
2954
 
2921
2955
  export declare const DEFAULT_ANALYTICS_MIRROR_TUNABLES: AnalyticsMirrorTunables;
2922
2956
 
@@ -2977,6 +3011,9 @@ export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
2977
3011
  * definition nothing keeps in step. */
2978
3012
  export declare const DEFAULT_MAX_RUNTIME_MS: number;
2979
3013
 
3014
+ /** Default replay bound — `mutations.replayAttempts` in `app.config.ts` moves it. */
3015
+ export declare const DEFAULT_MUTATION_REPLAY_ATTEMPTS = 3;
3016
+
2980
3017
  /** See {@link DrainDeps.claimLeaseMs}. */
2981
3018
  export declare const DEFAULT_OUTBOX_CLAIM_LEASE_MS: number;
2982
3019
 
@@ -3035,6 +3072,8 @@ export declare const DEFAULT_SOCKET_OVERRUN_AFTER_MS = 10000;
3035
3072
 
3036
3073
  export declare const DEFAULT_SOCKET_OVERSIZED_EVENT_BYTES = 262144;
3037
3074
 
3075
+ export declare const DEFAULT_SOCKET_STALL_WARN_AFTER_MS = 5000;
3076
+
3038
3077
  /**
3039
3078
  * How old a row may be before a reader should stop trusting it as current.
3040
3079
  *
@@ -3622,6 +3661,27 @@ export declare interface EffectStoreOps {
3622
3661
  readonly insertIgnore: (table: string, row: Row, options: {
3623
3662
  readonly conflictColumns: ReadonlyArray<string>;
3624
3663
  }) => Effect.Effect<Row, StoreError>;
3664
+ /**
3665
+ * The single-row reads, on the typed channel — the same three `ctx.store`
3666
+ * offers, taking a builder or a descriptor.
3667
+ *
3668
+ * Their absence made two doctor rules mutually unsatisfiable: `row-not-found`
3669
+ * says reach for `.maybeOne(builder)`, `store-effect-lift` says reach for
3670
+ * `yield* EffectStore`, and `EffectStoreOps` had no `.maybeOne`. An app
3671
+ * following both got `Property 'maybeOne' does not exist on type
3672
+ * 'EffectStoreOps'`. Nothing was hard about it: the object this service
3673
+ * closes over has always been the full `FluentStore` — only the declared type
3674
+ * was narrower, and every call site was already casting to say so.
3675
+ *
3676
+ * `one` fails with `NoRowFound` on its own channel rather than collapsing
3677
+ * into `StoreOperationFailed`, for the reason the catch boundary gives about
3678
+ * `TenantRowNotFound`: stringifying a deliberate, catchable refusal is how it
3679
+ * stops being one. There is no `onMissing` here — `Effect.catchTag` says the
3680
+ * same thing and keeps the failure channel exact.
3681
+ */
3682
+ readonly one: <R = Row>(query: QueryLike<R>) => Effect.Effect<R, StoreError | NoRowFound>;
3683
+ readonly maybeOne: <R = Row>(query: QueryLike<R>) => Effect.Effect<R | null, StoreError>;
3684
+ readonly first: <R = Row>(query: QueryLike<R>) => Effect.Effect<R | null, StoreError>;
3625
3685
  /** Bypass soft-delete. See `MutationStore.hardDelete`. */
3626
3686
  readonly hardDelete: (table: string, primaryKey: string) => Effect.Effect<boolean, StoreError>;
3627
3687
  /** Run one Effect program atomically against the transaction-scoped store.
@@ -3650,7 +3710,7 @@ export declare const enableGraphObservation: () => void;
3650
3710
  * not: their session rows written here were unreadable through the store, and
3651
3711
  * the error blamed the key.
3652
3712
  */
3653
- export declare const encryptField: (plaintext: string) => string;
3713
+ export declare const encryptField: (plaintext: string, binding?: FieldBinding) => string;
3654
3714
 
3655
3715
  /**
3656
3716
  * The dispatch-spine entry point. A no-op for a descriptor with no policy; a
@@ -3721,7 +3781,11 @@ export declare const evaluateTouchedRules: (params: {
3721
3781
  readonly subject: unknown;
3722
3782
  }) => Promise<void>;
3723
3783
 
3724
- declare type Event_2 = SubscriptionEvent<ReadonlyArray<Row>>;
3784
+ /** The outbox is generic in the payload: a descriptor subscription carries an
3785
+ * id-keyed row set (and deltas against it), a computed one carries whatever
3786
+ * its handler returned (snapshots only). Coalescing differs only in whether a
3787
+ * patch can be diffed — everything else is the same policy. */
3788
+ declare type Event_2<T> = SubscriptionEvent<T>;
3725
3789
 
3726
3790
  /**
3727
3791
  * How many deliveries one client may fall behind before the oldest are dropped.
@@ -4744,8 +4808,14 @@ export declare interface FluentStore extends Omit<MutationStore, 'update' | 'del
4744
4808
  * Scoping is identical to `query()` (tenant + soft-delete apply), because it
4745
4809
  * IS `query()` underneath — the probe just asks for one row more than it
4746
4810
  * needs so "the first of several" cannot masquerade as "the one you meant".
4811
+ *
4812
+ * `onMissing` turns the refusal into YOUR typed error: it receives the same
4813
+ * `{ table, found }` `NoRowFound` carries and whatever it returns is thrown
4814
+ * instead — `ctx.store.one(q, () => new NotFoundError({ resource: 'user', id }))`.
4815
+ * A contract that already declares its not-found error keeps it, without a
4816
+ * `maybeOne` + `if (row === null)` hop at every call site.
4747
4817
  */
4748
- one<R = Row_7>(query: QueryLike<R>): Promise<R>;
4818
+ one<R = Row_7>(query: QueryLike<R>, onMissing?: OnMissingRow): Promise<R>;
4749
4819
  /** Terminal: the first matching row or `null`, TYPED. Use when "any match" is
4750
4820
  * genuinely what you mean. */
4751
4821
  first<R = Row_7>(query: QueryLike<R>): Promise<R | null>;
@@ -5375,6 +5445,8 @@ export declare interface IssueInput {
5375
5445
  readonly metadata?: Readonly<Record<string, unknown>> | null;
5376
5446
  }
5377
5447
 
5448
+ export declare const isTransactionReadConflict: (err: unknown) => err is TransactionReadConflict;
5449
+
5378
5450
  /** Is `address` one of the peers we are willing to take a forwarded chain from? */
5379
5451
  export declare const isTrustedProxy: (address: string | undefined, config: TrustedProxyConfig) => boolean;
5380
5452
 
@@ -5629,12 +5701,17 @@ export declare const makeAdvisoryLockCoordinator: (store: DataStore, replicaId:
5629
5701
  log?: SchedulerLogger) => Coordinator;
5630
5702
 
5631
5703
  /**
5632
- * Build an AES-256-GCM cipher. Ciphertext format:
5633
- * `enc:v1:<ivB64>:<tagB64>:<ctB64>` — a random 12-byte IV per call (so
5634
- * the same plaintext encrypts differently each time), plus the GCM auth
5635
- * tag for tamper detection. `decrypt` throws on a tampered/wrong-key
5636
- * value (GCM auth failure) — that surfaces as a read error, never
5637
- * silent corruption.
5704
+ * Build an AES-256-GCM cipher. Two envelopes, one key:
5705
+ *
5706
+ * `enc:v1:<ivB64>:<tagB64>:<ctB64>` unbound — no binding given
5707
+ * `enc:v2:<r|c>:<ivB64>:<tagB64>:<ctB64>` bound — the GCM tag also covers
5708
+ * `binding.row` (`r`) or
5709
+ * `binding.column` (`c`)
5710
+ *
5711
+ * A random 12-byte IV per call (the same plaintext encrypts differently each
5712
+ * time), plus the GCM auth tag for tamper detection. `decrypt` throws on a
5713
+ * tampered / wrong-key value AND on a bound value read at another address —
5714
+ * both surface as a read error, never silent corruption.
5638
5715
  */
5639
5716
  export declare const makeAesCipher: (key: Buffer) => FieldCipher;
5640
5717
 
@@ -5786,7 +5863,7 @@ export declare const makeDisconnectConnectionExecutor: (deps: ConnectionBuiltinD
5786
5863
  * dev.ts (CLI) and any standalone test setup uses this to wire up the
5787
5864
  * tag before running the user's Effect.
5788
5865
  */
5789
- export declare const makeEffectStoreLayer: (store: MutationStore) => Layer.Layer<EffectStore>;
5866
+ export declare const makeEffectStoreLayer: (store: FluentStore) => Layer.Layer<EffectStore>;
5790
5867
 
5791
5868
  export declare const makeEventPublisher: (ctx: EventPublishContext) => EventPublisher;
5792
5869
 
@@ -5983,7 +6060,7 @@ export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (
5983
6060
  readonly ok: boolean;
5984
6061
  }>;
5985
6062
 
5986
- export declare const makeSubscriptionOutbox: (options: SubscriptionBackpressureOptions) => SubscriptionOutbox;
6063
+ export declare const makeSubscriptionOutbox: <T = ReadonlyArray<Row>>(options: SubscriptionBackpressureOptions) => SubscriptionOutbox<T>;
5987
6064
 
5988
6065
  /**
5989
6066
  * The factory the serve entrypoints inject into `makeMutationRunner`'s
@@ -6024,6 +6101,34 @@ export declare const makeWorkflowLayers: <Context>(workflows: ReadonlyArray<Disc
6024
6101
 
6025
6102
  export declare const makeWorkflowRunHandle: (workflowName: string, executionId: string) => WorkflowRunHandle;
6026
6103
 
6104
+ /**
6105
+ * Build the shared WORKFLOW START runner — the third member of the family,
6106
+ * beside `makeMutationRunner` and `makeActionRunner`, for the unary rpc a
6107
+ * `*.workflow.tsx` becomes on the wire.
6108
+ *
6109
+ * It existed on neither boot path. Both bound the start handler straight to
6110
+ * the workflow facade, outside every plugin interceptor and with no guard
6111
+ * check — measured with an anonymous socket: a query declaring
6112
+ * `guards: [{ scope }]` answered `ScopeError`, the workflow beside it answered
6113
+ * a run handle. So the three things this runner does are the three things a
6114
+ * start never had:
6115
+ *
6116
+ * 1. `enforceGuards` — the workflow's `guards:` / `openAccess:` decision,
6117
+ * BEFORE the run row is written or the engine is asked for anything.
6118
+ * Under `defaultDeny` an undecided workflow is refused per request, the
6119
+ * defense-in-depth behind the boot gate exactly as for a mutation.
6120
+ * 2. the plugin `interceptWorkflow` chain — audit records the start, rate
6121
+ * limiting counts it, rbac publishes the caller's role scopes so (1) can
6122
+ * see them. Without the chain, a role-derived scope guard could never pass.
6123
+ * 3. a metric sample under `workflow.<name>`, so the rpc panel shows starts
6124
+ * beside mutations instead of a surface that does not exist there.
6125
+ *
6126
+ * `start` is injected rather than the facade: the two boot paths build their
6127
+ * caller-context facade differently, and this runner owns the composition,
6128
+ * not the engine.
6129
+ */
6130
+ export declare const makeWorkflowStartRunner: (deps: WorkflowStartRunnerDeps) => (workflow: WorkflowStartLike, input: unknown, requestContext: ServeRequestContext, start: () => Promise<unknown>) => Promise<unknown>;
6131
+
6027
6132
  /**
6028
6133
  * Mark a wakeup as handled. The row is kept (not deleted) so a crash
6029
6134
  * between firing and the producer re-registering the next occurrence is
@@ -6286,6 +6391,9 @@ export declare type MetricsMode = 'off' | 'console' | 'otlp' | 'memory';
6286
6391
  */
6287
6392
  export declare type MirrorVersion = number;
6288
6393
 
6394
+ /** The error `.one()` throws: the factory's, or the framework's `NoRowFound`. */
6395
+ export declare const missingRowError: (info: NoRowFoundInfo, onMissing: OnMissingRow | undefined) => unknown;
6396
+
6289
6397
  export declare interface MutationLifecycle {
6290
6398
  readonly afterCommit: (work: () => Promise<unknown>) => void;
6291
6399
  }
@@ -6325,6 +6433,13 @@ export declare interface MutationOutputCodec<Output> {
6325
6433
 
6326
6434
  export declare interface MutationRunnerDeps {
6327
6435
  readonly store: TransactionalStore;
6436
+ /**
6437
+ * How many times a mutation's transaction runs before a replayable failure
6438
+ * (contention, a transaction-read conflict) surfaces. Resolved by the CLI
6439
+ * from `app.config.ts` `mutations.replayAttempts` (env
6440
+ * `VOLTRO_MUTATION_REPLAY_ATTEMPTS` wins); absent → the default.
6441
+ */
6442
+ readonly replayAttempts?: number;
6328
6443
  /**
6329
6444
  * `security.defaultDeny`, threaded to the dispatch spine: `true` refuses a
6330
6445
  * descriptor with NO access decision per-request (typed `ScopeError`, the
@@ -6490,24 +6605,14 @@ export declare const normalizeIp: (raw: string) => string | undefined;
6490
6605
  * never accidentally compare equal to a real one. */
6491
6606
  export declare const normalizeOrigin: (raw: string) => string;
6492
6607
 
6493
- /**
6494
- * `.one()` matched a number of rows other than exactly one.
6495
- *
6496
- * `found` distinguishes the two failure modes without a second error type:
6497
- * `0` is "the row you required is missing", `2` is "your filter is not as
6498
- * unique as you assumed" (the terminal probes with LIMIT 2, so `2` means
6499
- * "at least two" — it never counts the whole set just to report a number).
6500
- */
6501
- export declare class NoRowFound extends NoRowFound_base {
6502
- get message(): string;
6503
- }
6608
+ export { NoRowFound }
6504
6609
 
6505
- declare const NoRowFound_base: Schema.TaggedErrorClass<NoRowFound, "NoRowFound", {
6506
- readonly _tag: Schema.tag<"NoRowFound">;
6507
- } & {
6508
- table: typeof Schema.String;
6509
- found: typeof Schema.Number;
6510
- }>;
6610
+ /** What `.one()` hands an `onMissing` factory — the two facts `NoRowFound` carries. */
6611
+ export declare interface NoRowFoundInfo {
6612
+ readonly table: string;
6613
+ /** `0` — no row; `2` — at least two, the filter is not unique. */
6614
+ readonly found: number;
6615
+ }
6511
6616
 
6512
6617
  /** Note one read against the in-flight procedure. No scope → nothing happens. */
6513
6618
  export declare const noteRead: (descriptor: QueryDescriptor) => void;
@@ -6621,6 +6726,14 @@ export declare interface OneShotQueryRunnerDeps<D> extends QueryProducerDeps<D>
6621
6726
  readonly queryRows: (descriptor: D, context: ServeRequestContext) => Promise<ReadonlyArray<unknown>>;
6622
6727
  }
6623
6728
 
6729
+ /**
6730
+ * Turn `.one()`'s refusal into the caller's own typed error. Whatever it
6731
+ * returns is THROWN — so a contract that declares `NotFoundError({ resource,
6732
+ * id })` keeps declaring it, and the call site is `one(q, () => new
6733
+ * NotFoundError(...))` instead of `maybeOne` + a null branch at every read.
6734
+ */
6735
+ export declare type OnMissingRow = (info: NoRowFoundInfo) => unknown;
6736
+
6624
6737
  /**
6625
6738
  * Register teardown to run on SIGINT/SIGTERM, before the launches are drained
6626
6739
  * and the process exits. Returns a de-register function.
@@ -6814,6 +6927,12 @@ export declare interface OutboxHandlerDefinition {
6814
6927
  readonly maxAttempts?: number;
6815
6928
  }
6816
6929
 
6930
+ /** What the pump hands events to — the `emit` of a `Stream.asyncScoped`. */
6931
+ export declare interface OutboxSink<T> {
6932
+ readonly single: (event: Event_2<T>) => Promise<unknown>;
6933
+ readonly end: () => unknown;
6934
+ }
6935
+
6817
6936
  export declare type OutboxStatus = 'pending' | 'delivering' | 'delivered' | 'dead';
6818
6937
 
6819
6938
  export declare const outboxTable: TableLike;
@@ -6924,6 +7043,13 @@ export declare interface PluginRefRule {
6924
7043
  readonly targetTable: string;
6925
7044
  readonly policy: PluginRefOrphanPolicy;
6926
7045
  readonly onSoftDelete: boolean;
7046
+ /**
7047
+ * Fire on a HARD delete of the target. Default `true` — a `pluginRef` has no
7048
+ * foreign key, so the runtime is the only thing that can clean up. `false`
7049
+ * for a `reference(…, { onSoftDelete })` rule: its hard delete IS a foreign
7050
+ * key, and the database already did whatever `onDelete` says.
7051
+ */
7052
+ readonly onHardDelete?: boolean;
6927
7053
  }
6928
7054
 
6929
7055
  /** The store surface the enforcement needs. */
@@ -7074,6 +7200,36 @@ export declare const publishEvent: <Name extends string, Key extends Schema.Sche
7074
7200
  */
7075
7201
  export declare const publishServerTracerLayer: Layer.Layer<never, never, never>;
7076
7202
 
7203
+ export declare interface PumpOptions {
7204
+ /** Override the process-wide tunable (tests). */
7205
+ readonly stallWarnAfterMs?: number;
7206
+ readonly warn?: (message: string, fields: Record<string, unknown>) => void;
7207
+ }
7208
+
7209
+ /**
7210
+ * The pump between an outbox and its stream: hand events over ONE at a time,
7211
+ * awaiting each acceptance — that await is the backpressure signal the outbox
7212
+ * coalesces behind. `take()` resolves null once the outbox closes (scope
7213
+ * teardown, or the terminal overrun error has drained).
7214
+ *
7215
+ * ONE definition for every subscription binder (descriptor, adopted-on-resume,
7216
+ * computed). The three used to carry the same loop inline; a diagnostic added
7217
+ * to one of them would have been absent from the other two.
7218
+ *
7219
+ * **The stall diagnostic.** `@effect/rpc` delivers a stream as `Chunk`
7220
+ * messages and waits for the client's `Ack` of each before writing the next.
7221
+ * The typed client acknowledges by itself; a hand-rolled socket client that
7222
+ * only listens receives exactly one Chunk per subscription — the snapshot — and
7223
+ * every later delivery parks behind the missing Ack. From the outside that is
7224
+ * indistinguishable from "the server never produced an update", and it was
7225
+ * reported as exactly that: the producer, the matcher and the registry all
7226
+ * looked healthy because they were. The stream's buffer is ONE event deep, so
7227
+ * the second delivery a stalled consumer does not accept blocks HERE — and a
7228
+ * delivery the transport has not accepted within `stallWarnAfterMs` is said out
7229
+ * loud, with the cause named, and counted in `voltro_subscription_stalled_total`.
7230
+ */
7231
+ export declare const pumpOutbox: <T>(outbox: SubscriptionOutbox<T>, sink: OutboxSink<T>, label: string, options?: PumpOptions) => Promise<void>;
7232
+
7077
7233
  /**
7078
7234
  * The two scopes, composed once, for every path that finalises a descriptor.
7079
7235
  *
@@ -7346,7 +7502,8 @@ export declare interface ReactiveConfigInput {
7346
7502
  * threshold is closed with a typed `SubscriptionOverrun` (it re-subscribes
7347
7503
  * for a fresh snapshot). Resolved by the CLI's `reactiveTunables.ts`; env
7348
7504
  * overrides win: `VOLTRO_REACTIVE_MAX_BUFFERED_BYTES` /
7349
- * `VOLTRO_REACTIVE_OVERRUN_AFTER_MS` / `VOLTRO_REACTIVE_OVERSIZED_EVENT_BYTES`.
7505
+ * `VOLTRO_REACTIVE_OVERRUN_AFTER_MS` / `VOLTRO_REACTIVE_OVERSIZED_EVENT_BYTES` /
7506
+ * `VOLTRO_REACTIVE_STALL_WARN_AFTER_MS`.
7350
7507
  */
7351
7508
  readonly socket?: {
7352
7509
  /** Estimated pending bytes per subscription above which a persistently
@@ -7357,6 +7514,11 @@ export declare interface ReactiveConfigInput {
7357
7514
  readonly overrunAfterMs?: number;
7358
7515
  /** Per-event WARN threshold (telemetry, NOT a cap). Default 256 KiB. */
7359
7516
  readonly oversizedEventBytes?: number;
7517
+ /** How long a delivery may sit unaccepted by the transport before the
7518
+ * server says so — a WARN naming the subscription and the missing
7519
+ * `@effect/rpc` `Ack`, plus `voltro_subscription_stalled_total`.
7520
+ * Default 5_000 ms. */
7521
+ readonly stallWarnAfterMs?: number;
7360
7522
  };
7361
7523
  }
7362
7524
 
@@ -7377,6 +7539,9 @@ export declare interface ReactiveSocketTunables {
7377
7539
  readonly maxBufferedBytes: number;
7378
7540
  readonly overrunAfterMs: number;
7379
7541
  readonly oversizedEventBytes: number;
7542
+ /** How long a delivery may sit unaccepted by the transport before the pump
7543
+ * says so (WARN + `voltro_subscription_stalled_total`). See `pumpOutbox`. */
7544
+ readonly stallWarnAfterMs: number;
7380
7545
  }
7381
7546
 
7382
7547
  /** The resolved tunables, or the defaults when no boot path has set any
@@ -7574,6 +7739,10 @@ export declare const recordSample: (s: RecordedSample) => void;
7574
7739
  */
7575
7740
  export declare const recordScheduleRun: (r: ScheduleRunRecord) => void;
7576
7741
 
7742
+ /** A delivery the pump handed to the stream that the transport did not accept
7743
+ * within the stall threshold — the consumer is not acknowledging chunks. */
7744
+ export declare const recordStalled: (label: string) => void;
7745
+
7577
7746
  /** Move the live-subscriptions gauge for `label`'s tag by `delta` (+1 open, -1 close). */
7578
7747
  export declare const recordSubscriptionActive: (label: string, delta: number) => void;
7579
7748
 
@@ -8833,6 +9002,9 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
8833
9002
  /** Public raw-HTTP routes contributed by plugins (e.g. storage
8834
9003
  * byte-serving). Mounted on the same listener as rpc + inspect. */
8835
9004
  readonly httpRoutes?: ReadonlyArray< PluginHttpRoute>;
9005
+ /** Called after every plugin HTTP route answers — the composed plugins'
9006
+ * `onHttpRoute` hooks. Fire-and-forget; a throw never reaches the client. */
9007
+ readonly onHttpRoute?: HttpRouteObserver;
8836
9008
  /** The app's DataStore, for `PluginHttpRouteRequest.store`. A getter rather
8837
9009
  * than a value: routes mount before the store is built, and the two boot
8838
9010
  * paths build it on opposite sides of that point. */
@@ -9070,6 +9242,10 @@ export declare const runWakeLoop: (deps: WakeOrchestratorDeps, intervalMs?: numb
9070
9242
  */
9071
9243
  export declare const runWithDeadlockRetry: <T>(work: () => Promise<T>, options?: {
9072
9244
  readonly delay?: (attempt: number) => Promise<void>;
9245
+ /** How many times the transaction runs in total before the failure
9246
+ * surfaces. Default {@link DEFAULT_MUTATION_REPLAY_ATTEMPTS}; a hot row
9247
+ * with many simultaneous writers may need more — or a row lock. */
9248
+ readonly attempts?: number;
9073
9249
  }) => Promise<T>;
9074
9250
 
9075
9251
  /**
@@ -9671,6 +9847,8 @@ export declare class SelectBuilder {
9671
9847
  where(...args: ReadonlyArray<unknown>): SelectBuilder;
9672
9848
  orderBy(column: string, direction?: 'asc' | 'desc'): SelectBuilder;
9673
9849
  limit(n: number): SelectBuilder;
9850
+ /** Lock the matched rows for the rest of the transaction — see `Query.forUpdate`. */
9851
+ forUpdate(): SelectBuilder;
9674
9852
  offset(n: number): SelectBuilder;
9675
9853
  select(...columns: ReadonlyArray<string>): SelectBuilder;
9676
9854
  using(indexName: string): SelectBuilder;
@@ -9698,8 +9876,10 @@ export declare class SelectBuilder {
9698
9876
  * the bug would surface far away from its cause. One extra row on the
9699
9877
  * wire buys a loud failure at the point the assumption breaks. Use
9700
9878
  * `.first()` / `.maybeOne()` when you genuinely want "any match".
9879
+ *
9880
+ * `onMissing` swaps the thrown error for yours — see `OnMissingRow`.
9701
9881
  */
9702
- one(): Promise<Row>;
9882
+ one(onMissing?: OnMissingRow): Promise<Row>;
9703
9883
  /** Terminal: COUNT(*) of matching rows (real aggregate, not a fetch). */
9704
9884
  count(): Promise<number>;
9705
9885
  /** Terminal: does any row match? Uses a `LIMIT 1` probe. */
@@ -9719,6 +9899,8 @@ export declare type SerializedGuard = {
9719
9899
  readonly action: string;
9720
9900
  readonly resourceType: string;
9721
9901
  readonly resource: string | null;
9902
+ } | {
9903
+ readonly kind: 'authenticated';
9722
9904
  };
9723
9905
 
9724
9906
  /**
@@ -9775,7 +9957,7 @@ export declare const serverOnlyLeaks: (query: AuditableQuery, serverOnlyByTable:
9775
9957
  /** A plugin interceptor — wraps the base run Effect (Effect-native chain). */
9776
9958
  export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown, never>, meta: {
9777
9959
  readonly tag: string;
9778
- readonly kind: 'mutation' | 'action' | 'query';
9960
+ readonly kind: 'mutation' | 'action' | 'query' | 'workflow';
9779
9961
  readonly input: unknown;
9780
9962
  readonly subject: unknown;
9781
9963
  readonly traceId: string;
@@ -10328,13 +10510,13 @@ export declare interface SubscriptionDeliveryRecord {
10328
10510
  readonly rowCount: number;
10329
10511
  }
10330
10512
 
10331
- export declare interface SubscriptionOutbox {
10513
+ export declare interface SubscriptionOutbox<T = ReadonlyArray<Row>> {
10332
10514
  /** SYNC push from the dispatcher's emit callback. */
10333
- readonly push: (event: Event_2) => void;
10515
+ readonly push: (event: Event_2<T>) => void;
10334
10516
  /** Pump side: resolves with the next event to hand into the stream —
10335
10517
  * coalesced when more than one push landed since the last take — or null
10336
10518
  * once the outbox is closed and drained. */
10337
- readonly take: () => Promise<Event_2 | null>;
10519
+ readonly take: () => Promise<Event_2<T> | null>;
10338
10520
  /** True once the overrun policy fired (the error event has been queued;
10339
10521
  * the stream ends after it drains). */
10340
10522
  readonly overrun: () => boolean;
@@ -10436,6 +10618,10 @@ export declare interface SubscriptionSocketMetricsSnapshot {
10436
10618
  readonly total: number;
10437
10619
  readonly byLabel: Readonly<Record<string, number>>;
10438
10620
  };
10621
+ readonly stalledTotal: {
10622
+ readonly total: number;
10623
+ readonly byLabel: Readonly<Record<string, number>>;
10624
+ };
10439
10625
  }
10440
10626
 
10441
10627
  export declare const subscriptionSocketMetricsSnapshot: () => SubscriptionSocketMetricsSnapshot;
@@ -10722,11 +10908,19 @@ export declare interface TracingOptions {
10722
10908
  readonly sampler?: Sampler;
10723
10909
  }
10724
10910
 
10911
+ export declare const TRANSACTION_READ_CONFLICT_NAME = "VoltroTransactionReadConflict";
10912
+
10725
10913
  /** The store surface the runner needs — just the transactional wrap. */
10726
10914
  export declare interface TransactionalStore {
10727
10915
  transactional<T>(work: (tx: unknown) => Promise<T>): Promise<T>;
10728
10916
  }
10729
10917
 
10918
+ export declare class TransactionReadConflict extends Error {
10919
+ readonly conflict: VersionConflict;
10920
+ readonly name = "VoltroTransactionReadConflict";
10921
+ constructor(conflict: VersionConflict);
10922
+ }
10923
+
10730
10924
  /**
10731
10925
  * Transport-level security policy for the api listener.
10732
10926
  *
@@ -10872,6 +11066,10 @@ export declare type TupleSource = (req: {
10872
11066
  readonly load: DataLoader | undefined;
10873
11067
  }) => Promise<ReadonlyArray<RelationTuple>> | ReadonlyArray<RelationTuple>;
10874
11068
 
11069
+ /** The mutation runner's key for opening the read-version map on a transaction
11070
+ * view it obtained from the dialect store directly. */
11071
+ export declare const TX_READ_VERSIONS_KEY: unique symbol;
11072
+
10875
11073
  /**
10876
11074
  * Drop everything this connection accumulated. Called by the WS-close finalizer
10877
11075
  * and by explicit sign-out flows. Idempotent.
@@ -11786,6 +11984,13 @@ export declare class WorkflowSingletonHeldError extends Error {
11786
11984
  constructor(workflowName: string, executionId: string);
11787
11985
  }
11788
11986
 
11987
+ /** The slice of a `workflow()` definition the start runner reads — its name
11988
+ * and its resolved access decision (`workflowAccessDescriptor` produces it). */
11989
+ export declare interface WorkflowStartLike {
11990
+ readonly name: string;
11991
+ readonly guards?: ReadonlyArray<AnyCheckSpec> | undefined;
11992
+ }
11993
+
11789
11994
  export declare interface WorkflowStartOptions {
11790
11995
  readonly wait?: boolean;
11791
11996
  /**
@@ -11816,18 +12021,13 @@ export declare interface WorkflowStartOptions {
11816
12021
  /* Excluded from this release type: admitted */
11817
12022
  }
11818
12023
 
11819
- export declare const workflowToRpc: (workflow: WorkflowDefinitionLike) => Rpc.Rpc<string, Schema.Schema.Any, Schema.Struct<{
11820
- id: typeof Schema.String;
11821
- workflowName: typeof Schema.String;
11822
- executionId: Schema.NullOr<typeof Schema.String>;
11823
- status: Schema.Literal<["running", "queued", "dropped", "skipped"]>;
11824
- deferral: Schema.optional<Schema.Struct<{
11825
- mode: typeof Schema.String;
11826
- dueAt: Schema.NullOr<typeof Schema.Number>;
11827
- retryAfterMs: Schema.NullOr<typeof Schema.Number>;
11828
- intentId: Schema.NullOr<typeof Schema.String>;
11829
- }>>;
11830
- }>, Schema.Schema.All, never>;
12024
+ export declare interface WorkflowStartRunnerDeps {
12025
+ /** Composed plugin workflow-interceptor (`interceptWorkflow`). */
12026
+ readonly interceptor?: ServeRpcInterceptor;
12027
+ readonly recordMetric?: MutationRunnerDeps['recordMetric'];
12028
+ /** See {@link MutationRunnerDeps.defaultDeny} — same flag, same refusal. */
12029
+ readonly defaultDeny?: boolean;
12030
+ }
11831
12031
 
11832
12032
  export declare interface WorkflowUpdateOptions {
11833
12033
  readonly timeoutMs?: number;