@voltro/runtime 0.80.0 → 0.82.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
@@ -68,16 +68,19 @@ import { MetricBoundaries } from 'effect';
68
68
  import { MetricReader } from '@opentelemetry/sdk-metrics';
69
69
  import { MutationIdempotencyScope } from '@voltro/protocol';
70
70
  import { NoRowFound } from '@voltro/protocol';
71
+ import { NoSuchResource } from '@voltro/protocol';
71
72
  import { PendingApproval } from '@voltro/protocol';
72
73
  import { PluginHttpRoute } from '@voltro/protocol';
73
74
  import { PluginHttpRouteByteStream } from '@voltro/protocol';
74
75
  import { PluginRefOrphanPolicy } from '@voltro/database';
75
76
  import { Predicate } from '@voltro/database';
77
+ import { ProcedureWireError } from '@voltro/protocol';
76
78
  import { PublicApiSpec } from '@voltro/protocol';
77
79
  import { Query } from '@voltro/database';
78
80
  import { QueryDescriptor } from '@voltro/database';
79
81
  import { Redis } from 'ioredis';
80
82
  import { registerRetention } from '@voltro/database';
83
+ import { ResourceMissing } from '@voltro/protocol';
81
84
  import { RestRouteDescriptor } from '@voltro/protocol/rest';
82
85
  import { RetentionConflict } from '@voltro/database';
83
86
  import { RetentionSource } from '@voltro/database';
@@ -132,6 +135,9 @@ import { WorkflowControlRequest } from '@voltro/workflow';
132
135
  import { WorkflowControlResult as WorkflowControlResult_2 } from '@voltro/protocol';
133
136
  import { WorkflowParentClosePolicy } from '@voltro/protocol';
134
137
  import { WorkflowQueuedCancellationResult } from '@voltro/protocol';
138
+ import { WorkflowRestartCommand } from '@voltro/protocol';
139
+ import { WorkflowRestartObservation } from '@voltro/protocol';
140
+ import { WorkflowRestartResult } from '@voltro/protocol';
135
141
  import { WorkflowRetryObservation } from '@voltro/protocol';
136
142
  import { WorkflowRunHandle } from '@voltro/protocol';
137
143
  import { WorkflowRunSnapshot as WorkflowRunSnapshot_2 } from '@voltro/protocol';
@@ -909,6 +915,12 @@ export declare const API_CSP = "default-src 'none'; frame-ancestors 'none'; base
909
915
 
910
916
  export declare const API_KEYS_TABLE = "_voltro_api_keys";
911
917
 
918
+ /** Caller side: the token this app presents to the api it binds as `name`. */
919
+ export declare const apiBindingTokenEnv: (name: string) => string;
920
+
921
+ /** Caller side: where the api it binds as `name` listens (production). */
922
+ export declare const apiBindingUrlEnv: (name: string) => string;
923
+
912
924
  /** What `ApiKeyStore.ensureActor` is handed for the row it plants. */
