@voltro/protocol 0.81.0 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ import { RpcMiddleware } from '@effect/rpc';
8
8
  import { Schema } from 'effect';
9
9
  import { SqlClient } from '@effect/sql';
10
10
  import { Stream } from '@effect/rpc/RpcSchema';
11
+ import { VersionConflict } from '@voltro/database/wire';
11
12
 
12
13
  export declare interface ActionProcedureDescriptor<Name extends string, Input extends Schema.Schema.Any, Output extends Schema.Schema.Any, Error extends Schema.Schema.All> {
13
14
  readonly kind: 'action';
@@ -83,6 +84,46 @@ export declare const ADMIN_SCOPE = "admin:full";
83
84
  * so the wording lives beside the semantics it describes. */
84
85
  export declare const advisoryResourceGuardWarning: (tags: ReadonlyArray<string>) => string;
85
86
 
87
+ /**
88
+ * What an UNAUTHENTICATED caller is — the app's decision, never the request's.
89
+ *
90
+ * A request that matches no auth strategy still resolves to a Subject, and
91
+ * that Subject's `tenantId` decides what every tenant-scoped table and every
92
+ * `openAccess` procedure answers. Left to the request, the `x-tenant` header
93
+ * would choose it: any client could read any tenant's public-per-tenant data,
94
+ * and a procedure that was `openAccess` for the tenant's MEMBERS would answer
95
+ * for whichever tenant the header named. So the policy is declared once, in
96
+ * `app.config.ts` (`auth.anonymous`), and `voltro serve` refuses to boot
97
+ * without it.
98
+ *
99
+ * - `'refuse'` — no unauthenticated callers at all: `Unauthenticated`.
100
+ * - `'no-tenant'` — the caller carries the empty tenant `''`: every
101
+ * tenant-scoped read is empty (fails closed), tables without `tenant()` and
102
+ * procedures that need no tenant answer. The default of the composer, and
103
+ * the right choice for an api whose public surface is token-shaped (an
104
+ * invite preview, a signed link).
105
+ * - `'header'` — the tenant is what the request's `x-tenant` header names; a
106
+ * request without it is refused. For public-per-tenant data (a marketing
107
+ * site per tenant, public listings by slug) — and ONLY that: every
108
+ * `openAccess` procedure and every table without `tenant()` is then public
109
+ * for a client-chosen tenant.
110
+ * - `{ tenantId }` — every unauthenticated caller is that ONE tenant, chosen
111
+ * here; the header is not consulted.
112
+ *
113
+ * NEVER `null`: a null tenant is a SYSTEM subject to the store and reads
114
+ * unscoped, across every tenant.
115
+ */
116
+ export declare type AnonymousPolicy = 'refuse' | 'no-tenant' | 'header' | {
117
+ readonly tenantId: string;
118
+ };
119
+
120
+ /** The development boot's convenience, which no app config can name: the
121
+ * header's tenant, else the dev tenant. `voltro dev` runs an undecided app
122
+ * with it and says so; `voltro serve` refuses instead. */
123
+ export declare type AnonymousPolicyOrDevTenant = AnonymousPolicy | {
124
+ readonly headerOrTenant: string;
125
+ };
126
+
86
127
  export declare const anonymousSubject: (tenantId: string | null, credentialRejected?: string) => Subject;
87
128
 
88
129
  /** The erased policy a descriptor carries (input generic dropped). */
@@ -155,6 +196,9 @@ export declare interface AppliedScopes {
155
196
  readonly removed: ReadonlyArray<string>;
156
197
  }
157
198
 
199
+ /** Keep a pairing for the next boot; the app reads it when it builds its api config. */
200
+ export declare const applyPairing: (storage: PairingStorage, pairing: Pairing) => Promise<void>;
201
+
158
202
  export declare const applyRowPatch: (prev: ReadonlyArray<PatchRow>, patch: RowPatch, options?: ApplyRowPatchOptions) => ReadonlyArray<PatchRow>;
159
203
 
160
204
  /**
@@ -574,7 +618,7 @@ export declare interface AuthStrategy {
574
618
  readonly resolve: (input: AuthStrategyInput) => Promise<StrategyResolution> | StrategyResolution;
575
619
  /**
576
620
  * The bearer-token PREFIX this strategy claims, when it gates on one
577
- * (`'sk_'`, `'awb_'`). Declared so a collision is DETECTABLE.
621
+ * (`'sk_'`, `'acme_'`). Declared so a collision is DETECTABLE.
578
622
  *
579
623
  * Two strategies claiming the same prefix is not a harmless duplicate: the
580
624
  * chain is first-match-wins, so whichever runs first decides the Subject —
@@ -772,9 +816,14 @@ export declare const checkGuards: (subject: Subject, guards: ReadonlyArray<AnyCh
772
816
  *
773
817
  * Returns a typed `ScopeError` naming the first unmet scope, or `null` when
774
818
  * every guard passes. The runtime calls this in the dispatch spine BEFORE the
775
- * executor (mutations: before the transaction opens).
819
+ * executor (mutations: before the transaction opens). A resolver or tuple
820
+ * source that answers `'missing'` turns the refusal into `NoSuchResource` for
821
+ * a signed-in caller — the first failing guard decides, so a scope refusal
822
+ * ahead of it is never upgraded into a disclosure.
776
823
  */
777
- export declare const checkGuardsEffect: (subject: Subject, guards: ReadonlyArray<AnyCheckSpec> | undefined, input: unknown, options?: GuardCheckOptions) => Effect.Effect<ScopeError | Unauthenticated | null>;
824
+ export declare const checkGuardsEffect: (subject: Subject, guards: ReadonlyArray<AnyCheckSpec> | undefined, input: unknown, options?: GuardCheckOptions) => Effect.Effect<ScopeError | Unauthenticated | NoSuchResource | null>;
825
+
826
+ export declare const clearPairing: (storage: PairingStorage) => Promise<void>;
778
827
 
779
828
  export declare interface ClientDescriptor {
780
829
  readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
@@ -835,42 +884,17 @@ export declare interface ClientTarget {
835
884
  export declare const compareSurfaces: (mine: Readonly<Record<string, string>>, theirs: Readonly<Record<string, string>>) => SurfaceMismatch;
836
885
 
837
886
  export declare const composeAuthStrategies: (strategies: ReadonlyArray<AuthStrategy>, options?: {
838
- /** Fallback when no strategy matched. Default: returns the input's
839
- * tenant from `x-tenant` header or null. */
887
+ /** Fallback when no strategy matched. Replaces `anonymous` entirely: the
888
+ * function owns the anonymous subject AND the rejected-credential decision. */
840
889
  readonly fallback?: (input: AuthStrategyInput) => Subject;
841
890
  /** Hook for logging — called on every `failed` resolution. */
842
891
  readonly onStrategyFailed?: (event: {
843
892
  strategyId: string;
844
893
  reason: string;
845
894
  }) => void;
846
- /**
847
- * Reject anonymous callers that carry no tenant. When `true` and no
848
- * strategy matched AND no `x-tenant` header is present, the resolver
849
- * throws `Unauthenticated` instead of returning a null-tenant
850
- * anonymous Subject. Use it on multi-tenant apps where every call —
851
- * even an anonymous one — must be scoped to a tenant; without it a
852
- * tenant-less anonymous Subject can read across the whole DB on
853
- * tables that aren't `tenant()`-scoped. Ignored when a custom
854
- * `fallback` is supplied (the fallback owns that decision).
855
- */
856
- readonly anonymousTenantRequired?: boolean;
857
- /**
858
- * The ONE tenant every anonymous caller resolves to — chosen by the server,
859
- * never by the request. When set, the `x-tenant` header is not consulted:
860
- * a client naming its own tenant is exactly the thing a fixed anonymous
861
- * tenant exists to rule out. Ignored when `fallback` is supplied (that
862
- * function owns the anonymous subject).
863
- */
864
- readonly anonymousTenantId?: string;
865
- /**
866
- * The tenant an anonymous caller resolves to when its request names none
867
- * in `x-tenant`. The header still wins when present — this only replaces
868
- * the `null` tenant. It exists so a boot path can supply a default tenant
869
- * without a `fallback`: a `fallback` owns the rejected-credential decision,
870
- * this does not, so a presented-and-rejected credential is still refused.
871
- * Ignored when `anonymousTenantId` or `fallback` is set.
872
- */
873
- readonly anonymousTenantDefault?: string;
895
+ /** What an unauthenticated caller is — see {@link AnonymousPolicy}.
896
+ * Default `'no-tenant'`. Ignored when `fallback` is supplied. */
897
+ readonly anonymous?: AnonymousPolicyOrDevTenant;
874
898
  /**
875
899
  * The subject's memberships — the ONE source, strategy-independent. Read
876
900
  * only when a stored tenant selection exists for the subject and names a
@@ -1366,8 +1390,8 @@ export declare interface CoordinatedScheduleHandle {
1366
1390
  */
1367
1391
  export declare interface CoordinatedTaskOptions {
1368
1392
  /**
1369
- * Stop ticking entirely on an idle tick with no pending deadline, and come
1370
- * back only on `wake()`. Default `false`.
1393
+ * Stop ticking entirely on an idle tick that reports `nextDueInMs: null`
1394
+ * (nothing scheduled), and come back only on `wake()`. Default `false`.
1371
1395
  *
1372
1396
  * Pass `true` only when an arrival is GUARANTEED to call `wake()` — a change
1373
1397
  * subscription on the table your task drains, on a deployment where remote
@@ -1381,10 +1405,17 @@ export declare interface CoordinatedTaskOptions {
1381
1405
  export declare interface CoordinatedTickOutcome {
1382
1406
  /** `true` when the tick found nothing to do. Only an idle tick backs off. */
1383
1407
  readonly idle: boolean;
1384
- /** Milliseconds until the earliest deadline this task already knows about.
1385
- * Caps the backoff, so a task that is idle now but has something due in
1386
- * 400 ms is armed for 400 ms rather than for the idle ceiling. */
1387
- readonly nextDueInMs?: number;
1408
+ /** Milliseconds until the earliest deadline this task already knows about —
1409
+ * a retry, a re-poll, a lease running out. Caps the backoff, so a task that
1410
+ * is idle now but has something due in 400 ms is armed for 400 ms rather
1411
+ * than for the idle ceiling.
1412
+ *
1413
+ * `null` says "nothing is scheduled; new work can only arrive as a write",
1414
+ * and is the ONLY idle report that lets a `disarmWhenIdle` task stop
1415
+ * ticking. Leaving the field out backs off instead: work that comes due on
1416
+ * a clock writes nothing, so nothing would ever wake a task that disarmed
1417
+ * while holding it. */
1418
+ readonly nextDueInMs?: number | null;
1388
1419
  }
1389
1420
 
1390
1421
  /**
@@ -1402,6 +1433,14 @@ export declare type DeclaredAccess<Input = unknown> = ReadonlyArray<AnyGuardSpec
1402
1433
  /** Every channel declared in this process, by routing key. */
1403
1434
  export declare const declaredReactivityChannelKeys: () => ReadonlySet<string>;
1404
1435
 
1436
+ /**
1437
+ * Does this guard list name a resource — a relationship guard, or a scope guard
1438
+ * with a `resource` extractor? Exactly the guards whose resolver may answer
1439
+ * `'missing'`, so exactly the procedures whose wire union carries
1440
+ * {@link NoSuchResource}.
1441
+ */
1442
+ export declare const declaresResourceGuard: (guards: ReadonlyArray<AnyCheckSpec> | undefined) => boolean;
1443
+
1405
1444
  /**
1406
1445
  * How long a RUNNING claim holds without renewal.
1407
1446
  *
@@ -2025,6 +2064,20 @@ export declare const encodeRestError: (format: RestFormat, error: RestFormatErro
2025
2064
  */
2026
2065
  export declare const encodeRestSuccess: (format: RestFormat, value: unknown, ctx: RestFormatContext) => RestFormatResponse;
2027
2066
 
2067
+ /**
2068
+ * The text to SHOW for a failure value — its `message`, else its `_tag`, else
2069
+ * `String(value)`.
2070
+ *
2071
+ * Its partner above, for the same reason: every client surface hands over the
2072
+ * declared error as the plain `{ _tag, …fields, message }` object the wire
2073
+ * carries, and `String()` of a plain object is `[object Object]`. A
2074
+ * subscription used to hand over an Effect `Cause`, whose `String()` happened
2075
+ * to read, so `String(error)` in an error banner or an `error.tsx` looked right
2076
+ * until the value became what the docs always said it was. Works on an `Error`,
2077
+ * a `Schema.TaggedError` instance, the wire object and a bare string alike.
2078
+ */
2079
+ export declare const errorMessage: (err: unknown) => string;
2080
+
2028
2081
  /**
2029
2082
  * Read the `_tag` off a thrown error value, or `undefined` when it has none.
2030
2083
  *
@@ -2400,9 +2453,10 @@ export declare const finishIdempotent: (store: IdempotencyStore, scope: string,
2400
2453
  *
2401
2454
  * A query's success type is the three-member union above, and a one-shot read
2402
2455
  * takes only its first event: a `snapshot` resolves to its `data`; an `error`
2403
- * REJECTS — with the typed error on `cause` and `voltroError`, so a caller can
2404
- * still branch on its `_tag` — exactly as a failed exit would; a `delta` cannot
2405
- * be first and says so. Anything else is a mutation or action's own result and
2456
+ * REJECTS with the typed error ITSELF — exactly as a failed exit does, and the
2457
+ * same value a live subscription's `error` and the api's error bus hand over,
2458
+ * so one `_tag` switch serves every surface; a `delta` cannot be first and says
2459
+ * so. Anything else is a mutation or action's own result and
2406
2460
  * passes through, which may legitimately be a tagged object of the app's.
2407
2461
  *
2408
2462
  * Shared because there are two such reads — the server loader's `ctx.query`
@@ -2418,6 +2472,27 @@ export declare const firstEventValue: (tag: string, value: unknown, label: strin
2418
2472
  /** A route rendered for humans — logs, the inspect surface, the dashboard. */
2419
2473
  export declare const formatEventRoute: (route: string) => string;
2420
2474
 
2475
+ /**
2476
+ * The framework errors EVERY procedure of a kind carries on its wire union —
2477
+ * the unconditional members `wireErrorUnion` adds. The conditional ones (a
2478
+ * resource guard's `NoSuchResource`, `requiresApproval`'s refusals,
2479
+ * `MutationConnectionRequired`) are left out on purpose: a type that cannot see
2480
+ * the condition must not promise them.
2481
+ *
2482
+ * It exists for the TYPE-level reader, `defineExecutor`, which checks an
2483
+ * executor's failure channel against the union the server will judge it by. A
2484
+ * test pins it against `wireErrorUnion` per kind, so the two cannot drift.
2485
+ */
2486
+ export declare const FRAMEWORK_WIRE_ERRORS: {
2487
+ readonly query: readonly [typeof ScopeError, typeof Unauthenticated];
2488
+ readonly stream: readonly [typeof ScopeError, typeof Unauthenticated];
2489
+ readonly action: readonly [typeof ScopeError, typeof Unauthenticated, typeof ValidationError, typeof ValidationErrors];
2490
+ readonly mutation: readonly [typeof ScopeError, typeof Unauthenticated, typeof BusinessRuleViolation, typeof MutationInFlight, typeof VersionConflict, typeof ValidationError, typeof ValidationErrors];
2491
+ };
2492
+
2493
+ /** The error values {@link FRAMEWORK_WIRE_ERRORS} admits for `Kind`. */
2494
+ export declare type FrameworkWireError<Kind extends WireProcedureKind> = Schema.Schema.Type<(typeof FRAMEWORK_WIRE_ERRORS)[Kind][number]>;
2495
+
2421
2496
  /** How long a record of this status stays fresh — the replay window for a
2422
2497
  * completed response, the lease for a running one (never longer than the window). */
2423
2498
  export declare const freshFor: (status: IdempotencyRecord["status"], ttlMs: number, inFlightLeaseMs: number) => number;
@@ -3008,7 +3083,7 @@ export declare interface NestedTargetFields<Input = unknown> {
3008
3083
  /** Guard WHICH cached query entries this target patches: only entries whose
3009
3084
  * CURRENT value satisfies the predicate. Pure + browser-safe. Prevents a
3010
3085
  * patch from bleeding across sibling subscriptions that share a source table
3011
- * (the guard AWB hand-writes as `roadmap.id === input.roadmapId`). Absent →
3086
+ * (the guard an app would otherwise hand-write as `roadmap.id === input.roadmapId`). Absent →
3012
3087
  * every entry matching the target `table` is patched. */
3013
3088
  readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
3014
3089
  }
@@ -3063,6 +3138,32 @@ declare const NoRowFound_base: Schema.TaggedErrorClass<NoRowFound, "NoRowFound",
3063
3138
  found: typeof Schema.Number;
3064
3139
  }>;
3065
3140
 
3141
+ /**
3142
+ * A resource guard's refusal when the resource it names does not exist — as
3143
+ * far as THIS caller may know.
3144
+ *
3145
+ * Only an app can say that. The guard never infers absence from an empty tuple
3146
+ * set or a `false` answer: "no relation" and "no row" look identical from the
3147
+ * outside, and telling them apart is an existence oracle. So this is raised
3148
+ * only when a tuple source or resolver answers `'missing'`
3149
+ * ({@link ResourceMissing}), and only for a signed-in caller — an anonymous one
3150
+ * gets the ordinary refusal whatever the source said.
3151
+ *
3152
+ * `resourceType` is the relationship guard's type, or `null` for a scope guard
3153
+ * with a `resource` extractor (which names no type). Merged into the wire union
3154
+ * of every procedure that declares a resource-bearing guard.
3155
+ */
3156
+ export declare class NoSuchResource extends NoSuchResource_base {
3157
+ }
3158
+
3159
+ declare const NoSuchResource_base: Schema.TaggedErrorClass<NoSuchResource, "NoSuchResource", {
3160
+ readonly _tag: Schema.tag<"NoSuchResource">;
3161
+ } & {
3162
+ resourceType: Schema.NullOr<typeof Schema.String>;
3163
+ resourceId: typeof Schema.String;
3164
+ message: typeof Schema.String;
3165
+ }>;
3166
+
3066
3167
  /**
3067
3168
  * What a plugin contributes to the OTel layer via `contributeObservability`.
3068
3169
  * The OTel types are intentionally `unknown` so this browser-safe protocol
@@ -3147,6 +3248,38 @@ export declare const openAccessSpec: (reason: string) => OpenAccessSpec;
3147
3248
  */
3148
3249
  export declare const orderByBearerPrefix: (strategies: ReadonlyArray<AuthStrategy>, headers: Readonly<Record<string, string | undefined>>) => ReadonlyArray<AuthStrategy>;
3149
3250
 
3251
+ export declare interface Pairing {
3252
+ /** The api's base url as the DEVICE reaches it — a LAN address, never `localhost`. */
3253
+ readonly api: string;
3254
+ /** The api process name (`main`), for the client's api binding. */
3255
+ readonly name: string;
3256
+ /** The dev inspect token, when the CLI knows it. */
3257
+ readonly token?: string;
3258
+ }
3259
+
3260
+ export declare const PAIRING_HOST = "pair";
3261
+
3262
+ export declare const PAIRING_SCHEME = "voltro-dev";
3263
+
3264
+ /** Where `applyPairing` keeps the record — the platform's plain storage. */
3265
+ export declare const PAIRING_STORAGE_KEY = "voltro:pairing";
3266
+
3267
+ /** The link text. Refuses a loopback api: a phone cannot reach the laptop's `localhost`. */
3268
+ export declare const pairingLink: (pairing: Pairing) => string;
3269
+
3270
+ /** A key/value store in either spelling: the platform's `StoreStorage`
3271
+ * (`getItem`/`setItem`, what `platform.storage('local')` returns) or a
3272
+ * `get`/`set` pair (a secure store, a test fake). */
3273
+ export declare type PairingStorage = {
3274
+ readonly get: (key: string) => Promise<string | null> | string | null;
3275
+ readonly set: (key: string, value: string) => Promise<void> | void;
3276
+ readonly remove?: (key: string) => Promise<void> | void;
3277
+ } | {
3278
+ readonly getItem: (key: string) => Promise<string | null> | string | null;
3279
+ readonly setItem: (key: string, value: string) => Promise<void> | void;
3280
+ readonly removeItem?: (key: string) => Promise<void> | void;
3281
+ };
3282
+
3150
3283
  /** Split a route back into its three parts. */
3151
3284
  export declare const parseEventRoute: (route: string) => {
3152
3285
  readonly tenantId: string | null;
@@ -3154,6 +3287,11 @@ export declare const parseEventRoute: (route: string) => {
3154
3287
  readonly key: string;
3155
3288
  };
3156
3289
 
3290
+ /** The record from a link, or `null` for anything that is not a pairing link. */
3291
+ export declare const parsePairingLink: (url: string) => Pairing | null;
3292
+
3293
+ export declare const patchCellSchema: Schema.transform<typeof Schema.Unknown, typeof Schema.Unknown>;
3294
+
3157
3295
  /** A single row in a subscription result. Must carry an `id`; everything
3158
3296
  * else is opaque to the patch layer. */
3159
3297
  export declare type PatchRow = Readonly<Record<string, unknown>> & {
@@ -3267,10 +3405,12 @@ export declare interface PluginBindContext {
3267
3405
  readonly cache?: PluginCacheHandle;
3268
3406
  /**
3269
3407
  * The framework's already-open `SqlClient` (from `@effect/sql`) — the
3270
- * SAME pool the app's store uses. A plugin runs raw SQL through this
3271
- * instead of standing up its OWN `ManagedRuntime` + pool from env. Only
3272
- * present for SQL-backed stores; `undefined` on the in-memory store
3273
- * (there is no SQL engine) — guard with `if (ctx.sql)`.
3408
+ * SAME pool the app's store uses, with its TLS, credentials and schema.
3409
+ * For SQL the store's methods cannot express, against tables that are
3410
+ * DECLARED (`extendSchema.tables`, or the app's own schema): it never
3411
+ * creates, alters or drops a table, and a plugin never opens a connection
3412
+ * of its own instead. Prefer the bound `store` — it works on every
3413
+ * dialect. `undefined` on the in-memory store (no SQL engine).
3274
3414
  */
3275
3415
  readonly sql?: SqlClient.SqlClient;
3276
3416
  /**
@@ -3524,16 +3664,29 @@ export declare interface PluginDashboardMount {
3524
3664
  export declare type PluginDeactivateHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
3525
3665
 
3526
3666
  /**
3527
- * An environment variable a plugin reads. DECLARATION ONLY — metadata, not a
3528
- * read path: the plugin still reads the value itself (typically
3529
- * `options.X ?? process.env.X`). Declaring it makes the plugin's env needs
3530
- * visible to the env manifest (`/_voltro/inspect/env`), the generated
3531
- * `.env.example`, and the dashboard Env panel — so an operator can see, before
3532
- * running, exactly what each plugin expects.
3533
- *
3534
- * Structurally identical to `@voltro/env`'s `DeclaredEnvVar` (kept local here
3535
- * to avoid a protocol → env package dependency).
3667
+ * One place a plugin sends data outside the installation — folded by
3668
+ * `voltro compliance generate` into `egress.json`, the machine-readable list an
3669
+ * operator reviews before opening a firewall, and answers an air-gap question
3670
+ * from. Declare what the plugin CAN reach with the options it was given: a
3671
+ * storage plugin on the database provider reaches nothing; on s3 it reaches
3672
+ * the bucket's endpoint.
3536
3673
  */
3674
+ export declare interface PluginEgressDeclaration {
3675
+ /**
3676
+ * Where: a host (`api.example.com`), a URL, or — when the address is data,
3677
+ * not configuration — what decides it, prefixed `runtime:`
3678
+ * (`runtime:webhook targets`).
3679
+ */
3680
+ readonly destination: string;
3681
+ /** Why the data goes there, in one line. */
3682
+ readonly purpose: string;
3683
+ /** What leaves: `content` (what users write), `personal` (who someone is),
3684
+ * `files`, `metadata` (ids, names, timestamps), `telemetry`, `credentials`. */
3685
+ readonly data: ReadonlyArray<'content' | 'personal' | 'files' | 'metadata' | 'telemetry' | 'credentials'>;
3686
+ /** How an operator turns it off, and what the installation does then. */
3687
+ readonly disable: string;
3688
+ }
3689
+
3537
3690
  export declare interface PluginEnvVar {
3538
3691
  /** The env var name, e.g. `DD_API_KEY`. */
3539
3692
  readonly name: string;
@@ -4099,6 +4252,14 @@ export declare type PluginMigrationSqlClient = SqlClient.SqlClient;
4099
4252
  */
4100
4253
  export declare type PluginPermission = 'rpc:intercept:mutation' | 'rpc:intercept:query' | 'rpc:intercept:action' | 'rpc:intercept:workflow' | '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}`;
4101
4254
 
4255
+ /** What a plugin collects and uses — folded by `voltro compliance generate`
4256
+ * into PrivacyInfo.xcprivacy and the Play Data-safety answers. A plugin that
4257
+ * moves data (routes, tables) and declares nothing is listed by name. */
4258
+ export declare interface PluginPrivacyDeclaration {
4259
+ readonly collects?: ReadonlyArray<PrivacyCollectedData>;
4260
+ readonly requiredReasonApis?: ReadonlyArray<PrivacyRequiredReasonApi>;
4261
+ }
4262
+
4102
4263
  /**
4103
4264
  * Per-call context handed to a plugin-contributed rpc route's
4104
4265
  * executor. Mirror of the user-handler `AppContext` shape but typed
@@ -4373,9 +4534,10 @@ export declare interface PolicyGuardRequest {
4373
4534
  * Answers "does `subject` have `action` on `<resourceType>:<resourceId>`".
4374
4535
  * Registered once at boot via `setPolicyGuardResolver`. A failed Effect is
4375
4536
  * treated as a DENIAL — a resolver wanting another posture catches its own
4376
- * errors.
4537
+ * errors. Only `true` allows; `'missing'` refuses with {@link NoSuchResource}
4538
+ * for a signed-in caller.
4377
4539
  */
4378
- export declare type PolicyGuardResolver = (req: PolicyGuardRequest) => Effect.Effect<boolean, unknown>;
4540
+ export declare type PolicyGuardResolver = (req: PolicyGuardRequest) => Effect.Effect<ResourceVerdict, unknown>;
4379
4541
 
4380
4542
  /**
4381
4543
  * A relationship (ReBAC) guard — "may this subject perform ACTION on THIS row?"
@@ -4407,6 +4569,38 @@ export declare interface PolicyGuardSpec<Input = unknown> {
4407
4569
  readonly resource: (input: Input) => string | undefined;
4408
4570
  }
4409
4571
 
4572
+ /**
4573
+ * An environment variable a plugin reads. DECLARATION ONLY — metadata, not a
4574
+ * read path: the plugin still reads the value itself (typically
4575
+ * `options.X ?? process.env.X`). Declaring it makes the plugin's env needs
4576
+ * visible to the env manifest (`/_voltro/inspect/env`), the generated
4577
+ * `.env.example`, and the dashboard Env panel — so an operator can see, before
4578
+ * running, exactly what each plugin expects.
4579
+ *
4580
+ * Structurally identical to `@voltro/env`'s `DeclaredEnvVar` (kept local here
4581
+ * to avoid a protocol → env package dependency).
4582
+ */
4583
+ /** One kind of data an app or plugin collects, in the vocabulary both stores
4584
+ * understand (`voltro compliance generate` maps it to Apple's and Google's). */
4585
+ export declare interface PrivacyCollectedData {
4586
+ readonly category: 'name' | 'email' | 'phone' | 'physical-address' | 'identifiers' | 'device-id' | 'payment-info' | 'purchase-history' | 'precise-location' | 'coarse-location' | 'photos-or-videos' | 'audio' | 'user-content' | 'browsing-history' | 'search-history' | 'product-interaction' | 'crash-data' | 'performance-data' | 'other-diagnostic-data' | 'other-data';
4587
+ /** Linked to the user's identity. */
4588
+ readonly linked: boolean;
4589
+ /** Used to track the user across apps or sites (advertising). */
4590
+ readonly tracking: boolean;
4591
+ readonly purposes: ReadonlyArray<'app-functionality' | 'analytics' | 'product-personalization' | 'developer-advertising' | 'third-party-advertising' | 'other'>;
4592
+ /** Play: shared with third parties. Default false. */
4593
+ readonly shared?: boolean;
4594
+ /** Play: the user can decline it. Default false. */
4595
+ readonly optional?: boolean;
4596
+ }
4597
+
4598
+ /** Apple's required-reason API use (`NSPrivacyAccessedAPICategory…` + reason codes). */
4599
+ export declare interface PrivacyRequiredReasonApi {
4600
+ readonly api: string;
4601
+ readonly reasons: ReadonlyArray<string>;
4602
+ }
4603
+
4410
4604
  export declare const PROBLEM_JSON = "application/problem+json; charset=utf-8";
4411
4605
 
4412
4606
  /** RFC 9457 Problem Details for one failure: `type` from the tag, `title` from
@@ -4435,6 +4629,16 @@ export declare interface ProcedureLike {
4435
4629
 
4436
4630
  export declare const procedureShapeDigest: (rpc: ProcedureLike) => string;
4437
4631
 
4632
+ /**
4633
+ * The error values a procedure's wire union can carry, as far as types can
4634
+ * tell: its own `error:`, the framework's unconditional members for its kind,
4635
+ * and every plugin-registered error.
4636
+ */
4637
+ export declare type ProcedureWireError<D extends {
4638
+ readonly kind: WireProcedureKind;
4639
+ readonly error: Schema.Schema.All;
4640
+ }> = Schema.Schema.Type<D['error']> | FrameworkWireError<D['kind']> | RegisteredPluginError;
4641
+
4438
4642
  export declare const PROTOCOL_VERSION: 1;
4439
4643
 
4440
4644
  export declare type ProtocolVersion = typeof PROTOCOL_VERSION;
@@ -4577,6 +4781,13 @@ export declare interface PublicApiSpec {
4577
4781
  * optional, the rest inherited. See `PublicApiProfileConfig`.
4578
4782
  */
4579
4783
  readonly format?: PublicApiProfileConfig;
4784
+ /**
4785
+ * GET only: how long a CDN may keep a successful answer, and the tags it can
4786
+ * purge it by (`Cache-Tag`, `Surrogate-Key`). A request with a credential is
4787
+ * always answered `private` — see `RestCache`. `tags` receives the answer as
4788
+ * it goes on the wire.
4789
+ */
4790
+ readonly cache?: RestCache;
4580
4791
  }
4581
4792
 
4582
4793
  /**
@@ -4739,7 +4950,7 @@ export declare const queryToRpc: <Name extends string, Input extends Schema.Sche
4739
4950
  }>, Schema.Struct<{
4740
4951
  op: Schema.Literal<["mergeCells"]>;
4741
4952
  path: typeof Schema.String;
4742
- value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
4953
+ value: Schema.Record$<typeof Schema.String, Schema.transform<typeof Schema.Unknown, typeof Schema.Unknown>>;
4743
4954
  }>]>>;
4744
4955
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
4745
4956
  }>;
@@ -4824,6 +5035,11 @@ export declare type ReactivitySource = TableName | ReactivityChannel;
4824
5035
  */
4825
5036
  export declare type ReactivitySourceValue = string | ReactivityChannel;
4826
5037
 
5038
+ export declare const readPairing: (storage: PairingStorage) => Promise<Pairing | null>;
5039
+
5040
+ /** Every error a plugin registers, as one union (`never` before codegen). */
5041
+ export declare type RegisteredPluginError = VoltroPluginErrors[keyof VoltroPluginErrors];
5042
+
4827
5043
  /**
4828
5044
  * Declare, for this realm, what namespace each installed plugin actually
4829
5045
  * answers to. Emitted into `rpcGroup.generated.ts` by the codegen — one call
@@ -4840,6 +5056,19 @@ export declare type ReactivitySourceValue = string | ReactivityChannel;
4840
5056
  */
4841
5057
  export declare const registerPluginAliases: (aliases: Readonly<Record<string, string>>) => void;
4842
5058
 
5059
+ /**
5060
+ * The failure value as an `Error`, for a reporter that needs one — the value
5061
+ * itself when it already is one, else an `Error` named by its `_tag` (or
5062
+ * `NonErrorThrown`) with `errorMessage(value)` as its message and the value as
5063
+ * its `cause`.
5064
+ *
5065
+ * A loader that does not catch a refusal throws the declared error, which in a
5066
+ * web process is the plain object decoded from the wire. Handed to an error
5067
+ * reporter as it is, that becomes "Object captured as exception": no name to
5068
+ * group by, no stack, no message.
5069
+ */
5070
+ export declare const reportableError: (err: unknown) => Error;
5071
+
4843
5072
  /** Gate a handler on a scope; fails with a typed `ScopeError` if missing.
4844
5073
  * Checks the EFFECTIVE set so role-derived scopes count. */
4845
5074
  export declare const requireScope: (subject: Subject, scope: string) => Effect.Effect<void, ScopeError>;
@@ -4868,6 +5097,14 @@ export declare const resolveRestFormat: (config: PublicApiProfileConfig | undefi
4868
5097
  */
4869
5098
  export declare const resolveWorkflowAccess: (name: string, options: WorkflowAccessOptions<never>) => WorkflowAccess;
4870
5099
 
5100
+ /**
5101
+ * The answer a tuple source or resolver gives when the resource a guard names
5102
+ * does not exist for this caller. Return it only when disclosing that is
5103
+ * acceptable — typically after a TENANT-SCOPED read, so a row in another tenant
5104
+ * reads as missing rather than as "exists, not yours".
5105
+ */
5106
+ export declare type ResourceMissing = 'missing';
5107
+
4871
5108
  /** The question the runtime asks a registered resource-scope resolver. */
4872
5109
  export declare interface ResourceScopeRequest {
4873
5110
  /** The authenticated caller. */
@@ -4884,11 +5121,53 @@ export declare interface ResourceScopeRequest {
4884
5121
  * an Effect so it can read the app's role tables. It should NOT leak errors —
4885
5122
  * `checkGuardsEffect` treats a failed resolver as a DENIAL (fail-closed), so a
4886
5123
  * resolver that wants a different posture must catch its own errors.
5124
+ *
5125
+ * Only `true` allows. `'missing'` refuses with {@link NoSuchResource} (for a
5126
+ * signed-in caller); anything else refuses with `ScopeError`.
4887
5127
  */
4888
- export declare type ResourceScopeResolver = (req: ResourceScopeRequest) => Effect.Effect<boolean, unknown>;
5128
+ export declare type ResourceScopeResolver = (req: ResourceScopeRequest) => Effect.Effect<ResourceVerdict, unknown>;
5129
+
5130
+ /** A resource resolver's verdict: allowed, refused, or no such resource. */
5131
+ export declare type ResourceVerdict = boolean | ResourceMissing;
4889
5132
 
4890
5133
  export declare const REST_PROFILE_NAMES: ReadonlyArray<PublicApiProfileName>;
4891
5134
 
5135
+ /** A BINARY streaming response (download/export). The serve layer pipes the
5136
+ * ReadableStream without buffering — a body larger than the heap is fine.
5137
+ * The LAZY thunk form defers opening the source until the response streams. */
5138
+ declare interface RestByteResponse {
5139
+ readonly __voltroRestBytes: true;
5140
+ readonly byteStream: {
5141
+ readonly stream: ReadableStream<Uint8Array> | (() => ReadableStream<Uint8Array>);
5142
+ readonly contentLength?: number;
5143
+ readonly contentDisposition?: string;
5144
+ };
5145
+ readonly contentType?: string;
5146
+ readonly status?: number;
5147
+ readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
5148
+ }
5149
+
5150
+ /**
5151
+ * Caching for a GET route's successful (200) answers.
5152
+ *
5153
+ * An anonymous request's answer is `public` for `maxAge` seconds (`max-age`
5154
+ * and `s-maxage`), `stale-while-revalidate` when set, and carries `tags` as
5155
+ * `Cache-Tag` (comma-separated) and `Surrogate-Key` (space-separated) — the
5156
+ * headers CDNs purge by. A request that carries a credential (`Authorization`
5157
+ * or a cookie) gets `private, no-cache` and no tags, whatever is declared:
5158
+ * what a key or a session may see must never land in a shared cache. Every
5159
+ * such answer says `Vary: Authorization, Cookie`. Errors are not affected.
5160
+ */
5161
+ declare interface RestCache {
5162
+ /** Seconds a cache may serve the answer without asking again. */
5163
+ readonly maxAge: number;
5164
+ /** Seconds after that a stale answer may still be served while the cache refetches. */
5165
+ readonly staleWhileRevalidate?: number;
5166
+ /** Purge tags from the answer as it goes on the wire, and the input.
5167
+ * A tag with whitespace or a comma is dropped (no header can carry it). */
5168
+ readonly tags?: (output: unknown, input: unknown) => ReadonlyArray<string>;
5169
+ }
5170
+
4892
5171
  export declare type RestDates = 'iso' | 'epoch-ms';
4893
5172
 
4894
5173
  export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
@@ -4978,6 +5257,11 @@ declare interface RestGuardRejection {
4978
5257
  readonly message: string;
4979
5258
  }
4980
5259
 
5260
+ /** What a REST handler may return: the decoded output, a `response(...)` with
5261
+ * its own status and headers, an `sse(...)` stream, or `bytes(...)` for a
5262
+ * non-JSON body (a file, an export, an XML document). */
5263
+ declare type RestHandlerResult<O> = O | RestResponse<O> | RestStreamResponse | RestByteResponse;
5264
+
4981
5265
  declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
4982
5266
 
4983
5267
  export declare type RestNaming = 'camelCase' | 'snake_case';
@@ -5041,7 +5325,7 @@ declare interface RestRouteDescriptor<I, O> {
5041
5325
  /** Output schema. The handler returns the decoded `Type`; the desugar
5042
5326
  * encodes it to JSON for the `200` response. */
5043
5327
  readonly output: Schema.Schema<O, any, never>;
5044
- readonly handler: (input: I, ctx: RestRouteContext) => Promise<O | RestResponse<O>> | O | RestResponse<O>;
5328
+ readonly handler: (input: I, ctx: RestRouteContext) => Promise<RestHandlerResult<O>> | RestHandlerResult<O>;
5045
5329
  readonly summary?: string;
5046
5330
  readonly description?: string;
5047
5331
  readonly example?: RestRouteExample;
@@ -5098,6 +5382,9 @@ declare interface RestRouteDescriptor<I, O> {
5098
5382
  * of the JSON), so it is correct across content-encodings — the
5099
5383
  * transport's compression varies the bytes, not the representation. */
5100
5384
  readonly etag?: boolean;
5385
+ /** GET only: how long a shared cache (a CDN) may keep a successful answer,
5386
+ * and the tags it can be purged by. See {@link RestCache}. */
5387
+ readonly cache?: RestCache;
5101
5388
  }
5102
5389
 
5103
5390
  declare interface RestRouteExample {
@@ -5138,6 +5425,18 @@ declare interface RestRouteProjection {
5138
5425
  readonly format: RestFormat;
5139
5426
  }
5140
5427
 
5428
+ /**
5429
+ * What a REST handler returns to stream its response instead of resolving one
5430
+ * value: `sse((emit) => unsubscribe)`. The route's schema `output` is not used
5431
+ * for a stream (each frame is framed as it is produced), and everything the
5432
+ * desugar does BEFORE the handler — method gate, sunset, input decode, guards,
5433
+ * subject resolution — still applies.
5434
+ */
5435
+ declare interface RestStreamResponse {
5436
+ readonly __voltroRestStream: true;
5437
+ readonly stream: PluginHttpRouteStream;
5438
+ }
5439
+
5141
5440
  /**
5142
5441
  * Rewrite a descriptor's schema into the shape the profile puts on the wire:
5143
5442
  * `timestampMs` (`DateFromNumber`) becomes an RFC 3339 string under
@@ -5227,7 +5526,7 @@ export declare const rowPatchOpSchema: Schema.Union<[Schema.Struct<{
5227
5526
  }>, Schema.Struct<{
5228
5527
  op: Schema.Literal<["mergeCells"]>;
5229
5528
  path: typeof Schema.String;
5230
- value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
5529
+ value: Schema.Record$<typeof Schema.String, Schema.transform<typeof Schema.Unknown, typeof Schema.Unknown>>;
5231
5530
  }>]>;
5232
5531
 
5233
5532
  export declare const rowPatchSchema: Schema.Struct<{
@@ -5261,7 +5560,7 @@ export declare const rowPatchSchema: Schema.Struct<{
5261
5560
  }>, Schema.Struct<{
5262
5561
  op: Schema.Literal<["mergeCells"]>;
5263
5562
  path: typeof Schema.String;
5264
- value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
5563
+ value: Schema.Record$<typeof Schema.String, Schema.transform<typeof Schema.Unknown, typeof Schema.Unknown>>;
5265
5564
  }>]>>;
5266
5565
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
5267
5566
  }>;
@@ -5525,8 +5824,18 @@ export declare interface ServerErrorEvent {
5525
5824
  readonly name?: string;
5526
5825
  /** Distributed-trace id when one is in scope (workflows carry it). */
5527
5826
  readonly traceId?: string;
5528
- /** Extra context — op, runId, executionId, http status, etc. */
5827
+ /** Extra context — op, runId, executionId, http status, etc. Identity only:
5828
+ * never the primitive's data. */
5529
5829
  readonly fields?: Record<string, unknown>;
5830
+ /**
5831
+ * The error's CONTENT may carry data of a primitive declared encrypted
5832
+ * (`workflow({ encrypted: true })`): its message, its cause, a typed
5833
+ * failure's fields. A reporter that sends the event out of the process sends
5834
+ * {@link withheldServerError} instead — the class, the stack frames and the
5835
+ * identity fields, not the text. Encrypted at rest and then mailed to a
5836
+ * third party in plaintext would make the declaration decorative.
5837
+ */
5838
+ readonly contentSensitive?: boolean;
5530
5839
  }
5531
5840
 
5532
5841
  declare type ServerErrorListener = (event: ServerErrorEvent) => void;
@@ -5672,6 +5981,20 @@ export declare interface ServiceSubscription {
5672
5981
  /** The first segment of a dotted rpc tag. */
5673
5982
  export declare type ServiceTagHead<K extends string> = K extends `${infer H}.${string}` ? H : K;
5674
5983
 
5984
+ /** The request header carrying a framework session value outside a cookie. */
5985
+ export declare const SESSION_HEADER_NAME: "x-voltro-session";
5986
+
5987
+ /**
5988
+ * The session value a request presents: the dedicated header when set,
5989
+ * otherwise the named cookie. Pure — no verification, no IO.
5990
+ *
5991
+ * The header wins when both are present. A client that sets the header did so
5992
+ * deliberately; a cookie is ambient and may belong to a different app on the
5993
+ * same host (the reason `sessionExpiryFromHeaders` tolerates one that does
5994
+ * not verify). Explicit beats ambient.
5995
+ */
5996
+ export declare const sessionValueFromHeaders: (headers: Readonly<Record<string, string | undefined>>, cookieName: string, readCookie: (header: string | undefined, name: string) => string | undefined) => string | undefined;
5997
+
5675
5998
  /**
5676
5999
  * Publish the caller's fully-resolved scope set for this request (raw subject
5677
6000
  * scopes ∪ role-derived scopes ∪ any extra grants). Called by the rbac
@@ -5709,6 +6032,12 @@ export declare const shapeDigest: (text: string) => string;
5709
6032
  */
5710
6033
  export declare const sourceKeys: (source: ReactivitySourceValue | ReadonlyArray<ReactivitySourceValue> | undefined) => ReadonlyArray<string>;
5711
6034
 
6035
+ export declare const STORE_SCREENSHOT_PRESET_NAMES: [string, ...string[]];
6036
+
6037
+ export declare const STORE_SCREENSHOT_PRESETS: ReadonlyArray<StoreScreenshotPreset>;
6038
+
6039
+ export declare const STORE_SCREENSHOT_STORES: readonly ["app-store", "play", "mac-app-store", "microsoft-store"];
6040
+
5712
6041
  /**
5713
6042
  * Discriminated union of all framework-owned store errors. Use it on
5714
6043
  * a mutation's `error:` schema when you want every kind surfaced
@@ -5762,6 +6091,33 @@ declare const StoreOperationFailed_base: Schema.TaggedErrorClass<StoreOperationF
5762
6091
  cause: typeof Schema.String;
5763
6092
  }>;
5764
6093
 
6094
+ /** The PNG size the store receives: viewport × scale factor. */
6095
+ export declare const storeScreenshotPixels: (preset: StoreScreenshotPreset) => {
6096
+ readonly width: number;
6097
+ readonly height: number;
6098
+ };
6099
+
6100
+ export declare interface StoreScreenshotPreset {
6101
+ readonly name: string;
6102
+ readonly label: string;
6103
+ readonly kind: 'phone' | 'tablet' | 'desktop';
6104
+ /** The CSS viewport the page is laid out in. */
6105
+ readonly viewport: {
6106
+ readonly width: number;
6107
+ readonly height: number;
6108
+ };
6109
+ readonly deviceScaleFactor: number;
6110
+ /** Which stores accept this size. */
6111
+ readonly stores: ReadonlyArray<StoreScreenshotStore>;
6112
+ }
6113
+
6114
+ export declare const storeScreenshotPreset: (name: string) => StoreScreenshotPreset | undefined;
6115
+
6116
+ /** The presets a store takes, in table order. */
6117
+ export declare const storeScreenshotPresetsFor: (store: StoreScreenshotStore) => ReadonlyArray<StoreScreenshotPreset>;
6118
+
6119
+ export declare type StoreScreenshotStore = (typeof STORE_SCREENSHOT_STORES)[number];
6120
+
5765
6121
  export declare type StrategyResolution = {
5766
6122
  readonly kind: 'matched';
5767
6123
  readonly subject: Subject;
@@ -6095,7 +6451,7 @@ export declare const subscriptionEvent: <D extends Schema.Schema.Any>(data: D) =
6095
6451
  }>, Schema.Struct<{
6096
6452
  op: Schema.Literal<["mergeCells"]>;
6097
6453
  path: typeof Schema.String;
6098
- value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
6454
+ value: Schema.Record$<typeof Schema.String, Schema.transform<typeof Schema.Unknown, typeof Schema.Unknown>>;
6099
6455
  }>]>>;
6100
6456
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
6101
6457
  }>;
@@ -6398,7 +6754,7 @@ export declare const toRpc: <Name extends string, Input extends Schema.Schema.An
6398
6754
  }>, Schema.Struct<{
6399
6755
  op: Schema.Literal<["mergeCells"]>;
6400
6756
  path: typeof Schema.String;
6401
- value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
6757
+ value: Schema.Record$<typeof Schema.String, Schema.transform<typeof Schema.Unknown, typeof Schema.Unknown>>;
6402
6758
  }>]>>;
6403
6759
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
6404
6760
  }>;
@@ -6709,6 +7065,10 @@ export declare interface VoltroPlugin {
6709
7065
  * merge to the stricter `required` and the safer `secret`.
6710
7066
  */
6711
7067
  readonly declaredEnv?: ReadonlyArray<PluginEnvVar>;
7068
+ /** What this plugin collects and uses — see `PluginPrivacyDeclaration`. */
7069
+ readonly privacy?: PluginPrivacyDeclaration;
7070
+ /** Where this plugin sends data outside the installation — see `PluginEgressDeclaration`. Omit when it sends nothing. */
7071
+ readonly egress?: ReadonlyArray<PluginEgressDeclaration>;
6712
7072
  /**
6713
7073
  * The authorization scopes this plugin DEFINES for the app — its scope
6714
7074
  * vocabulary. Distinct from `permissions` above, which is what the plugin
@@ -6844,8 +7204,10 @@ export declare interface VoltroPlugin {
6844
7204
  * that wants the enablers declares `(store, ctx)` and reads
6845
7205
  * `ctx?.sql` / `ctx?.scheduleCoordinated(...)`; one that ignores `ctx`
6846
7206
  * keeps working unchanged.
7207
+ * A returned Promise is awaited before the app begins serving requests.
7208
+ * Store-dependent initialization may run here when activation happened first.
6847
7209
  */
6848
- readonly bindDataStore?: (store: DataStore, ctx?: PluginBindContext) => void;
7210
+ readonly bindDataStore?: (store: DataStore, ctx?: PluginBindContext) => void | Promise<void>;
6849
7211
  /**
6850
7212
  * Capabilities this plugin offers to OTHER framework machinery, by name.
6851
7213
  *
@@ -7055,6 +7417,26 @@ export declare interface VoltroPlugin {
7055
7417
  readonly onProcedureSurface?: (procedures: ReadonlyArray<ProcedureDescriptor>) => void;
7056
7418
  }
7057
7419
 
7420
+ /**
7421
+ * The plugins' cross-cutting errors, at the TYPE level.
7422
+ *
7423
+ * A plugin's `errorSchemas` reach every procedure's wire union at boot — a
7424
+ * runtime fact the type system cannot see. The codegen closes that gap: it
7425
+ * augments this interface in `rpcGroup.generated.ts` with one entry per
7426
+ * registered schema, so `defineExecutor` accepts a failure a plugin registers
7427
+ * exactly where the server will deliver it typed. Empty until the first
7428
+ * codegen run, which makes an unregistered plugin error a compile error rather
7429
+ * than a silent `InternalError`.
7430
+ *
7431
+ * declare module '@voltro/protocol' {
7432
+ * interface VoltroPluginErrors {
7433
+ * readonly '@acme/errors#NotFoundError': NotFoundError
7434
+ * }
7435
+ * }
7436
+ */
7437
+ export declare interface VoltroPluginErrors {
7438
+ }
7439
+
7058
7440
  /**
7059
7441
  * The table names THIS app declares — filled in by codegen, empty here.
7060
7442
  *
@@ -7168,7 +7550,7 @@ export declare const wireAudienceOf: (descriptor: {
7168
7550
  /**
7169
7551
  * The error union a procedure ACTUALLY puts on the wire — `descriptor.error`
7170
7552
  * plus everything the framework can produce for it before or around the
7171
- * executor.
7553
+ * executor, plus the plugins' cross-cutting errors (`extraErrors`).
7172
7554
  *
7173
7555
  * ── Why this is exported rather than inlined in the lifters ────────────────
7174
7556
  *
@@ -7198,13 +7580,34 @@ export declare const wireAudienceOf: (descriptor: {
7198
7580
  * `ScopeError` themselves — is what made the two sets agree by hand.
7199
7581
  *
7200
7582
  * One function now, so they cannot disagree again.
7583
+ *
7584
+ * The plugins' errors are part of that answer for the same reason. A plugin's
7585
+ * `errorSchemas` entry is on every lifted union, and the judgement above was
7586
+ * handed the union WITHOUT them — so an interceptor's `RateLimited`, or an
7587
+ * app-wide error a local plugin registers, decoded typed on a query and was
7588
+ * collapsed to `InternalError` on every mutation and action, on both boot
7589
+ * paths. Pass the same `extraErrors` the lifter receives.
7201
7590
  */
7202
7591
  export declare const wireErrorUnion: (descriptor: {
7203
7592
  readonly error: Schema.Schema.All;
7204
7593
  readonly guards?: DeclaredAccess | undefined;
7205
7594
  readonly requiresApproval?: AnyApprovalPolicy | undefined;
7206
7595
  readonly idempotencyScope?: MutationIdempotencyScope | undefined;
7207
- }, kind: "query" | "mutation" | "action" | "stream") => Schema.Schema.All;
7596
+ }, kind: "query" | "mutation" | "action" | "stream", extraErrors?: ExtraErrors) => Schema.Schema.All;
7597
+
7598
+ /** A procedure kind that has a wire error union. */
7599
+ export declare type WireProcedureKind = keyof typeof FRAMEWORK_WIRE_ERRORS;
7600
+
7601
+ /**
7602
+ * The error of a {@link ServerErrorEvent} with its content withheld: same
7603
+ * `name` (the class, or a typed failure's tag), the original stack FRAMES —
7604
+ * code locations, which carry no data — and a message saying where the full
7605
+ * error can be read by someone allowed to. No `cause`, no own properties.
7606
+ *
7607
+ * The stack's first line repeats the message, so it is rebuilt from the frames
7608
+ * rather than copied.
7609
+ */
7610
+ export declare const withheldServerError: (event: ServerErrorEvent) => Error;
7208
7611
 
7209
7612
  /**
7210
7613
  * Re-tag a procedure descriptor for the wire.
@@ -7698,6 +8101,147 @@ export declare const WorkflowQueuedControlTargetSchema: Schema.Struct<{
7698
8101
  requestId: typeof Schema.NonEmptyString;
7699
8102
  }>;
7700
8103
 
8104
+ export declare type WorkflowRestartCommand = typeof WorkflowRestartCommandSchema.Type;
8105
+
8106
+ export declare const WorkflowRestartCommandSchema: Schema.Struct<{
8107
+ selection: Schema.Union<[Schema.Struct<{
8108
+ kind: Schema.Literal<["failed-steps"]>;
8109
+ }>, Schema.Struct<{
8110
+ kind: Schema.Literal<["from-step"]>;
8111
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8112
+ }>]>;
8113
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8114
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8115
+ }>;
8116
+
8117
+ export declare type WorkflowRestartObservation = typeof WorkflowRestartObservationSchema.Type;
8118
+
8119
+ /** Pending is a durable reservation, not proof of reset or engine entry.
8120
+ * Entered confirms this command's generation; it does not claim run success. */
8121
+ export declare const WorkflowRestartObservationSchema: Schema.Union<[Schema.Struct<{
8122
+ kind: Schema.Literal<["not-found"]>;
8123
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8124
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8125
+ }>, Schema.Struct<{
8126
+ kind: Schema.Literal<["withdrawn"]>;
8127
+ workflowName: Schema.filter<typeof Schema.NonEmptyString>;
8128
+ withdrawnAt: Schema.filter<typeof Schema.Number>;
8129
+ selection: Schema.Union<[Schema.Struct<{
8130
+ kind: Schema.Literal<["failed-steps"]>;
8131
+ }>, Schema.Struct<{
8132
+ kind: Schema.Literal<["from-step"]>;
8133
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8134
+ }>]>;
8135
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8136
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8137
+ }>, Schema.Struct<{
8138
+ kind: Schema.Literal<["pending"]>;
8139
+ workflowName: Schema.filter<typeof Schema.NonEmptyString>;
8140
+ executionId: Schema.filter<typeof Schema.NonEmptyString>;
8141
+ originalGeneration: Schema.filter<typeof Schema.NonEmptyString>;
8142
+ requestedAt: Schema.filter<typeof Schema.Number>;
8143
+ dispatchAttempts: Schema.refine<number, typeof Schema.NonNegative>;
8144
+ selection: Schema.Union<[Schema.Struct<{
8145
+ kind: Schema.Literal<["failed-steps"]>;
8146
+ }>, Schema.Struct<{
8147
+ kind: Schema.Literal<["from-step"]>;
8148
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8149
+ }>]>;
8150
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8151
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8152
+ }>, Schema.filter<Schema.Struct<{
8153
+ kind: Schema.Literal<["entered"]>;
8154
+ enteredGeneration: Schema.filter<typeof Schema.NonEmptyString>;
8155
+ workflowName: Schema.filter<typeof Schema.NonEmptyString>;
8156
+ executionId: Schema.filter<typeof Schema.NonEmptyString>;
8157
+ originalGeneration: Schema.filter<typeof Schema.NonEmptyString>;
8158
+ requestedAt: Schema.filter<typeof Schema.Number>;
8159
+ dispatchAttempts: Schema.refine<number, typeof Schema.NonNegative>;
8160
+ selection: Schema.Union<[Schema.Struct<{
8161
+ kind: Schema.Literal<["failed-steps"]>;
8162
+ }>, Schema.Struct<{
8163
+ kind: Schema.Literal<["from-step"]>;
8164
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8165
+ }>]>;
8166
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8167
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8168
+ }>>]>;
8169
+
8170
+ export declare type WorkflowRestartResult = typeof WorkflowRestartResultSchema.Type;
8171
+
8172
+ /** A refused reservation never reset the journal. Transport failures instead
8173
+ * require reading the original target; they are not proof of refusal. */
8174
+ export declare const WorkflowRestartResultSchema: Schema.Union<[Schema.Union<[Schema.Struct<{
8175
+ kind: Schema.Literal<["not-found"]>;
8176
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8177
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8178
+ }>, Schema.Struct<{
8179
+ kind: Schema.Literal<["withdrawn"]>;
8180
+ workflowName: Schema.filter<typeof Schema.NonEmptyString>;
8181
+ withdrawnAt: Schema.filter<typeof Schema.Number>;
8182
+ selection: Schema.Union<[Schema.Struct<{
8183
+ kind: Schema.Literal<["failed-steps"]>;
8184
+ }>, Schema.Struct<{
8185
+ kind: Schema.Literal<["from-step"]>;
8186
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8187
+ }>]>;
8188
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8189
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8190
+ }>, Schema.Struct<{
8191
+ kind: Schema.Literal<["pending"]>;
8192
+ workflowName: Schema.filter<typeof Schema.NonEmptyString>;
8193
+ executionId: Schema.filter<typeof Schema.NonEmptyString>;
8194
+ originalGeneration: Schema.filter<typeof Schema.NonEmptyString>;
8195
+ requestedAt: Schema.filter<typeof Schema.Number>;
8196
+ dispatchAttempts: Schema.refine<number, typeof Schema.NonNegative>;
8197
+ selection: Schema.Union<[Schema.Struct<{
8198
+ kind: Schema.Literal<["failed-steps"]>;
8199
+ }>, Schema.Struct<{
8200
+ kind: Schema.Literal<["from-step"]>;
8201
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8202
+ }>]>;
8203
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8204
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8205
+ }>, Schema.filter<Schema.Struct<{
8206
+ kind: Schema.Literal<["entered"]>;
8207
+ enteredGeneration: Schema.filter<typeof Schema.NonEmptyString>;
8208
+ workflowName: Schema.filter<typeof Schema.NonEmptyString>;
8209
+ executionId: Schema.filter<typeof Schema.NonEmptyString>;
8210
+ originalGeneration: Schema.filter<typeof Schema.NonEmptyString>;
8211
+ requestedAt: Schema.filter<typeof Schema.Number>;
8212
+ dispatchAttempts: Schema.refine<number, typeof Schema.NonNegative>;
8213
+ selection: Schema.Union<[Schema.Struct<{
8214
+ kind: Schema.Literal<["failed-steps"]>;
8215
+ }>, Schema.Struct<{
8216
+ kind: Schema.Literal<["from-step"]>;
8217
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8218
+ }>]>;
8219
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8220
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8221
+ }>>]>, Schema.Struct<{
8222
+ kind: Schema.Literal<["not-reserved"]>;
8223
+ reason: Schema.Literal<["admission", "journal"]>;
8224
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8225
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8226
+ }>]>;
8227
+
8228
+ export declare type WorkflowRestartSelection = typeof WorkflowRestartSelectionSchema.Type;
8229
+
8230
+ export declare const WorkflowRestartSelectionSchema: Schema.Union<[Schema.Struct<{
8231
+ kind: Schema.Literal<["failed-steps"]>;
8232
+ }>, Schema.Struct<{
8233
+ kind: Schema.Literal<["from-step"]>;
8234
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
8235
+ }>]>;
8236
+
8237
+ export declare type WorkflowRestartTarget = typeof WorkflowRestartTargetSchema.Type;
8238
+
8239
+ /** Persist before submitting; read this same target after an uncertain reply. */
8240
+ export declare const WorkflowRestartTargetSchema: Schema.Struct<{
8241
+ requestId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
8242
+ runId: Schema.filter<typeof Schema.NonEmptyString>;
8243
+ }>;
8244
+
7701
8245
  export declare const workflowResumeDescriptor: ActionProcedureDescriptor<"__voltro.workflow.resume", Schema.Struct<{
7702
8246
  workflowName: typeof Schema.String;
7703
8247
  executionId: typeof Schema.String;