@voltro/protocol 0.1.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.
@@ -0,0 +1,2564 @@
1
+ import { Context } from 'effect';
2
+ import { DataStore } from '@voltro/database';
3
+ import { Effect } from 'effect';
4
+ import { Layer } from 'effect';
5
+ import { Rpc } from '@effect/rpc';
6
+ import { RpcMiddleware } from '@effect/rpc';
7
+ import { Schema } from 'effect';
8
+ import { SqlClient } from '@effect/sql';
9
+ import { Stream } from '@effect/rpc/RpcSchema';
10
+
11
+ export declare interface ActionProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
12
+ readonly kind: 'action';
13
+ readonly name: Name;
14
+ readonly input: Input;
15
+ readonly output: Output;
16
+ readonly error: Error;
17
+ /** Opt this action into a public REST endpoint (innovation/11). */
18
+ readonly publicApi: PublicApiSpec | undefined;
19
+ /** Opt this action into the auto-synthesized agent toolset (innovation/07). */
20
+ readonly exposeAsTool: ExposeAsTool | undefined;
21
+ }
22
+
23
+ export declare const actionToRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: ActionProcedureDescriptor<Name, Input, Output, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Schema.All, never>;
24
+
25
+ export declare const ADMIN_SCOPE = "admin:full";
26
+
27
+ export declare const anonymousSubject: (tenantId: string | null) => Subject;
28
+
29
+ /**
30
+ * Apply a `RowPatch` to `prev`, producing `next`. Exact inverse of
31
+ * `diffRows`: `applyRowPatch(prev, diffRows(prev, next))` deep-equals
32
+ * `next` for adds, removes, replaces, and reorderings.
33
+ *
34
+ * Builds an id→row map from prev, applies `add` / `replace` (which set the
35
+ * id → next row), then materialises the result strictly in the patch's
36
+ * `order`. A `remove`d id simply isn't in `order`, so it drops out of the
37
+ * output naturally — no key-type juggling to delete it from the map. Any
38
+ * id in `order` that the map doesn't resolve (a desync — the client missed
39
+ * an `add`) is skipped rather than throwing, so one dropped frame degrades
40
+ * to a slightly stale set instead of a crash.
41
+ */
42
+ export declare const applyRowPatch: (prev: ReadonlyArray<PatchRow>, patch: RowPatch) => ReadonlyArray<PatchRow>;
43
+
44
+ /**
45
+ * Throws `Unauthenticated` when the resolved Subject is anonymous (no
46
+ * real user identity). Handlers that require a signed-in caller put
47
+ * this as the first line of their executor:
48
+ *
49
+ * ```ts
50
+ * const execute = async (input, ctx) => {
51
+ * assertAuthenticated(ctx.request.subject)
52
+ * …
53
+ * }
54
+ * ```
55
+ */
56
+ export declare const assertAuthenticated: (subject: Subject, reason?: string) => void;
57
+
58
+ /** Optional callback-route mount surface for strategies that handle
59
+ * OAuth callbacks. Apps wire these via the framework's HTTP router
60
+ * when the strategy declares `mountRoutes`. */
61
+ export declare interface AuthCallbackRouter {
62
+ readonly get: (path: string, handler: AuthRouteHandler) => void;
63
+ readonly post: (path: string, handler: AuthRouteHandler) => void;
64
+ }
65
+
66
+ /**
67
+ * The framework's per-request auth middleware. Attach to an RpcGroup with
68
+ * `group.middleware(AuthMiddleware)` to require every call to flow through
69
+ * an auth resolver before reaching its handler. The application provides
70
+ * the resolver via `Layer.succeed(AuthMiddleware, AuthMiddleware.of(fn))`,
71
+ * where `fn` receives `{headers, payload, rpc, clientId}` and returns an
72
+ * `Effect<Subject>` — typically by inspecting headers / Bearer token and
73
+ * looking the caller up.
74
+ *
75
+ * `provides: SubjectService` means the resolver's return value is placed
76
+ * into the handler's context tagged as SubjectService; handlers read it
77
+ * with `yield* SubjectService`. Connections with no headers (or whatever
78
+ * the resolver requires) can fail the middleware — that becomes a
79
+ * mutationResult error / error frame for the caller.
80
+ *
81
+ * Apps that don't need per-request resolution can skip attaching this
82
+ * middleware and provide SubjectService directly with a static
83
+ * `Layer.succeed(SubjectService, anonymousSubject(tenantId))` instead.
84
+ */
85
+ export declare class AuthMiddleware extends AuthMiddleware_base {
86
+ }
87
+
88
+ declare const AuthMiddleware_base: RpcMiddleware.TagClass<AuthMiddleware, "@voltro/AuthMiddleware", {
89
+ readonly provides: typeof SubjectService;
90
+ }>;
91
+
92
+ export declare type AuthRouteHandler = (req: AuthRouteRequest) => Promise<AuthRouteResponse>;
93
+
94
+ export declare interface AuthRouteRequest {
95
+ readonly url: URL;
96
+ readonly headers: Readonly<Record<string, string>>;
97
+ readonly body: () => Promise<string>;
98
+ }
99
+
100
+ export declare interface AuthRouteResponse {
101
+ readonly status: number;
102
+ readonly headers?: Readonly<Record<string, string>>;
103
+ readonly body: string;
104
+ }
105
+
106
+ /** Pluggable auth strategy. Strategies are SYNC-fast on no-match
107
+ * (cookie-name lookup) and cache any JWKS / DB roundtrips on match
108
+ * so steady-state verification stays CPU-local. */
109
+ export declare interface AuthStrategy {
110
+ /** Stable id (`'voltro-password'`, `'workos'`, `'kinde'`, …). Used
111
+ * in logs + `Subject.metadata.provider`. */
112
+ readonly id: string;
113
+ readonly resolve: (input: AuthStrategyInput) => Promise<StrategyResolution> | StrategyResolution;
114
+ }
115
+
116
+ /** Per-call input the framework hands every strategy. */
117
+ export declare interface AuthStrategyInput {
118
+ readonly headers: Readonly<Record<string, string | undefined>>;
119
+ readonly clientId: number;
120
+ }
121
+
122
+ /** Strategies that need server-side callbacks (OAuth code-exchange,
123
+ * magic-link landing, etc.) ALSO export this companion via the
124
+ * `routes` factory below. Kept separate from `AuthStrategy.resolve`
125
+ * so apps that only need pure JWT-verify strategies don't pay the
126
+ * callback-wiring cost. */
127
+ export declare interface AuthStrategyWithCallback extends AuthStrategy {
128
+ readonly mountRoutes: (router: AuthCallbackRouter) => void;
129
+ }
130
+
131
+ /**
132
+ * Decide what to do for an incoming (scope, key) in ONE atomic store call. On
133
+ * `fresh` the caller runs the handler then calls `finish`/`fail`; on
134
+ * `replay`/`conflict` it returns immediately without touching the handler.
135
+ */
136
+ export declare const beginIdempotent: (store: IdempotencyStore, scope: string, key: string, ttlMs: number, now: number) => Promise<IdempotencyOutcome>;
137
+
138
+ /**
139
+ * Validate `plugin.framework` (npm-semver range) against the running
140
+ * voltro version. Returns either `{ ok: true }` or
141
+ * `{ ok: false, reason }` so the boot path can log a clear warning.
142
+ *
143
+ * Intentionally NOT throwing — incompat is a soft signal in v1.
144
+ * Aborting boot on a missed minor would freeze ecosystem evolution.
145
+ * The framework logs WARN and proceeds; the operator gets visibility
146
+ * + decides whether to pin or upgrade.
147
+ *
148
+ * Supports the subset apps in the wild actually use:
149
+ * - `^x.y.z` — caret: same major
150
+ * - `~x.y.z` — tilde: same major+minor
151
+ * - `>=x.y.z` — minimum version
152
+ * - `>=x.y <a.b` — compound (AND)
153
+ * - `x.y.z` — exact
154
+ * - `*` — any
155
+ */
156
+ export declare const checkFrameworkCompat: (pluginName: string, range: string | undefined, runningVersion: string) => {
157
+ ok: true;
158
+ } | {
159
+ ok: false;
160
+ reason: string;
161
+ };
162
+
163
+ export declare interface ClientDescriptor {
164
+ readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
165
+ /** Query only: table(s) the cache should match this subscription against. */
166
+ readonly source?: string | ReadonlyArray<string> | undefined;
167
+ /** Mutation only: tables this mutation writes, with op. */
168
+ readonly targets?: ReadonlyArray<ClientTarget> | undefined;
169
+ /** The procedure's input `Schema`, carried to the client so schema-driven
170
+ * UI (forms, query-bound pickers) and the capability map can introspect
171
+ * it with no extra fetch. Browser-safe: the schema is already loaded
172
+ * value-level via the rpc group, so this is the same object by reference. */
173
+ readonly input?: Schema.Schema.Any | undefined;
174
+ /** The procedure's output `Schema` — carried so schema-driven tables
175
+ * (`<DataTable>` derives its columns from it) + the capability map can
176
+ * introspect it. Same browser-safety rationale as `input`. */
177
+ readonly output?: Schema.Schema.Any | undefined;
178
+ }
179
+
180
+ /**
181
+ * Client-side view of a target. Carries the same fields as `TargetSpec`
182
+ * but with input/row generics erased — the client sees concrete runtime
183
+ * row shapes, not Schema instances. Optional `shape`/`identify` flow
184
+ * through because the codegen references the live descriptor (rather
185
+ * than emitting JSON), so function refs survive the ES-module boundary.
186
+ */
187
+ export declare interface ClientTarget {
188
+ readonly table: string;
189
+ readonly op: 'insert' | 'update' | 'delete';
190
+ readonly order?: 'prepend' | 'append' | undefined;
191
+ readonly shape?: ((input: Record<string, unknown>, optimisticIdOrCurrent?: unknown) => Record<string, unknown>) | undefined;
192
+ readonly identify?: ((input: Record<string, unknown>) => string) | undefined;
193
+ }
194
+
195
+ /** Compose multiple strategies into a single resolver function. The
196
+ * composer evaluates them in declaration order; first `matched`
197
+ * wins; first `failed` short-circuits to anonymous (does NOT fall
198
+ * through — see StrategyResolution doc).
199
+ *
200
+ * Apps wrap the return in `AuthMiddleware.of(...)` themselves so they
201
+ * can inject the connection-override fast-path (`getConnectionSubject`)
202
+ * ahead of the strategy chain — that path lives in `@voltro/runtime`
203
+ * and the protocol package stays runtime-agnostic.
204
+ *
205
+ * Returns an async resolver `(input) => Promise<Subject>`.
206
+ */
207
+ export declare const composeAuthStrategies: (strategies: ReadonlyArray<AuthStrategy>, options?: {
208
+ /** Fallback when no strategy matched. Default: returns the input's
209
+ * tenant from `x-tenant` header or null. */
210
+ readonly fallback?: (input: AuthStrategyInput) => Subject;
211
+ /** Hook for logging — called on every `failed` resolution. */
212
+ readonly onStrategyFailed?: (event: {
213
+ strategyId: string;
214
+ reason: string;
215
+ }) => void;
216
+ /**
217
+ * Reject anonymous callers that carry no tenant. When `true` and no
218
+ * strategy matched AND no `x-tenant` header is present, the resolver
219
+ * throws `Unauthenticated` instead of returning a null-tenant
220
+ * anonymous Subject. Use it on multi-tenant apps where every call —
221
+ * even an anonymous one — must be scoped to a tenant; without it a
222
+ * tenant-less anonymous Subject can read across the whole DB on
223
+ * tables that aren't `tenant()`-scoped. Ignored when a custom
224
+ * `fallback` is supplied (the fallback owns that decision).
225
+ */
226
+ readonly anonymousTenantRequired?: boolean;
227
+ }) => ((input: AuthStrategyInput) => Promise<Subject>);
228
+
229
+ /**
230
+ * Compose a list of Effect-native interceptors into ONE function the
231
+ * runtime calls. Reduces right-to-left so the array order reads
232
+ * naturally as outer → inner: `[A, B, C]` produces A(B(C(next))). The
233
+ * output is `undefined` when the list is empty so callers can skip the
234
+ * wrap entirely.
235
+ *
236
+ * Used for mutation / query / action chains alike.
237
+ */
238
+ export declare const composeRpcInterceptors: (interceptors: ReadonlyArray<RpcInterceptor>) => RpcInterceptor | undefined;
239
+
240
+ export declare class ConnectionInfo extends ConnectionInfo_base {
241
+ }
242
+
243
+ declare const ConnectionInfo_base: Context.TagClass<ConnectionInfo, "@voltro/ConnectionInfo", ConnectionInfoValue>;
244
+
245
+ /**
246
+ * Second middleware on every RpcGroup that wants the
247
+ * `ConnectionInfo` service available to handlers. Resolves from the
248
+ * per-call `{clientId}` the @effect/rpc transport passes in. Handlers
249
+ * that re-bind the connection's subject (e.g. `auth.signin` writing
250
+ * an override after a successful credential check) read clientId
251
+ * from this service.
252
+ *
253
+ * Apps without a soft-reauth flow can skip attaching this middleware.
254
+ * The framework provides a default-live Layer in `@voltro/runtime`
255
+ * which the rpcServer wires alongside AuthMiddleware.
256
+ */
257
+ export declare class ConnectionInfoMiddleware extends ConnectionInfoMiddleware_base {
258
+ }
259
+
260
+ declare const ConnectionInfoMiddleware_base: RpcMiddleware.TagClass<ConnectionInfoMiddleware, "@voltro/ConnectionInfoMiddleware", {
261
+ readonly provides: typeof ConnectionInfo;
262
+ }>;
263
+
264
+ export declare interface ConnectionInfoValue {
265
+ /** Per-connection identifier assigned by the rpc transport. Stable for the
266
+ * lifetime of the underlying WebSocket connection; reused as the key for
267
+ * the per-connection subject override map. */
268
+ readonly clientId: number;
269
+ }
270
+
271
+ /**
272
+ * A handle to a cluster-coordinated periodic task the plugin armed via
273
+ * `PluginBindContext.scheduleCoordinated`. Keep it to `stop()` the task
274
+ * in `onDeactivate` — though the framework also stops every task a plugin
275
+ * armed at shutdown, so an explicit `stop()` is only needed for tasks the
276
+ * plugin wants to cancel EARLY (before shutdown).
277
+ */
278
+ export declare interface CoordinatedScheduleHandle {
279
+ /** Stop the periodic task. Idempotent. */
280
+ readonly stop: () => void;
281
+ /** The task name (for logging / dedup diagnostics). */
282
+ readonly name: string;
283
+ }
284
+
285
+ export declare const defineAction: <const Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
286
+ readonly name: Name;
287
+ readonly input: Input;
288
+ readonly output: Output;
289
+ readonly error?: Error;
290
+ /** Project this action as a public REST endpoint (innovation/11). */
291
+ readonly publicApi?: PublicApiSpec;
292
+ /** Expose this action as an agent tool (innovation/07). */
293
+ readonly exposeAsTool?: ExposeAsTool;
294
+ }) => ActionProcedureDescriptor<Name, Input, Output, Error>;
295
+
296
+ export declare const defineMutation: <const Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
297
+ readonly name: Name;
298
+ readonly input: Input;
299
+ readonly output: Output;
300
+ readonly error?: Error;
301
+ /** Optional declarative target(s) — drives auto-optimistic. */
302
+ readonly target?: Target<Schema.Schema.Type<Input>, Schema.Schema.Type<Output>>;
303
+ /** Project this mutation as a public REST endpoint (innovation/11). */
304
+ readonly publicApi?: PublicApiSpec;
305
+ /** Expose this mutation as an agent tool (innovation/07). */
306
+ readonly exposeAsTool?: ExposeAsTool;
307
+ }) => MutationProcedureDescriptor<Name, Input, Output, Error>;
308
+
309
+ /**
310
+ * Builder helper that returns the input object unchanged AT RUNTIME
311
+ * but enforces the `VoltroPlugin` shape at the TYPE level. Use over
312
+ * plain object literals when you want the type-checker to catch
313
+ * misnamed hooks or missing required fields at the plugin's
314
+ * declaration site rather than at the `app.config.ts` consumption
315
+ * site.
316
+ *
317
+ * ```ts
318
+ * export const myPlugin = (options: MyOptions): VoltroPlugin =>
319
+ * definePlugin({
320
+ * name: '@vendor/my-plugin',
321
+ * framework: '^1.0.0',
322
+ * interceptMutation: (next, ctx) =>
323
+ * next.pipe(Effect.tap(() => recordAudit(ctx))),
324
+ * onActivate: (ctx) => Effect.sync(() => ctx.logger.info('warming caches')),
325
+ * onDeactivate: () => Effect.void,
326
+ * })
327
+ * ```
328
+ *
329
+ * Identity function — kept narrow so the compiler picks excess-property
330
+ * checks on the literal.
331
+ */
332
+ export declare const definePlugin: (plugin: VoltroPlugin) => VoltroPlugin;
333
+
334
+ /**
335
+ * Helper to declare one rpc route contributed by a plugin. Returns
336
+ * the input unchanged; exists for the type-level enforcement (plus
337
+ * symmetry with `defineMutation` / `defineQuery` user-side).
338
+ */
339
+ export declare const definePluginRoute: (route: PluginRpcRoute) => PluginRpcRoute;
340
+
341
+ /**
342
+ * Declarative builder for a plugin's `services` layer. Wraps the
343
+ * `Context.Tag` + `Layer.succeed` pair so a plugin author doesn't
344
+ * have to manage both. Use when a plugin's service interface is
345
+ * straightforward (a record of functions); reach for raw Layer when
346
+ * the implementation depends on other services (Layer.effect) or
347
+ * needs resource acquisition (Layer.scoped).
348
+ */
349
+ export declare const definePluginService: <I, S>(identifier: string, implementation: S) => {
350
+ readonly Tag: Context.Tag<I, S>;
351
+ readonly Live: Layer.Layer<I, never, never>;
352
+ };
353
+
354
+ export declare const defineQuery: <const Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
355
+ readonly name: Name;
356
+ readonly input: Input;
357
+ readonly output: Output;
358
+ readonly error?: Error;
359
+ /** Optional: the store table(s) this query reads. Declaring it enables
360
+ * auto-optimistic patch routing from mutations targeting any of those
361
+ * tables. For a COMPUTED query (handler returns a shaped value) it is the
362
+ * reactive trigger set: the handler re-runs when ANY listed table changes
363
+ * — pass an array to depend on several (e.g. a matrix joining two tables). */
364
+ readonly source?: string | ReadonlyArray<string>;
365
+ /** Opt into server-side snapshot caching with auto-invalidation. */
366
+ readonly cache?: QueryCacheConfig;
367
+ /** Project this query as a public REST endpoint (innovation/11). */
368
+ readonly publicApi?: PublicApiSpec;
369
+ /** Expose this query as an agent tool (innovation/07). */
370
+ readonly exposeAsTool?: ExposeAsTool;
371
+ }) => QueryProcedureDescriptor<Name, Input, Output, Error>;
372
+
373
+ /**
374
+ * Declare a non-reactive server→client stream. `handler(input, ctx)`
375
+ * returns a `Stream<Element>` (e.g. an agent run's `AgentEvent`s); the
376
+ * client consumes it via `useAgentStream`. Reusable for ANY push stream.
377
+ */
378
+ export declare const defineStream: <const Name extends string, Input extends Schema.Schema.Any, Element extends Schema.Schema.Any, Error extends Schema.Schema.All = typeof Schema.Never>(options: {
379
+ readonly name: Name;
380
+ readonly input: Input;
381
+ readonly element: Element;
382
+ readonly error?: Error;
383
+ }) => StreamProcedureDescriptor<Name, Input, Element, Error>;
384
+
385
+ export declare interface DeleteTarget<Input = unknown> {
386
+ readonly table: string;
387
+ readonly op: 'delete';
388
+ /** Identify the row to remove. Default: `input.id`. */
389
+ readonly identify?: ((input: Input) => string) | undefined;
390
+ }
391
+
392
+ /**
393
+ * Compute the patch that turns `prev` into `next`, keyed by row id.
394
+ *
395
+ * Algorithm:
396
+ * - Index both sides by id.
397
+ * - For each id in next: not in prev → `add`; in prev but content
398
+ * differs → `replace`; in prev and equal → no op.
399
+ * - For each id in prev but not in next → `remove`.
400
+ * - `order` is next's id sequence verbatim, so apply can reconstruct
401
+ * the exact ordering (including a pure reorder, where `ops` is empty).
402
+ *
403
+ * Throws if any row lacks an `id` — the caller (dispatcher) only diffs
404
+ * id-keyed result sets; a result without ids must ship a full snapshot,
405
+ * not a patch.
406
+ */
407
+ export declare const diffRows: (prev: ReadonlyArray<PatchRow>, next: ReadonlyArray<PatchRow>) => RowPatch;
408
+
409
+ /** Normalize the `exposeAsTool` shorthand. `true` is only valid when the
410
+ * descriptor carries a top-level `description`; callers pass that in. */
411
+ export declare type ExposeAsTool = boolean | ExposeAsToolSpec;
412
+
413
+ export declare interface ExposeAsToolSpec {
414
+ /** Shown to the model — REQUIRED to expose (a tool with no description is
415
+ * unusable). What the tool does + when to call it. */
416
+ readonly description: string;
417
+ /** Require human confirmation of the concrete call before it executes.
418
+ * Default posture: writes (mutation/action) confirm, reads don't. */
419
+ readonly confirm?: boolean;
420
+ /** Cap how many times the agent may call this tool per run. */
421
+ readonly maxPerRun?: number;
422
+ }
423
+
424
+ /**
425
+ * Cross-cutting error schemas a plugin contributes to EVERY procedure's
426
+ * wire error union (see `VoltroPlugin.errorSchemas`). Unioned into each
427
+ * rpc's `error:` so an interceptor that fails with one of these (e.g. a
428
+ * rate-limit `RateLimited`) decodes as a TYPED error on the client
429
+ * instead of crossing the wire as an untyped defect. Passed identically
430
+ * by the server group (cli/dev.ts) and the generated client group
431
+ * (cli/codegen.ts) so both ends agree on the wire shape.
432
+ */
433
+ export declare type ExtraErrors = ReadonlyArray<Schema.Schema.All>;
434
+
435
+ /** Release a claim after the handler errored, so a retry can re-process. */
436
+ export declare const failIdempotent: (store: IdempotencyStore, scope: string, key: string) => Promise<void>;
437
+
438
+ /** Record a completed response for replay. */
439
+ export declare const finishIdempotent: (store: IdempotencyStore, scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
440
+
441
+ export declare const hasCallbackRoutes: (s: AuthStrategy) => s is AuthStrategyWithCallback;
442
+
443
+ /** True if the subject holds `scope` (or the `admin:full` bypass). */
444
+ export declare const hasScope: (subject: Subject, scope: string) => boolean;
445
+
446
+ /**
447
+ * Short-circuit response shape. Returning a `HttpInterceptResponse`
448
+ * from the interceptor halts the pipeline — the framework sends the
449
+ * response and never invokes the downstream handler.
450
+ *
451
+ * Returning `null` (or calling `next()` and returning its result)
452
+ * lets the request continue to the normal pipeline.
453
+ */
454
+ export declare interface HttpInterceptResponse {
455
+ readonly status: number;
456
+ readonly body?: string;
457
+ readonly headers?: Readonly<Record<string, string>>;
458
+ }
459
+
460
+ /**
461
+ * Per-call context passed to HTTP-request interceptors. Fires BEFORE
462
+ * auth-resolution, BEFORE rpc-routing, BEFORE inspect — at the very
463
+ * top of the HTTP pipeline. Use for pre-auth concerns: rate-limit
464
+ * (token bucket per IP), geo-block (451), bot-detection, header
465
+ * injection for downstream observability, CORS-override.
466
+ *
467
+ * The interceptor is NOT an auth replacement — `AuthMiddleware` still
468
+ * runs on the rpc path. If you need to GATE auth, wrap a different
469
+ * surface (`interceptAction` for unary calls, `interceptMutation` for
470
+ * writes). HTTP-interceptors run on EVERY HTTP request including
471
+ * inspect endpoints and the rpc websocket upgrade.
472
+ */
473
+ export declare interface HttpRequestContext {
474
+ /** HTTP method (`'GET'`, `'POST'`, etc.). */
475
+ readonly method: string;
476
+ /** Request path (no query string). */
477
+ readonly path: string;
478
+ /** Lowercased request headers. */
479
+ readonly headers: Readonly<Record<string, string>>;
480
+ /** Best-effort remote address. `undefined` when running behind a
481
+ * proxy without `x-forwarded-for`. */
482
+ readonly remoteAddr: string | undefined;
483
+ }
484
+
485
+ /**
486
+ * Wraps every HTTP request before the framework's auth + routing.
487
+ * Composed across plugins in declaration order — first listed =
488
+ * outermost. The chain short-circuits as soon as a plugin returns
489
+ * a `HttpInterceptResponse`; later plugins don't see the request.
490
+ *
491
+ * Plugin MUST declare the `http:intercept` permission or boot fails
492
+ * loudly. Plugins without that permission get their `onHttpRequest`
493
+ * hook stripped at activation (so the rest of the plugin still
494
+ * works).
495
+ */
496
+ export declare type HttpRequestInterceptor = (next: () => Promise<HttpInterceptResponse | null>, ctx: HttpRequestContext) => Promise<HttpInterceptResponse | null>;
497
+
498
+ export declare type IdempotencyOutcome = {
499
+ readonly kind: 'fresh';
500
+ } | {
501
+ readonly kind: 'replay';
502
+ readonly response: IdempotencyResponse;
503
+ } | {
504
+ readonly kind: 'conflict';
505
+ };
506
+
507
+ export declare interface IdempotencyRecord {
508
+ readonly scope: string;
509
+ readonly key: string;
510
+ readonly status: 'in_flight' | 'completed';
511
+ readonly response: IdempotencyResponse | null;
512
+ /** epoch ms */
513
+ readonly createdAt: number;
514
+ }
515
+
516
+ export declare interface IdempotencyResponse {
517
+ readonly status: number;
518
+ readonly body: unknown;
519
+ }
520
+
521
+ /** Build the scope key — keep keys from colliding across tenants + endpoints. */
522
+ export declare const idempotencyScope: (tenantId: string | null | undefined, method: string, path: string) => string;
523
+
524
+ export declare interface IdempotencyStore {
525
+ /** Current record for (scope, key), or null. Read-side (inspect/dashboard). */
526
+ readonly get: (scope: string, key: string) => Promise<IdempotencyRecord | null>;
527
+ /**
528
+ * Atomically claim (scope, key) IF absent OR stale (older than `ttlMs`):
529
+ * write a fresh `in_flight` row and return `'claimed'`. Otherwise return the
530
+ * EXISTING (still-fresh) record. The atomicity here is the whole game — two
531
+ * concurrent same-key requests both hit this, exactly one gets `'claimed'`,
532
+ * the loser reads the winner's record. In SQL this is one
533
+ * `INSERT … ON CONFLICT DO UPDATE … WHERE existing.createdAt < now - ttl`.
534
+ */
535
+ readonly claim: (scope: string, key: string, now: number, ttlMs: number) => Promise<'claimed' | IdempotencyRecord>;
536
+ /** Flip the claim to `completed` and store the response. */
537
+ readonly complete: (scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
538
+ /** Drop the claim (handler errored → a retry may re-process). */
539
+ readonly release: (scope: string, key: string) => Promise<void>;
540
+ }
541
+
542
+ /** `/<id>` — the op path for a row id. Ids are JSON-pointer-escaped
543
+ * (`~` → `~0`, `/` → `~1`) so a slash or tilde inside a string id can't
544
+ * corrupt the pointer. */
545
+ export declare const idToPath: (id: RowId) => string;
546
+
547
+ export declare interface InsertTarget<Input = unknown, Row = unknown> {
548
+ readonly table: string;
549
+ readonly op: 'insert';
550
+ readonly order?: 'prepend' | 'append' | undefined;
551
+ /**
552
+ * Build the optimistic row from the mutation input. The framework
553
+ * injects `id` (from `optimisticId`) and the `optimistic: true` flag
554
+ * around the return — your `shape` returns ONLY the user-controllable
555
+ * row body. Default when omitted: `{ ...input, id, optimistic: true }`.
556
+ *
557
+ * Return type intentionally excludes `id`: it's server-generated;
558
+ * the optimistic id placeholder is the framework's responsibility.
559
+ * Excluding `optimistic` (also framework-injected) follows the same
560
+ * principle — `Omit<Row, 'id' | 'optimistic'>` lets you describe
561
+ * EVERYTHING ELSE without re-stating the bookkeeping fields.
562
+ *
563
+ * The `optimisticId` parameter is exposed for rare cases where the
564
+ * shape function needs to reference it (e.g., setting a foreign key
565
+ * on a child row spread in the same optimistic insert).
566
+ */
567
+ readonly shape?: ((input: Input, optimisticId: string) => Omit<Row, 'id' | 'optimistic'>) | undefined;
568
+ }
569
+
570
+ /** True when every row in the set carries an `id`. The dispatcher uses
571
+ * this to decide patch-vs-full-snapshot: a result whose rows aren't
572
+ * id-keyed (rare — a custom projection that drops the id) can't be
573
+ * diffed by id and falls back to shipping the full data. */
574
+ export declare const isIdKeyed: (rows: ReadonlyArray<Readonly<Record<string, unknown>>>) => rows is ReadonlyArray<PatchRow>;
575
+
576
+ export declare const isSystemSubject: (subject: Subject) => boolean;
577
+
578
+ /** In-memory store — the default for single-process dev + the test double. */
579
+ export declare const memoryIdempotencyStore: () => IdempotencyStore;
580
+
581
+ export declare interface MutationProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
582
+ readonly kind: 'mutation';
583
+ readonly name: Name;
584
+ readonly input: Input;
585
+ readonly output: Output;
586
+ readonly error: Error;
587
+ /** Declarative target(s) — drives auto-optimistic on the client AND
588
+ * surfaces which tables this mutation touches (debug, future
589
+ * query-invalidation analytics). Mutations without a target run
590
+ * normally but skip auto-optimistic. */
591
+ readonly target: Target | undefined;
592
+ /** Opt this mutation into a public REST endpoint (innovation/11). */
593
+ readonly publicApi: PublicApiSpec | undefined;
594
+ /** Opt this mutation into the auto-synthesized agent toolset (innovation/07). */
595
+ readonly exposeAsTool: ExposeAsTool | undefined;
596
+ }
597
+
598
+ export declare const mutationToRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: MutationProcedureDescriptor<Name, Input, Output, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Schema.All, never>;
599
+
600
+ /**
601
+ * Project a runtime descriptor down to the subset the client needs.
602
+ * Functions (`shape`, `identify`) are passed through by reference —
603
+ * the client's auto-optimistic uses them as-is when present.
604
+ */
605
+ export declare const normalizeDescriptor: (descriptor: ProcedureDescriptor) => ClientDescriptor;
606
+
607
+ /**
608
+ * What a plugin contributes to the OTel layer via `contributeObservability`.
609
+ * The OTel types are intentionally `unknown` so this browser-safe protocol
610
+ * package never imports `@opentelemetry/*` (the cli casts to the concrete
611
+ * `SpanProcessor` / `MetricReader` / `Sampler` when wiring `buildTracingLayer`).
612
+ */
613
+ export declare interface ObservabilityContribution {
614
+ /** Merged into the OTel Resource (e.g. `service.version`,
615
+ * `deployment.environment`). */
616
+ readonly resourceAttributes?: Record<string, string>;
617
+ /** Additional OTel `SpanProcessor`s installed alongside the framework's
618
+ * exporter + buffer sink (e.g. a vendor OTLP exporter or `SentrySpanProcessor`). */
619
+ readonly spanProcessors?: ReadonlyArray<unknown>;
620
+ /** Additional OTel `MetricReader`s (e.g. a vendor OTLP metric reader). */
621
+ readonly metricReaders?: ReadonlyArray<unknown>;
622
+ /** OTel `Sampler` (e.g. Sentry's `SentrySampler` honouring `tracesSampleRate`). */
623
+ readonly sampler?: unknown;
624
+ }
625
+
626
+ /** A single row in a subscription result. Must carry an `id`; everything
627
+ * else is opaque to the patch layer. */
628
+ export declare type PatchRow = Readonly<Record<string, unknown>> & {
629
+ readonly id: RowId;
630
+ };
631
+
632
+ /** Inverse of `idToPath` for the string case. Numeric ids round-trip via
633
+ * the `order` array (which preserves the original type), so apply never
634
+ * needs to re-parse a number out of a path. */
635
+ export declare const pathToId: (path: string) => string;
636
+
637
+ /**
638
+ * Called once at app boot, after `voltro dev` resolves the plugin
639
+ * list and before the rpc server starts accepting connections. Use
640
+ * for warming caches, opening connection pools, registering metrics,
641
+ * or any one-shot setup the plugin needs. Throws abort boot —
642
+ * malformed plugin config should surface as a clear, immediate error.
643
+ */
644
+ export declare type PluginActivateHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
645
+
646
+ /**
647
+ * Server-only context delivered to `bindDataStore`, ALONGSIDE the store,
648
+ * AFTER the framework has opened its store + pool. Gives a plugin the
649
+ * framework's ALREADY-OPEN handles so it never rebuilds a pg pool from
650
+ * raw env (the anti-pattern `@voltro/plugin-ratelimit` used to hand-roll)
651
+ * and never runs an un-coordinated per-replica `setInterval` sweep (what
652
+ * presence + governance used to do).
653
+ *
654
+ * These fields are SERVER-only — they are TYPES only in this browser-safe
655
+ * protocol package (the `DataStore` / `SqlClient` imports are erased), and
656
+ * the live values exist only inside the serve pipeline that calls
657
+ * `bindDataStore`. Nothing in the browser-loaded descriptor/schema graph
658
+ * touches them.
659
+ */
660
+ export declare interface PluginBindContext {
661
+ /**
662
+ * The framework's already-open `SqlClient` (from `@effect/sql`) — the
663
+ * SAME pool the app's store uses. A plugin runs raw SQL through this
664
+ * instead of standing up its OWN `ManagedRuntime` + pool from env. Only
665
+ * present for SQL-backed stores; `undefined` on the in-memory store
666
+ * (there is no SQL engine) — guard with `if (ctx.sql)`.
667
+ */
668
+ readonly sql?: SqlClient.SqlClient;
669
+ /**
670
+ * Run `effect` every `intervalMs` on ONLY ONE replica per tick —
671
+ * cluster-coordinated via the framework's existing claim-table
672
+ * exactly-once gate (the same seam the cron scheduler uses). Replaces
673
+ * the hand-rolled `setInterval` a plugin used to run inside
674
+ * `bindDataStore`, which fired on EVERY replica un-coordinated. On a
675
+ * single-process / memory / sqlite deployment it simply runs every tick
676
+ * locally (correct — one process needs no fan-out dedup).
677
+ *
678
+ * `name` is the task's stable id (namespace it, e.g. `presence.sweep`);
679
+ * it becomes the claim key. Returns a handle whose `stop()` cancels the
680
+ * task early. The framework also stops every armed task at shutdown.
681
+ */
682
+ readonly scheduleCoordinated: (name: string, intervalMs: number, effect: () => void | Promise<void>) => CoordinatedScheduleHandle;
683
+ }
684
+
685
+ /**
686
+ * Post-commit change event delivered to a plugin's `onChangeEvent` tap.
687
+ * Minimal, browser-safe shape (protocol must not depend on the database
688
+ * package's concrete `ChangeEvent`) — the framework maps its internal event
689
+ * to this before fan-out.
690
+ */
691
+ export declare interface PluginChangeEvent {
692
+ readonly table: string;
693
+ readonly op: 'insert' | 'update' | 'delete';
694
+ /** Row after the change — present on insert + update, null on delete. */
695
+ readonly new: Record<string, unknown> | null;
696
+ /** Row before the change — present on update + delete, null on insert. */
697
+ readonly old: Record<string, unknown> | null;
698
+ /** How the event reached this process: absent/'inline' = this process's
699
+ * own write; 'injected' = delivered over a cross-instance transport
700
+ * (broadcast bus / CDC consumer). Combine with `changeScope` to act
701
+ * exactly once per change fleet-wide. */
702
+ readonly origin?: 'inline' | 'injected';
703
+ /** The store's change visibility (see `DataStore.changeScope`):
704
+ * 'local' — own writes inline (skip origin 'injected' for
705
+ * exactly-once-on-the-writer); 'fleet' — every replica sees the full
706
+ * stream (elect one worker instead). */
707
+ readonly changeScope: 'local' | 'fleet';
708
+ }
709
+
710
+ /**
711
+ * Plugin-supplied codegen contribution. Emitted into the user app's
712
+ * `rpcGroup.generated.ts` as an extra section AFTER the framework's
713
+ * own output. Use for typed bindings that don't fit the rpc wire —
714
+ * helper exports, type re-exports, accessor functions that wrap the
715
+ * plugin's runtime apis.
716
+ *
717
+ * The framework calls this function once per codegen run. Returns:
718
+ * - a string → emitted verbatim
719
+ * - `null` → contributes nothing
720
+ *
721
+ * The function MUST be pure: it gets the plugin name + the api name
722
+ * + the list of discovered rpc-tag-and-kind tuples (for plugins that
723
+ * want to emit per-rpc bindings). No filesystem / network access —
724
+ * codegen runs as a build step on every dev restart.
725
+ *
726
+ * Typical pattern: emit a typed accessor for the plugin's service Tag:
727
+ *
728
+ * codegen: (ctx) => `
729
+ * export const use${capitalize(ctx.pluginAlias)} = () =>
730
+ * (ctx: AppContext) => ctx[${JSON.stringify(ctx.pluginAlias)}]
731
+ * `
732
+ *
733
+ * The output is wrapped between `// <plugin:NAME>` / `// </plugin:NAME>`
734
+ * markers in the generated file so the user can identify the source.
735
+ */
736
+ export declare type PluginCodegen = (ctx: PluginCodegenContext) => string | null;
737
+
738
+ export declare interface PluginCodegenContext {
739
+ /** The plugin's full name (`@voltro/plugin-audit`). */
740
+ readonly pluginName: string;
741
+ /** The derived alias (`audit`). */
742
+ readonly pluginAlias: string;
743
+ /** The api app's name from `app.config.ts`. */
744
+ readonly apiName: string;
745
+ /** Flat list of rpc tags discovered in the app (user routes only). */
746
+ readonly rpcTags: ReadonlyArray<{
747
+ readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
748
+ readonly tag: string;
749
+ }>;
750
+ }
751
+
752
+ /**
753
+ * A remote-mounted dashboard surface a plugin contributes. The
754
+ * dashboard-host (voltro-cloud / voltro-devtools) renders the mount
755
+ * by `import()`ing the ESM module at `bundleUrl` and calling the
756
+ * exported component with host-provided context (auth subject,
757
+ * routing, theme tokens).
758
+ *
759
+ * Not iframe — true in-process mount. Trade-offs:
760
+ * - Plugin's bundle MUST be ESM with React/Effect as peerDeps; the
761
+ * host pins the versions. Mismatch logs a warning + still mounts.
762
+ * - No CSS isolation. Plugin authors are expected to use scoped
763
+ * Tailwind classes (the dashboard ships the framework token set)
764
+ * or CSS modules. Inline `style` on a top-level wrapper avoids
765
+ * bleeding host styles in.
766
+ * - Plugin code shares the host JS realm — honor-system sandboxing.
767
+ * Mounts are gated by the `dashboard:mount` permission to keep
768
+ * the operator's audit trail explicit.
769
+ *
770
+ * The framework surfaces declared mounts via
771
+ * `/_voltro/inspect/plugins/dashboard-mounts`. The actual mount
772
+ * runtime ships in voltro-cloud-dashboard + voltro-devtools.
773
+ */
774
+ export declare interface PluginDashboardMount {
775
+ /** Stable id within the plugin (kebab-case). Full id surfaces as
776
+ * `<plugin-alias>:<mount.id>` in the dashboard's mount registry. */
777
+ readonly id: string;
778
+ /**
779
+ * Where the dashboard host should mount this contribution:
780
+ * - `'page'` — full-page mount under `/plugins/<alias>/<route>`
781
+ * - `'widget'` — card-shaped tile on the dashboard home
782
+ * - `'nav'` — sidebar nav item linking to a `page`-mount route
783
+ */
784
+ readonly slot: 'page' | 'widget' | 'nav';
785
+ /** Required for `slot: 'page'`. Path is mounted under
786
+ * `/plugins/<plugin-alias>/<route>`. Ignored for widget/nav. */
787
+ readonly route?: string;
788
+ /** Human-readable label (nav text, page title, widget header). */
789
+ readonly label: string;
790
+ /** Optional icon — emoji or a `lucide-react` icon name. */
791
+ readonly icon?: string;
792
+ /**
793
+ * Absolute URL the host fetches at mount time. Must serve an ESM
794
+ * module. CDN-hosted or self-hosted (`https://my-plugin.example/v1/bundle.mjs`).
795
+ * The plugin author is responsible for HTTPS + immutable versioning.
796
+ */
797
+ readonly bundleUrl: string;
798
+ /** Export name to read from the imported module. Defaults to
799
+ * `'default'`. The export must be a React component. */
800
+ readonly exportName?: string;
801
+ /** Optional dashboard-framework version range the mount supports
802
+ * (npm-semver). Host logs a warning on mismatch but mounts. */
803
+ readonly dashboardVersion?: string;
804
+ }
805
+
806
+ /**
807
+ * Called once at graceful shutdown (SIGTERM in production; HMR-reload
808
+ * in dev). Mirror of `onActivate`: close connections, flush buffers,
809
+ * cancel timers. The framework waits up to 5s for `onDeactivate` to
810
+ * settle before SIGKILL falls through.
811
+ */
812
+ export declare type PluginDeactivateHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
813
+
814
+ /**
815
+ * An environment variable a plugin reads. DECLARATION ONLY — metadata, not a
816
+ * read path: the plugin still reads the value itself (typically
817
+ * `options.X ?? process.env.X`). Declaring it makes the plugin's env needs
818
+ * visible to the env manifest (`/_voltro/inspect/env`), the generated
819
+ * `.env.example`, and the dashboard Env panel — so an operator can see, before
820
+ * running, exactly what each plugin expects.
821
+ *
822
+ * Structurally identical to `@voltro/env`'s `DeclaredEnvVar` (kept local here
823
+ * to avoid a protocol → env package dependency).
824
+ */
825
+ export declare interface PluginEnvVar {
826
+ /** The env var name, e.g. `DD_API_KEY`. */
827
+ readonly name: string;
828
+ /** True when the plugin cannot function without it (no sensible default). */
829
+ readonly required: boolean;
830
+ /** True for tokens / passwords / credential URLs — the manifest then
831
+ * reports `isSet` only, never the value. */
832
+ readonly secret: boolean;
833
+ /** One-line description — shown in `.env.example` + the manifest. */
834
+ readonly description?: string;
835
+ /** Example value for `.env.example`. Never a real secret. */
836
+ readonly example?: string;
837
+ }
838
+
839
+ /**
840
+ * A cross-cutting error schema a plugin merges into EVERY procedure's
841
+ * wire error union. Use when a plugin interceptor can fail with a typed
842
+ * error (e.g. a rate-limit `RateLimited`) that must decode TYPED on the
843
+ * client rather than crossing as an untyped defect.
844
+ *
845
+ * Two halves because the error must be applied in two places that can't
846
+ * share a value:
847
+ * - `schema`: the live `effect/Schema` — used at runtime when the cli
848
+ * assembles the SERVER rpc group.
849
+ * - `import`: where to import the same schema from — used by codegen to
850
+ * emit the import into the generated CLIENT rpc group, so both ends
851
+ * agree on the wire shape. `name` must be the exported identifier.
852
+ *
853
+ * Requires the plugin to ship the schema as a NAMED export from `module`.
854
+ */
855
+ export declare interface PluginErrorSchema {
856
+ readonly schema: Schema.Schema.All;
857
+ readonly import: {
858
+ readonly module: string;
859
+ readonly name: string;
860
+ };
861
+ }
862
+
863
+ /** A public raw-HTTP route a plugin serves on the framework listener. */
864
+ export declare interface PluginHttpRoute {
865
+ /** HTTP method, or `'*'` for any (the handler decides). */
866
+ readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'DELETE';
867
+ /** Absolute path prefix, e.g. `/_voltro/storage`. Matches the path AND
868
+ * any sub-path (`/_voltro/storage/abc123`). */
869
+ readonly path: string;
870
+ readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
871
+ }
872
+
873
+ export declare interface PluginHttpRouteRequest {
874
+ readonly method: string;
875
+ /** Path WITHOUT query string. */
876
+ readonly path: string;
877
+ /** Query string WITHOUT the leading `?` (empty when absent). Parse with
878
+ * `new URLSearchParams(req.query)`. */
879
+ readonly query: string;
880
+ readonly headers: Record<string, string>;
881
+ readonly rawBody: Uint8Array;
882
+ }
883
+
884
+ /**
885
+ * One HTTP endpoint a plugin contributes under the framework's
886
+ * `/_voltro/inspect/*` introspection surface. Plugins use this to
887
+ * surface tooling / dashboards that don't fit the rpc wire (e.g.
888
+ * Server-Sent-Event streams of plugin-internal state, on-demand
889
+ * health probes, plugin-specific debug dumps).
890
+ *
891
+ * Path convention: `/_voltro/inspect/plugins/<plugin-slug>/<route.path>`.
892
+ * The plugin slug (derived from `plugin.name` with `@scope/` +
893
+ * `plugin-` stripped, KEBAB-CASE — `plugin-cdc-out` → `cdc-out`,
894
+ * instance suffix `#x` → `--x`) is prepended by the framework so
895
+ * plugin endpoints never collide with the framework's own inspect
896
+ * endpoints OR with another plugin's. Kebab (not the camelCase rpc
897
+ * alias) because it's a URL every dashboard fetches. The dashboard
898
+ * lists every plugin's endpoints grouped by plugin in the manifest.
899
+ *
900
+ * Plugin endpoints inherit the SAME auth resolver the framework's
901
+ * own inspect endpoints use (`VOLTRO_INSPECT_TOKEN` by default;
902
+ * customisable by passing an `authResolver` at the http-handler
903
+ * layer). The plugin can NOT bypass auth — that's the framework's
904
+ * job, not the plugin's.
905
+ */
906
+ /** What a plugin HTTP route returns. `body` may be bytes (e.g. a served
907
+ * blob) or text; `headers` carries redirects (302 `location`) + cache
908
+ * policy. */
909
+ export declare interface PluginHttpRouteResult {
910
+ readonly status: number;
911
+ readonly body?: string | Uint8Array;
912
+ readonly contentType?: string;
913
+ readonly headers?: Record<string, string>;
914
+ }
915
+
916
+ export declare interface PluginInspectEndpoint {
917
+ /** HTTP method. */
918
+ readonly method: 'GET' | 'POST' | 'DELETE';
919
+ /**
920
+ * Path relative to the plugin's inspect prefix. Leading slash
921
+ * optional. `health` → `/_voltro/inspect/plugins/<slug>/health`.
922
+ */
923
+ readonly path: string;
924
+ /** Optional human-readable description; surfaced in the dashboard. */
925
+ readonly description?: string;
926
+ /**
927
+ * Effect-based handler. Receives the raw request + headers,
928
+ * returns the response shape. Body parsing is the handler's
929
+ * job — the framework just hands the string through. JSON/CORS
930
+ * are sugar on top: returning `{ json: ... }` from the response
931
+ * shape sets the right content-type + serialises.
932
+ *
933
+ * Long-running responses (SSE, streaming) are NOT supported in
934
+ * v1 — every endpoint returns one buffered response. SSE plugins
935
+ * can route to a separate path mounted by the plugin's own
936
+ * `routes:` array via the rpc layer, which DOES stream.
937
+ */
938
+ readonly handler: (request: PluginInspectRequest) => Effect.Effect<PluginInspectResponse, unknown, never>;
939
+ }
940
+
941
+ export declare interface PluginInspectRequest {
942
+ readonly method: 'GET' | 'POST' | 'DELETE';
943
+ /** Full URL the client requested (path + query). */
944
+ readonly url: string;
945
+ /** Lowercased headers. */
946
+ readonly headers: Readonly<Record<string, string>>;
947
+ /** Raw body (empty string on GET / DELETE). */
948
+ readonly body: string;
949
+ }
950
+
951
+ export declare type PluginInspectResponse = {
952
+ readonly kind: 'json';
953
+ readonly status?: number;
954
+ readonly data: unknown;
955
+ } | {
956
+ readonly kind: 'text';
957
+ readonly status?: number;
958
+ readonly contentType?: string;
959
+ readonly body: string;
960
+ };
961
+
962
+ /**
963
+ * Called ONCE per host across the plugin's lifetime — the first time
964
+ * it's seen by the host. Used for one-time schema migrations + resource
965
+ * provisioning that survive across deactivate/activate cycles. Skipped
966
+ * on subsequent boots once the host has recorded the install.
967
+ *
968
+ * v1 model: install state is in-process only (in-memory marker keyed by
969
+ * plugin name + version). A future slice will persist it to
970
+ * `_voltro_plugin_installs` so install/uninstall idempotency survives
971
+ * process restarts. For now, treat `onInstall` as "what runs on a fresh
972
+ * dev process the first time this plugin is wired" — sufficient for the
973
+ * "create a schema table on first wire-up" use case.
974
+ */
975
+ export declare type PluginInstallHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
976
+
977
+ /**
978
+ * Per-app context handed to lifecycle hooks. The shape is intentionally
979
+ * thin — the framework's deeper services (DataStore, Logger, etc.) are
980
+ * available via the per-request `AppContext` plugins see in their rpc
981
+ * interceptors. Lifecycle hooks run OUTSIDE any request, so they
982
+ * receive a minimal slice:
983
+ *
984
+ * - `app`: the resolved app-config metadata
985
+ * (`{ name, type, voltroVersion }`).
986
+ * - `logger`: a scoped `@voltro/logger` instance keyed to the
987
+ * plugin's `name`, so log lines are attributable.
988
+ * - `env`: the process env (alias of `process.env` for typing).
989
+ * - `config`: the plugin's own validated config (if `configSchema`
990
+ * declared) — already decoded against the plugin's Schema, ready
991
+ * for the hook to consume. Unknown otherwise.
992
+ */
993
+ export declare interface PluginLifecycleContext {
994
+ readonly app: {
995
+ readonly name: string;
996
+ readonly type: 'api' | 'web';
997
+ readonly voltroVersion: string;
998
+ };
999
+ readonly logger: {
1000
+ info: (message: string, fields?: Record<string, unknown>) => void;
1001
+ warn: (message: string, fields?: Record<string, unknown>) => void;
1002
+ error: (message: string, fields?: Record<string, unknown>) => void;
1003
+ debug: (message: string, fields?: Record<string, unknown>) => void;
1004
+ };
1005
+ readonly env: NodeJS.ProcessEnv;
1006
+ /** Validated plugin config (if `configSchema` was declared). */
1007
+ readonly config: unknown;
1008
+ }
1009
+
1010
+ export declare interface PluginMigration {
1011
+ readonly id: string;
1012
+ readonly description?: string;
1013
+ readonly up: (sql: PluginMigrationSqlClient) => Effect.Effect<void, unknown>;
1014
+ readonly down?: (sql: PluginMigrationSqlClient) => Effect.Effect<void, unknown>;
1015
+ }
1016
+
1017
+ /**
1018
+ * A plugin-supplied custom migration. Runs ONCE per app DB, tracked in
1019
+ * the framework's `_voltro_plugin_migrations` ledger so re-runs of
1020
+ * `voltro dev` skip already-applied steps. Use for setup the
1021
+ * declarative-tables path can't express: extension installs (`CREATE
1022
+ * EXTENSION pgcrypto`), seed data, DDL for pre-existing /
1023
+ * externally-owned tables the plugin wraps, etc.
1024
+ *
1025
+ * Ids are stable strings the plugin author owns. The framework
1026
+ * namespaces them as `<plugin-alias>__<migration.id>` in the ledger
1027
+ * so two plugins can each ship a `'001-init'` without collision.
1028
+ *
1029
+ * The `up` Effect runs against the live `SqlClient` after the
1030
+ * framework's own schema apply. Failures abort boot (loud +
1031
+ * recoverable: fix + restart). Down-migrations are optional and not
1032
+ * auto-run — they exist for ops tooling.
1033
+ */
1034
+ /**
1035
+ * The `sql` parameter the framework hands the migration is the live
1036
+ * `SqlClient.SqlClient` from `@effect/sql`. The reference is type-only,
1037
+ * so it adds no runtime dependency — `@voltro/protocol` still ships
1038
+ * without pulling `@effect/sql` into a plugin's bundle.
1039
+ */
1040
+ export declare type PluginMigrationSqlClient = SqlClient.SqlClient;
1041
+
1042
+ /**
1043
+ * Declared permission scope. The framework gates hook surfaces on
1044
+ * the declared set: plugins that ship `onHttpRequest` without
1045
+ * declaring `'http:intercept'` are rejected at boot. Pattern-perms
1046
+ * support a `:*` wildcard suffix (`'secrets:read:auth0:*'` covers
1047
+ * `'secrets:read:auth0:clientSecret'`).
1048
+ *
1049
+ * The static surface is a typed union so plugin authors get autocomplete;
1050
+ * pattern perms fall through the template-literal arm. Surfaced in the
1051
+ * boot log + `/_voltro/inspect/plugins` so operators audit what each
1052
+ * plugin asks for before installing.
1053
+ */
1054
+ export declare type PluginPermission = 'rpc:intercept:mutation' | 'rpc:intercept:query' | 'rpc:intercept:action' | 'schedule:fire' | 'schedule:read' | 'workflow:step' | 'http:intercept' | 'inspect:read' | 'inspect:write' | 'dashboard:mount' | 'store:write' | 'migration:run' | 'store:changes:read' | `secrets:read:${string}` | `network:outbound:${string}` | `plugin:hook:${string}`;
1055
+
1056
+ /**
1057
+ * Per-call context handed to a plugin-contributed rpc route's
1058
+ * executor. Mirror of the user-handler `AppContext` shape but typed
1059
+ * loosely so the plugin doesn't need to depend on `@voltro/runtime`.
1060
+ */
1061
+ export declare interface PluginRouteContext {
1062
+ readonly request: {
1063
+ readonly subject: Subject;
1064
+ readonly traceId: string;
1065
+ };
1066
+ }
1067
+
1068
+ /**
1069
+ * Client-facing declaration of a plugin RPC route's descriptor, so the codegen
1070
+ * can emit it into the generated client rpc group (`appGroup` + `appDescriptors`)
1071
+ * — without this, a plugin route is mounted server-side but the browser client
1072
+ * can't resolve its tag ("not reachable on client").
1073
+ *
1074
+ * `import` MUST point at a BROWSER-SAFE module that exports the route's descriptor
1075
+ * value (tag + input/output/error Schema, NO executor, NO `node:*`/server deps) —
1076
+ * the same discipline `PluginErrorSchema.import` follows (e.g.
1077
+ * `@voltro/plugin-storage/rpc`). The generated file is loaded value-level by the
1078
+ * web client + guarded by `assertBrowserSafeRpcGroup`, so a server import here is
1079
+ * a boot failure. `tag` is the route's EFFECTIVE tag (`<alias>.<name>`); `kind`
1080
+ * selects `queryToRpc` / `mutationToRpc` / `actionToRpc`.
1081
+ */
1082
+ export declare interface PluginRpcClientDescriptor {
1083
+ readonly tag: string;
1084
+ readonly kind: RpcKind;
1085
+ readonly import: {
1086
+ readonly module: string;
1087
+ readonly name: string;
1088
+ };
1089
+ }
1090
+
1091
+ /**
1092
+ * One descriptor + executor pair a plugin contributes to the host's
1093
+ * rpc surface. Same shape as a user-authored mutation/query/action,
1094
+ * but registered programmatically by the plugin instead of via file
1095
+ * discovery.
1096
+ *
1097
+ * The plugin's name is prepended to the rpc tag at registration time
1098
+ * unless the descriptor already carries a dot — e.g. a plugin named
1099
+ * `@voltro/plugin-audit` exporting `route({ name: 'list' })` lands
1100
+ * as `audit.list`; exporting `route({ name: 'admin.events' })` lands
1101
+ * unchanged. This guarantees plugin routes never collide with
1102
+ * user-authored routes (user routes don't ship the plugin prefix) and
1103
+ * lets the dashboard show them grouped by plugin.
1104
+ */
1105
+ export declare interface PluginRpcRoute {
1106
+ /** 'mutation' | 'query' | 'action' — determines the runtime path. */
1107
+ readonly kind: RpcKind;
1108
+ /** RPC tag relative to the plugin (e.g. `'list'`, `'admin.events'`). */
1109
+ readonly name: string;
1110
+ /** Optional human-readable description; surfaced in the dashboard. */
1111
+ readonly description?: string;
1112
+ /** Input schema — same `effect/Schema` shape user routes use. */
1113
+ readonly input: Schema.Schema.Any;
1114
+ /** Output schema. */
1115
+ readonly output: Schema.Schema.Any;
1116
+ /** Optional typed-error schema (TaggedError union). */
1117
+ readonly error?: Schema.Schema.All;
1118
+ /**
1119
+ * REACTIVE source table(s) — ONLY for `kind: 'query'`. When set, the
1120
+ * framework drives the query REACTIVELY: it re-runs the executor and pushes
1121
+ * a fresh result over the SAME subscription/WS transport app reactive
1122
+ * queries use, every time one of the named tables changes. This makes a
1123
+ * plugin query PUSH-DRIVEN — no client polling — by reusing the framework's
1124
+ * computed-reactive-query machinery (the descriptor is browser-safe, the
1125
+ * executor runs server-side).
1126
+ *
1127
+ * The executor returns the query's VALUE (an array / object) — the same
1128
+ * shape it returns for a poll; the framework recomputes it on change. Omit
1129
+ * for a plain (poll-only) plugin query, a mutation, or an action. For a
1130
+ * query that mirrors a plugin table, set this to that table's name (e.g.
1131
+ * `@voltro/plugin-presence` sets `_voltro_presence`, which is `.reactive()`).
1132
+ *
1133
+ * `| undefined` is explicit so a plugin can spread a browser-safe query
1134
+ * DESCRIPTOR (which always carries `source: … | undefined`) into a route
1135
+ * literal under `exactOptionalPropertyTypes` without a cast.
1136
+ */
1137
+ readonly source?: string | ReadonlyArray<string> | undefined;
1138
+ /**
1139
+ * Effect-only executor. The base layer is provided by the framework
1140
+ * (DataStore, HttpClient, the plugin's own service Tags) — the
1141
+ * executor declares its requirements through Effect's R channel like
1142
+ * any other handler. Mutations land inside `store.transactional()`
1143
+ * automatically; queries return a `QueryDescriptor`-shaped result;
1144
+ * actions run without a transaction.
1145
+ */
1146
+ readonly execute: (input: never, ctx: PluginRouteContext) => Effect.Effect<unknown, unknown, never>;
1147
+ }
1148
+
1149
+ /**
1150
+ * What a plugin contributes to the host app's schema. `tables` join
1151
+ * the user's table set + go through the same `applySchema()` path
1152
+ * (idempotent CREATE TABLE IF NOT EXISTS). `migrations` run after
1153
+ * `applySchema()` against the live SqlClient, tracked in
1154
+ * `_voltro_plugin_migrations`.
1155
+ *
1156
+ * Plugin MUST declare `store:write` for `tables`, `migration:run`
1157
+ * for `migrations`. Boot fails if the declared-permission set is
1158
+ * insufficient.
1159
+ */
1160
+ export declare interface PluginSchemaContribution {
1161
+ readonly tables?: ReadonlyArray<unknown>;
1162
+ readonly migrations?: ReadonlyArray<PluginMigration>;
1163
+ }
1164
+
1165
+ /**
1166
+ * A scaffold template a plugin contributes to `voltro init` /
1167
+ * `voltro add-app`. Templates are listed under the plugin's name in
1168
+ * `voltro list-templates`; the operator picks one and the CLI seeds
1169
+ * the project tree.
1170
+ *
1171
+ * The framework keeps the template registry per-plugin scoped so two
1172
+ * plugins can each ship an `auth` template without collision: the
1173
+ * full template id is `<plugin-alias>:<template.id>`.
1174
+ *
1175
+ * v1 ships the registration shape + listing; the actual
1176
+ * `voltro init --from-plugin` invocation lands once a real consumer
1177
+ * needs it. Until then, templates are surfaced in
1178
+ * `/_voltro/inspect/plugins` so the dashboard can preview them.
1179
+ */
1180
+ export declare interface PluginTemplate {
1181
+ /** Stable id within the plugin (kebab-case). The framework
1182
+ * prepends the plugin alias when surfacing — `audit:starter` /
1183
+ * `webhooks:stripe-receiver`. */
1184
+ readonly id: string;
1185
+ /** Human-readable label for the picker. */
1186
+ readonly title: string;
1187
+ /** One-line description; surfaced in `voltro list-templates`. */
1188
+ readonly description: string;
1189
+ /** Which kind of app this template scaffolds. */
1190
+ readonly kind: 'api' | 'web' | 'fullstack';
1191
+ /**
1192
+ * Source directory inside the plugin's package. The CLI resolves
1193
+ * it relative to the plugin's npm-resolved root. Files inside are
1194
+ * copied verbatim into the target project; `package.json` is
1195
+ * patched to point dependencies at the plugin.
1196
+ */
1197
+ readonly sourcePath: string;
1198
+ /** Optional list of post-install hints surfaced after scaffolding. */
1199
+ readonly postInstallSteps?: ReadonlyArray<string>;
1200
+ }
1201
+
1202
+ /**
1203
+ * Called ONCE when the host removes the plugin. Mirror of `onInstall`.
1204
+ * Used to clean up the plugin's persisted resources (drop tables,
1205
+ * delete queue topics) so an uninstall fully reverses an install.
1206
+ *
1207
+ * v1 model: triggered explicitly by `voltro plugins uninstall <name>`
1208
+ * (CLI command — see manifest plan). NOT triggered on plain shutdown
1209
+ * (that's `onDeactivate`). Plugins are responsible for making
1210
+ * `onUninstall` idempotent — repeating it should be a no-op once the
1211
+ * resources are gone.
1212
+ */
1213
+ export declare type PluginUninstallHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
1214
+
1215
+ export declare type ProcedureDescriptor = QueryProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | MutationProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | ActionProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | StreamProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All>;
1216
+
1217
+ export declare const PROTOCOL_VERSION: 1;
1218
+
1219
+ export declare type ProtocolVersion = typeof PROTOCOL_VERSION;
1220
+
1221
+ export declare interface PublicApiSpec {
1222
+ /** Derived by kind when omitted: query→GET, mutation/action→POST. */
1223
+ readonly method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1224
+ /** Derived from the tag + version when omitted: `/<version>/<tag-as-path>`. */
1225
+ readonly path?: string;
1226
+ /** Path version segment; multiple versions coexist as separate descriptors. */
1227
+ readonly version?: string;
1228
+ /** Additional API-key scopes required ON TOP of the handler's RBAC. */
1229
+ readonly scopes?: ReadonlyArray<string>;
1230
+ /** Per-endpoint rate limit (rides @voltro/plugin-ratelimit when mounted). */
1231
+ readonly rateLimit?: {
1232
+ readonly limit: number;
1233
+ readonly window: string;
1234
+ };
1235
+ /** Honor `Idempotency-Key` for mutating methods (existing HTTP idempotency). */
1236
+ readonly idempotent?: boolean;
1237
+ /** Replacement hint → `Deprecation: true` header. */
1238
+ readonly deprecated?: string;
1239
+ /** ISO date → `Sunset:` header, `410` past the date. */
1240
+ readonly sunset?: string;
1241
+ readonly summary?: string;
1242
+ readonly description?: string;
1243
+ /** Streaming queries over request/response: 'snapshot' (default — first
1244
+ * snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
1245
+ readonly stream?: 'snapshot' | 'sse';
1246
+ }
1247
+
1248
+ /** Publish a server-primitive error. Cheap + sync; a broken reporter can
1249
+ * never break the caller (each listener is isolated). Safe no-op when
1250
+ * nothing is subscribed — primitives call it unconditionally. */
1251
+ export declare const publishServerError: (event: ServerErrorEvent) => void;
1252
+
1253
+ /**
1254
+ * Server-side snapshot caching for a query (see `defineQuery`). When set,
1255
+ * the dispatcher caches the initial snapshot result and auto-invalidates it
1256
+ * when a mutation writes any table the query depends on.
1257
+ */
1258
+ export declare interface QueryCacheConfig {
1259
+ /** Fresh window — seconds (number) or a duration string (`'30s'`,
1260
+ * `'5m'`, `'1h'`). */
1261
+ readonly ttl: number | string;
1262
+ /** Stale-while-revalidate window past `ttl` (same units). While stale,
1263
+ * the cached value is served immediately and refreshed in the
1264
+ * background. */
1265
+ readonly swr?: number | string | undefined;
1266
+ /**
1267
+ * Cross-subject safety — REQUIRED, no default, because guessing wrong
1268
+ * leaks rows. `'subject'` keys the cache by the caller's subject id
1269
+ * (safe for tenant- or row-filtered queries). `'global'` shares ONE
1270
+ * entry across every caller — legal ONLY when the resolved query is
1271
+ * subject-independent (reference data). Rubric: does the resolved
1272
+ * predicate depend on the caller? Yes → `subject`; no → `global`.
1273
+ */
1274
+ readonly scope: 'subject' | 'global';
1275
+ }
1276
+
1277
+ export declare interface QueryProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
1278
+ readonly kind: 'query';
1279
+ readonly name: Name;
1280
+ readonly input: Input;
1281
+ readonly output: Output;
1282
+ readonly error: Error;
1283
+ /** Which DataStore table(s) this query reads. Drives auto-optimistic patch
1284
+ * routing — mutations that target ANY of these tables patch caches keyed
1285
+ * to queries with a matching `source`. A computed query re-runs when ANY
1286
+ * listed table changes (pass an array to depend on several). Optional:
1287
+ * queries without a source never receive auto-patches (joins, aggregates). */
1288
+ readonly source: string | ReadonlyArray<string> | undefined;
1289
+ /** Server-side snapshot cache config. Absent → never cached (the
1290
+ * default; the dispatcher already keeps live subscriptions fresh). */
1291
+ readonly cache: QueryCacheConfig | undefined;
1292
+ /** Opt this query into a public REST endpoint (innovation/11). */
1293
+ readonly publicApi: PublicApiSpec | undefined;
1294
+ /** Opt this query into the auto-synthesized agent toolset (innovation/07). */
1295
+ readonly exposeAsTool: ExposeAsTool | undefined;
1296
+ }
1297
+
1298
+ export declare const queryToRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: QueryProcedureDescriptor<Name, Input, Output, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Schema.Union<[Schema.Struct<{
1299
+ _tag: Schema.Literal<["snapshot"]>;
1300
+ revision: typeof Schema.Number;
1301
+ data: Output;
1302
+ computed: Schema.optional<typeof Schema.Boolean>;
1303
+ }>, Schema.Struct<{
1304
+ _tag: Schema.Literal<["delta"]>;
1305
+ revision: typeof Schema.Number;
1306
+ emittedAt: typeof Schema.Number;
1307
+ patch: Schema.Struct<{
1308
+ ops: Schema.Array$<Schema.Union<[Schema.Struct<{
1309
+ op: Schema.Literal<["add"]>;
1310
+ path: typeof Schema.String;
1311
+ value: Schema.refine<{
1312
+ readonly [x: string]: unknown;
1313
+ } & Readonly<Record<string, unknown>> & {
1314
+ readonly id: RowId;
1315
+ }, Schema.Schema<{
1316
+ readonly [x: string]: unknown;
1317
+ }, {
1318
+ readonly [x: string]: unknown;
1319
+ }, never>>;
1320
+ }>, Schema.Struct<{
1321
+ op: Schema.Literal<["replace"]>;
1322
+ path: typeof Schema.String;
1323
+ value: Schema.refine<{
1324
+ readonly [x: string]: unknown;
1325
+ } & Readonly<Record<string, unknown>> & {
1326
+ readonly id: RowId;
1327
+ }, Schema.Schema<{
1328
+ readonly [x: string]: unknown;
1329
+ }, {
1330
+ readonly [x: string]: unknown;
1331
+ }, never>>;
1332
+ }>, Schema.Struct<{
1333
+ op: Schema.Literal<["remove"]>;
1334
+ path: typeof Schema.String;
1335
+ }>]>>;
1336
+ order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
1337
+ }>;
1338
+ }>, Schema.Struct<{
1339
+ _tag: Schema.Literal<["error"]>;
1340
+ error: typeof Schema.Unknown;
1341
+ revision: Schema.optional<typeof Schema.Number>;
1342
+ }>]>, Schema.Schema.All>, typeof Schema.Never, never>;
1343
+
1344
+ /** Gate a handler on a scope; fails with a typed `ScopeError` if missing. */
1345
+ export declare const requireScope: (subject: Subject, scope: string) => Effect.Effect<void, ScopeError>;
1346
+
1347
+ /** The id of a row, addressed in op paths as `/<id>`. */
1348
+ export declare type RowId = string | number;
1349
+
1350
+ /** The full delta payload: the per-row ops plus the authoritative id
1351
+ * ordering of the resulting set. */
1352
+ export declare interface RowPatch {
1353
+ readonly ops: ReadonlyArray<RowPatchOp>;
1354
+ /** Id sequence of the resulting (next) row set, in order. */
1355
+ readonly order: ReadonlyArray<RowId>;
1356
+ }
1357
+
1358
+ /**
1359
+ * One RFC-6902-style op against an id-keyed row set. `path` is always
1360
+ * `/<id>` (the row's id), NOT an array index — this is the deliberate
1361
+ * departure that makes the diff reorder-stable.
1362
+ *
1363
+ * - `replace` — the row with this id exists in both prev and next but
1364
+ * its content changed; `value` is the full next row.
1365
+ * - `add` — the row with this id is new in next; `value` is it.
1366
+ * - `remove` — the row with this id is gone in next.
1367
+ */
1368
+ export declare type RowPatchOp = {
1369
+ readonly op: 'add';
1370
+ readonly path: string;
1371
+ readonly value: PatchRow;
1372
+ } | {
1373
+ readonly op: 'replace';
1374
+ readonly path: string;
1375
+ readonly value: PatchRow;
1376
+ } | {
1377
+ readonly op: 'remove';
1378
+ readonly path: string;
1379
+ };
1380
+
1381
+ export declare const rowPatchOpSchema: Schema.Union<[Schema.Struct<{
1382
+ op: Schema.Literal<["add"]>;
1383
+ path: typeof Schema.String;
1384
+ value: Schema.refine<{
1385
+ readonly [x: string]: unknown;
1386
+ } & Readonly<Record<string, unknown>> & {
1387
+ readonly id: RowId;
1388
+ }, Schema.Schema<{
1389
+ readonly [x: string]: unknown;
1390
+ }, {
1391
+ readonly [x: string]: unknown;
1392
+ }, never>>;
1393
+ }>, Schema.Struct<{
1394
+ op: Schema.Literal<["replace"]>;
1395
+ path: typeof Schema.String;
1396
+ value: Schema.refine<{
1397
+ readonly [x: string]: unknown;
1398
+ } & Readonly<Record<string, unknown>> & {
1399
+ readonly id: RowId;
1400
+ }, Schema.Schema<{
1401
+ readonly [x: string]: unknown;
1402
+ }, {
1403
+ readonly [x: string]: unknown;
1404
+ }, never>>;
1405
+ }>, Schema.Struct<{
1406
+ op: Schema.Literal<["remove"]>;
1407
+ path: typeof Schema.String;
1408
+ }>]>;
1409
+
1410
+ export declare const rowPatchSchema: Schema.Struct<{
1411
+ ops: Schema.Array$<Schema.Union<[Schema.Struct<{
1412
+ op: Schema.Literal<["add"]>;
1413
+ path: typeof Schema.String;
1414
+ value: Schema.refine<{
1415
+ readonly [x: string]: unknown;
1416
+ } & Readonly<Record<string, unknown>> & {
1417
+ readonly id: RowId;
1418
+ }, Schema.Schema<{
1419
+ readonly [x: string]: unknown;
1420
+ }, {
1421
+ readonly [x: string]: unknown;
1422
+ }, never>>;
1423
+ }>, Schema.Struct<{
1424
+ op: Schema.Literal<["replace"]>;
1425
+ path: typeof Schema.String;
1426
+ value: Schema.refine<{
1427
+ readonly [x: string]: unknown;
1428
+ } & Readonly<Record<string, unknown>> & {
1429
+ readonly id: RowId;
1430
+ }, Schema.Schema<{
1431
+ readonly [x: string]: unknown;
1432
+ }, {
1433
+ readonly [x: string]: unknown;
1434
+ }, never>>;
1435
+ }>, Schema.Struct<{
1436
+ op: Schema.Literal<["remove"]>;
1437
+ path: typeof Schema.String;
1438
+ }>]>>;
1439
+ order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
1440
+ }>;
1441
+
1442
+ /**
1443
+ * Effect-native interceptor. Receives the rest of the chain as an
1444
+ * `Effect`; the interceptor returns an `Effect` that wraps it (or
1445
+ * substitutes a different one). Whatever the returned Effect produces
1446
+ * becomes the final result.
1447
+ *
1448
+ * Typical patterns:
1449
+ *
1450
+ * // Pre-only authz: short-circuit BEFORE the executor runs.
1451
+ * const guard: RpcInterceptor = (next, ctx) =>
1452
+ * ctx.tag.startsWith('admin.') && ctx.subject.type !== 'user'
1453
+ * ? Effect.fail(new Forbidden({ tag: ctx.tag }))
1454
+ * : next
1455
+ *
1456
+ * // Post-only side effects: tap the success/failure channels.
1457
+ * const log: RpcInterceptor = (next, ctx) =>
1458
+ * next.pipe(
1459
+ * Effect.tap((result) => recordAudit(ctx.tag, ctx.input, result, ctx.subject)),
1460
+ * Effect.tapError((err) => recordAuditError(ctx.tag, err)),
1461
+ * )
1462
+ *
1463
+ * // Tracing: wrap with a span; OTel spans nest automatically.
1464
+ * const trace: RpcInterceptor = (next, ctx) =>
1465
+ * next.pipe(Effect.withSpan(`plugin.audit.${ctx.kind}`, { attributes: { tag: ctx.tag } }))
1466
+ *
1467
+ * The `next` Effect MUST be flowed through somehow — either yielded,
1468
+ * piped, or returned — for the executor to run. Returning a different
1469
+ * Effect substitutes the result entirely (rare, e.g. cache hits).
1470
+ */
1471
+ export declare type RpcInterceptor = (next: Effect.Effect<unknown, unknown>, context: RpcInterceptorContext) => Effect.Effect<unknown, unknown>;
1472
+
1473
+ /**
1474
+ * Per-call context passed to every rpc interceptor. The fields are
1475
+ * deliberately shallow — plugins shouldn't need the full AppContext +
1476
+ * DataStore to do their job. For runtime services (DataStore, custom
1477
+ * Tags, etc.) declare them via `definePlugin({ services })` and read
1478
+ * via `yield* MyTag` in the interceptor body; the framework provides
1479
+ * them automatically.
1480
+ */
1481
+ export declare interface RpcInterceptorContext {
1482
+ /** Procedure tag, e.g. `todos.create`. */
1483
+ readonly tag: string;
1484
+ /** Discriminator — `'mutation' | 'query' | 'action'`. */
1485
+ readonly kind: RpcKind;
1486
+ /** The validated input the executor is about to (or already did) receive. */
1487
+ readonly input: unknown;
1488
+ /** The resolved subject (user / apiKey / serviceAccount / anonymous). */
1489
+ readonly subject: Subject;
1490
+ /** Per-call trace id (matches OTel spans + inspect events). */
1491
+ readonly traceId: string;
1492
+ /** Active OTel span id (16-hex), when tracing is on. Lets observability
1493
+ * plugins (Sentry/Datadog) attach an error to the exact span, not just
1494
+ * the trace. Absent when no tracer is installed. */
1495
+ readonly spanId?: string;
1496
+ }
1497
+
1498
+ /**
1499
+ * Which kind of rpc the interceptor is wrapping. The runtime passes
1500
+ * `kind: 'mutation'` to interceptors registered as `interceptMutation`,
1501
+ * `kind: 'query'` to `interceptQuery`, and `kind: 'action'` to
1502
+ * `interceptAction` — one plugin can implement any combination.
1503
+ */
1504
+ export declare type RpcKind = 'mutation' | 'query' | 'action';
1505
+
1506
+ /**
1507
+ * Per-firing context passed to schedule-fire interceptors. Shape
1508
+ * parallels `RpcInterceptorContext` so plugin authors can lift the
1509
+ * same patterns across rpc + schedule surfaces.
1510
+ */
1511
+ export declare interface ScheduleFireContext {
1512
+ /** Schedule name (e.g. `'nightlyBilling'`). */
1513
+ readonly name: string;
1514
+ /** When the firing was scheduled by the cron expression. */
1515
+ readonly scheduledAt: Date;
1516
+ /** When the firing actually started. */
1517
+ readonly firedAt: Date;
1518
+ /** Run id recorded in `_voltro_schedule_runs` (`'unrecorded'` if
1519
+ * the run-row insert failed). */
1520
+ readonly runId: string;
1521
+ /** What triggered this firing. `'self'`/`'external'`/`'manual'`. */
1522
+ readonly trigger: 'self' | 'external' | 'manual';
1523
+ }
1524
+
1525
+ /**
1526
+ * Wraps a single schedule firing. Receives `next` as a thunk so the
1527
+ * interceptor can choose to skip the firing entirely (don't call
1528
+ * `next()`), wrap it with side effects, or substitute behaviour.
1529
+ * Throwing/rejecting propagates as a handler failure recorded in
1530
+ * `_voltro_schedule_runs.status = 'failed'`. The watchdog
1531
+ * (`maxRuntimeMs`) wraps the WHOLE chain — exceeding it aborts.
1532
+ *
1533
+ * Use for: per-schedule observability (custom metrics/traces beyond
1534
+ * the framework's defaults), durable-lock extension, feature-flag-
1535
+ * gated suppression, suspend/resume integration with external
1536
+ * orchestrators.
1537
+ *
1538
+ * Typical pattern:
1539
+ *
1540
+ * onScheduleFire: async (next, ctx) => {
1541
+ * if (await isFeatureKilled(ctx.name)) return // suppress
1542
+ * const start = Date.now()
1543
+ * try {
1544
+ * await next()
1545
+ * } finally {
1546
+ * recordCustomMetric({ name: ctx.name, durationMs: Date.now() - start })
1547
+ * }
1548
+ * }
1549
+ */
1550
+ export declare type ScheduleFireInterceptor = (next: () => Promise<void>, ctx: ScheduleFireContext) => Promise<void>;
1551
+
1552
+ export declare class ScopeError extends ScopeError_base {
1553
+ }
1554
+
1555
+ declare const ScopeError_base: Schema.TaggedErrorClass<ScopeError, "ScopeError", {
1556
+ readonly _tag: Schema.tag<"ScopeError">;
1557
+ } & {
1558
+ required: typeof Schema.String;
1559
+ message: typeof Schema.String;
1560
+ }>;
1561
+
1562
+ export declare interface ServerErrorEvent {
1563
+ /** The thrown value (Error | tagged error | anything). */
1564
+ readonly error: unknown;
1565
+ /** Which primitive caught it. */
1566
+ readonly source: ServerErrorSource;
1567
+ /** Human identifier: route path / aggregate name / schedule name /
1568
+ * workflow name / webhook id / startup id / subscriber table. */
1569
+ readonly name?: string;
1570
+ /** Distributed-trace id when one is in scope (workflows carry it). */
1571
+ readonly traceId?: string;
1572
+ /** Extra context — op, runId, executionId, http status, etc. */
1573
+ readonly fields?: Record<string, unknown>;
1574
+ }
1575
+
1576
+ declare type ServerErrorListener = (event: ServerErrorEvent) => void;
1577
+
1578
+ export declare type ServerErrorSource = 'rest' | 'aggregate' | 'subscriber' | 'reaction' | 'schedule' | 'workflow' | 'webhook' | 'startup';
1579
+
1580
+ /** A strategy's verdict on a request.
1581
+ * - `matched`: this strategy claims the request; here's the Subject.
1582
+ * - `skip`: not my request (e.g. cookie absent); try next strategy.
1583
+ * - `failed`: this IS my request BUT validation failed (signature
1584
+ * mismatch, expired JWT). Composer bails to anonymous + logs;
1585
+ * silently falling through would mask attacks. */
1586
+ export declare type StrategyResolution = {
1587
+ readonly kind: 'matched';
1588
+ readonly subject: Subject;
1589
+ } | {
1590
+ readonly kind: 'skip';
1591
+ } | {
1592
+ readonly kind: 'failed';
1593
+ readonly reason: string;
1594
+ };
1595
+
1596
+ /**
1597
+ * A non-reactive server→client stream — the load-bearing distinction from
1598
+ * a query: a query streams a reactive `SubscriptionEvent` (snapshot+delta)
1599
+ * envelope over a table; a stream emits PLAIN `element`s (e.g. an agent
1600
+ * run's `AgentEvent`s), one-shot, no table, no snapshot. Backs A3's
1601
+ * agent runtime + any other server-push stream.
1602
+ */
1603
+ export declare interface StreamProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Element extends Schema.Schema.Any, Error extends Schema.Schema.All> {
1604
+ readonly kind: 'stream';
1605
+ readonly name: Name;
1606
+ readonly input: Input;
1607
+ readonly element: Element;
1608
+ readonly error: Error;
1609
+ }
1610
+
1611
+ export declare const streamToRpc: <Name extends string, Input extends Schema.Schema.Any, Element extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: StreamProcedureDescriptor<Name, Input, Element, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Element, Schema.Schema.All>, typeof Schema.Never, never>;
1612
+
1613
+ export declare const Subject: Schema.Union<[Schema.Struct<{
1614
+ type: Schema.Literal<["user"]>;
1615
+ id: typeof Schema.String;
1616
+ tenantId: typeof Schema.String;
1617
+ scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
1618
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
1619
+ }>, Schema.Struct<{
1620
+ type: Schema.Literal<["apiKey"]>;
1621
+ id: typeof Schema.String;
1622
+ tenantId: typeof Schema.String;
1623
+ scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
1624
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
1625
+ }>, Schema.Struct<{
1626
+ type: Schema.Literal<["serviceAccount"]>;
1627
+ id: typeof Schema.String;
1628
+ tenantId: typeof Schema.String;
1629
+ scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
1630
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
1631
+ }>, Schema.Struct<{
1632
+ type: Schema.Literal<["anonymous"]>;
1633
+ id: typeof Schema.Null;
1634
+ tenantId: Schema.NullOr<typeof Schema.String>;
1635
+ }>, Schema.Struct<{
1636
+ type: Schema.Literal<["system"]>;
1637
+ id: typeof Schema.String;
1638
+ tenantId: typeof Schema.Null;
1639
+ scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
1640
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
1641
+ }>]>;
1642
+
1643
+ export declare type Subject = typeof Subject.Type;
1644
+
1645
+ /** The subject's scopes (empty for anonymous / unscoped). */
1646
+ export declare const subjectScopes: (subject: Subject) => ReadonlyArray<string>;
1647
+
1648
+ export declare class SubjectService extends SubjectService_base {
1649
+ }
1650
+
1651
+ declare const SubjectService_base: Context.TagClass<SubjectService, "@voltro/Subject", {
1652
+ readonly id: string;
1653
+ readonly type: "user";
1654
+ readonly tenantId: string;
1655
+ readonly scopes?: readonly string[] | undefined;
1656
+ readonly metadata?: {
1657
+ readonly [x: string]: unknown;
1658
+ } | undefined;
1659
+ } | {
1660
+ readonly id: string;
1661
+ readonly type: "apiKey";
1662
+ readonly tenantId: string;
1663
+ readonly scopes?: readonly string[] | undefined;
1664
+ readonly metadata?: {
1665
+ readonly [x: string]: unknown;
1666
+ } | undefined;
1667
+ } | {
1668
+ readonly id: string;
1669
+ readonly type: "serviceAccount";
1670
+ readonly tenantId: string;
1671
+ readonly scopes?: readonly string[] | undefined;
1672
+ readonly metadata?: {
1673
+ readonly [x: string]: unknown;
1674
+ } | undefined;
1675
+ } | {
1676
+ readonly id: null;
1677
+ readonly type: "anonymous";
1678
+ readonly tenantId: string | null;
1679
+ } | {
1680
+ readonly id: string;
1681
+ readonly type: "system";
1682
+ readonly tenantId: null;
1683
+ readonly scopes?: readonly string[] | undefined;
1684
+ readonly metadata?: {
1685
+ readonly [x: string]: unknown;
1686
+ } | undefined;
1687
+ }>;
1688
+
1689
+ /** Subscribe to server-primitive errors. Returns an unsubscribe fn. */
1690
+ export declare const subscribeServerErrors: (listener: ServerErrorListener) => (() => void);
1691
+
1692
+ export declare type SubscriptionEvent<T> = {
1693
+ readonly _tag: 'snapshot';
1694
+ readonly revision: number;
1695
+ readonly data: T;
1696
+ readonly computed?: boolean;
1697
+ } | {
1698
+ readonly _tag: 'delta';
1699
+ readonly revision: number;
1700
+ readonly emittedAt: number;
1701
+ readonly patch: RowPatch;
1702
+ } | {
1703
+ readonly _tag: 'error';
1704
+ readonly error: unknown;
1705
+ readonly revision?: number;
1706
+ };
1707
+
1708
+ export declare const subscriptionEvent: <D extends Schema.Schema.Any>(data: D) => Schema.Union<[Schema.Struct<{
1709
+ _tag: Schema.Literal<["snapshot"]>;
1710
+ revision: typeof Schema.Number;
1711
+ data: D;
1712
+ computed: Schema.optional<typeof Schema.Boolean>;
1713
+ }>, Schema.Struct<{
1714
+ _tag: Schema.Literal<["delta"]>;
1715
+ revision: typeof Schema.Number;
1716
+ emittedAt: typeof Schema.Number;
1717
+ patch: Schema.Struct<{
1718
+ ops: Schema.Array$<Schema.Union<[Schema.Struct<{
1719
+ op: Schema.Literal<["add"]>;
1720
+ path: typeof Schema.String;
1721
+ value: Schema.refine<{
1722
+ readonly [x: string]: unknown;
1723
+ } & Readonly<Record<string, unknown>> & {
1724
+ readonly id: RowId;
1725
+ }, Schema.Schema<{
1726
+ readonly [x: string]: unknown;
1727
+ }, {
1728
+ readonly [x: string]: unknown;
1729
+ }, never>>;
1730
+ }>, Schema.Struct<{
1731
+ op: Schema.Literal<["replace"]>;
1732
+ path: typeof Schema.String;
1733
+ value: Schema.refine<{
1734
+ readonly [x: string]: unknown;
1735
+ } & Readonly<Record<string, unknown>> & {
1736
+ readonly id: RowId;
1737
+ }, Schema.Schema<{
1738
+ readonly [x: string]: unknown;
1739
+ }, {
1740
+ readonly [x: string]: unknown;
1741
+ }, never>>;
1742
+ }>, Schema.Struct<{
1743
+ op: Schema.Literal<["remove"]>;
1744
+ path: typeof Schema.String;
1745
+ }>]>>;
1746
+ order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
1747
+ }>;
1748
+ }>, Schema.Struct<{
1749
+ _tag: Schema.Literal<["error"]>;
1750
+ error: typeof Schema.Unknown;
1751
+ revision: Schema.optional<typeof Schema.Number>;
1752
+ }>]>;
1753
+
1754
+ export declare const systemSubject: (id: string, scopes?: ReadonlyArray<string>) => Subject;
1755
+
1756
+ export declare type Target<Input = unknown, Row = unknown> = TargetSpec<Input, Row> | ReadonlyArray<TargetSpec<Input, Row>>;
1757
+
1758
+ export declare type TargetSpec<Input = unknown, Row = unknown> = InsertTarget<Input, Row> | UpdateTarget<Input, Row> | DeleteTarget<Input>;
1759
+
1760
+ export declare const toRpc: <Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: QueryProcedureDescriptor<Name, Input, Output, Err> | MutationProcedureDescriptor<Name, Input, Output, Err> | ActionProcedureDescriptor<Name, Input, Output, Err> | StreamProcedureDescriptor<Name, Input, Output, Err>) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Schema.Union<[Schema.Struct<{
1761
+ _tag: Schema.Literal<["snapshot"]>;
1762
+ revision: typeof Schema.Number;
1763
+ data: Output;
1764
+ computed: Schema.optional<typeof Schema.Boolean>;
1765
+ }>, Schema.Struct<{
1766
+ _tag: Schema.Literal<["delta"]>;
1767
+ revision: typeof Schema.Number;
1768
+ emittedAt: typeof Schema.Number;
1769
+ patch: Schema.Struct<{
1770
+ ops: Schema.Array$<Schema.Union<[Schema.Struct<{
1771
+ op: Schema.Literal<["add"]>;
1772
+ path: typeof Schema.String;
1773
+ value: Schema.refine<{
1774
+ readonly [x: string]: unknown;
1775
+ } & Readonly<Record<string, unknown>> & {
1776
+ readonly id: RowId;
1777
+ }, Schema.Schema<{
1778
+ readonly [x: string]: unknown;
1779
+ }, {
1780
+ readonly [x: string]: unknown;
1781
+ }, never>>;
1782
+ }>, Schema.Struct<{
1783
+ op: Schema.Literal<["replace"]>;
1784
+ path: typeof Schema.String;
1785
+ value: Schema.refine<{
1786
+ readonly [x: string]: unknown;
1787
+ } & Readonly<Record<string, unknown>> & {
1788
+ readonly id: RowId;
1789
+ }, Schema.Schema<{
1790
+ readonly [x: string]: unknown;
1791
+ }, {
1792
+ readonly [x: string]: unknown;
1793
+ }, never>>;
1794
+ }>, Schema.Struct<{
1795
+ op: Schema.Literal<["remove"]>;
1796
+ path: typeof Schema.String;
1797
+ }>]>>;
1798
+ order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
1799
+ }>;
1800
+ }>, Schema.Struct<{
1801
+ _tag: Schema.Literal<["error"]>;
1802
+ error: typeof Schema.Unknown;
1803
+ revision: Schema.optional<typeof Schema.Number>;
1804
+ }>]>, Schema.Schema.All>, typeof Schema.Never, never> | Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Schema.All, never> | Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Output, Schema.Schema.All>, typeof Schema.Never, never>;
1805
+
1806
+ /**
1807
+ * Coerce a timestamp read (`Date` / epoch-ms number / ISO string) to
1808
+ * epoch milliseconds. Unrecognised inputs collapse to `0`.
1809
+ */
1810
+ export declare const tsMs: (v: unknown) => number;
1811
+
1812
+ /**
1813
+ * Typed error every handler can throw to signal "you must be signed
1814
+ * in to perform this action". The framework's @effect/rpc layer
1815
+ * preserves the `_tag` across the wire, and `@voltro/client`'s error
1816
+ * bus surfaces it to the consuming app so the dashboard can
1817
+ * auto-redirect to the sign-in page (cleared session + UI swap).
1818
+ *
1819
+ * Distinct from TenantMismatch (which means "you ARE signed in but
1820
+ * touching the wrong tenant"). Unauthenticated means "no real subject
1821
+ * resolved at all" — anonymous, expired token, missing cookie, etc.
1822
+ */
1823
+ export declare class Unauthenticated extends Unauthenticated_base {
1824
+ }
1825
+
1826
+ declare const Unauthenticated_base: Schema.TaggedErrorClass<Unauthenticated, "Unauthenticated", {
1827
+ readonly _tag: Schema.tag<"Unauthenticated">;
1828
+ } & {
1829
+ /** Optional context — e.g. "auth.signin required", "session expired". */
1830
+ reason: Schema.optional<typeof Schema.String>;
1831
+ }>;
1832
+
1833
+ export declare const UNDO_APPLY_TAG: "__voltro.undo.apply";
1834
+
1835
+ export declare const UNDO_LOG_TAG: "__voltro.undo.log";
1836
+
1837
+ export declare const UNDO_REDO_TAG: "__voltro.undo.redo";
1838
+
1839
+ /** `__voltro.undo.apply` — undo one invocation (synthesize + apply its inverse).
1840
+ * No `target` (it writes whatever tables the change-set touched — its effects
1841
+ * reach the client via those tables' own reactive deltas, not optimistic). */
1842
+ export declare const undoApplyDescriptor: MutationProcedureDescriptor<"__voltro.undo.apply", Schema.Struct<{
1843
+ invocationId: typeof Schema.String;
1844
+ }>, Schema.Struct<{
1845
+ ok: typeof Schema.Boolean;
1846
+ }>, Schema.Union<[typeof UndoNotFound, typeof UndoForbidden, typeof UndoConflict]>>;
1847
+
1848
+ /** The row changed since the action (a concurrent writer), OR the action
1849
+ * crossed an external side effect — a blind restore would clobber/lie, so
1850
+ * undo refuses. `reason` distinguishes the two (mirrors the engine's
1851
+ * UndoBoundaryError boundary). */
1852
+ export declare class UndoConflict extends UndoConflict_base {
1853
+ }
1854
+
1855
+ declare const UndoConflict_base: Schema.TaggedErrorClass<UndoConflict, "UndoConflict", {
1856
+ readonly _tag: Schema.tag<"UndoConflict">;
1857
+ } & {
1858
+ invocationId: typeof Schema.String;
1859
+ reason: Schema.Literal<["conflict", "action"]>;
1860
+ }>;
1861
+
1862
+ /** The action belongs to a different subject — undo is per-actor. */
1863
+ export declare class UndoForbidden extends UndoForbidden_base {
1864
+ }
1865
+
1866
+ declare const UndoForbidden_base: Schema.TaggedErrorClass<UndoForbidden, "UndoForbidden", {
1867
+ readonly _tag: Schema.tag<"UndoForbidden">;
1868
+ } & {
1869
+ invocationId: typeof Schema.String;
1870
+ }>;
1871
+
1872
+ /** One undoable action in the calling subject's recent history. The heavy
1873
+ * `changes` payload is server-only — the client list needs only this. */
1874
+ export declare const UndoLogEntry: Schema.Struct<{
1875
+ /** The invocation id (= the `_voltro_undo_log` row id) to pass to apply/redo. */
1876
+ id: typeof Schema.String;
1877
+ /** Rpc tag of the captured mutation (e.g. `todos.create`). */
1878
+ tag: typeof Schema.String;
1879
+ /** Human label ("create todo") — falls back to the tag. */
1880
+ label: Schema.NullOr<typeof Schema.String>;
1881
+ /** True once undone (so the UI shows it as redo-able). */
1882
+ undone: typeof Schema.Boolean;
1883
+ /** True when the action crossed an external side effect → not undoable. */
1884
+ crossesAction: typeof Schema.Boolean;
1885
+ /** Display-only creation time (stringified). */
1886
+ createdAt: typeof Schema.String;
1887
+ }>;
1888
+
1889
+ export declare type UndoLogEntry = Schema.Schema.Type<typeof UndoLogEntry>;
1890
+
1891
+ /** `__voltro.undo.log` — the calling subject's recent undoable actions, newest
1892
+ * first. Reactive (source `_voltro_undo_log`) so the list updates live as the
1893
+ * subject makes + undoes changes. */
1894
+ export declare const undoLogQueryDescriptor: QueryProcedureDescriptor<"__voltro.undo.log", Schema.Struct<{
1895
+ limit: Schema.optional<typeof Schema.Number>;
1896
+ }>, Schema.Array$<Schema.Struct<{
1897
+ /** The invocation id (= the `_voltro_undo_log` row id) to pass to apply/redo. */
1898
+ id: typeof Schema.String;
1899
+ /** Rpc tag of the captured mutation (e.g. `todos.create`). */
1900
+ tag: typeof Schema.String;
1901
+ /** Human label ("create todo") — falls back to the tag. */
1902
+ label: Schema.NullOr<typeof Schema.String>;
1903
+ /** True once undone (so the UI shows it as redo-able). */
1904
+ undone: typeof Schema.Boolean;
1905
+ /** True when the action crossed an external side effect → not undoable. */
1906
+ crossesAction: typeof Schema.Boolean;
1907
+ /** Display-only creation time (stringified). */
1908
+ createdAt: typeof Schema.String;
1909
+ }>>, typeof Schema.Never>;
1910
+
1911
+ /** No undo-log row for that invocation (already pruned, or wrong id). */
1912
+ export declare class UndoNotFound extends UndoNotFound_base {
1913
+ }
1914
+
1915
+ declare const UndoNotFound_base: Schema.TaggedErrorClass<UndoNotFound, "UndoNotFound", {
1916
+ readonly _tag: Schema.tag<"UndoNotFound">;
1917
+ } & {
1918
+ invocationId: typeof Schema.String;
1919
+ }>;
1920
+
1921
+ /** `__voltro.undo.redo` — re-apply a previously-undone invocation's forward changes. */
1922
+ export declare const undoRedoDescriptor: MutationProcedureDescriptor<"__voltro.undo.redo", Schema.Struct<{
1923
+ invocationId: typeof Schema.String;
1924
+ }>, Schema.Struct<{
1925
+ ok: typeof Schema.Boolean;
1926
+ }>, Schema.Union<[typeof UndoNotFound, typeof UndoForbidden, typeof UndoConflict]>>;
1927
+
1928
+ export declare interface UpdateTarget<Input = unknown, Row = unknown> {
1929
+ readonly table: string;
1930
+ readonly op: 'update';
1931
+ /** Identify the row to patch. Default: `input.id`. */
1932
+ readonly identify?: ((input: Input) => string) | undefined;
1933
+ /** Build the patch. Default: merges input over current. */
1934
+ readonly shape?: ((input: Input, current: Row) => Row) | undefined;
1935
+ }
1936
+
1937
+ export declare interface VoltroPlugin {
1938
+ /**
1939
+ * Plugin identifier — printed in `voltro dev` boot logs + surfaced in
1940
+ * the inspect manifest. Use a stable string scoped to the package
1941
+ * (e.g. `@voltro/audit`, `acme.invoicing`). Required so multiple
1942
+ * instances of the same package can be disambiguated by suffix
1943
+ * (`@voltro/audit#analytics`).
1944
+ */
1945
+ readonly name: string;
1946
+ /**
1947
+ * Plugin version — the package's own semver. Distinct from
1948
+ * `framework` (the FRAMEWORK range the plugin works with). Used by
1949
+ * the manifest endpoint + future signing + install/uninstall
1950
+ * idempotency. Optional today; recommended.
1951
+ */
1952
+ readonly version?: string;
1953
+ /** Optional human-readable description; surfaced by the dashboard. */
1954
+ readonly description?: string;
1955
+ /**
1956
+ * Compatible framework version range, in npm-semver syntax
1957
+ * (`'^1.0.0'`, `'>=2.3.0 <3'`). The runtime checks the running
1958
+ * framework against this at boot — incompatible plugins log a
1959
+ * boot-time warning naming the constraint + the running version,
1960
+ * but DON'T abort boot. App author makes the call; surfacing the
1961
+ * mismatch is the framework's job.
1962
+ *
1963
+ * Absent → no compat check (plugin opts out of versioning). Suitable
1964
+ * for in-tree plugins that ship lockstep with the framework.
1965
+ */
1966
+ readonly framework?: string;
1967
+ /**
1968
+ * Declared permission scopes. v1 advisory; future slice enforces.
1969
+ * Surfaced in the boot log + `/_voltro/inspect/plugins` endpoint
1970
+ * so operators audit what each plugin is asking for before granting.
1971
+ */
1972
+ readonly permissions?: ReadonlyArray<PluginPermission>;
1973
+ /**
1974
+ * Environment variables this plugin reads (declaration only — metadata, not
1975
+ * a read path). The plugin still reads its values directly (`options.X ??
1976
+ * process.env.X`); declaring them here surfaces the plugin's env needs in the
1977
+ * manifest (`/_voltro/inspect/env`), the generated `.env.example`, and the
1978
+ * dashboard Env panel. Mirror exactly what the plugin reads.
1979
+ */
1980
+ readonly declaredEnv?: ReadonlyArray<PluginEnvVar>;
1981
+ /**
1982
+ * Optional `effect/Schema` describing the plugin's user-supplied
1983
+ * config. When present the framework decodes the operator's config
1984
+ * payload against this schema at boot — invalid config aborts boot
1985
+ * with a typed error message. The decoded value lands in
1986
+ * `PluginLifecycleContext.config` so lifecycle hooks consume the
1987
+ * already-validated shape.
1988
+ *
1989
+ * For plugins that take options via a factory function (the
1990
+ * idiomatic TS pattern), `configSchema` is optional — the factory
1991
+ * already validates at the type level. Declare `configSchema` when
1992
+ * the plugin will surface a config form in the dashboard, or when
1993
+ * the plugin reads dynamic config from env / a config file.
1994
+ */
1995
+ readonly configSchema?: Schema.Schema.Any;
1996
+ /**
1997
+ * Wraps every MUTATION handler call. Composed in declaration order
1998
+ * across the plugin list: outermost = first listed in app.config.ts.
1999
+ * Absent → the plugin contributes nothing at the mutation boundary
2000
+ * (schema-only / lifecycle-only plugins are fine).
2001
+ */
2002
+ readonly interceptMutation?: RpcInterceptor;
2003
+ /**
2004
+ * Wraps every QUERY (streaming subscription handler) call. Same
2005
+ * composition semantics as `interceptMutation`. Most observability
2006
+ * plugins want to install this AND `interceptMutation` so the audit
2007
+ * trail covers both read + write.
2008
+ */
2009
+ readonly interceptQuery?: RpcInterceptor;
2010
+ /**
2011
+ * Wraps every ACTION (unary non-transactional handler) call. Same
2012
+ * composition semantics as `interceptMutation`. Useful for plugins
2013
+ * that need to govern outbound HTTP / file IO calls (rate limiting,
2014
+ * tenant-scoped IO quotas).
2015
+ */
2016
+ readonly interceptAction?: RpcInterceptor;
2017
+ /**
2018
+ * Effect Layer that contributes services into the per-request
2019
+ * context. Anything declared here is yieldable from any handler in
2020
+ * the host app (`const myTag = yield* MyTag`). Use this when a
2021
+ * plugin needs to expose APIs to handlers without forcing every
2022
+ * handler to import the plugin directly — handlers depend on the
2023
+ * Tag, the plugin owns the Live implementation.
2024
+ *
2025
+ * The layer is provided ONCE at boot — it must be self-contained
2026
+ * (`RIn = never`). For per-call data, expose a `Service` whose
2027
+ * methods return Effects that consume per-call inputs.
2028
+ */
2029
+ readonly services?: Layer.Layer<never, never, never>;
2030
+ /**
2031
+ * RPC routes the plugin contributes. Registered alongside the
2032
+ * user-authored routes — same wire protocol, same dashboard
2033
+ * surface. The plugin's name is prepended to each route's `name`
2034
+ * field unless the name already carries a dot.
2035
+ *
2036
+ * Use for plugin-shaped endpoints the user app shouldn't have to
2037
+ * re-implement: webhook ingestion targets, OAuth callbacks, plugin
2038
+ * admin queries, etc.
2039
+ */
2040
+ readonly routes?: ReadonlyArray<PluginRpcRoute>;
2041
+ /**
2042
+ * Plugin-contributed inspect endpoints. Mounted under
2043
+ * `/_voltro/inspect/plugins/<plugin-slug>/<path>` — see
2044
+ * `PluginInspectEndpoint`. Use for plugin-specific tooling that
2045
+ * doesn't fit the rpc wire (health probes, debug dumps,
2046
+ * on-demand stats exports).
2047
+ */
2048
+ readonly inspectEndpoints?: ReadonlyArray<PluginInspectEndpoint>;
2049
+ /**
2050
+ * Public raw-HTTP routes the plugin serves directly (not rpc, not
2051
+ * inspect). Mounted by BOTH `voltro dev` and the production `serveApi`
2052
+ * on the same listener — so e.g. `@voltro/plugin-storage` can serve
2053
+ * `GET /_voltro/storage/:id` (302-presigned or streamed bytes) in dev
2054
+ * AND prod. The handler owns its own auth + returns a status, an
2055
+ * optional body (string or bytes), content-type, and headers (so it can
2056
+ * 302-redirect or set immutable cache headers).
2057
+ */
2058
+ readonly httpRoutes?: ReadonlyArray<PluginHttpRoute>;
2059
+ /**
2060
+ * Called once by the serve pipeline (dev + serveApi) AFTER the app's
2061
+ * DataStore is built — earlier than this it doesn't exist (dev activates
2062
+ * plugins before the store). Lets a plugin bind a DB-backed resource it
2063
+ * couldn't construct from static config: e.g. `@voltro/plugin-storage`
2064
+ * swaps its in-memory ref store for `dataStoreRefStore(store)` so blob
2065
+ * metadata persists in `_voltro_storage_refs`.
2066
+ *
2067
+ * `store` is the framework `DataStore` — TYPED now (was `unknown`); a
2068
+ * `(s) => s as DataStore` implementation still compiles (DataStore →
2069
+ * DataStore is a no-op cast) so the tightening is additive. `ctx` is the
2070
+ * post-store bind context: the framework's already-open `SqlClient`
2071
+ * (`ctx.sql`) so a plugin reuses the app's pool instead of rebuilding one
2072
+ * from env, and `ctx.scheduleCoordinated(name, intervalMs, effect)` for
2073
+ * a cluster-coordinated sweep that runs on ONE replica per tick instead
2074
+ * of an un-coordinated per-replica `setInterval`.
2075
+ *
2076
+ * `ctx` is OPTIONAL in the type so the tightening stays additive: the
2077
+ * framework ALWAYS passes it, but existing single-arg call sites (a
2078
+ * plugin's `bindDataStore(store)` in a unit test) still compile. A plugin
2079
+ * that wants the enablers declares `(store, ctx)` and reads
2080
+ * `ctx?.sql` / `ctx?.scheduleCoordinated(...)`; one that ignores `ctx`
2081
+ * keeps working unchanged.
2082
+ */
2083
+ readonly bindDataStore?: (store: DataStore, ctx?: PluginBindContext) => void;
2084
+ /**
2085
+ * Post-commit ChangeEvent tap — an `Effect<void, E>` the runtime SUPERVISES.
2086
+ * Fires for EVERY committed store change (insert/update/delete on any table),
2087
+ * AFTER the transaction commits and AFTER the framework's own subscribers.
2088
+ *
2089
+ * The runtime forks the returned Effect under the plugin's supervision scope
2090
+ * and routes its failure channel to the plugin-scoped logger — so a change-tap
2091
+ * gets a REAL typed error channel (`Effect.retry`, `Effect.timeout`,
2092
+ * `Effect.catchTag`, durable enqueue) instead of fire-and-forget
2093
+ * `.catch(warn)` glue. The fork keeps the tap non-blocking: a slow or failing
2094
+ * tap can't back-pressure or break the change stream, and a failure is logged
2095
+ * scoped to the plugin, never a defect that crosses into the stream.
2096
+ *
2097
+ * Still NOT durable at the framework layer — a crash between commit and the
2098
+ * fork loses the event; a durable consumer builds durability INSIDE the
2099
+ * Effect (a `store.insert` into an outbox the way `@voltro/plugin-cdc-out`
2100
+ * does, retried against the typed error channel). This is the seam
2101
+ * `@voltro/plugin-search` uses to mirror tables into an external index
2102
+ * without a user-authored `*.subscribe.ts`. Requires the
2103
+ * `store:changes:read` permission.
2104
+ *
2105
+ * Exactly-once / change-scope semantics are unchanged: read
2106
+ * `event.origin` + `event.changeScope` inside the Effect to act once per
2107
+ * change fleet-wide (skip `origin:'injected'` on `'local'` scope; elect one
2108
+ * worker on `'fleet'`).
2109
+ */
2110
+ readonly onChangeEvent?: (event: PluginChangeEvent) => Effect.Effect<void, unknown>;
2111
+ /**
2112
+ * Wraps every schedule firing. Composed across plugins in
2113
+ * declaration order (first-in-array = outermost wrapper). Runs
2114
+ * INSIDE the framework's `maxRuntimeMs` watchdog — the watchdog
2115
+ * applies to the whole chain. See `ScheduleFireInterceptor`.
2116
+ */
2117
+ readonly onScheduleFire?: ScheduleFireInterceptor;
2118
+ /**
2119
+ * Wraps every `step()` (== `Activity.make`) invocation inside a
2120
+ * workflow body. Composed across plugins. The interceptor sees
2121
+ * the user's `execute` Effect AS an Effect — it can `Effect.tap`,
2122
+ * `Effect.retry`, `Effect.withSpan`, etc. See
2123
+ * `WorkflowStepInterceptor`. Bypassed on workflow REPLAY (only
2124
+ * fires on the first execution per step).
2125
+ */
2126
+ readonly onWorkflowStep?: WorkflowStepInterceptor;
2127
+ /**
2128
+ * Plugin-supplied codegen contribution. The framework calls this
2129
+ * during `voltro dev`'s codegen step and emits the returned string
2130
+ * into `rpcGroup.generated.ts` as a marked section. Use for typed
2131
+ * accessor bindings that wrap the plugin's runtime apis. See
2132
+ * `PluginCodegen`.
2133
+ */
2134
+ readonly codegen?: PluginCodegen;
2135
+ /**
2136
+ * Templates this plugin contributes to `voltro init` / `voltro add-app`.
2137
+ * Each entry is a self-contained directory tree the CLI seeds into
2138
+ * a new project. The framework lists them under the plugin's name
2139
+ * in `voltro list-templates`.
2140
+ */
2141
+ readonly templates?: ReadonlyArray<PluginTemplate>;
2142
+ /**
2143
+ * Pre-auth HTTP-pipeline interceptor. Fires at the very top of the
2144
+ * HTTP request handler, BEFORE auth resolution, BEFORE rpc routing,
2145
+ * BEFORE inspect. Use for rate-limit, geo-block, bot-detection,
2146
+ * outbound header injection. See `HttpRequestInterceptor`.
2147
+ *
2148
+ * Requires `'http:intercept'` permission — boot fails loudly if
2149
+ * the hook is declared without the matching permission.
2150
+ */
2151
+ readonly onHttpRequest?: HttpRequestInterceptor;
2152
+ /**
2153
+ * Dashboard mount contributions — remote-loaded ESM modules the
2154
+ * dashboard host renders alongside framework pages. See
2155
+ * `PluginDashboardMount` for the trade-offs vs iframe.
2156
+ *
2157
+ * Requires `'dashboard:mount'` permission. Surfaces under
2158
+ * `/_voltro/inspect/plugins/dashboard-mounts`; the actual mount
2159
+ * runtime lives in voltro-cloud-dashboard + voltro-devtools.
2160
+ */
2161
+ readonly dashboard?: ReadonlyArray<PluginDashboardMount>;
2162
+ /**
2163
+ * Schema + migration contribution. `tables` are merged with the
2164
+ * user's table set and run through the same idempotent
2165
+ * `applySchema()` path. `migrations` run after the schema apply
2166
+ * against the live SqlClient and are tracked in
2167
+ * `_voltro_plugin_migrations` so they execute exactly once per
2168
+ * app database.
2169
+ *
2170
+ * `tables` requires `'store:write'`; `migrations` requires
2171
+ * `'migration:run'`. Either one missing → boot fails with the
2172
+ * specific permission name in the error.
2173
+ */
2174
+ readonly extendSchema?: PluginSchemaContribution;
2175
+ /**
2176
+ * Cross-cutting error schemas merged into every procedure's wire error
2177
+ * union. Lets a plugin interceptor fail with a typed error (e.g.
2178
+ * `RateLimited`) that decodes TYPED on the client instead of as an
2179
+ * untyped defect. The cli applies these to the server rpc group; codegen
2180
+ * emits the matching imports into the generated client group. See
2181
+ * `PluginErrorSchema`.
2182
+ */
2183
+ readonly errorSchemas?: ReadonlyArray<PluginErrorSchema>;
2184
+ /**
2185
+ * Client-facing RPC route descriptors for `routes` the web client calls
2186
+ * (e.g. `storage.mintUploadTicket` behind `useUpload`). The codegen emits each
2187
+ * into the generated client rpc group so its tag resolves in the browser. Each
2188
+ * `import` MUST resolve to a BROWSER-SAFE module (descriptor/schema only) — see
2189
+ * `PluginRpcClientDescriptor`. Omit for routes only ever called server-side.
2190
+ */
2191
+ readonly rpcClientDescriptors?: ReadonlyArray<PluginRpcClientDescriptor>;
2192
+ /**
2193
+ * One-time setup invoked the FIRST time this plugin is seen by the
2194
+ * host. Use for schema migrations + resource provisioning that
2195
+ * survive across deactivate/activate cycles. See `PluginInstallHook`.
2196
+ */
2197
+ readonly onInstall?: PluginInstallHook;
2198
+ /**
2199
+ * One-time teardown invoked when the host removes the plugin
2200
+ * (`voltro plugins uninstall <name>`). Mirror of `onInstall`.
2201
+ */
2202
+ readonly onUninstall?: PluginUninstallHook;
2203
+ /**
2204
+ * One-shot setup invoked at app boot. Throws abort boot.
2205
+ * Lifecycle hooks run OUTSIDE any request — receive `PluginLifecycleContext`,
2206
+ * not `AppContext`.
2207
+ */
2208
+ readonly onActivate?: PluginActivateHook;
2209
+ /**
2210
+ * One-shot teardown invoked at app shutdown. The framework waits
2211
+ * up to 5s for it to resolve before SIGKILL.
2212
+ */
2213
+ readonly onDeactivate?: PluginDeactivateHook;
2214
+ /**
2215
+ * Contribute to the app's OpenTelemetry observability layer — extra
2216
+ * span processors / metric readers / resource attributes / a sampler.
2217
+ * Gathered at boot (before the tracer builds) and merged into the
2218
+ * framework's NodeSdk ALONGSIDE its own exporter + buffer sink (the
2219
+ * framework keeps owning the tracer — vendor SDKs run as OTel
2220
+ * CONSUMERS, never the global provider, so the in-app Traces dashboard
2221
+ * stays intact). This is how `@voltro/plugin-sentry` /
2222
+ * `@voltro/plugin-datadog` route the framework's spans to the vendor
2223
+ * with zero `OTEL_*` env. The hook may be async (lazy-init the vendor
2224
+ * SDK here). Returns of `undefined`/empty are no-ops.
2225
+ */
2226
+ readonly contributeObservability?: (ctx: PluginLifecycleContext) => Promise<ObservabilityContribution | undefined> | ObservabilityContribution | undefined;
2227
+ }
2228
+
2229
+ export declare const workflowCancelDescriptor: ActionProcedureDescriptor<"__voltro.workflow.cancel", Schema.Struct<{
2230
+ workflowName: typeof Schema.String;
2231
+ executionId: typeof Schema.String;
2232
+ }>, Schema.Struct<{
2233
+ ok: typeof Schema.Boolean;
2234
+ }>, typeof Schema.Never>;
2235
+
2236
+ export declare const WorkflowControlInputSchema: Schema.Struct<{
2237
+ workflowName: typeof Schema.String;
2238
+ executionId: typeof Schema.String;
2239
+ }>;
2240
+
2241
+ export declare type WorkflowDomainEventRow = Schema.Schema.Type<typeof WorkflowDomainEventRowSchema>;
2242
+
2243
+ export declare const WorkflowDomainEventRowSchema: Schema.Struct<{
2244
+ id: typeof Schema.String;
2245
+ name: typeof Schema.String;
2246
+ payload: typeof Schema.Unknown;
2247
+ source: typeof Schema.String;
2248
+ subject: Schema.NullOr<typeof Schema.Unknown>;
2249
+ traceId: Schema.NullOr<typeof Schema.String>;
2250
+ occurredAt: typeof Schema.Date;
2251
+ }>;
2252
+
2253
+ export declare const WorkflowDomainEventsInputSchema: Schema.Struct<{
2254
+ name: Schema.optional<typeof Schema.String>;
2255
+ limit: Schema.optional<typeof Schema.Number>;
2256
+ }>;
2257
+
2258
+ export declare const workflowDomainEventsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.domainEvents", Schema.Struct<{
2259
+ name: Schema.optional<typeof Schema.String>;
2260
+ limit: Schema.optional<typeof Schema.Number>;
2261
+ }>, Schema.Array$<Schema.Struct<{
2262
+ id: typeof Schema.String;
2263
+ name: typeof Schema.String;
2264
+ payload: typeof Schema.Unknown;
2265
+ source: typeof Schema.String;
2266
+ subject: Schema.NullOr<typeof Schema.Unknown>;
2267
+ traceId: Schema.NullOr<typeof Schema.String>;
2268
+ occurredAt: typeof Schema.Date;
2269
+ }>>, typeof Schema.Never>;
2270
+
2271
+ export declare const WorkflowEventDeliveriesInputSchema: Schema.Struct<{
2272
+ eventId: typeof Schema.String;
2273
+ }>;
2274
+
2275
+ export declare const workflowEventDeliveriesQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.event.deliveries", Schema.Struct<{
2276
+ eventId: typeof Schema.String;
2277
+ }>, Schema.Array$<Schema.Struct<{
2278
+ id: typeof Schema.String;
2279
+ eventId: typeof Schema.String;
2280
+ eventName: typeof Schema.String;
2281
+ triggerId: typeof Schema.String;
2282
+ workflowName: typeof Schema.String;
2283
+ executionId: Schema.NullOr<typeof Schema.String>;
2284
+ status: Schema.Literal<["starting", "started", "skipped", "failed"]>;
2285
+ idempotencyKey: typeof Schema.String;
2286
+ skipped: typeof Schema.Boolean;
2287
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2288
+ createdAt: typeof Schema.Date;
2289
+ completedAt: Schema.NullOr<typeof Schema.Date>;
2290
+ }>>, typeof Schema.Never>;
2291
+
2292
+ export declare type WorkflowEventDeliveryRow = Schema.Schema.Type<typeof WorkflowEventDeliveryRowSchema>;
2293
+
2294
+ export declare const WorkflowEventDeliveryRowSchema: Schema.Struct<{
2295
+ id: typeof Schema.String;
2296
+ eventId: typeof Schema.String;
2297
+ eventName: typeof Schema.String;
2298
+ triggerId: typeof Schema.String;
2299
+ workflowName: typeof Schema.String;
2300
+ executionId: Schema.NullOr<typeof Schema.String>;
2301
+ status: Schema.Literal<["starting", "started", "skipped", "failed"]>;
2302
+ idempotencyKey: typeof Schema.String;
2303
+ skipped: typeof Schema.Boolean;
2304
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2305
+ createdAt: typeof Schema.Date;
2306
+ completedAt: Schema.NullOr<typeof Schema.Date>;
2307
+ }>;
2308
+
2309
+ export declare type WorkflowParentClosePolicy = Schema.Schema.Type<typeof WorkflowParentClosePolicySchema>;
2310
+
2311
+ export declare const WorkflowParentClosePolicySchema: Schema.Literal<["cancel", "terminate", "abandon"]>;
2312
+
2313
+ export declare const workflowResumeDescriptor: ActionProcedureDescriptor<"__voltro.workflow.resume", Schema.Struct<{
2314
+ workflowName: typeof Schema.String;
2315
+ executionId: typeof Schema.String;
2316
+ }>, Schema.Struct<{
2317
+ ok: typeof Schema.Boolean;
2318
+ }>, typeof Schema.Never>;
2319
+
2320
+ export declare type WorkflowRunEventRow = Schema.Schema.Type<typeof WorkflowRunEventRowSchema>;
2321
+
2322
+ export declare const WorkflowRunEventRowSchema: Schema.Struct<{
2323
+ id: typeof Schema.String;
2324
+ runId: typeof Schema.String;
2325
+ eventType: typeof Schema.String;
2326
+ payload: Schema.NullOr<typeof Schema.Unknown>;
2327
+ occurredAt: typeof Schema.Date;
2328
+ stepName: Schema.NullOr<typeof Schema.String>;
2329
+ attempt: Schema.NullOr<typeof Schema.Number>;
2330
+ }>;
2331
+
2332
+ export declare const workflowRunEventsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.run.events", Schema.Struct<{
2333
+ /** `_voltro_workflow_runs.id`; step/event rows reference this id. */
2334
+ runId: typeof Schema.String;
2335
+ }>, Schema.Array$<Schema.Struct<{
2336
+ id: typeof Schema.String;
2337
+ runId: typeof Schema.String;
2338
+ eventType: typeof Schema.String;
2339
+ payload: Schema.NullOr<typeof Schema.Unknown>;
2340
+ occurredAt: typeof Schema.Date;
2341
+ stepName: Schema.NullOr<typeof Schema.String>;
2342
+ attempt: Schema.NullOr<typeof Schema.Number>;
2343
+ }>>, typeof Schema.Never>;
2344
+
2345
+ export declare type WorkflowRunHandle = Schema.Schema.Type<typeof WorkflowRunHandleSchema>;
2346
+
2347
+ export declare const WorkflowRunHandleSchema: Schema.Struct<{
2348
+ /** Stable handle callers pass to `useWorkflowRun(...)`; currently the durable execution id. */
2349
+ id: typeof Schema.String;
2350
+ /** Workflow tag, e.g. `notes.summarise`. */
2351
+ workflowName: typeof Schema.String;
2352
+ /** Deterministic durable id derived from workflow name + idempotency key. */
2353
+ executionId: typeof Schema.String;
2354
+ /** Starting is fire-and-return; waiting is explicit through workflow run APIs. */
2355
+ status: Schema.Literal<["running"]>;
2356
+ }>;
2357
+
2358
+ export declare const workflowRunQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.run", Schema.Struct<{
2359
+ /** `_voltro_workflow_runs.id` OR durable `executionId`. */
2360
+ id: typeof Schema.String;
2361
+ }>, Schema.Array$<Schema.Struct<{
2362
+ id: typeof Schema.String;
2363
+ tag: typeof Schema.String;
2364
+ executionId: typeof Schema.String;
2365
+ status: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
2366
+ payload: typeof Schema.Unknown;
2367
+ workflowVersion: Schema.NullOr<typeof Schema.String>;
2368
+ workflowPatches: Schema.NullOr<typeof Schema.Unknown>;
2369
+ output: Schema.NullOr<typeof Schema.Unknown>;
2370
+ subject: Schema.NullOr<typeof Schema.Unknown>;
2371
+ source: Schema.NullOr<typeof Schema.String>;
2372
+ errorTag: Schema.NullOr<typeof Schema.String>;
2373
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2374
+ cancelled: typeof Schema.Boolean;
2375
+ startedAt: typeof Schema.Date;
2376
+ completedAt: Schema.NullOr<typeof Schema.Date>;
2377
+ durationMs: Schema.NullOr<typeof Schema.Number>;
2378
+ traceId: Schema.NullOr<typeof Schema.String>;
2379
+ parentExecutionId: Schema.NullOr<typeof Schema.String>;
2380
+ parentClosePolicy: Schema.NullOr<Schema.Literal<["cancel", "terminate", "abandon"]>>;
2381
+ }>>, typeof Schema.Never>;
2382
+
2383
+ export declare const WorkflowRunRefSchema: Schema.Struct<{
2384
+ /** `_voltro_workflow_runs.id` OR durable `executionId`. */
2385
+ id: typeof Schema.String;
2386
+ }>;
2387
+
2388
+ export declare type WorkflowRunRow = Schema.Schema.Type<typeof WorkflowRunRowSchema>;
2389
+
2390
+ export declare const WorkflowRunRowSchema: Schema.Struct<{
2391
+ id: typeof Schema.String;
2392
+ tag: typeof Schema.String;
2393
+ executionId: typeof Schema.String;
2394
+ status: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
2395
+ payload: typeof Schema.Unknown;
2396
+ workflowVersion: Schema.NullOr<typeof Schema.String>;
2397
+ workflowPatches: Schema.NullOr<typeof Schema.Unknown>;
2398
+ output: Schema.NullOr<typeof Schema.Unknown>;
2399
+ subject: Schema.NullOr<typeof Schema.Unknown>;
2400
+ source: Schema.NullOr<typeof Schema.String>;
2401
+ errorTag: Schema.NullOr<typeof Schema.String>;
2402
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2403
+ cancelled: typeof Schema.Boolean;
2404
+ startedAt: typeof Schema.Date;
2405
+ completedAt: Schema.NullOr<typeof Schema.Date>;
2406
+ durationMs: Schema.NullOr<typeof Schema.Number>;
2407
+ traceId: Schema.NullOr<typeof Schema.String>;
2408
+ parentExecutionId: Schema.NullOr<typeof Schema.String>;
2409
+ parentClosePolicy: Schema.NullOr<Schema.Literal<["cancel", "terminate", "abandon"]>>;
2410
+ }>;
2411
+
2412
+ export declare type WorkflowRunsInput = Schema.Schema.Type<typeof WorkflowRunsInputSchema>;
2413
+
2414
+ export declare const WorkflowRunsInputSchema: Schema.Struct<{
2415
+ tag: Schema.optional<typeof Schema.String>;
2416
+ status: Schema.optional<Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>>;
2417
+ limit: Schema.optional<typeof Schema.Number>;
2418
+ }>;
2419
+
2420
+ export declare const workflowRunsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.runs", Schema.Struct<{
2421
+ tag: Schema.optional<typeof Schema.String>;
2422
+ status: Schema.optional<Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>>;
2423
+ limit: Schema.optional<typeof Schema.Number>;
2424
+ }>, Schema.Array$<Schema.Struct<{
2425
+ id: typeof Schema.String;
2426
+ tag: typeof Schema.String;
2427
+ executionId: typeof Schema.String;
2428
+ status: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
2429
+ payload: typeof Schema.Unknown;
2430
+ workflowVersion: Schema.NullOr<typeof Schema.String>;
2431
+ workflowPatches: Schema.NullOr<typeof Schema.Unknown>;
2432
+ output: Schema.NullOr<typeof Schema.Unknown>;
2433
+ subject: Schema.NullOr<typeof Schema.Unknown>;
2434
+ source: Schema.NullOr<typeof Schema.String>;
2435
+ errorTag: Schema.NullOr<typeof Schema.String>;
2436
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2437
+ cancelled: typeof Schema.Boolean;
2438
+ startedAt: typeof Schema.Date;
2439
+ completedAt: Schema.NullOr<typeof Schema.Date>;
2440
+ durationMs: Schema.NullOr<typeof Schema.Number>;
2441
+ traceId: Schema.NullOr<typeof Schema.String>;
2442
+ parentExecutionId: Schema.NullOr<typeof Schema.String>;
2443
+ parentClosePolicy: Schema.NullOr<Schema.Literal<["cancel", "terminate", "abandon"]>>;
2444
+ }>>, typeof Schema.Never>;
2445
+
2446
+ export declare type WorkflowRunStatus = Schema.Schema.Type<typeof WorkflowRunStatusSchema>;
2447
+
2448
+ export declare const WorkflowRunStatusSchema: Schema.Literal<["running", "succeeded", "failed", "cancelled", "suspended"]>;
2449
+
2450
+ export declare type WorkflowRunStepRow = Schema.Schema.Type<typeof WorkflowRunStepRowSchema>;
2451
+
2452
+ export declare const WorkflowRunStepRowSchema: Schema.Struct<{
2453
+ id: typeof Schema.String;
2454
+ runId: typeof Schema.String;
2455
+ stepName: typeof Schema.String;
2456
+ attempt: typeof Schema.Number;
2457
+ status: Schema.Literal<["running", "succeeded", "failed"]>;
2458
+ input: Schema.NullOr<typeof Schema.Unknown>;
2459
+ retryPolicy: Schema.NullOr<typeof Schema.Unknown>;
2460
+ output: Schema.NullOr<typeof Schema.Unknown>;
2461
+ errorTag: Schema.NullOr<typeof Schema.String>;
2462
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2463
+ errorCause: Schema.NullOr<typeof Schema.Unknown>;
2464
+ startedAt: typeof Schema.Date;
2465
+ completedAt: Schema.NullOr<typeof Schema.Date>;
2466
+ durationMs: Schema.NullOr<typeof Schema.Number>;
2467
+ }>;
2468
+
2469
+ export declare const workflowRunStepsQueryDescriptor: QueryProcedureDescriptor<"__voltro.workflow.run.steps", Schema.Struct<{
2470
+ /** `_voltro_workflow_runs.id`; step/event rows reference this id. */
2471
+ runId: typeof Schema.String;
2472
+ }>, Schema.Array$<Schema.Struct<{
2473
+ id: typeof Schema.String;
2474
+ runId: typeof Schema.String;
2475
+ stepName: typeof Schema.String;
2476
+ attempt: typeof Schema.Number;
2477
+ status: Schema.Literal<["running", "succeeded", "failed"]>;
2478
+ input: Schema.NullOr<typeof Schema.Unknown>;
2479
+ retryPolicy: Schema.NullOr<typeof Schema.Unknown>;
2480
+ output: Schema.NullOr<typeof Schema.Unknown>;
2481
+ errorTag: Schema.NullOr<typeof Schema.String>;
2482
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2483
+ errorCause: Schema.NullOr<typeof Schema.Unknown>;
2484
+ startedAt: typeof Schema.Date;
2485
+ completedAt: Schema.NullOr<typeof Schema.Date>;
2486
+ durationMs: Schema.NullOr<typeof Schema.Number>;
2487
+ }>>, typeof Schema.Never>;
2488
+
2489
+ export declare const WorkflowRunTableRefSchema: Schema.Struct<{
2490
+ /** `_voltro_workflow_runs.id`; step/event rows reference this id. */
2491
+ runId: typeof Schema.String;
2492
+ }>;
2493
+
2494
+ export declare const workflowSignalDescriptor: ActionProcedureDescriptor<"__voltro.workflow.signal", Schema.Struct<{
2495
+ id: typeof Schema.String;
2496
+ signalName: typeof Schema.String;
2497
+ payload: Schema.optional<typeof Schema.Unknown>;
2498
+ }>, Schema.Struct<{
2499
+ eventId: typeof Schema.String;
2500
+ }>, typeof Schema.Never>;
2501
+
2502
+ export declare const WorkflowSignalInputSchema: Schema.Struct<{
2503
+ id: typeof Schema.String;
2504
+ signalName: typeof Schema.String;
2505
+ payload: Schema.optional<typeof Schema.Unknown>;
2506
+ }>;
2507
+
2508
+ /**
2509
+ * Per-step plugin interceptor. Wraps every `step()` (==
2510
+ * `Activity.make`) invocation inside a workflow body. Composed
2511
+ * across plugins at boot. The interceptor sees the user's `execute`
2512
+ * Effect AS an Effect — it can wrap with `Effect.tap`,
2513
+ * `Effect.retry`, `Effect.withSpan`, etc.
2514
+ *
2515
+ * Useful for: per-step audit beyond the framework's row-recorder,
2516
+ * step-level retry-policy override, step-level resource scoping,
2517
+ * external observability injection.
2518
+ *
2519
+ * The interceptor runs INSIDE `@effect/workflow`'s replay logic —
2520
+ * on a workflow resume, the framework replays cached Activity
2521
+ * outputs without re-running the user effect, and the interceptor
2522
+ * is bypassed on replay (only fires on the first execution).
2523
+ */
2524
+ export declare interface WorkflowStepContext {
2525
+ /** Run id from `_voltro_workflow_runs` (`'unrecorded'` outside
2526
+ * the framework's wrapper, e.g. in unit tests). */
2527
+ readonly runId: string;
2528
+ /** Step name (== first arg of `Activity.make({ name })`). */
2529
+ readonly stepName: string;
2530
+ /** Attempt number — 1 on first try, 2+ on retry. */
2531
+ readonly attempt: number;
2532
+ }
2533
+
2534
+ export declare type WorkflowStepInterceptor = <Success, ErrorE, R>(next: Effect.Effect<Success, ErrorE, R>, ctx: WorkflowStepContext) => Effect.Effect<Success, ErrorE, R>;
2535
+
2536
+ export declare const workflowUpdateDescriptor: ActionProcedureDescriptor<"__voltro.workflow.update", Schema.Struct<{
2537
+ id: typeof Schema.String;
2538
+ updateName: typeof Schema.String;
2539
+ payload: Schema.optional<typeof Schema.Unknown>;
2540
+ timeoutMs: Schema.optional<typeof Schema.Number>;
2541
+ }>, Schema.Struct<{
2542
+ eventId: typeof Schema.String;
2543
+ updateId: typeof Schema.String;
2544
+ completedEventId: typeof Schema.String;
2545
+ result: typeof Schema.Unknown;
2546
+ }>, typeof Schema.Never>;
2547
+
2548
+ export declare const WorkflowUpdateInputSchema: Schema.Struct<{
2549
+ id: typeof Schema.String;
2550
+ updateName: typeof Schema.String;
2551
+ payload: Schema.optional<typeof Schema.Unknown>;
2552
+ timeoutMs: Schema.optional<typeof Schema.Number>;
2553
+ }>;
2554
+
2555
+ export declare type WorkflowUpdateResult = Schema.Schema.Type<typeof WorkflowUpdateResultSchema>;
2556
+
2557
+ export declare const WorkflowUpdateResultSchema: Schema.Struct<{
2558
+ eventId: typeof Schema.String;
2559
+ updateId: typeof Schema.String;
2560
+ completedEventId: typeof Schema.String;
2561
+ result: typeof Schema.Unknown;
2562
+ }>;
2563
+
2564
+ export { }