913
925
  export declare interface ApiKeyActor {
914
926
  readonly id: string;
@@ -1107,6 +1119,19 @@ export declare interface AppAccess {
1107
1119
  readonly scopes: ReadonlyArray<string>;
1108
1120
  }
1109
1121
 
1122
+ /**
1123
+ * The typed clients of the apps this app calls — augmented per app by the
1124
+ * codegen'd `apis.generated.ts`:
1125
+ *
1126
+ * declare module '@voltro/runtime' {
1127
+ * interface AppApis { readonly cloud: ServiceApi<CloudServiceProcedures> }
1128
+ * }
1129
+ *
1130
+ * Empty until an app declares `apis:`.
1131
+ */
1132
+ export declare interface AppApis {
1133
+ }
1134
+
1110
1135
  export declare interface AppContext {
1111
1136
  /** Run database work with a freshly transaction-bound context. Reuses an
1112
1137
  * existing mutation transaction (no savepoint). The callback may be retried;
@@ -1204,6 +1229,19 @@ export declare interface AppContext {
1204
1229
  * subject", by construction.
1205
1230
  */
1206
1231
  readonly connections?: ConnectionsFacade;
1232
+ /**
1233
+ * The other apps of the project this app calls (`ctx.apis.<name>`) — one
1234
+ * typed client per `apis:` binding in `app.config.ts`, typed from the
1235
+ * callee's `service:` procedures by the codegen'd `apis.generated.ts`.
1236
+ *
1237
+ * await ctx.apis.cloud.deployments.activate({ deploymentId, tenantId })
1238
+ * yield* ctx.apis.cloud.deployments.activate.effect(input) // in a workflow step
1239
+ *
1240
+ * Always present — an app with no bindings gets an empty object, so the field
1241
+ * never reads as "maybe wired". A call without an answer fails with
1242
+ * `ApiUnavailable`; every answer the callee gives arrives as itself.
1243
+ */
1244
+ readonly apis: AppApis;
1207
1245
  /**
1208
1246
  * Request-scoped batching (`ctx.load` / `ctx.loadMany`). Coalesces
1209
1247
  * same-tick reads of one table into a single `WHERE id IN (...)`.
@@ -1460,6 +1498,9 @@ export declare const assertCan: (subject: RebacSubject, action: string, resource
1460
1498
  */
1461
1499
  export declare const assertConnectionCipherConfigured: (definitions: ReadonlyArray<ConnectionDefinition>) => void;
1462
1500
 
1501
+ /** Refuse a caller map that could not be what the author meant. */
1502
+ export declare const assertServiceCallersConfig: (callers: ServiceCallersConfig) => void;
1503
+
1463
1504
  /**
1464
1505
  * Boot gate: every mounted incoming-webhook route must carry a verification
1465
1506
  * declaration. Throws `UnverifiedWebhookRoute` for the first that does not.
@@ -1640,6 +1681,19 @@ export declare interface AuditableQuery {
1640
1681
 
1641
1682
  export declare interface AuthMiddlewareLayerOptions {
1642
1683
  readonly resolveSubject: ResolveSubjectFn;
1684
+ /**
1685
+ * The apps allowed to call one rpc tag, or `undefined` when the tag is not a
1686
+ * `service:` procedure. Both boot paths pass the same lookup over the
1687
+ * procedures they registered.
1688
+ *
1689
+ * This is the OUTER wall for a service credential: the guard check refuses a
1690
+ * service subject at every procedure that does not name it, but framework
1691
+ * built-ins (`__voltro.*`) and plugin routes reach their handlers without a
1692
+ * descriptor guard. Here, before any handler, a service subject reaches only
1693
+ * the tags that name it — so a leaked service token opens exactly the doors
1694
+ * the callee declared and nothing the framework adds.
1695
+ */
1696
+ readonly serviceCallersFor?: (tag: string) => ReadonlyArray<string> | undefined;
1643
1697
  }
1644
1698
 
1645
1699
  /**
@@ -1745,7 +1799,7 @@ export declare const bindConnectionCredential: (clientId: number, credential: Co
1745
1799
  * interrupt registry subscriptions use — so one reconnect policy, one devtools
1746
1800
  * view, one connection lifecycle for both primitives.
1747
1801
  */
1748
- export declare const bindEvent: (input: BindEventInput) => Stream.Stream<EventStreamEvent<unknown>, ScopeError | Unauthenticated, SubjectService | ConnectionInfo>;
1802
+ export declare const bindEvent: (input: BindEventInput) => Stream.Stream<EventStreamEvent<unknown>, ScopeError | Unauthenticated | NoSuchResource, SubjectService | ConnectionInfo>;
1749
1803
 
1750
1804
  export declare interface BindEventInput {
1751
1805
  readonly bus: EventBus;
@@ -2591,6 +2645,12 @@ export declare interface CoordinatedScheduleDeps {
2591
2645
  * it holds. Without either, a REMOTE replica's enqueue produces no local
2592
2646
  * event, and a disarmed task would sleep through it forever. The backoff
2593
2647
  * ceiling exists for exactly that case and is the correct choice there.
2648
+ *
2649
+ * The task's half of the bargain: an idle tick disarms only when it returns
2650
+ * `nextDueInMs: null` ("nothing is scheduled"). Work that comes due on a
2651
+ * clock is not an arrival, so the tick reports its instant as `nextDueInMs`
2652
+ * and the task is armed for exactly then. An idle tick that says neither
2653
+ * backs off instead of disarming.
2594
2654
  */
2595
2655
  readonly disarmWhenIdle?: boolean;
2596
2656
  }
@@ -2657,16 +2717,25 @@ export declare interface CoordinatedTickOutcome {
2657
2717
  readonly idle: boolean;
2658
2718
  /**
2659
2719
  * Milliseconds until the earliest thing this task already knows is coming —
2660
- * a debounce window closing, a lease expiring. Caps the backoff, so a task
2661
- * that is idle RIGHT NOW but has a deadline in 400 ms is armed for 400 ms
2662
- * rather than for 30 s.
2720
+ * a debounce window closing, a lease expiring, a retry or a re-poll whose
2721
+ * time arrives. Caps the backoff, so a task that is idle RIGHT NOW but has a
2722
+ * deadline in 400 ms is armed for 400 ms rather than for 30 s.
2663
2723
  *
2664
- * This is the part a fixed interval cannot express and the part that makes
2665
- * the backoff safe: without it, backing off is a bet that nothing time-based
2666
- * is pending, and deferring controls are exactly the case where that bet is
2667
- * wrong.
2724
+ * `null` is a statement, not an absence: "I looked, and nothing is scheduled
2725
+ * — the next piece of work can only arrive as a write". It is the ONLY idle
2726
+ * report that lets a `disarmWhenIdle` task stop ticking. Omitting the field
2727
+ * means "I did not say", and a task that did not say backs off toward the
2728
+ * ceiling instead of disarming.
2729
+ *
2730
+ * The distinction exists because the failure is silent and permanent. Work
2731
+ * that becomes due on a CLOCK — a row released with a future `enqueuedAt`, a
2732
+ * lease that runs out — produces no write, so no change event, so no
2733
+ * `wake()`. A dispatcher that reported `{ idle: true }` while holding such a
2734
+ * row disarmed and never came back; the row sat until an unrelated write
2735
+ * happened to wake it. Making "nothing scheduled" an explicit answer turns a
2736
+ * forgotten deadline into a slower tick rather than a hang.
2668
2737
  */
2669
- readonly nextDueInMs?: number;
2738
+ readonly nextDueInMs?: number | null;
2670
2739
  }
2671
2740
 
2672
2741
  export declare type CoordinationOutcome = 'single' | 'wonLock' | 'lostLock' | 'external' | 'cluster';
@@ -3221,6 +3290,18 @@ export declare const decideApprovalInvocation: (input: {
3221
3290
  readonly status: "approved" | "rejected";
3222
3291
  }>;
3223
3292
 
3293
+ /**
3294
+ * `unknown` when the executor's failures are all declared; otherwise a type the
3295
+ * handler cannot satisfy, whose one property spells out the undeclared tag(s)
3296
+ * and the three ways to resolve them — that text is what the compile error at
3297
+ * the handler shows.
3298
+ */
3299
+ export declare type DeclaredFailureCheck<D extends ExecutorDescriptor, R> = [
3300
+ UndeclaredErrorTags<D, R>
3301
+ ] extends [never] ? unknown : {
3302
+ readonly undeclaredFailure: `'${UndeclaredErrorTags<D, R>}' is not in this procedure's wire union — add it to the descriptor's \`error:\`, register it through a plugin's \`errorSchemas\`, or map it away (Effect.orDie for a defect)`;
3303
+ };
3304
+
3224
3305
  /**
3225
3306
  * Decrypt a value produced by `encryptField` or by an `.encrypted()` column —
3226
3307
  * one encoding, so that sentence is now true in both directions.
@@ -3303,6 +3384,10 @@ export declare const DEFAULT_HSTS = "max-age=15552000; includeSubDomains";
3303
3384
  */
3304
3385
  export declare const DEFAULT_KEEP_ALIVE_MS = 72000;
3305
3386
 
3387
+ export declare const DEFAULT_LEVEL_RESYNC_MS: number;
3388
+
3389
+ export declare const DEFAULT_LEVEL_STUCK_MS: number;
3390
+
3306
3391
  /** Ceiling the idle backoff climbs to. Deliberately short enough to be a
3307
3392
  * FLOOR under a missed wake rather than a substitute for one. */
3308
3393
  export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
@@ -3487,7 +3572,7 @@ export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayloa
3487
3572
  * Runtime identity — returns `fn` unchanged; the whole value is the compile-time
3488
3573
  * check. See the module header for the failure it prevents.
3489
3574
  */
3490
- export declare const defineExecutor: <D extends ExecutorDescriptor, F extends (input: ExecutorInput<D>, ctx: AppContext) => ExecutorReturn<D, unknown, unknown>>(_descriptor: D, fn: F) => ((input: ExecutorInput<D>, ctx: AppContext) => ReturnType<F>);
3575
+ export declare const defineExecutor: <D extends ExecutorDescriptor, F extends (input: ExecutorInput<D>, ctx: AppContext) => ExecutorReturn<D, unknown, unknown>>(_descriptor: D, fn: F & DeclaredFailureCheck<D, ReturnType<F>>) => ((input: ExecutorInput<D>, ctx: AppContext) => ReturnType<F>);
3491
3576
 
3492
3577
  /**
3493
3578
  * Declare a data-quality expectation. Validates the shape at declaration time so
@@ -3541,7 +3626,12 @@ export declare const defineOutboxHandler: <R = never>(definition: OutboxHandlerD
3541
3626
 
3542
3627
  /** Declare a reaction. Validates that the MANDATORY guard is present — an
3543
3628
  * ungated reaction is a spend-storm footgun, so this fails LOUD at boot. */
3544
- export declare const defineReaction: (def: ReactionDefinition) => ReactionDefinition;
3629
+ export declare function defineReaction(def: LevelReactionDefinition): LevelReactionDefinition;
3630
+
3631
+ export declare function defineReaction(def: EdgeReactionDefinition): EdgeReactionDefinition;
3632
+
3633
+ /** A value already typed as either form — narrowed at runtime by its `watch`. */
3634
+ export declare function defineReaction(def: ReactionDefinition): ReactionDefinition;
3545
3635
 
3546
3636
  /** Register a resource policy (the static, discoverable relationship registry
3547
3637
  * the capability map exposes). Validates + fails LOUD on a malformed or
@@ -3786,6 +3876,9 @@ export declare class Dispatcher {
3786
3876
  /** Query labels already warned about for an undeclared raw read, so a hot
3787
3877
  * subscription logs once rather than once per subscriber. */
3788
3878
  private readonly rawReadWarned;
3879
+ /** The CRDT-managed columns of `table` (empty when it has none) — what a
3880
+ * subscription's backpressure outbox folds incrementally. */
3881
+ crdtColumnsFor(table: string): ReadonlySet<string>;
3789
3882
  constructor(deps: DispatcherDependencies);
3790
3883
  /**
3791
3884
  * REL-14 — a raw read that nothing can invalidate, said out loud.
@@ -4043,7 +4136,10 @@ export declare interface DrainResult {
4043
4136
  */
4044
4137
  readonly scanned: number;
4045
4138
  /**
4046
- * When the earliest NOT-yet-due pending row comes due, as an epoch ms.
4139
+ * When the queue next has work that no write will announce, as an epoch ms:
4140
+ * the earliest NOT-yet-due pending row, or the earliest `delivering` claim
4141
+ * whose lease runs out (its deliverer may be gone, and the reclaim is due
4142
+ * then).
4047
4143
  *
4048
4144
  * `undefined` means nothing in the read window is waiting. A LOWER bound (the
4049
4145
  * window is capped by `batchSize`), which is the safe direction: understating
@@ -4051,7 +4147,10 @@ export declare interface DrainResult {
4051
4147
  *
4052
4148
  * This is what lets the runner arm for a backoff instead of polling until it
4053
4149
  * expires — the one thing in this loop that genuinely needs a clock, since a
4054
- * retry becoming due is not a write anybody can be notified about.
4150
+ * retry becoming due, or a lease running out, is not a write anybody can be
4151
+ * notified about. A runner that stopped ticking on an empty due window
4152
+ * without this would leave a lost deliverer's row claimed until something
4153
+ * unrelated wrote to the table.
4055
4154
  */
4056
4155
  readonly nextAttemptAt?: number;
4057
4156
  }
@@ -4064,6 +4163,15 @@ export declare interface DrainResult {
4064
4163
  */
4065
4164
  export declare const dueWakeups: (store: DataStore, now: Date, limit?: number) => Promise<ReadonlyArray<Wakeup>>;
4066
4165
 
4166
+ /** A reaction to each CHANGE of a table (edge-triggered). */
4167
+ export declare interface EdgeReactionDefinition extends ReactionBase {
4168
+ /** The table whose changes trigger the reaction (+ optional op filter). */
4169
+ readonly watch: ReactionEdgeWatch;
4170
+ /** Optional predicate over the change — skip when it returns false. */
4171
+ readonly when?: (event: ReactionEvent) => boolean;
4172
+ readonly guards: ReactionGuards;
4173
+ }
4174
+
4067
4175
  /** The minimal Effect-store surface `insertIgnoreRowOutcomeEffect` needs. */
4068
4176
  export declare interface EffectInsertIgnoreOutcomeStore {
4069
4177
  readonly insertIgnoreWithOutcome: (table: string, row: Row, options: {
@@ -4634,8 +4742,13 @@ export declare interface ExecutorDescriptor {
4634
4742
  readonly kind: 'query' | 'mutation' | 'action';
4635
4743
  readonly input: Schema.Schema.Any;
4636
4744
  readonly output: Schema.Schema.Any;
4745
+ readonly error: Schema.Schema.All;
4637
4746
  }
4638
4747
 
4748
+ /** The failure type of an executor's return, when it returns an Effect. A
4749
+ * Promise's rejection is untyped and a plain value cannot fail. */
4750
+ declare type ExecutorFailure<R> = R extends Effect.Effect<infer _A, infer E, infer _R> ? E : never;
4751
+
4639
4752
  /** The decoded INPUT a handler receives (the Type side of the input schema). */
4640
4753
  export declare type ExecutorInput<D extends ExecutorDescriptor> = Schema.Schema.Type<D['input']>;
4641
4754
 
@@ -5238,6 +5351,14 @@ export declare type ExternalTriggerOutcome =
5238
5351
 
5239
5352
  export { extractDbCause }
5240
5353
 
5354
+ /** The literal `_tag`s of a failure type. `any` contributes none (nothing can
5355
+ * be proven about it); `UnknownException` — Effect's wrapper for a failure it
5356
+ * could not type, e.g. `Effect.tryPromise` without `catch` — counts as
5357
+ * untagged, which is what it means. */
5358
+ declare type FailureTag<E> = 0 extends (1 & E) ? never : E extends {
5359
+ readonly _tag: infer T;
5360
+ } ? T extends 'UnknownException' ? never : T extends string ? T : never : never;
5361
+
5241
5362
  export declare interface FieldChange {
5242
5363
  readonly from: unknown;
5243
5364
  readonly to: unknown;
@@ -5577,6 +5698,8 @@ export declare interface GroupState {
5577
5698
  readonly extreme?: number;
5578
5699
  }
5579
5700
 
5701
+ export declare const guardUndeclaredTables: (underlying: DataStore, isKnownTable: (table: string) => boolean) => DataStore;
5702
+
5580
5703
  /** What a user handler may return. */
5581
5704
  export declare type HandlerBody<R = never> = void | Promise<unknown> | Effect.Effect<unknown, unknown, R>;
5582
5705
 
@@ -6016,6 +6139,12 @@ export declare const isIdleNow: (opts: IdleCheckOptions) => Promise<boolean>;
6016
6139
 
6017
6140
  export declare const isInfraError: (e: unknown) => boolean;
6018
6141
 
6142
+ /** Narrow a reaction definition to the level form. */
6143
+ export declare const isLevelReaction: (def: ReactionDefinition) => def is LevelReactionDefinition;
6144
+
6145
+ /** Narrow a reaction to the level form. */
6146
+ export declare const isLevelWatch: (watch: ReactionWatch) => watch is ReactionLevelWatch;
6147
+
6019
6148
  export declare const isObservingGraph: () => boolean;
6020
6149
 
6021
6150
  /**
@@ -6123,6 +6252,8 @@ export declare const isTransactionReadConflict: (err: unknown) => err is Transac
6123
6252
  /** Is `address` one of the peers we are willing to take a forwarded chain from? */
6124
6253
  export declare const isTrustedProxy: (address: string | undefined, config: TrustedProxyConfig) => boolean;
6125
6254
 
6255
+ export declare const isWellFormedServiceToken: (value: string) => boolean;
6256
+
6126
6257
  export declare const isWorkflowEventTriggerDefinition: (value: unknown) => value is WorkflowEventTriggerDefinition;
6127
6258
 
6128
6259
  /** A maintainable aggregate op. (countDistinct / window funcs are NOT here —
@@ -6246,6 +6377,34 @@ export declare const lastWriteProvenance: (row: Record<string, unknown> | null |
6246
6377
  readonly traceId?: string | null;
6247
6378
  }) => ProvenanceResult;
6248
6379
 
6380
+ /**
6381
+ * A reaction to every ROW of a live query's current result (level-triggered).
6382
+ *
6383
+ * The act is a workflow, and the workflow's `idempotencyKey` is what makes it
6384
+ * exactly-once across the fleet: every replica that sees the row may try to
6385
+ * start it, and the engine collapses them into one execution. So there is no
6386
+ * `dedupeKey` here, and the workflow's key must identify the unit of work — a
6387
+ * row that can come BACK into the set (an org suspended, unsuspended, suspended
6388
+ * again) needs a key carrying that generation (`suspend:${orgId}:${suspendedAt}`).
6389
+ *
6390
+ * The workflow must move the row OUT of the set (its first step flips a status,
6391
+ * its failure path records a terminal one). A row still in the set long after
6392
+ * it was started is reported as `stuck`, never silently restarted.
6393
+ */
6394
+ export declare interface LevelReactionDefinition extends ReactionBase {
6395
+ readonly watch: ReactionLevelWatch;
6396
+ /** The identity of a row in the set — what `stuck` reports and the runner tracks. */
6397
+ readonly key: (row: never) => string;
6398
+ /** Only rows it returns true for act. */
6399
+ readonly when?: (row: never) => boolean;
6400
+ /** Re-read the whole set this often even when nothing changed, so a start that
6401
+ * failed is retried. Default 5 minutes, minimum 30 seconds. */
6402
+ readonly resync?: number | string;
6403
+ /** Report a row still in the set this long after it was started. Default 15 minutes. */
6404
+ readonly stuckAfter?: number | string;
6405
+ readonly guards?: Omit<ReactionGuards, 'dedupeKey'>;
6406
+ }
6407
+
6249
6408
  export declare class LinearScanMatcher implements Matcher {
6250
6409
  private readonly byTable;
6251
6410
  register(sub: SubscriptionRecord): void;
@@ -6556,8 +6715,18 @@ export declare const makeEventPublisher: (ctx: EventPublishContext) => EventPubl
6556
6715
 
6557
6716
  export declare const makeEventsFacade: (options: EventsFacadeOptions) => EventsAppContext;
6558
6717
 
6559
- /** Convenience: derive a key from a passphrase and build the cipher. */
6560
- export declare const makeFieldCipher: (passphrase: string) => FieldCipher;
6718
+ /**
6719
+ * Derive a key from a passphrase and build the cipher — with the keys a
6720
+ * ROTATION retires, when there are any.
6721
+ *
6722
+ * Writes use the current key; reads try the current one, then each previous
6723
+ * one. GCM authenticates, so a wrong key fails its tag rather than producing
6724
+ * garbage, and trying them in turn is safe. Without this, a key change made
6725
+ * every value written before it unreadable — measured: every sleeping or
6726
+ * suspended run of an `encrypted: true` workflow failed its replay with
6727
+ * `MalformedMessage`, and every `.encrypted()` column read failed.
6728
+ */
6729
+ export declare const makeFieldCipher: (passphrase: string, previous?: ReadonlyArray<string>) => FieldCipher;
6561
6730
 
6562
6731
  /** Build the fluent `select` / `update` / `delete` entry functions over a
6563
6732
  * backend + scope. Wired into `wrapStoreWithMixinBehaviour` so they hang
@@ -6999,6 +7168,16 @@ declare interface MemoryRywOptions {
6999
7168
  */
7000
7169
  export declare const memoryRywPositionStore: (options?: MemoryRywOptions) => RywPositionStore;
7001
7170
 
7171
+ /**
7172
+ * Merge a compression result's headers onto a response's own. `Vary` is a
7173
+ * LIST: a route that already varies (`Vary: Authorization, Cookie` — a
7174
+ * cached answer that differs per credential) keeps its fields and gains
7175
+ * `Accept-Encoding`. Spreading the two objects instead let the lower-case
7176
+ * `vary` replace the route's `Vary` (HTTP header names are case-insensitive),
7177
+ * and a shared cache could then serve one caller's answer to another.
7178
+ */
7179
+ export declare const mergeCompressionHeaders: <V>(own: Readonly<Record<string, V>>, compression: Readonly<Record<string, string>>) => Record<string, V | string>;
7180
+
7002
7181
  /**
7003
7182
  * Merge a name→value map into a `Cookie` header string, replacing same-named
7004
7183
  * pairs and preserving every other cookie the connection carries.
@@ -7069,6 +7248,11 @@ export declare type MetricsMode = 'off' | 'console' | 'otlp' | 'memory';
7069
7248
  /** The lowest cadence any platform scheduler can express. */
7070
7249
  export declare const MIN_EXTERNALIZABLE_INTERVAL_MS = 60000;
7071
7250
 
7251
+ export declare const MIN_LEVEL_RESYNC_MS = 30000;
7252
+
7253
+ /** Mint a fresh service token. `voltro dev` and `voltro secret generate` call this. */
7254
+ export declare const mintServiceToken: () => string;
7255
+
7072
7256
  /**
7073
7257
  * Ordering token for one mirrored write.
7074
7258
  *
@@ -7738,6 +7922,10 @@ export declare interface OutboxFacadeDeps {
7738
7922
  readonly afterCommit?: (work: () => Promise<unknown>) => void;
7739
7923
  /** Wake the drain loop. */
7740
7924
  readonly nudge?: () => void;
7925
+ /** The declared handler's `maxAttempts` for an effect. The row carries its
7926
+ * attempt budget from the moment it is enqueued, so this is where a
7927
+ * handler's `maxAttempts` takes effect; an enqueue's own option wins. */
7928
+ readonly maxAttemptsOf?: (effect: string) => number | undefined;
7741
7929
  readonly now?: () => Date;
7742
7930
  }
7743
7931
 
@@ -8282,17 +8470,23 @@ export declare type ReactionAct = {
8282
8470
  readonly payload?: (event: ReactionEvent) => unknown;
8283
8471
  };
8284
8472
 
8285
- export declare interface ReactionDefinition {
8473
+ declare interface ReactionBase {
8286
8474
  readonly name: string;
8287
- /** The table whose changes trigger the reaction (+ optional op filter). */
8288
- readonly watch: {
8289
- readonly table: string;
8290
- readonly on?: ReactionOp | ReadonlyArray<ReactionOp>;
8291
- };
8292
- /** Optional predicate over the change — skip when it returns false. */
8293
- readonly when?: (event: ReactionEvent) => boolean;
8294
8475
  readonly act: ReactionAct;
8295
- readonly guards: ReactionGuards;
8476
+ }
8477
+
8478
+ export declare type ReactionDefinition = EdgeReactionDefinition | LevelReactionDefinition;
8479
+
8480
+ /** `'5 minutes'` / `'30s'` / `'8 days'` / `300000` → milliseconds. */
8481
+ export declare const reactionDurationMs: (value: number | string, field: string, reaction: string) => number;
8482
+
8483
+ /**
8484
+ * The EDGE form: react to each change of one table, as it happens.
8485
+ * Best-effort — a change made while the reacting process is down is not seen.
8486
+ */
8487
+ export declare interface ReactionEdgeWatch {
8488
+ readonly table: string;
8489
+ readonly on?: ReactionOp | ReadonlyArray<ReactionOp>;
8296
8490
  }
8297
8491
 
8298
8492
  /** The change a reaction reacts to (the framework's CDC shape). */
@@ -8338,6 +8532,23 @@ export declare interface ReactionGuards {
8338
8532
  };
8339
8533
  }
8340
8534
 
8535
+ /**
8536
+ * The LEVEL form: react to every ROW of a live query's current result — the set
8537
+ * of open work — for as long as it is in the set.
8538
+ *
8539
+ * Each delivery of the query is the whole current set, so work that appeared
8540
+ * while the reacting process was down, restarting or deploying is seen on the
8541
+ * first delivery after it comes back: nothing is lost. `api` names a binding in
8542
+ * `apis:` (the query is another app's `service:` query); omit it to watch one
8543
+ * of this app's own queries.
8544
+ */
8545
+ export declare interface ReactionLevelWatch {
8546
+ readonly query: string;
8547
+ readonly api?: string;
8548
+ /** The query's input. Default `{}`. */
8549
+ readonly input?: unknown;
8550
+ }
8551
+
8341
8552
  export declare type ReactionOp = 'insert' | 'update' | 'delete';
8342
8553
 
8343
8554
  export declare type ReactionOutcome = 'acted' | 'skipped-op' | 'skipped-when' | 'skipped-dedupe' | 'skipped-ratelimit' | 'skipped-budget';
@@ -8396,6 +8607,8 @@ export declare interface ReactionRunDeps {
8396
8607
  readonly now?: () => number;
8397
8608
  }
8398
8609
 
8610
+ export declare type ReactionWatch = ReactionEdgeWatch | ReactionLevelWatch;
8611
+
8399
8612
  /** The reactive tunables a caller may declare. Every field optional; every
8400
8613
  * default above. */
8401
8614
  export declare interface ReactiveConfigInput {
@@ -8723,7 +8936,7 @@ export declare const recordTableAccess: (table: string, op: ObservedOp) => void;
8723
8936
 
8724
8937
  /** Feed a committed change into the process recorder (gated + loop-safe). The
8725
8938
  * serve paths call this from their `store.onChange` tap. */
8726
- export declare const recordTimelineEvent: (change: CdcChange & {
8939
+ export declare const recordTimelineEvent: (change: CdcChange & Pick<ChangeEvent, "oversized" | "netChange"> & {
8727
8940
  readonly tenantId?: string | null;
8728
8941
  }) => void;
8729
8942
 
@@ -9522,7 +9735,8 @@ export declare type ResolveSubjectFn = (input: {
9522
9735
  export declare const resolveTimelineMode: (env?: Record<string, string | undefined>) => TimelineMode;
9523
9736
 
9524
9737
  /** Select identity before opening a transaction, never while one owns writes.
9525
- * Only the framework's system subject may select a different tenant. */
9738
+ * Only a cross-tenant machine subject — the framework's system subject, or a
9739
+ * service calling a `service:` procedure — may select a different tenant. */
9526
9740
  export declare const resolveTransactionSubject: (subject: Subject, tenantId: string | undefined, transactionActive: boolean) => Subject;
9527
9741
 
9528
9742
  /**
@@ -10035,6 +10249,9 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
10035
10249
  * boot-path parity rule asks for.
10036
10250
  */
10037
10251
  readonly publishSurfaceDigest?: boolean;
10252
+ /** The bound tags that are `service:` procedures — published as
10253
+ * `serviceSurface`, kept out of the client `surface`. */
10254
+ readonly serviceSurfaceTags?: ReadonlySet<string>;
10038
10255
  /** Non-sensitive fleet identity written on the rpc WebSocket open/close
10039
10256
  * records. Defaults to the process identity resolver (POD_NAME/HOSTNAME).
10040
10257
  * Embedders may state it explicitly when they own process identity. */
@@ -10248,7 +10465,7 @@ export declare const runProvidedEffect: <A>(effect: Effect.Effect<A, unknown, ne
10248
10465
  * `agentActor(via:'agent')` scoped to the row's tenant. Idempotent: the same
10249
10466
  * dedupe key never acts twice.
10250
10467
  */
10251
- export declare const runReaction: (reaction: ReactionDefinition, event: ReactionEvent, deps: ReactionRunDeps) => Promise<ReactionOutcome>;
10468
+ export declare const runReaction: (reaction: EdgeReactionDefinition, event: ReactionEvent, deps: ReactionRunDeps) => Promise<ReactionOutcome>;
10252
10469
 
10253
10470
  /**
10254
10471
  * Start the full single-node orchestrator: bind the router on the public
@@ -10553,10 +10770,13 @@ export declare type ScheduleFireInterceptor = (next: () => Promise<void>, ctx: S
10553
10770
  * thing that silently did nothing. Same bridge a file-based migration's `up`
10554
10771
  * has carried since it shipped.
10555
10772
  *
10556
- * The app supplies its outbound HttpClient, including its tracing and SSRF
10557
- * policy. Other requirements must be provided by the handler.
10773
+ * The app supplies its outbound HttpClient (tracing and SSRF policy
10774
+ * included) AND the same service layer a mutation handler runs under —
10775
+ * plugin services such as `MailService`, the app's own layers — so a schedule
10776
+ * may `yield*` them. Both `voltro dev` and `voltro serve` build the runner
10777
+ * from that layer; the type is open on requirements for exactly that reason.
10558
10778
  */
10559
- export declare type ScheduleHandler = (ctx: ScheduleContext) => void | Promise<void> | Effect.Effect<unknown, unknown, HttpClient.HttpClient>;
10779
+ export declare type ScheduleHandler = (ctx: ScheduleContext) => void | Promise<void> | Effect.Effect<unknown, unknown, HttpClient.HttpClient | unknown>;
10560
10780
 
10561
10781
  /** What happens when a firing arrives while the previous run of the
10562
10782
  * same schedule is still in flight. */
@@ -11208,6 +11428,49 @@ export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown,
11208
11428
  /** The published tracer, or `undefined` when tracing is off / no server booted. */
11209
11429
  export declare const serverTracer: () => Tracer.Tracer | undefined;
11210
11430
 
11431
+ /** The bearer prefix every service credential carries. */
11432
+ export declare const SERVICE_TOKEN_PREFIX = "vsvc_";
11433
+
11434
+ /**
11435
+ * One accepted caller. Declared in the callee's `app.config.ts`:
11436
+ *
11437
+ * serviceCallers: { worker: {} }
11438
+ *
11439
+ * The object is the per-caller configuration slot; today it carries nothing,
11440
+ * because the token comes from the environment by convention and the grant
11441
+ * comes from the procedures. It is an object rather than `true` so a later
11442
+ * per-caller option (a rate limit, an mTLS identity) is an addition, not a
11443
+ * change of shape.
11444
+ */
11445
+ export declare type ServiceCallerConfig = Readonly<Record<string, never>>;
11446
+
11447
+ /** Callee side: the second accepted value during a rotation. */
11448
+ export declare const serviceCallerNextTokenEnv: (caller: string) => string;
11449
+
11450
+ export declare type ServiceCallersConfig = Readonly<Record<string, ServiceCallerConfig>>;
11451
+
11452
+ /**
11453
+ * The strategy an app with `serviceCallers` puts FIRST in its chain.
11454
+ *
11455
+ * It claims only `Bearer vsvc_…`. Every other request is a `skip`, so an app's
11456
+ * users never pay more than a prefix check.
11457
+ */
11458
+ export declare const serviceCallerStrategy: (options: ServiceCallerStrategyOptions) => AuthStrategy;
11459
+
11460
+ export declare interface ServiceCallerStrategyOptions {
11461
+ readonly callers: ServiceCallersConfig;
11462
+ /** Read lazily — at request time and in `verifyConfig` — never while the
11463
+ * config is imported, so `voltro build` needs no production secret. */
11464
+ readonly env?: () => Readonly<Record<string, string | undefined>>;
11465
+ }
11466
+
11467
+ /** Callee side: the env var holding the token a caller presents. */
11468
+ export declare const serviceCallerTokenEnv: (caller: string) => string;
11469
+
11470
+ /** `worker` → `WORKER`, `build-runner` → `BUILD_RUNNER`, `voltroCloudWorker` →
11471
+ * `VOLTRO_CLOUD_WORKER`. The env-var spelling of an app name. */
11472
+ export declare const serviceEnvSegment: (name: string) => string;
11473
+
11211
11474
  export declare const setBufferedBytes: (label: string, bytes: number) => void;
11212
11475
 
11213
11476
  /** Register the process-wide connection resolver (or clear with `undefined`).
@@ -11718,6 +11981,19 @@ export declare interface SubscriptionBackpressureOptions {
11718
11981
  readonly oversizedEventBytes: number;
11719
11982
  /** Query tag for logs + metrics. */
11720
11983
  readonly label: string;
11984
+ /**
11985
+ * The subscription's CRDT columns (plan 18), when its table has any. Its
11986
+ * deltas carry those cells as INCREMENTAL updates (`mergeCells`), so the
11987
+ * rows kept here fold them into the held cells (`merge`), and a coalesced
11988
+ * patch diffs them incrementally again (`incremental`). Without it a held
11989
+ * cell would become the last increment, and a coalesced patch would REPLACE
11990
+ * the client's whole text with it.
11991
+ */
11992
+ readonly crdt?: {
11993
+ readonly columns: ReadonlySet<string>;
11994
+ readonly merge: (column: string, held: unknown, update: unknown) => unknown;
11995
+ readonly incremental: (column: string, prevValue: unknown, nextValue: unknown) => unknown | undefined;
11996
+ };
11721
11997
  readonly now?: () => number;
11722
11998
  readonly warn?: (message: string, fields: Record<string, unknown>) => void;
11723
11999
  }
@@ -11978,6 +12254,9 @@ export declare const TERMINAL_RUN_STATUSES: ReadonlySet<string>;
11978
12254
  export declare type TimeBucket = 'minute' | 'hour' | 'day' | 'week' | 'month';
11979
12255
 
11980
12256
  export declare interface TimelineEvent {
12257
+ /** Preserves the store's warning that images are not original commit images. */
12258
+ readonly oversized?: ChangeEvent['oversized'];
12259
+ readonly netChange?: ChangeEvent['netChange'];
11981
12260
  /** Monotonic per-process sequence — the scrubber's x-axis. */
11982
12261
  readonly seq: number;
11983
12262
  /** Wall-clock ms when recorded. */
@@ -12011,13 +12290,18 @@ export declare class TimelineRecorder {
12011
12290
  private readonly mode;
12012
12291
  private readonly capacity;
12013
12292
  private readonly now;
12293
+ /** Identity of this bounded history; sequence numbers are only meaningful within it. */
12294
+ private historyId;
12295
+ get recorderId(): string;
12296
+ /** A proven delivery gap makes every earlier reconstruction unsafe. */
12297
+ invalidate(): void;
12014
12298
  private readonly ring;
12015
12299
  private seqCounter;
12016
12300
  constructor(mode?: TimelineMode, capacity?: number, now?: () => number);
12017
12301
  get enabled(): boolean;
12018
12302
  /** Record one change (assigns the next seq, redacts, applies the mode gate +
12019
12303
  * the no-feedback skip). Returns the assigned seq, or -1 when skipped. */
12020
- record(change: CdcChange & {
12304
+ record(change: CdcChange & Pick<ChangeEvent, 'oversized' | 'netChange'> & {
12021
12305
  readonly tenantId?: string | null;
12022
12306
  }): number;
12023
12307
  /** The highest seq recorded so far (the scrubber's "now"). */
@@ -12301,6 +12585,17 @@ export declare const tryClaimWakeup: (store: DataStore, ref: WakeupRef) => Promi
12301
12585
  * `_voltro_rebac_tuples`; an app whose relationships live in its own tables
12302
12586
  * (a `teamMembers` row, say) registers its own instead of copying data into a
12303
12587
  * framework table.
12588
+ *
12589
+ * Return `'missing'` when the resource itself does not exist for this caller:
12590
+ * the guard then refuses with the typed `NoSuchResource` instead of
12591
+ * `ScopeError`, so an id-route can answer "not found" before its executor runs.
12592
+ * The source is the only place that can know — an empty tuple list means "no
12593
+ * relation", never "no row", because inferring absence from it would tell any
12594
+ * caller which ids exist. Decide the disclosure yourself: answer `'missing'`
12595
+ * from a tenant-scoped read (a row in another tenant then reads as missing, not
12596
+ * as "exists, not yours"), and never for a resource the caller could not have
12597
+ * learnt about otherwise. An anonymous caller never receives it, whatever the
12598
+ * source answers.
12304
12599
  */
12305
12600
  export declare type TupleSource = (req: {
12306
12601
  readonly subjectId: string | null;
@@ -12346,7 +12641,7 @@ export declare type TupleSource = (req: {
12346
12641
  * legitimately have no request.
12347
12642
  */
12348
12643
  readonly load: DataLoader | undefined;
12349
- }) => Promise<ReadonlyArray<RelationTuple>> | ReadonlyArray<RelationTuple>;
12644
+ }) => Promise<ReadonlyArray<RelationTuple> | ResourceMissing> | ReadonlyArray<RelationTuple> | ResourceMissing;
12350
12645
 
12351
12646
  /** The mutation runner's key for opening the read-version map on a transaction
12352
12647
  * view it obtained from the dialect store directly. */
@@ -12365,6 +12660,39 @@ declare interface TypedUpsertOptions<T extends AnyWritableTable> {
12365
12660
  */
12366
12661
  export declare const unbindConnection: (clientId: number) => void;
12367
12662
 
12663
+ /**
12664
+ * The tagged failures an executor can produce that its procedure's wire union
12665
+ * does not carry.
12666
+ *
12667
+ * Such a failure does not reach the caller as itself. On a mutation or action
12668
+ * the server refuses to encode a tag its union cannot represent and sends
12669
+ * `InternalError` instead; on a query it arrives under a tag the client's typed
12670
+ * union does not know. Either way the error the author raised is not the error
12671
+ * the caller can match on — and before this check, widening a shared helper's
12672
+ * error type compiled at every call site while every one of them started
12673
+ * failing that way.
12674
+ *
12675
+ * UNTAGGED failures (`Error`, `unknown`) are not reported: they never claimed to
12676
+ * be a typed outcome, and the runtime treats them as defects by construction.
12677
+ *
12678
+ * Nor are the STORE's refusals (`StoreError`: `ConstraintViolation`,
12679
+ * `TenantScopeViolation`, …). The framework raises them, not the handler's
12680
+ * author, and what an undeclared one does is a documented contract: it reaches
12681
+ * the caller as `InternalError` carrying its own sentence; declare it to surface
12682
+ * it typed. Every `EffectStore` write carries the whole set in its failure
12683
+ * channel, so counting them made the check fire on every write executor —
12684
+ * measured on one app: 550 of 564 findings, burying the 14 that were the
12685
+ * app's own tags, which is what this check exists to find.
12686
+ */
12687
+ export declare type UndeclaredErrorTags<D extends ExecutorDescriptor, R> = Exclude<FailureTag<ExecutorFailure<R>>, FailureTag<ProcedureWireError<D>> | FailureTag<StoreError>>;
12688
+
12689
+ export declare class UndeclaredTableAccess extends Error {
12690
+ readonly table: string;
12691
+ readonly operation: string;
12692
+ readonly _tag = "UndeclaredTableAccess";
12693
+ constructor(table: string, operation: string);
12694
+ }
12695
+
12368
12696
  /** The store surface an inverse op needs (a subset of the framework DataStore). */
12369
12697
  export declare interface UndoApplyStore {
12370
12698
  insert(table: string, row: Record<string, unknown>): Promise<unknown> | unknown;
@@ -13025,11 +13353,10 @@ export declare interface WorkflowFacadeOptions {
13025
13353
  * Inspect HTTP + the `voltro workflows retry` CLI delegate to the same
13026
13354
  * implementation. */
13027
13355
  readonly retry?: (runId: string, options: WorkflowRetryOptions) => Promise<WorkflowRetryResult>;
13028
- /** Backs {@link WorkflowsAppContext.redrive}. Supplied by the CLI's facade
13029
- * builder, which owns the runs/steps store query + the cluster journal
13030
- * adapter. Inspect HTTP + `voltro workflows redrive` delegate to the same
13031
- * implementation. */
13032
- readonly redrive?: (runId: string) => Promise<WorkflowRedriveResult>;
13356
+ /** Shared resident-authorized restart command and receipt adapters. */
13357
+ readonly restart?: (command: WorkflowRestartCommand) => Promise<WorkflowRestartResult>;
13358
+ readonly readRestart?: (target: Pick<WorkflowRestartCommand, 'runId' | 'requestId'>) => Promise<WorkflowRestartObservation>;
13359
+ readonly withdrawRestart?: (command: WorkflowRestartCommand) => Promise<WorkflowRestartObservation>;
13033
13360
  /** Fired (fire-and-forget) right after a workflow is ENQUEUED — a start,
13034
13361
  * child, or run. The CLI wires this to a cross-replica "wake" broadcast so
13035
13362
  * the replica that OWNS the new run's shard polls immediately instead of
@@ -13207,16 +13534,6 @@ export declare interface WorkflowReadCtx {
13207
13534
  } | undefined;
13208
13535
  }
13209
13536
 
13210
- /** Result of {@link WorkflowsAppContext.redrive} — whether the failed run's
13211
- * durable journal was re-driven, how many failed step attempts were reset so
13212
- * they re-execute, and a `reason` when it declined (no journal / still
13213
- * running / already succeeded). */
13214
- export declare interface WorkflowRedriveResult {
13215
- readonly redriven: boolean;
13216
- readonly activitiesReset: number;
13217
- readonly reason: string | null;
13218
- }
13219
-
13220
13537
  /** Options for {@link WorkflowsAppContext.retry}. */
13221
13538
  export declare interface WorkflowRetryOptions {
13222
13539
  /** Persist once per deliberate retry and reuse after a lost response. */
@@ -13404,14 +13721,13 @@ export declare interface WorkflowsAppContext {
13404
13721
  retry(runId: string, options: WorkflowRetryOptions): Promise<WorkflowRetryResult>;
13405
13722
  /** Observe a durable retry intent without submitting another attempt. */
13406
13723
  readRetry(runId: string, requestId: string): Promise<WorkflowRetryObservation>;
13407
- /** Re-drive a terminally-`failed` run from the step it died on, reusing
13408
- * its durable journal — the counterpart to {@link retry} (fresh execution,
13409
- * empty journal) and to {@link resume} (which only re-drives a *suspended*
13410
- * run). Addressed by run id (`wfrun_…`). Completed steps replay from the
13411
- * journal; only the failed steps re-execute. Fix the downstream cause
13412
- * first, then redrive. Refuses a run that is not a not-yet-discarded
13413
- * failure. */
13414
- redrive(runId: string): Promise<WorkflowRedriveResult>;
13724
+ /** Restart a failed run with its recorded successful steps. Persist the request
13725
+ * ID and selection before submitting; a pending receipt is not engine entry. */
13726
+ restart(runId: string, options: Omit<WorkflowRestartCommand, 'runId'>): Promise<WorkflowRestartResult>;
13727
+ /** Read a saved restart request without dispatching work. */
13728
+ readRestart(runId: string, requestId: string): Promise<WorkflowRestartObservation>;
13729
+ /** Permanently fence an unreserved request. Accepted work is returned unchanged. */
13730
+ withdrawRestart(runId: string, options: Omit<WorkflowRestartCommand, 'runId'>): Promise<WorkflowRestartObservation>;
13415
13731
  }
13416
13732
 
13417
13733
  export declare interface WorkflowSignalTarget {
@@ -13563,3 +13879,10 @@ export declare const wrapStoreWithMixinBehaviour: (rawUnderlying: DataStore, ctx
13563
13879
  export declare const writeReplicaObservation: (store: DataStore, kind: ReplicaObservationKind, payload: unknown) => Promise<void>;
13564
13880
 
13565
13881
  export { }
13882
+
13883
+
13884
+ declare module '@voltro/protocol' {
13885
+ interface VoltroPluginErrors {
13886
+ readonly '@fixture/errors#RegisteredByPlugin': RegisteredByPlugin;
13887
+ }
13888
+ }