@voltro/protocol 0.85.0 → 0.87.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +5987 -0
  2. package/README.md +14 -0
  3. package/THIRD-PARTY-NOTICES.md +1122 -31
  4. package/dist/apiRoleEndpoints.js +108 -0
  5. package/dist/apiRoleObservation.js +68 -0
  6. package/dist/apikey.d.ts +10 -0
  7. package/dist/approval.js +64 -0
  8. package/dist/approvalDescriptors.js +26 -0
  9. package/dist/auth.js +389 -0
  10. package/dist/boundedRing.js +27 -0
  11. package/dist/businessRule.js +11 -0
  12. package/dist/canonicalJson.js +14 -0
  13. package/dist/connection.js +73 -0
  14. package/dist/devActivity.js +4 -0
  15. package/dist/errorSchema.js +24 -0
  16. package/dist/errorValue.js +21 -0
  17. package/dist/event.js +110 -0
  18. package/dist/formCapture.js +12 -0
  19. package/dist/httpStreamMaxDuration.js +9 -0
  20. package/dist/idempotency.js +121 -0
  21. package/dist/idempotencyInput.js +123 -0
  22. package/dist/identity.js +5 -9
  23. package/dist/index.d.ts +1095 -139
  24. package/dist/index.js +54 -1700
  25. package/dist/inspect.d.ts +1 -1
  26. package/dist/inspect.js +14 -240
  27. package/dist/inspectBackfill.js +52 -0
  28. package/dist/inspectCommandRejection.js +21 -0
  29. package/dist/inspectProjection.js +133 -0
  30. package/dist/inspectStorageProjection.js +86 -0
  31. package/dist/inspectValueIdentity.js +4 -0
  32. package/dist/inspectWorkflowExecution.js +34 -0
  33. package/dist/inspectWorkflowQueuedStart.js +31 -0
  34. package/dist/jsonSchema.js +54 -0
  35. package/dist/jwt.d.ts +10 -0
  36. package/dist/jwt.js +1 -1
  37. package/dist/outbox.js +10 -0
  38. package/dist/pairing.js +75 -0
  39. package/dist/plugin.js +87 -0
  40. package/dist/pluginAccountMerge.js +88 -0
  41. package/dist/pluginNaming.js +32 -0
  42. package/dist/pluginPrivacy.js +166 -0
  43. package/dist/procedure.js +280 -0
  44. package/dist/publicApi.js +325 -0
  45. package/dist/queryOptionalSources.js +14 -0
  46. package/dist/reactivityChannel.js +34 -0
  47. package/dist/rest.d.ts +260 -64
  48. package/dist/rest.js +345 -499
  49. package/dist/restFormat.js +163 -0
  50. package/dist/restReceipt.js +43 -0
  51. package/dist/revalidate.js +13 -0
  52. package/dist/rowEquality.js +31 -0
  53. package/dist/rowPatch.js +182 -0
  54. package/dist/schemaShape.js +67 -0
  55. package/dist/scopes.js +273 -0
  56. package/dist/secretWindows.js +71 -0
  57. package/dist/serverErrorBus.js +15 -0
  58. package/dist/serviceCall.js +14 -0
  59. package/dist/session.d.ts +89 -6
  60. package/dist/session.js +62 -63
  61. package/dist/{sessionHeader-DriTZhmv.js → sessionHeader.js} +1 -1
  62. package/dist/ssrfPolicy.js +30 -0
  63. package/dist/storageAccess.js +14 -0
  64. package/dist/storeErrors.js +66 -0
  65. package/dist/storeScreenshots.js +90 -0
  66. package/dist/strictInput.js +27 -0
  67. package/dist/subscriptionEvent.js +37 -0
  68. package/dist/tenantSelection.js +40 -0
  69. package/dist/time.js +7 -0
  70. package/dist/undo.js +38 -0
  71. package/dist/validation.js +39 -0
  72. package/dist/webSocketGateway.js +16 -0
  73. package/dist/workflow.js +297 -0
  74. package/dist/workflowAccess.js +61 -0
  75. package/dist/workflowRestart.js +45 -0
  76. package/dist/workflowRetry.js +44 -0
  77. package/dist/workflowStartOutcome.js +30 -0
  78. package/package.json +5 -3
  79. package/dist/restFormat-BlgTLxm1.js +0 -298
  80. package/dist/scopes-ClywBij8.js +0 -548
package/dist/index.d.ts CHANGED
@@ -1,13 +1,23 @@
1
+ import { ConnectionKind as ConnectionKind_2 } from '@voltro/kv/connection';
2
+ import { ConnectionRef } from '@voltro/kv/connection';
3
+ import { ConnectionRegistryShape } from '@voltro/kv/connection';
4
+ import { ConnectionUnavailablePolicy } from '@voltro/kv/connection';
1
5
  import { Context } from 'effect';
2
6
  import { DataStore } from '@voltro/database';
3
7
  import { Effect } from 'effect';
8
+ import { HotStoreShape } from '@voltro/kv/hot/contract';
4
9
  import { Layer } from 'effect';
5
10
  import { Option } from 'effect';
6
11
  import { Rpc } from 'effect/rpc';
7
12
  import { RpcMiddleware } from 'effect/rpc';
8
13
  import { Schema } from 'effect';
9
14
  import { SchemaAST } from 'effect';
15
+ import { Scope } from 'effect';
10
16
  import { SqlClient } from 'effect/sql';
17
+ import { StorageAddress } from '@voltro/kv/storage';
18
+ import { StorageDestinationRouter } from '@voltro/kv/storage';
19
+ import { StorageError } from '@voltro/kv/storage';
20
+ import { StorageProvider } from '@voltro/kv/storage';
11
21
  import { Stream } from 'effect/rpc/RpcSchema';
12
22
  import { VersionConflict } from '@voltro/database/wire';
13
23
  import { YieldableError } from 'effect/Cause';
@@ -137,7 +147,7 @@ export declare interface AnyApprovalPolicy {
137
147
 
138
148
  /** A guard entry is a scope check, a relationship check, an identity check, a
139
149
  * declared no-check-needed decision, or a service-only decision. */
140
- export declare type AnyCheckSpec = GuardCheckSpec | PolicyCheckSpec | AuthenticatedCheckSpec | OpenAccessSpec | ServiceAccessSpec;
150
+ export declare type AnyCheckSpec = GuardCheckSpec | PolicyCheckSpec | AuthenticatedCheckSpec | UpgradedCheckSpec | OpenAccessSpec | ServiceAccessSpec;
141
151
 
142
152
  /** Any declared event, with the generics erased — for registries and audits. */
143
153
  /** The erased descriptor every registry holds. `guards` reads the key type
@@ -147,7 +157,10 @@ export declare type AnyCheckSpec = GuardCheckSpec | PolicyCheckSpec | Authentica
147
157
  export declare type AnyEventDescriptor = EventDescriptor<string, Schema.Top, Schema.Top, DeclaredAccess<never>>;
148
158
 
149
159
  /** A guard is a scope check, a relationship check, or an identity check. */
150
- export declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input> | AuthenticatedGuardSpec;
160
+ export declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input> | AuthenticatedGuardSpec | UpgradedGuardSpec;
161
+
162
+ /** Work performed by an API process; schema and descriptor loading is separate. */
163
+ export declare const API_WORKLOADS: readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"];
151
164
 
152
165
  export declare const APIKEY_ISSUE_ORG_SCOPE = "apikeys:issue:org";
153
166
 
@@ -180,6 +193,118 @@ export declare const APIKEY_ISSUE_OTHER_SCOPE = "apikeys:issue:other";
180
193
  */
181
194
  export declare const APIKEY_ISSUE_SELF_SCOPE = "apikeys:issue:self";
182
195
 
196
+ export declare interface ApiRoleEndpoint {
197
+ readonly role: string;
198
+ readonly routes: ReadonlyArray<string> | null;
199
+ readonly workloads: ReadonlyArray<string>;
200
+ readonly httpPath: string | null;
201
+ readonly webSocketPath: string;
202
+ }
203
+
204
+ export declare interface ApiRoleEndpoints {
205
+ readonly version: 1;
206
+ readonly transport: 'compose-nginx' | 'kubernetes-nginx';
207
+ readonly roles: ReadonlyArray<ApiRoleEndpoint>;
208
+ }
209
+
210
+ export declare const apiRoleEndpointUrl: (baseUrl: string, endpoint: ApiRoleEndpoint, transport: "http" | "websocket") => string;
211
+
212
+ export declare interface ApiRoleObservationReport {
213
+ readonly replicaId: string;
214
+ readonly startedAt: number;
215
+ readonly role: string;
216
+ readonly workloads: ReadonlyArray<ApiWorkload>;
217
+ readonly capturedAt: number;
218
+ readonly publisher: {
219
+ readonly ready: boolean;
220
+ readonly stopped: boolean;
221
+ readonly pendingBuckets: number;
222
+ readonly droppedBuckets: number;
223
+ readonly rejectedSegments: number;
224
+ readonly writeFailures: number;
225
+ readonly observationErrors: number;
226
+ };
227
+ readonly currentMeasuredSeconds: ReadonlyArray<{
228
+ readonly role: string;
229
+ readonly workload: ApiWorkload;
230
+ readonly seconds: number;
231
+ }>;
232
+ readonly history: {
233
+ readonly state: 'pending' | 'available' | 'unavailable';
234
+ readonly assessment: ApiRoleWindowAssessment | null;
235
+ /** Measured portions only; no share is inferred from incomplete coverage. */
236
+ readonly measuredSeconds: ReadonlyArray<{
237
+ readonly workload: ApiWorkload;
238
+ readonly seconds: number;
239
+ }>;
240
+ };
241
+ }
242
+
243
+ /** Browser-safe wire contract; no runtime or database imports. */
244
+ export declare const ApiRoleObservationSchema: Schema.Struct<{
245
+ readonly replicaId: Schema.NonEmptyString;
246
+ readonly startedAt: Schema.Number;
247
+ readonly role: Schema.NonEmptyString;
248
+ readonly workloads: Schema.$Array<Schema.Literals<readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"]>>;
249
+ readonly capturedAt: Schema.Number;
250
+ readonly publisher: Schema.Struct<{
251
+ readonly ready: Schema.Boolean;
252
+ readonly stopped: Schema.Boolean;
253
+ readonly pendingBuckets: Schema.Number;
254
+ readonly droppedBuckets: Schema.Number;
255
+ readonly rejectedSegments: Schema.Number;
256
+ readonly writeFailures: Schema.Number;
257
+ readonly observationErrors: Schema.Number;
258
+ }>;
259
+ readonly currentMeasuredSeconds: Schema.$Array<Schema.Struct<{
260
+ readonly role: Schema.NonEmptyString;
261
+ readonly workload: Schema.Literals<readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"]>;
262
+ readonly seconds: Schema.Number;
263
+ }>>;
264
+ readonly history: Schema.Struct<{
265
+ readonly state: Schema.Literals<readonly ["pending", "available", "unavailable"]>;
266
+ readonly measuredSeconds: Schema.$Array<Schema.Struct<{
267
+ readonly workload: Schema.Literals<readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"]>;
268
+ readonly seconds: Schema.Number;
269
+ }>>;
270
+ readonly assessment: Schema.NullOr<Schema.Struct<{
271
+ readonly from: Schema.Number;
272
+ readonly until: Schema.Number;
273
+ readonly complete: Schema.Boolean;
274
+ readonly missingBuckets: Schema.Number;
275
+ readonly incompleteWorkloads: Schema.$Array<Schema.Literals<readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"]>>;
276
+ readonly reason: Schema.Literals<readonly ["invalid-observation", "incomplete", "idle", "observed"]>;
277
+ readonly totals: Schema.$Array<Schema.Struct<{
278
+ readonly workload: Schema.Literals<readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"]>;
279
+ readonly seconds: Schema.Number;
280
+ readonly share: Schema.Number;
281
+ }>>;
282
+ readonly splitCandidates: Schema.$Array<Schema.Literals<readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"]>>;
283
+ }>>;
284
+ }>;
285
+ }>;
286
+
287
+ /** Reserved web proxy base; the API name isolates identical role names. */
288
+ export declare const apiRoleProxyBasePath: (apiName: string) => string;
289
+
290
+ export declare type ApiRoleRpcWorkload = 'rpc' | 'subscriptions' | 'events';
291
+
292
+ export declare interface ApiRoleWindowAssessment {
293
+ readonly from: number;
294
+ readonly until: number;
295
+ readonly complete: boolean;
296
+ readonly missingBuckets: number;
297
+ readonly incompleteWorkloads: ReadonlyArray<ApiWorkload>;
298
+ readonly reason: 'invalid-observation' | 'incomplete' | 'idle' | 'observed';
299
+ readonly totals: ReadonlyArray<{
300
+ readonly workload: ApiWorkload;
301
+ readonly seconds: number;
302
+ readonly share: number;
303
+ }>;
304
+ /** Advisory evidence only: no topology mutation and no acceptance claim. */
305
+ readonly splitCandidates: ReadonlyArray<ApiWorkload>;
306
+ }
307
+
183
308
  export declare class ApiUnavailable extends ApiUnavailable_base {
184
309
  }
185
310
 
@@ -193,6 +318,8 @@ declare const ApiUnavailable_base: Schema.Class<ApiUnavailable, Schema.TaggedStr
193
318
  readonly message: Schema.String;
194
319
  }>, YieldableError>;
195
320
 
321
+ export declare type ApiWorkload = typeof API_WORKLOADS[number];
322
+
196
323
  /** The subject a decision produces, plus what the decision took away. `removed`
197
324
  * is never non-empty for a `grant`. */
198
325
  export declare interface AppliedScopes {
@@ -546,7 +673,7 @@ export declare interface AuthenticatedGuardSpec {
546
673
  * an auth resolver before reaching its handler. The application provides
547
674
  * the resolver via `Layer.succeed(AuthMiddleware, AuthMiddleware.of(fn))`,
548
675
  * where `fn` receives `{headers, payload, rpc, clientId}` and returns an
549
- * `Effect<Subject, Unauthenticated>` — typically by inspecting headers / Bearer token and
676
+ * `Effect<Subject, Unauthenticated | RequestedTenantRefused>` — typically by inspecting headers / Bearer token and
550
677
  * looking the caller up.
551
678
  *
552
679
  * `provides: SubjectService` means the resolver's return value is placed
@@ -562,7 +689,7 @@ export declare interface AuthenticatedGuardSpec {
562
689
  export declare class AuthMiddleware extends AuthMiddleware_base {
563
690
  }
564
691
 
565
- declare const AuthMiddleware_base: RpcMiddleware.ServiceClass<AuthMiddleware, "@voltro/AuthMiddleware", SubjectService, typeof Unauthenticated, never, never, false>;
692
+ declare const AuthMiddleware_base: RpcMiddleware.ServiceClass<AuthMiddleware, "@voltro/AuthMiddleware", SubjectService, Schema.Union<readonly [typeof Unauthenticated, typeof RequestedTenantRefused]>, never, never, false>;
566
693
 
567
694
  export declare type AuthRouteHandler = (req: AuthRouteRequest) => Promise<AuthRouteResponse>;
568
695
 
@@ -625,6 +752,13 @@ export declare interface AuthStrategy {
625
752
  export declare interface AuthStrategyInput {
626
753
  readonly headers: Readonly<Record<string, string | undefined>>;
627
754
  readonly clientId: number;
755
+ /** HTTP request target, supplied by REST transports. Request selectors may
756
+ * read an explicit tenant hint here; it is never authentication or authority.
757
+ * Absent for connection transports and non-HTTP callers. */
758
+ readonly http?: {
759
+ readonly path: string;
760
+ readonly query: string;
761
+ };
628
762
  /**
629
763
  * The app's DataStore, for a strategy that must READ to identify the caller.
630
764
  *
@@ -673,8 +807,9 @@ export declare interface AuthStrategyWithCallback extends AuthStrategy {
673
807
  export declare type AuthSubjectGuard = (subject: Subject, ctx: AuthSubjectGuardContext) => Promise<AuthSubjectGuardVerdict> | AuthSubjectGuardVerdict;
674
808
 
675
809
  export declare interface AuthSubjectGuardContext {
676
- /** `'request'` for a live call, `'workflow'` for a durable run being executed. */
677
- readonly origin: 'request' | 'workflow';
810
+ /** `'request'` for a live call, `'workflow'` for a durable run being executed,
811
+ * `'room'` for a Room seat redeemed on a Room host (no request headers). */
812
+ readonly origin: 'request' | 'workflow' | 'room';
678
813
  /** The request's headers. EMPTY for `origin: 'workflow'`. */
679
814
  readonly headers: Readonly<Record<string, string | undefined>>;
680
815
  /** `undefined` for `origin: 'workflow'` and for transports with no connection. */
@@ -724,7 +859,12 @@ export declare type AuthSubjectGuardVerdict = {
724
859
  * `fresh` the caller runs the handler then calls `finish`/`fail`; on
725
860
  * `replay`/`conflict` it returns immediately without touching the handler.
726
861
  */
727
- export declare const beginIdempotent: (store: IdempotencyStore, scope: string, key: string, ttlMs: number, now: number, inFlightLeaseMs?: number) => Promise<IdempotencyOutcome>;
862
+ export declare const beginIdempotent: (store: IdempotencyStore, scope: string, key: string, inputHash: string, ttlMs: number, now: number, inFlightLeaseMs?: number) => Promise<IdempotencyOutcome>;
863
+
864
+ /** Bind a process-local authority refresh to an independent subject snapshot.
865
+ * Neither the resolver nor its credentials become serializable subject fields.
866
+ * Request and durable-execution hosts use the same identity-preserving refresh. */
867
+ export declare const bindSubjectAuthority: (subject: Subject, resolve: () => Promise<Subject>) => Subject;
728
868
 
729
869
  /** Bounded insertion-order storage. Appending overwrites one slot when full;
730
870
  * reads never expose its circular physical order or retained array. */
@@ -771,6 +911,24 @@ export declare interface CachedScopeDecision {
771
911
  readonly fresh: boolean;
772
912
  }
773
913
 
914
+ /**
915
+ * Canonical JSON — object keys sorted recursively, so two structurally equal
916
+ * inputs that differ only in key order digest identically. A retry from a
917
+ * different client build must not mint a second approval.
918
+ * Preserve own JSON fields such as `__proto__`; a prototype setter must never
919
+ * discard part of the content that an approval identity binds.
920
+ *
921
+ * Lives here, not in `@voltro/runtime`, so identity digests in modules that
922
+ * must not load the server runtime (storage metadata, browser-safe code) use
923
+ * the same encoding.
924
+ */
925
+ export declare const canonicalJson: (value: unknown) => string;
926
+
927
+ export declare const captureApiRoleEndpoints: (value: unknown) => ApiRoleEndpoints;
928
+
929
+ /** Capture optional integration observations without changing reactive triggers. */
930
+ export declare const captureOptionalQuerySources: (name: string, source: string | ReadonlyArray<string> | undefined, optional: unknown) => ReadonlyArray<string> | undefined;
931
+
774
932
  /**
775
933
  * Validate `plugin.framework` (npm-semver range) against the running
776
934
  * voltro version. Returns either `{ ok: true }` or
@@ -809,7 +967,10 @@ export declare const checkGuards: (subject: Subject, guards: ReadonlyArray<AnyCh
809
967
  * guard has a `resource`, it extracts the resource id from `input` and asks the
810
968
  * resolver whether the caller holds each scope on THAT resource. A globally-held
811
969
  * scope (or the `admin:full` bypass) still satisfies without a resolver call;
812
- * only the gap ("no global grant") falls through to the resolver. A failed
970
+ * only the gap ("no global grant") falls through to the resolver. A guard with
971
+ * `resourceOnly: true` always requires the resource resolver's verdict, even
972
+ * for globally-held scopes or `admin:full`, and refuses if its resource or
973
+ * resolver is unavailable. A failed
813
974
  * resolver Effect is treated as a denial (fail-closed).
814
975
  *
815
976
  * Returns a typed `ScopeError` naming the first unmet scope, or `null` when
@@ -842,6 +1003,9 @@ export declare interface ClientDescriptor {
842
1003
  * (`<DataTable>` derives its columns from it) + the capability map can
843
1004
  * introspect it. Same browser-safety rationale as `input`. */
844
1005
  readonly output?: Schema.Top | undefined;
1006
+ /** The procedure's own declared error schema, by reference like input/output.
1007
+ * This is not the widened framework/plugin wire error union. */
1008
+ readonly error?: Schema.Top | undefined;
845
1009
  }
846
1010
 
847
1011
  /**
@@ -893,15 +1057,15 @@ export declare const composeAuthStrategies: (strategies: ReadonlyArray<AuthStrat
893
1057
  /** What an unauthenticated caller is — see {@link AnonymousPolicy}.
894
1058
  * Default `'no-tenant'`. Ignored when `fallback` is supplied. */
895
1059
  readonly anonymous?: AnonymousPolicyOrDevTenant;
896
- /**
897
- * The subject's memberships — the ONE source, strategy-independent. Read
898
- * only when a stored tenant selection exists for the subject and names a
899
- * tenant other than the credential's, and then on every such request: a
900
- * membership revoked a moment ago must not keep a selection alive, and a
901
- * switch made a moment ago must be visible on the next call, so there is
902
- * no staleness window to configure here. Requires `tenantSelection`.
903
- */
1060
+ /** Fresh strategy-independent memberships. Explicit requested tenants are
1061
+ * checked on every resolution, including the credential tenant. Stored
1062
+ * selections read memberships only when they differ from the credential.
1063
+ * Requires `tenantSelection` or `requestedTenant`. */
904
1064
  readonly memberships?: MembershipsSource;
1065
+ /** Request-local tenant hint, checked against fresh `memberships` before
1066
+ * scopes and guards. Explicit targets bypass the stored selection without
1067
+ * changing it. Invalid hints fail closed, never fall back. */
1068
+ readonly requestedTenant?: RequestedTenantSource;
905
1069
  /**
906
1070
  * Where a subject's tenant selection is stored. Read for every matched
907
1071
  * user / apiKey / serviceAccount subject (one point lookup); a stored
@@ -1012,6 +1176,8 @@ export declare const composeAuthStrategies: (strategies: ReadonlyArray<AuthStrat
1012
1176
  readonly getStore?: () => unknown;
1013
1177
  }) => ((input: AuthStrategyInput) => Promise<SubjectResolution>);
1014
1178
 
1179
+ export declare const composeReplayAuthorityBindings: (plugins: ReadonlyArray<unknown>) => RpcReplayAuthorityBinding | undefined;
1180
+
1015
1181
  /**
1016
1182
  * Compose a list of Effect-native interceptors into ONE function the
1017
1183
  * runtime calls. Reduces right-to-left so the array order reads
@@ -1082,6 +1248,11 @@ export declare class ConnectionInfoMiddleware extends ConnectionInfoMiddleware_b
1082
1248
  declare const ConnectionInfoMiddleware_base: RpcMiddleware.ServiceClass<ConnectionInfoMiddleware, "@voltro/ConnectionInfoMiddleware", ConnectionInfo, Schema.Never, never, never, false>;
1083
1249
 
1084
1250
  export declare interface ConnectionInfoValue {
1251
+ /** The effective per-call Cookie header, including current connection
1252
+ * credential patches. Server-only input for app-specific signed-cookie
1253
+ * verification; not a verified identity. Never serialize it into responses
1254
+ * or public page state. Absent when the caller presented no cookies. */
1255
+ readonly cookieHeader?: string;
1085
1256
  /** Per-connection identifier assigned by the rpc transport. Stable for the
1086
1257
  * lifetime of the underlying WebSocket connection; reused as the key for
1087
1258
  * the per-connection subject override map. */
@@ -1345,8 +1516,9 @@ declare const ConstraintViolation_base: Schema.Class<ConstraintViolation, Schema
1345
1516
  * plugin wants to cancel EARLY (before shutdown).
1346
1517
  */
1347
1518
  export declare interface CoordinatedScheduleHandle {
1348
- /** Stop the periodic task. Idempotent. */
1349
- readonly stop: () => void;
1519
+ /** Stop future admission immediately; await completion before releasing the
1520
+ * task dependencies. Idempotent; synchronous handles finish immediately. */
1521
+ readonly stop: () => void | Promise<void>;
1350
1522
  /** The task name (for logging / dedup diagnostics). */
1351
1523
  readonly name: string;
1352
1524
  /**
@@ -1405,6 +1577,13 @@ export declare interface CoordinatedTickOutcome {
1405
1577
  readonly nextDueInMs?: number | null;
1406
1578
  }
1407
1579
 
1580
+ /** Capture the complete participant list once. Undeclared SQL tables refuse
1581
+ * before any hook; erasure and merge are distinct policies and never substitute. */
1582
+ export declare const createPluginAccountMergeRegistry: (plugins: ReadonlyArray<VoltroPlugin>) => PluginAccountMergeRegistry;
1583
+
1584
+ /** Captures hooks once. Does not read a store, schedule work, retry or grant authority. */
1585
+ export declare const createPluginPrivacyRegistry: (plugins: ReadonlyArray<VoltroPlugin>) => PluginPrivacyRegistry;
1586
+
1408
1587
  /** Index reuse for one subscription's immutable snapshots and returned rows.
1409
1588
  * Arbitrary mutable input must use applyRowPatch. A different base rebuilds
1410
1589
  * the index; reset releases it on snapshot replacement or owner disposal. */
@@ -1413,6 +1592,9 @@ export declare const createRowPatchApplier: () => {
1413
1592
  readonly reset: () => void;
1414
1593
  };
1415
1594
 
1595
+ /** The latest authenticated authority for a request or live subscription. */
1596
+ export declare const currentSubjectAuthority: (subject: Subject) => Subject;
1597
+
1416
1598
  /**
1417
1599
  * What a descriptor CARRIES: the author's guards, or the erased form of their
1418
1600
  * `openAccess:` decision — never both.
@@ -1425,6 +1607,9 @@ export declare const createRowPatchApplier: () => {
1425
1607
  */
1426
1608
  export declare type DeclaredAccess<Input = unknown> = ReadonlyArray<AnyGuardSpec<Input> | OpenAccessSpec | ServiceAccessSpec>;
1427
1609
 
1610
+ /** The `_tag` literals a descriptor's `error:` union declares (empty for none). */
1611
+ export declare const declaredErrorTags: (error: Schema.Top | undefined) => ReadonlySet<string>;
1612
+
1428
1613
  /** Every channel declared in this process, by routing key. */
1429
1614
  export declare const declaredReactivityChannelKeys: () => ReadonlySet<string>;
1430
1615
 
@@ -1597,10 +1782,9 @@ export declare const defineEvent: <const Name extends string, Key extends Schema
1597
1782
  * resource-scoped rule ("may this terminal watch this arena") stays
1598
1783
  * declarative. `ScopeError` is merged into the wire error union automatically.
1599
1784
  *
1600
- * Re-checked when the SUBJECT changes (a revoked role ends the stream), not on
1601
- * every delivery. Per-delivery authorization is the design a comparable
1602
- * product measured into a scaling wall: one check per subscriber per message
1603
- * makes throughput scale with the audience instead of the publish rate.
1785
+ * Checked before subscription and each delivery. Resource resolvers can observe
1786
+ * live revocation; captured subject scopes remain bounded by credential expiry.
1787
+ * Additional private payload filtering uses the app-owned clientPolicy.
1604
1788
  */
1605
1789
  readonly guards?: Guards<Schema.Schema.Type<Key>>;
1606
1790
  /**
@@ -1661,6 +1845,8 @@ export declare const defineEvent: <const Name extends string, Key extends Schema
1661
1845
  * to miss one?" — not "is this event frequent?".
1662
1846
  */
1663
1847
  readonly delivery?: EventDeliverySemantics;
1848
+ /** Required private-client policy ID, resolved from the installing app. */
1849
+ readonly clientPolicy?: string;
1664
1850
  }) => EventDescriptor<Name, Key, Payload>;
1665
1851
 
1666
1852
  export declare const defineMutation: <const Name extends string, Input extends Schema.Top, Output extends Schema.Top, Error extends Schema.Top = typeof Schema.Never>(options: {
@@ -1693,6 +1879,13 @@ export declare const defineMutation: <const Name extends string, Input extends S
1693
1879
  * same way. Not a durable outbox, a distributed lock or a cross-tab merge.
1694
1880
  */
1695
1881
  readonly serialize?: MutationSerialization<Schema.Schema.Type<Input>>;
1882
+ /** Exclude mutations without a valid row-image inverse (for example a
1883
+ * derived-index rebuild) from automatic undo. Omitted means the normal
1884
+ * environment-controlled capture. Transactions, guards and audit still run. */
1885
+ readonly undo?: {
1886
+ readonly capture: false;
1887
+ readonly reason: string;
1888
+ };
1696
1889
  /** Optional declarative target(s) — drives auto-optimistic. */
1697
1890
  readonly target?: Target<Schema.Schema.Type<Input>, Schema.Schema.Type<Output>>;
1698
1891
  /** Declarative authorization guard(s) — the caller must hold the named
@@ -1846,6 +2039,9 @@ export declare const defineQuery: <const Name extends string, Input extends Sche
1846
2039
  * import edge is what makes a channel `source:` impossible to leave stale,
1847
2040
  * which a table name (a bare string) can always be. */
1848
2041
  readonly source?: ReactivitySource | ReadonlyArray<ReactivitySource>;
2042
+ /** Names in source that observe an optional integration. These remain in
2043
+ * the complete trigger set; unavailable optional tables warn in check. */
2044
+ readonly optionalSource?: ReadonlyArray<string>;
1849
2045
  /** Opt into server-side snapshot caching with auto-invalidation. */
1850
2046
  readonly cache?: QueryCacheConfig;
1851
2047
  /** Declarative authorization guard(s) — the caller must hold the named
@@ -1990,6 +2186,38 @@ export declare interface DeleteTarget<Input = unknown> extends NestedTargetField
1990
2186
  readonly identify?: ((input: Input) => string | ReadonlyArray<string>) | undefined;
1991
2187
  }
1992
2188
 
2189
+ /** The HMR custom event `voltro dev` publishes its activity on. */
2190
+ export declare const DEV_ACTIVITY_EVENT = "voltro:dev-activity";
2191
+
2192
+ /** What `voltro dev` reports over {@link DEV_ACTIVITY_EVENT}. A `document`
2193
+ * message describes ONE document request (`id` is per server process); a
2194
+ * `compile` message says whether Vite is transforming browser modules. */
2195
+ export declare type DevActivityMessage = {
2196
+ readonly type: 'document';
2197
+ readonly id: number;
2198
+ /** Path + search exactly as the browser asked for it. */
2199
+ readonly url: string;
2200
+ /** `start` once, when the request arrives; then whichever the server
2201
+ * is doing as it changes — `compiling` while Vite transforms modules
2202
+ * for it, `rendering` once a server render runs, `waiting` for
2203
+ * anything else (Vite's own html, a middleware). */
2204
+ readonly phase: 'start' | 'waiting' | 'compiling' | 'rendering';
2205
+ } | {
2206
+ readonly type: 'document';
2207
+ readonly id: number;
2208
+ readonly url: string;
2209
+ readonly phase: 'end';
2210
+ /** `document`: the browser got a page to show. `redirect`: it will ask
2211
+ * for `location` next. `no-document`: nothing will replace this page
2212
+ * (204/205, an attachment). `aborted`: the browser went away first. */
2213
+ readonly outcome: 'document' | 'redirect' | 'no-document' | 'aborted';
2214
+ readonly location?: string;
2215
+ } | {
2216
+ readonly type: 'compile';
2217
+ readonly consumer: 'client';
2218
+ readonly active: boolean;
2219
+ };
2220
+
1993
2221
  /** Pair, list and revoke devices (`devices: true`) for the caller's tenant.
1994
2222
  * One scope, not three: a device always acts as itself, in the pairer's
1995
2223
  * tenant, so there is no self / org / other distinction to draw.
@@ -2048,6 +2276,8 @@ export declare const effectiveScopes: (subject: Subject) => ReadonlyArray<string
2048
2276
  */
2049
2277
  export declare const encodeEventKey: (key: unknown) => string;
2050
2278
 
2279
+ export declare const encodeNativeFormCapture: (selector: string, presentationDigest: string) => string;
2280
+
2051
2281
  /** Encode a failure under `format`: the tagged body the desugar always wrote
2052
2282
  * (`{ error, …fields }`), Problem Details, or the format's own encoder. */
2053
2283
  export declare const encodeRestError: (format: RestFormat, error: RestFormatError, ctx: RestFormatContext) => RestFormatResponse;
@@ -2059,6 +2289,17 @@ export declare const encodeRestError: (format: RestFormat, error: RestFormatErro
2059
2289
  */
2060
2290
  export declare const encodeRestSuccess: (format: RestFormat, value: unknown, ctx: RestFormatContext) => RestFormatResponse;
2061
2291
 
2292
+ export declare interface EnqueueOptions {
2293
+ /** Deduplicate while an earlier intent is still pending, not permanently. */
2294
+ readonly idempotencyKey?: string;
2295
+ readonly maxAttempts?: number;
2296
+ readonly delayMs?: number;
2297
+ }
2298
+
2299
+ /** The member of an error union that declares `tag`, as a schema of its own —
2300
+ * for the spec's per-status responses. */
2301
+ export declare const errorMemberSchema: (error: Schema.Top | undefined, tag: string) => Schema.Top | undefined;
2302
+
2062
2303
  /**
2063
2304
  * The text to SHOW for a failure value — its `message`, else its `_tag`, else
2064
2305
  * `String(value)`.
@@ -2076,8 +2317,8 @@ export declare const errorMessage: (err: unknown) => string;
2076
2317
  /**
2077
2318
  * Read the `_tag` off a thrown error value, or `undefined` when it has none.
2078
2319
  *
2079
- * It lives here, beside `toRpc`, because `toRpc` is what puts the tag on the
2080
- * wire — one file owns both ends of that contract. It used to live in
2320
+ * It lives in protocol because `toRpc` (procedure.ts) is what puts the tag on
2321
+ * the wire — one package owns both ends of that contract. It used to live in
2081
2322
  * `@voltro/client`, and being there had a cost nobody could see from inside the
2082
2323
  * framework: an app's shared error handler lived in a package that pulled only
2083
2324
  * `@voltro/i18n`, so reading a tag meant taking a dependency on the whole client
@@ -2119,6 +2360,34 @@ export declare const eventAttached: Schema.Struct<{
2119
2360
  readonly _tag: Schema.Literal<"attached">;
2120
2361
  }>;
2121
2362
 
2363
+ /** App-owned, server-only callbacks. Descriptors carry only the policy ID. */
2364
+ export declare interface EventClientPolicy {
2365
+ readonly id: string;
2366
+ /** Before bus subscription, including reattachment/resume. Only true admits. */
2367
+ readonly admit: (input: {
2368
+ readonly subject: Subject;
2369
+ readonly event: string;
2370
+ readonly key: unknown;
2371
+ }) => Effect.Effect<boolean, unknown>;
2372
+ /** Recheck current membership AND payload visibility. Deny ends the stream;
2373
+ * drop withholds this payload but keeps the subscription. No client filtering. */
2374
+ /** Register revocation before initial admission. The returned effect ends when
2375
+ * access may have changed; completion or failure ends the client stream.
2376
+ * Acquire resources in the supplied Scope; no detached background owner. */
2377
+ readonly monitor?: (input: {
2378
+ readonly subject: Subject;
2379
+ readonly event: string;
2380
+ readonly key: unknown;
2381
+ }) => Effect.Effect<Effect.Effect<void, unknown>, unknown, Scope.Scope>;
2382
+ readonly deliver: (input: {
2383
+ readonly subject: Subject;
2384
+ readonly event: string;
2385
+ readonly key: unknown;
2386
+ readonly payload: unknown;
2387
+ readonly emittedAt: number;
2388
+ }) => Effect.Effect<'allow' | 'drop' | 'deny', unknown>;
2389
+ }
2390
+
2122
2391
  /**
2123
2392
  * What it means for a subscriber to fall behind.
2124
2393
  *
@@ -2155,9 +2424,8 @@ export declare interface EventDescriptor<Name extends string, Key extends Schema
2155
2424
  readonly key: Key;
2156
2425
  readonly payload: Payload;
2157
2426
  /** Who may LISTEN — the author's guards, or the erased form of their
2158
- * `openAccess:` decision, never both (see `DeclaredAccess`). Re-checked when
2159
- * the subject changes, not per delivery — see `EventDefinition.guards` for
2160
- * why that distinction is deliberate. */
2427
+ * `openAccess:` decision, never both (see `DeclaredAccess`). Resource guards are checked before subscription and delivery; credential
2428
+ * expiry bounds captured subject authority. */
2161
2429
  readonly guards: Guards | undefined;
2162
2430
  /** The declared `openAccess:` reason, when there is one — what the boot gate
2163
2431
  * and `voltro doctor` read. The enforcement paths read `guards`. */
@@ -2168,6 +2436,8 @@ export declare interface EventDescriptor<Name extends string, Key extends Schema
2168
2436
  readonly delivery: EventDeliverySemantics | undefined;
2169
2437
  /** Opt this event into outbound HTTP delivery. See `EventWebhookSpec`. */
2170
2438
  readonly webhook: EventWebhookSpec | undefined;
2439
+ /** Required app-owned server policy, in addition to guards. No server imports in descriptors. */
2440
+ readonly clientPolicy?: string;
2171
2441
  }
2172
2442
 
2173
2443
  /**
@@ -2248,6 +2518,9 @@ export declare const eventResumePoint: Schema.Struct<{
2248
2518
 
2249
2519
  export declare const eventRoute: (tenantId: string | null, event: string, key: unknown) => string;
2250
2520
 
2521
+ /** Browser-safe execution ownership marker; wire schemas remain unchanged. */
2522
+ export declare const EventRpcWorkload: Context.Service<true, true>;
2523
+
2251
2524
  export declare type EventStreamEvent<P> = {
2252
2525
  readonly _tag: 'attached';
2253
2526
  } | {
@@ -2303,15 +2576,6 @@ export declare const eventSubscribeInput: <K extends Schema.Top>(key: K) => Sche
2303
2576
  }>>>;
2304
2577
  }>;
2305
2578
 
2306
- /**
2307
- * Lift an event descriptor into its rpc.
2308
- *
2309
- * A `stream: true` rpc, like a query — the framework's push transport is the
2310
- * WebSocket a subscription already holds open, and reusing it is why `useEvent`
2311
- * shares one connection lifecycle, one reconnect policy and one devtools view
2312
- * with `useSubscription` instead of being a second realtime concept in the same
2313
- * app.
2314
- */
2315
2579
  export declare const eventToRpc: <Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload, ReadonlyArray<unknown>>, extraErrors?: ReadonlyArray<Schema.Top>) => Rpc.Rpc<Name, Schema.Struct<{
2316
2580
  readonly key: Key;
2317
2581
  readonly resume: Schema.optional<Schema.$Array<Schema.Struct<{
@@ -2394,8 +2658,8 @@ export declare interface ExposeAsToolSpec {
2394
2658
  */
2395
2659
  export declare type ExtraErrors = ReadonlyArray<Schema.Top>;
2396
2660
 
2397
- /** Release a claim after the handler errored, so a retry can re-process. */
2398
- export declare const failIdempotent: (store: IdempotencyStore, scope: string, key: string) => Promise<void>;
2661
+ /** Release this failed execution's running claim without deleting a successor. */
2662
+ export declare const failIdempotent: (store: IdempotencyStore, claim: IdempotencyClaim) => Promise<boolean>;
2399
2663
 
2400
2664
  /**
2401
2665
  * The field-addressed issues carried by a wire error, or `[]` when the error
@@ -2433,8 +2697,8 @@ export declare const findAdvisoryResourceGuards: (procedures: ReadonlyArray<{
2433
2697
  readonly guards?: ReadonlyArray<AnyCheckSpec>;
2434
2698
  }>) => ReadonlyArray<string>;
2435
2699
 
2436
- /** Record a completed response for replay. */
2437
- export declare const finishIdempotent: (store: IdempotencyStore, scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
2700
+ /** Record a response only while this original execution owns the claim. */
2701
+ export declare const finishIdempotent: (store: IdempotencyStore, claim: IdempotencyClaim, response: IdempotencyResponse, now: number) => Promise<boolean>;
2438
2702
 
2439
2703
  /**
2440
2704
  * The schema as the generator should see it: every `Number` node checked
@@ -2466,9 +2730,16 @@ export declare const finiteNumberView: (ast: SchemaAST.AST, memo?: Map<SchemaAST
2466
2730
  */
2467
2731
  export declare const firstEventValue: (tag: string, value: unknown, label: string) => unknown;
2468
2732
 
2733
+ export declare const FORM_CAPTURE_FIELD = "__voltro_capture";
2734
+
2735
+ export declare const FORM_CAPTURE_META_NAME = "voltro:form-source";
2736
+
2469
2737
  /** A route rendered for humans — logs, the inspect surface, the dashboard. */
2470
2738
  export declare const formatEventRoute: (route: string) => string;
2471
2739
 
2740
+ /** Build selector (snapshot/input/execution) followed by its public class digest. */
2741
+ export declare const formCaptureSource: (value: unknown) => string | undefined;
2742
+
2472
2743
  /**
2473
2744
  * The framework errors EVERY procedure of a kind carries on its wire union —
2474
2745
  * the unconditional members `wireErrorUnion` adds. The conditional ones (a
@@ -2481,10 +2752,10 @@ export declare const formatEventRoute: (route: string) => string;
2481
2752
  * test pins it against `wireErrorUnion` per kind, so the two cannot drift.
2482
2753
  */
2483
2754
  export declare const FRAMEWORK_WIRE_ERRORS: {
2484
- readonly query: readonly [typeof ScopeError, typeof Unauthenticated];
2485
- readonly stream: readonly [typeof ScopeError, typeof Unauthenticated];
2486
- readonly action: readonly [typeof ScopeError, typeof Unauthenticated, typeof ValidationError, typeof ValidationErrors];
2487
- readonly mutation: readonly [typeof ScopeError, typeof Unauthenticated, typeof BusinessRuleViolation, typeof MutationInFlight, typeof VersionConflict, typeof ValidationError, typeof ValidationErrors];
2755
+ readonly query: readonly [typeof ScopeError, typeof Unauthenticated, typeof RequestedTenantRefused];
2756
+ readonly stream: readonly [typeof ScopeError, typeof Unauthenticated, typeof RequestedTenantRefused];
2757
+ readonly action: readonly [typeof ScopeError, typeof Unauthenticated, typeof RequestedTenantRefused, typeof MutationInFlight, typeof MutationIdempotencyRefused, typeof ValidationError, typeof ValidationErrors];
2758
+ readonly mutation: readonly [typeof ScopeError, typeof Unauthenticated, typeof RequestedTenantRefused, typeof BusinessRuleViolation, typeof MutationInFlight, typeof MutationIdempotencyRefused, typeof VersionConflict, typeof ValidationError, typeof ValidationErrors];
2488
2759
  };
2489
2760
 
2490
2761
  /** The error values {@link FRAMEWORK_WIRE_ERRORS} admits for `Kind`. */
@@ -2545,6 +2816,9 @@ export declare interface GuardCheckSpec {
2545
2816
  * global-scope check (the extractor is advisory), exactly as before.
2546
2817
  */
2547
2818
  readonly resource?: (input: unknown) => string | undefined;
2819
+ /** Require an explicit resource resolver verdict, even for globally held
2820
+ * scopes and `admin:full`. Missing resolver/resource refuses access. */
2821
+ readonly resourceOnly?: boolean;
2548
2822
  }
2549
2823
 
2550
2824
  /**
@@ -2566,35 +2840,17 @@ export declare interface GuardSpec<Input = unknown> {
2566
2840
  /** How an array of scopes combines. `'all'` (default) = AND (hold every
2567
2841
  * scope); `'any'` = OR (hold at least one). Ignored for a single scope. */
2568
2842
  readonly mode?: 'all' | 'any';
2569
- /**
2570
- * PURE `input → resource id` extractor. Browser-safe (no DB, no server
2571
- * import) — exactly like `target.identify`. Omit for a plain subject-scope
2572
- * guard.
2573
- *
2574
- * **On a `GuardSpec` this id is ADVISORY.** A scope guard answers "what may
2575
- * this subject do at all", against the subject's global scope set; the id is
2576
- * carried for logging and for a future subject-scope resolver that narrows by
2577
- * resource. It does not, on its own, make the check per-resource.
2578
- *
2579
- * **If your authority is per-resource, you want {@link PolicyGuardSpec}, not
2580
- * this field** — `guards: [{ action, resourceType, resource }]`, backed by
2581
- * `defineResourcePolicy` + a tuple source you register. That is built, wired
2582
- * on both boot paths, fail-closed without a resolver, and documented under
2583
- * *Authentication → Authorization*. An app whose relationships already live
2584
- * in its own tables (a `teamMembers` row, say) registers its own tuple source
2585
- * rather than copying data across; see `policyGuardResolver.ts`.
2586
- *
2587
- * That paragraph is here because its absence cost a deployment their access
2588
- * gate. This comment used to describe the resolver as "a future ReBAC /
2589
- * `accessPolicy()` resolver" — written before the ReBAC path shipped and
2590
- * never updated. They read the type, quoted the sentence, concluded there was
2591
- * "nothing in between" declaring an untruth and turning the gate off, and set
2592
- * `security: { defaultDeny: false }` on an app with 565 undecided procedures.
2593
- * The capability they needed was two fields away. A doc comment that says
2594
- * "future" about something shipped is not a small inaccuracy: it is the only
2595
- * thing a careful reader has, and it argued them out of a feature.
2596
- */
2843
+ /** Pure browser-safe input → target resource id selector. With a registered
2844
+ * setResourceScopeResolver, scopes can be checked on this resource. Without
2845
+ * resourceOnly, an existing global scope or admin:full may satisfy the guard
2846
+ * without consulting that resolver. For per-resource authority over relationships use
2847
+ * PolicyGuardSpec: `defineResourcePolicy` plus a registered tuple source. That is built, wired
2848
+ * on both boot paths and fail-closed without a resolver — an earlier comment called it "future"
2849
+ * and argued a deployment out of its access gate. */
2597
2850
  readonly resource?: (input: Input) => string | undefined;
2851
+ /** Require an explicit current resource verdict, including for globally held
2852
+ * scopes or admin:full. Missing resolver/resource refuses access. */
2853
+ readonly resourceOnly?: boolean;
2598
2854
  }
2599
2855
 
2600
2856
  /** Does this descriptor declare an access decision — a guard, or a deliberate
@@ -2624,8 +2880,9 @@ export declare const hasEnforcedGuard: (guards: DeclaredAccess | undefined) => b
2624
2880
  * the RAW subject scopes — use `hasEffectiveScope` to include rbac roles. */
2625
2881
  export declare const hasScope: (subject: Subject, scope: string) => boolean;
2626
2882
 
2627
- /** Renew a fresh claim's lease while its handler runs; returns the stop. */
2628
- export declare const holdIdempotentClaim: (store: IdempotencyStore, scope: string, key: string, inFlightLeaseMs?: number, now?: () => number) => (() => void);
2883
+ /** Renew only the captured execution identity; ownership loss stops renewal.
2884
+ * Stopping this timer never proves a remote business operation has stopped. */
2885
+ export declare const holdIdempotentClaim: (store: IdempotencyStore, claim: IdempotencyClaim, inFlightLeaseMs?: number, now?: () => number) => (() => void);
2629
2886
 
2630
2887
  /**
2631
2888
  * Short-circuit response shape. Returning a `HttpInterceptResponse`
@@ -2696,11 +2953,35 @@ export declare type HttpRequestInterceptor = (next: () => Promise<HttpInterceptR
2696
2953
  /** Fire-and-forget observer of plugin HTTP route calls — see {@link PluginHttpRouteEvent}. */
2697
2954
  export declare type HttpRouteObserver = (event: PluginHttpRouteEvent) => void;
2698
2955
 
2956
+ /** Identity of one acquired execution, never reused when a key is reclaimed.
2957
+ * This is receipt ownership, not authority to perform a business side effect. */
2958
+ export declare interface IdempotencyClaim {
2959
+ readonly scope: string;
2960
+ readonly key: string;
2961
+ readonly id: string;
2962
+ readonly inputHash: string;
2963
+ }
2964
+
2965
+ export declare type IdempotencyClaimResult = {
2966
+ readonly kind: 'claimed';
2967
+ readonly claim: IdempotencyClaim;
2968
+ } | {
2969
+ readonly kind: 'existing';
2970
+ readonly record: IdempotencyRecord;
2971
+ } | {
2972
+ readonly kind: 'conflict';
2973
+ };
2974
+
2699
2975
  export declare type IdempotencyOutcome = {
2700
2976
  readonly kind: 'fresh';
2977
+ readonly claim: IdempotencyClaim;
2701
2978
  } | {
2702
2979
  readonly kind: 'replay';
2703
2980
  readonly response: IdempotencyResponse;
2981
+ } | {
2982
+ readonly kind: 'input-conflict';
2983
+ } | {
2984
+ readonly kind: 'input-unbound';
2704
2985
  } | {
2705
2986
  readonly kind: 'conflict';
2706
2987
  };
@@ -2708,6 +2989,8 @@ export declare type IdempotencyOutcome = {
2708
2989
  export declare interface IdempotencyRecord {
2709
2990
  readonly scope: string;
2710
2991
  readonly key: string;
2992
+ /** Null means a retained receipt predates declared-input binding. */
2993
+ readonly inputHash: string | null;
2711
2994
  readonly status: 'in_flight' | 'completed';
2712
2995
  readonly response: IdempotencyResponse | null;
2713
2996
  /** epoch ms */
@@ -2717,38 +3000,41 @@ export declare interface IdempotencyRecord {
2717
3000
  export declare interface IdempotencyResponse {
2718
3001
  readonly status: number;
2719
3002
  readonly body: unknown;
3003
+ /** Framework-owned REST qualification metadata. Custom stores preserve it
3004
+ * unchanged, separately from the arbitrary application output in body. */
3005
+ readonly rest?: unknown;
2720
3006
  }
2721
3007
 
2722
3008
  /** Build the scope key — keep keys from colliding across tenants + endpoints. */
2723
3009
  export declare const idempotencyScope: (tenantId: string | null | undefined, method: string, path: string) => string;
2724
3010
 
2725
3011
  export declare interface IdempotencyStore {
2726
- /** Current record for (scope, key), or null. Read-side (inspect/dashboard). */
2727
3012
  readonly get: (scope: string, key: string) => Promise<IdempotencyRecord | null>;
2728
- /**
2729
- * Atomically claim (scope, key) IF absent OR stale — a COMPLETED record older
2730
- * than `ttlMs` (the replay window), or an IN-FLIGHT one whose lease
2731
- * (`inFlightLeaseMs`, renewed by the live handler) lapsed:
2732
- * write a fresh `in_flight` row and return `'claimed'`. Otherwise return the
2733
- * EXISTING (still-fresh) record. The atomicity here is the whole game — two
2734
- * concurrent same-key requests both hit this, exactly one gets `'claimed'`,
2735
- * the loser reads the winner's record. In SQL this is one
2736
- * `INSERT … ON CONFLICT DO UPDATE … WHERE existing.createdAt < now - ttl`.
2737
- */
2738
- readonly claim: (scope: string, key: string, now: number, ttlMs: number, inFlightLeaseMs: number) => Promise<'claimed' | IdempotencyRecord>;
2739
- /**
2740
- * Extend a RUNNING claim's lease: bump its timestamp while it is still
2741
- * `in_flight` (a completed or released claim is left alone). The server calls
2742
- * it while the handler runs; a process that dies stops calling it, and its
2743
- * claim goes stale after `inFlightLeaseMs` instead of after the whole replay
2744
- * window.
2745
- */
2746
- readonly renew: (scope: string, key: string, now: number) => Promise<void>;
2747
- /** Flip the claim to `completed` and store the response. */
2748
- readonly complete: (scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
2749
- /** Drop the claim (handler errored → a retry may re-process). */
2750
- readonly release: (scope: string, key: string) => Promise<void>;
2751
- }
3013
+ /** Atomically acquire an absent/stale key with a new, never-reused identity.
3014
+ * Otherwise return the existing receipt, or conflict if contention prevents
3015
+ * observing a stable record. Expiry does not prove an old executor stopped. */
3016
+ readonly claim: (scope: string, key: string, inputHash: string, now: number, ttlMs: number, inFlightLeaseMs: number) => Promise<IdempotencyClaimResult>;
3017
+ /** Atomically match original scope/key/id AND in_flight status. False means
3018
+ * this execution no longer owns a running receipt; never touch its successor. */
3019
+ readonly renew: (claim: IdempotencyClaim, now: number) => Promise<boolean>;
3020
+ /** Complete only the original running claim and start its replay window now. */
3021
+ readonly complete: (claim: IdempotencyClaim, response: IdempotencyResponse, now: number) => Promise<boolean>;
3022
+ /** Release only the original running claim. Completed receipts are retained. */
3023
+ readonly release: (claim: IdempotencyClaim) => Promise<boolean>;
3024
+ }
3025
+
3026
+ /** What an identity's authority resolved to: the subject to run as, or why not. */
3027
+ export declare type IdentityAuthorityOutcome = {
3028
+ readonly kind: 'resolved';
3029
+ readonly subject: Subject;
3030
+ } | {
3031
+ readonly kind: 'unavailable';
3032
+ readonly reason: string;
3033
+ } | {
3034
+ readonly kind: 'vetoed';
3035
+ readonly code: string;
3036
+ readonly message: string;
3037
+ };
2752
3038
 
2753
3039
  /** `/<id>` — the op path for a row id. Ids are JSON-pointer-escaped
2754
3040
  * (`~` → `~0`, `/` → `~1`) so a slash or tilde inside a string id can't
@@ -2894,6 +3180,10 @@ export declare const isSystemSubject: (subject: Subject) => boolean;
2894
3180
 
2895
3181
  export declare const isTenantSelectable: (subject: Subject) => subject is TenantSelectableSubject;
2896
3182
 
3183
+ export declare const isUpgradedCheck: (g: AnyCheckSpec) => g is UpgradedCheckSpec;
3184
+
3185
+ export declare const isUpgradedGuard: <I>(g: AnyGuardSpec<I>) => g is UpgradedGuardSpec;
3186
+
2897
3187
  /**
2898
3188
  * Is this descriptor allowed on the wire at all?
2899
3189
  *
@@ -2971,7 +3261,7 @@ export declare interface Membership {
2971
3261
  * project from wherever it keeps them. `input.store` is the app's DataStore. */
2972
3262
  export declare type MembershipsSource = (subject: Subject, input: AuthStrategyInput) => Promise<ReadonlyArray<Membership>> | ReadonlyArray<Membership>;
2973
3263
 
2974
- /** In-memory store — the default for single-process dev + the test double. */
3264
+ /** In-memory store — one process only, with unique ownership per acquisition. */
2975
3265
  export declare const memoryIdempotencyStore: () => IdempotencyStore;
2976
3266
 
2977
3267
  /** The refusal a missing access decision produces. Exported so the boot gate
@@ -2983,6 +3273,25 @@ export declare const missingAccessDecision: (procedure?: string) => ScopeError;
2983
3273
  * fits several tries. */
2984
3274
  export declare const MUTATION_IN_FLIGHT_RETRY_AFTER_MS = 1000;
2985
3275
 
3276
+ /** Transaction-bound context for a plugin's required database contribution.
3277
+ * The executor result is available before the business transaction commits. */
3278
+ export declare interface MutationCommitContext extends RpcInterceptorContext {
3279
+ readonly kind: 'mutation';
3280
+ readonly store: DataStore;
3281
+ readonly output: unknown;
3282
+ /** Same enqueue facade as the executor, bound to this replay attempt. */
3283
+ readonly outbox?: OutboxEnqueueFacade;
3284
+ /** Runner invocation time, including guards and rolled-back retries. */
3285
+ readonly startedAt?: number;
3286
+ /** Elapsed milliseconds before required contribution work. */
3287
+ readonly durationMs?: number;
3288
+ }
3289
+
3290
+ /** Required transactional work, not an external side effect. A failure rolls
3291
+ * back the mutation. The hook may run again after a rolled-back contention
3292
+ * attempt, so all effects must remain on the supplied transaction store. */
3293
+ export declare type MutationCommitHook = (context: MutationCommitContext) => Effect.Effect<void, unknown>;
3294
+
2986
3295
  /** A connection-scoped RPC cannot execute or replay without a live WebSocket.
2987
3296
  * This is a transport precondition, not an authorization or membership grant. */
2988
3297
  export declare class MutationConnectionRequired extends MutationConnectionRequired_base {
@@ -2993,6 +3302,18 @@ declare const MutationConnectionRequired_base: Schema.Class<MutationConnectionRe
2993
3302
  readonly traceId: Schema.String;
2994
3303
  }>, YieldableError>;
2995
3304
 
3305
+ /** Input refusal is terminal for this key, not an in-flight retry invitation.
3306
+ * No stored hash, input or response is disclosed. Check the original operation
3307
+ * before deliberately starting another business action. */
3308
+ export declare class MutationIdempotencyRefused extends MutationIdempotencyRefused_base {
3309
+ get message(): string;
3310
+ }
3311
+
3312
+ declare const MutationIdempotencyRefused_base: Schema.Class<MutationIdempotencyRefused, Schema.TaggedStruct<"MutationIdempotencyRefused", {
3313
+ readonly traceId: Schema.String;
3314
+ readonly reason: Schema.Literals<readonly ["input-conflict", "input-unbound", "input-unavailable"]>;
3315
+ }>, YieldableError>;
3316
+
2996
3317
  /** Ordinary writes deduplicate across reconnects. A connection-side effect
2997
3318
  * (for example joining a room) must execute once for EACH native connection. */
2998
3319
  export declare type MutationIdempotencyScope = 'subject' | 'connection';
@@ -3020,6 +3341,12 @@ export declare interface MutationProcedureDescriptor<Name extends string, Input
3020
3341
  readonly idempotencyScope?: MutationIdempotencyScope | undefined;
3021
3342
  /** Same-key calls run in invocation order on the client — see `defineMutation`. */
3022
3343
  readonly serialize?: MutationSerialization<Schema.Schema.Type<Input>> | undefined;
3344
+ /** Exclude this invocation from automatic row-image undo capture, with an
3345
+ * explicit domain reason. Does not disable transactions, guards or audit. */
3346
+ readonly undo?: {
3347
+ readonly capture: false;
3348
+ readonly reason: string;
3349
+ } | undefined;
3023
3350
  /** Declarative target(s) — drives auto-optimistic on the client AND
3024
3351
  * surfaces which tables this mutation touches (debug, future
3025
3352
  * query-invalidation analytics). Mutations without a target run
@@ -3081,6 +3408,12 @@ export declare interface MutationSerialization<Input = unknown> {
3081
3408
 
3082
3409
  export declare const mutationToRpc: <Name extends string, Input extends Schema.Top, Output extends Schema.Top, Err extends Schema.Top>(descriptor: MutationProcedureDescriptor<Name, Input, Output, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Top, never, never>;
3083
3410
 
3411
+ export declare interface NativeFormCaptureDocument {
3412
+ readonly querySelectorAll: (selector: string) => ArrayLike<{
3413
+ readonly getAttribute: (name: string) => string | null;
3414
+ }>;
3415
+ }
3416
+
3084
3417
  export declare interface NestedTargetFields<Input = unknown> {
3085
3418
  /** Dot-path to the nested array within the query VALUE to patch (e.g.
3086
3419
  * `'snapshot.projects'`). Absent → patch the top-level row array (default). */
@@ -3252,6 +3585,30 @@ export declare const openAccessSpec: (reason: string) => OpenAccessSpec;
3252
3585
  */
3253
3586
  export declare const orderByBearerPrefix: (strategies: ReadonlyArray<AuthStrategy>, headers: Readonly<Record<string, string | undefined>>) => ReadonlyArray<AuthStrategy>;
3254
3587
 
3588
+ /** Enqueue-only capability bound to the caller's actual transaction store. */
3589
+ export declare interface OutboxEnqueueFacade {
3590
+ /** Returns the stable delivery identity. Delivery happens after commit. */
3591
+ enqueue(effect: string, payload: Record<string, unknown>, options?: EnqueueOptions): Promise<string>;
3592
+ }
3593
+
3594
+ /** Stable delivery context, shared by app and plugin handlers. */
3595
+ export declare interface OutboxHandlerContext {
3596
+ readonly outboxId: string;
3597
+ readonly idempotencyKey: string | null;
3598
+ readonly payload: Record<string, unknown>;
3599
+ readonly attempt: number;
3600
+ readonly subjectId: string | null;
3601
+ readonly tenantId: string | null;
3602
+ readonly traceId: string | null;
3603
+ }
3604
+
3605
+ /** Delivery happens after commit and may repeat; handlers must be idempotent. */
3606
+ export declare interface OutboxHandlerDefinition<R = never> {
3607
+ readonly effect: string;
3608
+ readonly handler: (context: OutboxHandlerContext) => Promise<unknown> | Effect.Effect<unknown, unknown, R>;
3609
+ readonly maxAttempts?: number;
3610
+ }
3611
+
3255
3612
  export declare interface Pairing {
3256
3613
  /** The api's base url as the DEVICE reaches it — a LAN address, never `localhost`. */
3257
3614
  readonly api: string;
@@ -3294,6 +3651,21 @@ export declare const parseEventRoute: (route: string) => {
3294
3651
  /** The record from a link, or `null` for anything that is not a pairing link. */
3295
3652
  export declare const parsePairingLink: (url: string) => Pairing | null;
3296
3653
 
3654
+ /**
3655
+ * Parse a `revalidate` page export into a duration in milliseconds.
3656
+ *
3657
+ * Accepted shapes:
3658
+ * - number → seconds (Next.js compatibility)
3659
+ * - '<N> seconds' / 's'
3660
+ * - '<N> minutes' / 'm'
3661
+ * - '<N> hours' / 'h'
3662
+ * - '<N> days' / 'd'
3663
+ *
3664
+ * Returns null if the value is missing or unrecognised — caller treats
3665
+ * `null` as "don't cache" (every request re-renders, same as plain SSR).
3666
+ */
3667
+ export declare const parseRevalidateMs: (value: unknown) => number | null;
3668
+
3297
3669
  export declare const patchCellSchema: Schema.decodeTo<Schema.Unknown, Schema.Unknown, never, never>;
3298
3670
 
3299
3671
  /** A single row in a subscription result. Must carry an `id`; everything
@@ -3339,6 +3711,49 @@ export declare type PendingApproval = Schema.Schema.Type<typeof PendingApproval>
3339
3711
  /** The normalisation above, applied to any JSON Schema fragment. */
3340
3712
  export declare const plainJsonSchema: (value: unknown) => unknown;
3341
3713
 
3714
+ export declare interface PluginAccountMergeContext extends PluginAccountMergeRequest {
3715
+ /** Borrow the coordinator's existing transaction. No external I/O, separate
3716
+ * pool, nested transaction or session grant belongs in a data hook. */
3717
+ readonly store: DataStore;
3718
+ }
3719
+
3720
+ export declare interface PluginAccountMergeCoverage {
3721
+ readonly participants: ReadonlyArray<{
3722
+ readonly name: string;
3723
+ readonly tables: ReadonlyArray<string>;
3724
+ }>;
3725
+ readonly missing: ReadonlyArray<string>;
3726
+ }
3727
+
3728
+ export declare class PluginAccountMergeFailure extends PluginAccountMergeFailure_base {
3729
+ }
3730
+
3731
+ declare const PluginAccountMergeFailure_base: Schema.Class<PluginAccountMergeFailure, Schema.TaggedStruct<"PluginAccountMergeFailure", {
3732
+ readonly code: Schema.Literals<readonly ["invalid_request", "coverage_missing", "hook_failed"]>;
3733
+ readonly requestId: Schema.String;
3734
+ readonly plugin: Schema.NullOr<Schema.String>;
3735
+ /** Hooks that returned before failure; never evidence of SQL commit. */
3736
+ readonly returned: Schema.$Array<Schema.String>;
3737
+ readonly missing: Schema.$Array<Schema.String>;
3738
+ }>, YieldableError>;
3739
+
3740
+ export declare type PluginAccountMergeHook = (context: PluginAccountMergeContext) => Effect.Effect<void, unknown>;
3741
+
3742
+ export declare interface PluginAccountMergeRegistry {
3743
+ readonly coverage: (kind: 'user' | 'guest') => PluginAccountMergeCoverage;
3744
+ /** The caller owns both-account authorization, confirmation, the transaction
3745
+ * and durable receipt. This registry neither commits nor withdraws identity. */
3746
+ readonly mergeInTransaction: (context: PluginAccountMergeContext) => Effect.Effect<ReadonlyArray<string>, PluginAccountMergeFailure>;
3747
+ }
3748
+
3749
+ export declare interface PluginAccountMergeRequest {
3750
+ readonly requestId: string;
3751
+ readonly tenantId: string;
3752
+ readonly sourceId: string;
3753
+ readonly sourceKind: 'user' | 'guest';
3754
+ readonly targetId: string;
3755
+ }
3756
+
3342
3757
  /**
3343
3758
  * Called once at app boot, after `voltro dev` resolves the plugin
3344
3759
  * list and before the rpc server starts accepting connections. Use
@@ -3361,6 +3776,37 @@ export declare type PluginActivateHook = (ctx: PluginLifecycleContext) => Effect
3361
3776
  export declare const pluginAlias: (pluginName: string) => string;
3362
3777
 
3363
3778
  export declare interface PluginBindContext {
3779
+ /** Selected API work; absent custom-host selection means all workloads. */
3780
+ readonly workloads?: ReadonlyArray<ApiWorkload>;
3781
+ /** Starts the app's workflows from server work that has no request, such as
3782
+ * Room results. Present when the app declares workflows. */
3783
+ readonly workflows?: PluginWorkflowStarter;
3784
+ /** Execute an already-provided Effect, preserving typed failures as rejection
3785
+ * values. This does not inject request identity, app services or a new scope. */
3786
+ readonly runEffect?: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>;
3787
+ /** Execute server work with the actual shared app services, including user
3788
+ * Layer overrides, in a scope owned by this call. Available after the host
3789
+ * assembles its service layer, never during store binding or shutdown. No
3790
+ * request Subject, request-local store or transaction is injected. Consume
3791
+ * borrowed services inside the Effect; do not return them beyond its scope.
3792
+ * The host drains admitted calls before plugin/pool teardown. */
3793
+ readonly runWithAppServices?: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>;
3794
+ /** App-owned subject hook registry. The server caller owns authorization and withdrawal. */
3795
+ readonly privacyRegistry?: PluginPrivacyRegistry;
3796
+ /** App-owned plugin namespace; tenant acquisition remains explicit and isolated. */
3797
+ readonly hotStore?: (context?: {
3798
+ readonly tenantId: string;
3799
+ }) => Promise<HotStoreShape>;
3800
+ /** Same borrowed registry as lifecycle hooks; the API host owns its pools. */
3801
+ readonly connectionRegistry?: ConnectionRegistryShape;
3802
+ readonly connectionReference?: PluginConnectionReference;
3803
+ readonly connectionAvailability?: PluginConnectionAvailability;
3804
+ /** Resolve the registered owner's retained destination for physical reference
3805
+ * protection. Borrowed app wiring; never substitutes the caller's provider. */
3806
+ readonly storageReferenceProvider?: (input: {
3807
+ readonly storageInstance: string;
3808
+ readonly address: StorageAddress;
3809
+ }) => Effect.Effect<StorageProvider, StorageError>;
3364
3810
  /**
3365
3811
  * This process's replica identity — the same value the event bus stamps as
3366
3812
  * its publish `origin` and the membership registry announces under.
@@ -3380,6 +3826,16 @@ export declare interface PluginBindContext {
3380
3826
  * who is on the channel, and an instance that dies simply goes quiet.
3381
3827
  */
3382
3828
  readonly membership?: {
3829
+ /** Current receiver-observed members from the same app-owned registry.
3830
+ * Optional for custom hosts providing only change notifications. Borrowed:
3831
+ * plugins must not start, stop or replace the membership owner. */
3832
+ readonly list?: () => ReadonlyArray<{
3833
+ readonly instanceId: string;
3834
+ readonly startedAt: number;
3835
+ readonly meta: Readonly<Record<string, string | number>> | undefined;
3836
+ readonly lastHeardAt: number;
3837
+ readonly self: boolean;
3838
+ }>;
3383
3839
  readonly onChange: (listener: (event: {
3384
3840
  readonly kind: 'joined' | 'left' | 'restarted';
3385
3841
  readonly instanceId: string;
@@ -3487,6 +3943,14 @@ export declare interface PluginCacheHandle {
3487
3943
 
3488
3944
  export declare interface PluginCapabilities {
3489
3945
  readonly mediaPersist?: PluginMediaPersistCapability;
3946
+ /** Borrow this app instance's bound object transport; the caller owns no pools. */
3947
+ readonly storageProvider?: () => StorageProvider;
3948
+ /** Address-aware object ownership; request tenant resolution is never global. */
3949
+ readonly storageDestinations?: {
3950
+ readonly router: StorageDestinationRouter;
3951
+ readonly directDestination: string | undefined;
3952
+ readonly registry: () => ConnectionRegistryShape | undefined;
3953
+ };
3490
3954
  }
3491
3955
 
3492
3956
  /**
@@ -3608,6 +4072,36 @@ export declare interface PluginCodegenContext {
3608
4072
  }>;
3609
4073
  }
3610
4074
 
4075
+ /** Resolves only this plugin's admitted role; omitted policy means fail. */
4076
+ export declare type PluginConnectionAvailability = (role: string) => ConnectionUnavailablePolicy;
4077
+
4078
+ export declare interface PluginConnectionNeed {
4079
+ readonly onUnavailable?: ConnectionUnavailablePolicy;
4080
+ readonly kind: ConnectionKind_2;
4081
+ readonly role: string;
4082
+ readonly crossNode?: boolean;
4083
+ /** Use the plugin's direct transport only when no declaration or binding selects this role. */
4084
+ readonly optional?: boolean;
4085
+ }
4086
+
4087
+ /**
4088
+ * Per-app context handed to lifecycle hooks. The shape is intentionally
4089
+ * thin — the framework's deeper services (DataStore, Logger, etc.) are
4090
+ * available via the per-request `AppContext` plugins see in their rpc
4091
+ * interceptors. Lifecycle hooks run OUTSIDE any request, so they
4092
+ * receive a minimal slice:
4093
+ *
4094
+ * - `app`: the resolved app-config metadata
4095
+ * (`{ name, type, voltroVersion }`).
4096
+ * - `logger`: a scoped `@voltro/logger` instance keyed to the
4097
+ * plugin's `name`, so log lines are attributable.
4098
+ * - `env`: the process env (alias of `process.env` for typing).
4099
+ * - `config`: the plugin's own validated config (if `configSchema`
4100
+ * declared) — already decoded against the plugin's Schema, ready
4101
+ * for the hook to consume. Unknown otherwise.
4102
+ */
4103
+ export declare type PluginConnectionReference = <K extends ConnectionKind_2>(role: string, kind: K) => ConnectionRef<K> | '';
4104
+
3611
4105
  /**
3612
4106
  * A remote-mounted dashboard surface a plugin contributes. The
3613
4107
  * dashboard-host (voltro-cloud / voltro-devtools) renders the mount
@@ -3718,6 +4212,8 @@ export declare interface PluginEnvVar {
3718
4212
  readonly generate?: 'base64url' | 'hex' | 'p256';
3719
4213
  }
3720
4214
 
4215
+ export declare type PluginEraseHook = (subjectId: string, context: PluginPrivacyHookContext) => Effect.Effect<void, unknown>;
4216
+
3721
4217
  /**
3722
4218
  * A cross-cutting error schema a plugin merges into EVERY procedure's
3723
4219
  * wire error union. Use when a plugin interceptor can fail with a typed
@@ -3756,8 +4252,26 @@ export declare interface PluginErrorSchema {
3756
4252
  };
3757
4253
  }
3758
4254
 
4255
+ /**
4256
+ * Per-call context handed to a plugin-contributed rpc route's
4257
+ * executor. Mirror of the user-handler `AppContext` shape but typed
4258
+ * loosely so the plugin doesn't need to depend on `@voltro/runtime`.
4259
+ */
4260
+ /** Structural view of the existing request-scoped typed publisher. No runtime import. */
4261
+ export declare interface PluginEventPublisher {
4262
+ publish<Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, payload: Schema.Schema.Type<Payload>): Effect.Effect<unknown, unknown>;
4263
+ }
4264
+
4265
+ export declare type PluginExportHook = (subjectId: string, context: PluginPrivacyHookContext) => Effect.Effect<unknown, unknown>;
4266
+
3759
4267
  /** A public raw-HTTP route a plugin serves on the framework listener. */
3760
4268
  export declare interface PluginHttpRoute {
4269
+ /** Executable API workload owning this route. Omitted means `rpc`.
4270
+ * Discovery and descriptor generation remain independent of role selection. */
4271
+ readonly workload?: ApiWorkload;
4272
+ /** The already-mounted REST projection, retained for SDK/schema inspection.
4273
+ * Do not remount it through generic REST auth or idempotency bindings. */
4274
+ readonly restContract?: RestRouteDescriptor<any, any>;
3761
4275
  /** HTTP method, or `'*'` for any (the handler decides). The server mounts the
3762
4276
  * path for exactly the methods its routes declare; `'*'` mounts it for every
3763
4277
  * method the router knows (34), so keep that for a route that really serves
@@ -3786,6 +4300,13 @@ export declare interface PluginHttpRoute {
3786
4300
  */
3787
4301
  readonly match?: 'prefix' | 'exact';
3788
4302
  readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
4303
+ /** Lazy, backpressured input instead of a buffered rawBody on real HTTP.
4304
+ * Requires match: 'exact' and exclusive ownership of the router-equivalent
4305
+ * path (including other methods). The required handle remains the buffered
4306
+ * in-process entry. Both entries must apply the same authorization/codec.
4307
+ * maxBodyBytes counts consumed bytes; overflow overrides the handler's
4308
+ * response with 413 even when the handler catches the read error. */
4309
+ readonly handleStream?: (req: PluginHttpRouteStreamRequest) => Promise<PluginHttpRouteResult>;
3789
4310
  /** Per-route body cap override (bytes) — wins over the listener's shared
3790
4311
  * `maxBodyBytes`. NOTE: routes SHARING a path share one body read, so the
3791
4312
  * widest override on the path's group applies to the whole group. */
@@ -3826,6 +4347,9 @@ export declare interface PluginHttpRoute {
3826
4347
  * handler branches on: exempting `/saml` exempts `POST /saml/anything`.
3827
4348
  */
3828
4349
  readonly originGuard?: 'exempt';
4350
+ readonly cors?: RestCors;
4351
+ /** Bearer-only routes must discard cookie authority before authentication. */
4352
+ readonly credentials?: 'bearer';
3829
4353
  }
3830
4354
 
3831
4355
  /**
@@ -4009,11 +4533,23 @@ export declare interface PluginHttpRouteResult {
4009
4533
  */
4010
4534
  export declare interface PluginHttpRouteStream {
4011
4535
  readonly subscribe: (emit: (chunk: string) => void) => () => void;
4536
+ /** Per-connection lifetime in milliseconds (1..2147483647). Ending the
4537
+ * response releases the subscription and keep-alive timer. Absent: no cap. */
4538
+ readonly maxDurationMs?: number;
4012
4539
  /** Keep-alive comment interval in ms (default 15000; `0` disables). Without
4013
4540
  * it an idle SSE connection is dropped by proxies after ~30–60s. */
4014
4541
  readonly keepAliveMs?: number;
4015
4542
  }
4016
4543
 
4544
+ /** A lazy binary request on the framework listener. No body bytes are read
4545
+ * before the handler runs. Consume once, through EOF, before accepting an
4546
+ * upload; returning early drains the unread body without validating it.
4547
+ * The signal ends on disconnect, interruption or handler completion. */
4548
+ export declare interface PluginHttpRouteStreamRequest extends Omit<PluginHttpRouteRequest, 'rawBody'> {
4549
+ readonly body: AsyncIterable<Uint8Array>;
4550
+ readonly signal: AbortSignal;
4551
+ }
4552
+
4017
4553
  export declare interface PluginInspectEndpoint {
4018
4554
  /** HTTP method. */
4019
4555
  readonly method: 'GET' | 'POST' | 'DELETE';
@@ -4142,23 +4678,9 @@ export declare const pluginInstanceName: (args: {
4142
4678
  readonly instance?: string | undefined;
4143
4679
  }) => string;
4144
4680
 
4145
- /**
4146
- * Per-app context handed to lifecycle hooks. The shape is intentionally
4147
- * thin — the framework's deeper services (DataStore, Logger, etc.) are
4148
- * available via the per-request `AppContext` plugins see in their rpc
4149
- * interceptors. Lifecycle hooks run OUTSIDE any request, so they
4150
- * receive a minimal slice:
4151
- *
4152
- * - `app`: the resolved app-config metadata
4153
- * (`{ name, type, voltroVersion }`).
4154
- * - `logger`: a scoped `@voltro/logger` instance keyed to the
4155
- * plugin's `name`, so log lines are attributable.
4156
- * - `env`: the process env (alias of `process.env` for typing).
4157
- * - `config`: the plugin's own validated config (if `configSchema`
4158
- * declared) — already decoded against the plugin's Schema, ready
4159
- * for the hook to consume. Unknown otherwise.
4160
- */
4161
4681
  export declare interface PluginLifecycleContext {
4682
+ /** Selected API work; absent custom-host selection means all workloads. */
4683
+ readonly workloads?: ReadonlyArray<ApiWorkload>;
4162
4684
  readonly app: {
4163
4685
  readonly name: string;
4164
4686
  readonly type: 'api' | 'web';
@@ -4173,6 +4695,11 @@ export declare interface PluginLifecycleContext {
4173
4695
  readonly env: NodeJS.ProcessEnv;
4174
4696
  /** Validated plugin config (if `configSchema` was declared). */
4175
4697
  readonly config: unknown;
4698
+ /** App-owned command registry, available before API plugin activation. Never dispose it. */
4699
+ readonly connectionRegistry?: ConnectionRegistryShape;
4700
+ /** Resolve only this plugin's declared role through the app's binding/default. */
4701
+ readonly connectionReference?: PluginConnectionReference;
4702
+ readonly connectionAvailability?: PluginConnectionAvailability;
4176
4703
  }
4177
4704
 
4178
4705
  /** Where the offloaded-inference dispatcher puts a media artifact a provider
@@ -4257,7 +4784,13 @@ export declare type PluginMigrationSqlClient = SqlClient.SqlClient;
4257
4784
  * boot log + `/_voltro/inspect/plugins` so operators audit what each
4258
4785
  * plugin asks for before installing.
4259
4786
  */
4260
- 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}`;
4787
+ export declare type PluginPermission = 'rpc:authority:replay' | 'rpc:intercept:mutation' | 'rpc:intercept:query' | 'rpc:intercept:action' | 'rpc:intercept:workflow' | 'rpc:intercept:stream' | 'schedule:fire' | 'schedule:read' | 'workflow:step' | 'http:intercept' | 'inspect:read' | 'inspect:write' | 'dashboard:mount' | 'outbox:handle' | 'store:write' | 'migration:run' | 'store:changes:read' | `secrets:read:${string}` | `network:outbound:${string}` | `plugin:hook:${string}`;
4788
+
4789
+ export declare interface PluginPrivacyCoverage {
4790
+ readonly participants: ReadonlyArray<PluginPrivacyParticipant>;
4791
+ /** Plugin declarations only. App-owned tables require their own declared hooks. */
4792
+ readonly missing: ReadonlyArray<string>;
4793
+ }
4261
4794
 
4262
4795
  /** What a plugin collects and uses — folded by `voltro compliance generate`
4263
4796
  * into PrivacyInfo.xcprivacy and the Play Data-safety answers. A plugin that
@@ -4267,12 +4800,76 @@ export declare interface PluginPrivacyDeclaration {
4267
4800
  readonly requiredReasonApis?: ReadonlyArray<PrivacyRequiredReasonApi>;
4268
4801
  }
4269
4802
 
4270
- /**
4271
- * Per-call context handed to a plugin-contributed rpc route's
4272
- * executor. Mirror of the user-handler `AppContext` shape but typed
4273
- * loosely so the plugin doesn't need to depend on `@voltro/runtime`.
4274
- */
4803
+ export declare type PluginPrivacyErasePhase = typeof PluginPrivacyErasePhaseSchema.Type;
4804
+
4805
+ /** Resource/actor withdrawal must finish before destructive data hooks. */
4806
+ export declare const PluginPrivacyErasePhaseSchema: Schema.Literals<readonly ["prepare", "data", "identity", "finalize"]>;
4807
+
4808
+ export declare interface PluginPrivacyErasure {
4809
+ readonly requestId: string;
4810
+ readonly target: PluginPrivacyTarget;
4811
+ readonly completed: ReadonlyArray<string>;
4812
+ }
4813
+
4814
+ export declare interface PluginPrivacyExport {
4815
+ readonly requestId: string;
4816
+ readonly target: PluginPrivacyTarget;
4817
+ readonly plugins: ReadonlyArray<{
4818
+ readonly name: string;
4819
+ readonly data: unknown;
4820
+ }>;
4821
+ }
4822
+
4823
+ /** Never carries a driver's error, credentials, exported rows or the hook's cause. */
4824
+ export declare class PluginPrivacyFailure extends PluginPrivacyFailure_base {
4825
+ }
4826
+
4827
+ declare const PluginPrivacyFailure_base: Schema.Class<PluginPrivacyFailure, Schema.TaggedStruct<"PluginPrivacyFailure", {
4828
+ readonly code: Schema.Literals<readonly ["invalid_request", "coverage_missing", "hook_failed", "unavailable"]>;
4829
+ readonly operation: Schema.Literals<readonly ["export", "erase"]>;
4830
+ readonly requestId: Schema.String;
4831
+ readonly plugin: Schema.NullOr<Schema.String>;
4832
+ readonly completed: Schema.$Array<Schema.String>;
4833
+ readonly missing: Schema.$Array<Schema.String>;
4834
+ }>, YieldableError>;
4835
+
4836
+ export declare interface PluginPrivacyHookContext {
4837
+ readonly requestId: string;
4838
+ readonly tenantId: string | null;
4839
+ readonly kind: 'user' | 'guest';
4840
+ /** Only the finalizer receives these immutable, successfully completed hook names. */
4841
+ readonly completed?: ReadonlyArray<string>;
4842
+ }
4843
+
4844
+ export declare interface PluginPrivacyParticipant {
4845
+ readonly name: string;
4846
+ readonly tables: ReadonlyArray<string>;
4847
+ readonly erasePhase: PluginPrivacyErasePhase;
4848
+ }
4849
+
4850
+ /** Borrowed app-owned hook port. Authorization and durable withdrawal belong to the caller. */
4851
+ export declare interface PluginPrivacyRegistry {
4852
+ readonly coverage: (kind: PluginPrivacyTarget['kind']) => PluginPrivacyCoverage;
4853
+ readonly exportSubject: (request: PluginPrivacyRequest) => Effect.Effect<PluginPrivacyExport, PluginPrivacyFailure>;
4854
+ readonly eraseSubject: (request: PluginPrivacyRequest) => Effect.Effect<PluginPrivacyErasure, PluginPrivacyFailure>;
4855
+ }
4856
+
4857
+ /** Reuse the request identity on recovery; hooks must be idempotent. */
4858
+ export declare interface PluginPrivacyRequest {
4859
+ readonly requestId: string;
4860
+ readonly target: PluginPrivacyTarget;
4861
+ }
4862
+
4863
+ /** A server-authorized subject, never an identity accepted from a client body. */
4864
+ export declare interface PluginPrivacyTarget {
4865
+ readonly subjectId: string;
4866
+ readonly tenantId: string | null;
4867
+ readonly kind: 'user' | 'guest';
4868
+ }
4869
+
4275
4870
  export declare interface PluginRouteContext {
4871
+ /** Borrows the host request publisher, preserving tenant and transaction ownership. */
4872
+ readonly events?: PluginEventPublisher;
4276
4873
  /** Request-scoped transaction for mutation routes. Reuse it instead of opening
4277
4874
  * a transaction on the plugin's bound root store. Absent on direct calls. */
4278
4875
  readonly mutationStore?: DataStore;
@@ -4375,6 +4972,8 @@ export declare interface PluginRpcRoute {
4375
4972
  * checking.
4376
4973
  */
4377
4974
  readonly source?: ReactivitySourceValue | ReadonlyArray<ReactivitySourceValue> | undefined;
4975
+ /** Optional integration observers; each name must also occur in source. */
4976
+ readonly optionalSource?: ReadonlyArray<string> | undefined;
4378
4977
  /**
4379
4978
  * The route's ACCESS DECISION — the same two-field vocabulary user
4380
4979
  * procedures carry, because a plugin route is dispatched through the exact
@@ -4441,6 +5040,23 @@ export declare interface PluginSchemaContribution {
4441
5040
  */
4442
5041
  export declare const pluginSlug: (pluginName: string) => string;
4443
5042
 
5043
+ export declare interface PluginSubjectMerge {
5044
+ readonly tables: ReadonlyArray<string>;
5045
+ /** Source kinds whose data this implementation can reconcile. */
5046
+ readonly kinds: ReadonlyArray<'user' | 'guest'>;
5047
+ }
5048
+
5049
+ /** SQL tables owned by this hook pair. External resources belong in the hooks too. */
5050
+ export declare interface PluginSubjectPrivacy {
5051
+ readonly tables: ReadonlyArray<string>;
5052
+ readonly kinds: ReadonlyArray<PluginPrivacyTarget['kind']>;
5053
+ /** Preparation withdraws active work before data hooks. Identity/session
5054
+ * deletion follows every data hook, regardless of plugin order. */
5055
+ /** Terminal request bookkeeping follows every data and identity hook.
5056
+ * At most one finalizer may own completion for each subject kind. */
5057
+ readonly erasePhase?: PluginPrivacyErasePhase;
5058
+ }
5059
+
4444
5060
  /**
4445
5061
  * The wire tag a plugin's own client code must call for one of its routes.
4446
5062
  *
@@ -4523,6 +5139,20 @@ export declare interface PluginTemplate {
4523
5139
  */
4524
5140
  export declare type PluginUninstallHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
4525
5141
 
5142
+ /** Starts app workflows from plugin server work outside a request. */
5143
+ export declare interface PluginWorkflowStarter {
5144
+ /** Resolves once the run is accepted. Runs as the plugin's system subject
5145
+ * (`plugin:<name>`); rejects while the host's workflow runtime is not ready. */
5146
+ start(workflowName: string, payload: unknown): Promise<unknown>;
5147
+ }
5148
+
5149
+ export declare type PluginWorkloadHook = (context: PluginLifecycleContext) => Effect.Effect<PluginWorkloadResource, unknown> | Promise<PluginWorkloadResource> | PluginWorkloadResource;
5150
+
5151
+ export declare interface PluginWorkloadResource {
5152
+ /** Await drainage; rejection retains ownership and must be retryable. */
5153
+ stop(): Effect.Effect<void, unknown> | Promise<void> | void;
5154
+ }
5155
+
4526
5156
  /** The runtime-erased form of `PolicyGuardSpec` — a relationship check. */
4527
5157
  export declare interface PolicyCheckSpec {
4528
5158
  readonly action: string | ((input: unknown) => string);
@@ -4579,6 +5209,17 @@ export declare interface PolicyGuardSpec<Input = unknown> {
4579
5209
  readonly resource: (input: Input) => string | undefined;
4580
5210
  }
4581
5211
 
5212
+ export declare interface PreparedIdempotentInput {
5213
+ readonly input: unknown;
5214
+ readonly fingerprint: string;
5215
+ }
5216
+
5217
+ /** Capture the declared schema's wire input before waiting for a receipt.
5218
+ * Decode an independent snapshot for execution and require an equal round trip.
5219
+ * The principal comes from server authentication, never a client digest/header.
5220
+ * This binds identity and input, not current permissions or a business lease. */
5221
+ export declare const prepareIdempotentInput: (schema: Schema.Top, offered: unknown, subject: Subject, transportInput?: unknown) => Promise<PreparedIdempotentInput>;
5222
+
4582
5223
  /**
4583
5224
  * An environment variable a plugin reads. DECLARATION ONLY — metadata, not a
4584
5225
  * read path: the plugin still reads the value itself (typically
@@ -4673,6 +5314,9 @@ export declare interface PublicApiClientConfig {
4673
5314
  readonly baseUrl?: string;
4674
5315
  }
4675
5316
 
5317
+ /** A descriptor kind that can be projected to REST (streams are not, in v1). */
5318
+ declare type PublicApiDescriptor = QueryProcedureDescriptor<string, Schema.Top, Schema.Top, Schema.Top> | MutationProcedureDescriptor<string, Schema.Top, Schema.Top, Schema.Top> | ActionProcedureDescriptor<string, Schema.Top, Schema.Top, Schema.Top>;
5319
+
4676
5320
  /** The `publicApi:` block of an api `app.config.ts`, and the shape of a
4677
5321
  * descriptor's `publicApi.format` override (every field optional there). */
4678
5322
  export declare interface PublicApiProfileConfig {
@@ -4749,6 +5393,10 @@ export declare interface PublicApiSpec {
4749
5393
  /** Streaming queries over request/response: 'snapshot' (default — first
4750
5394
  * snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
4751
5395
  readonly stream?: 'snapshot' | 'sse';
5396
+ /** Per-connection SSE lifetime in milliseconds (1..2147483647). Requires
5397
+ * an SSE query projection. Clients may reconnect for a freshly authorized
5398
+ * snapshot; expiry does not cancel a domain operation. */
5399
+ readonly streamMaxDurationMs?: number;
4752
5400
  /**
4753
5401
  * HTTP status per DECLARED typed error tag, for the projection. A typed
4754
5402
  * error the handler fails with — one of the descriptor's `error:` union —
@@ -4760,6 +5408,11 @@ export declare interface PublicApiSpec {
4760
5408
  * publicApi: { errorStatus: { AccessDeniedError: 403, NotFoundError: 404 } }
4761
5409
  */
4762
5410
  readonly errorStatus?: Readonly<Record<string, number>>;
5411
+ /** Additional HTTP failure statuses for the same declared error schema.
5412
+ * Documents dedicated handlers that choose a status from the error's fields;
5413
+ * does not change `errorStatus` or the generic projection's runtime mapping.
5414
+ * Values must be integer HTTP error statuses (400–599); duplicates are omitted. */
5415
+ readonly additionalErrorStatuses?: Readonly<Record<string, ReadonlyArray<number>>>;
4763
5416
  /**
4764
5417
  * The spec's tag group(s) for this operation. Default: the rpc tag's prefix
4765
5418
  * (`teams.list` → `teams`) — the first path segment, what a consumer looks
@@ -4861,6 +5514,16 @@ export declare interface QueryCacheConfig {
4861
5514
  * cardinality of a cache the author sized per org.
4862
5515
  */
4863
5516
  readonly scope: 'subject' | 'tenant' | 'global';
5517
+ /**
5518
+ * The handler's result also depends on something no table write
5519
+ * invalidates — the clock, randomness, `ctx.connections`, an outgoing HTTP
5520
+ * call, `ctx.kv`. Applies to a COMPUTED query (a handler that returns a
5521
+ * value): without it such a result is computed but NOT kept, and the dev log
5522
+ * names what was used. With it, `ttl` is the bound on how stale that part may
5523
+ * be. `Date.now()`, `Math.random()` and a `fetch` outside the framework's
5524
+ * HTTP client are not observable — a handler that uses them must say so here.
5525
+ */
5526
+ readonly volatile?: boolean | undefined;
4864
5527
  }
4865
5528
 
4866
5529
  export declare interface QueryProcedureDescriptor<Name extends string, Input extends Schema.Top, Output extends Schema.Top, Error extends Schema.Top> {
@@ -4875,6 +5538,9 @@ export declare interface QueryProcedureDescriptor<Name extends string, Input ext
4875
5538
  * listed table changes (pass an array to depend on several). Optional:
4876
5539
  * queries without a source never receive auto-patches (joins, aggregates). */
4877
5540
  readonly source: string | ReadonlyArray<string> | undefined;
5541
+ /** Optional integration observers, all also present in source. They keep
5542
+ * invalidation active when installed; absence is a visible graph warning. */
5543
+ readonly optionalSource?: ReadonlyArray<string>;
4878
5544
  /** Server-side snapshot cache config. Absent → never cached (the
4879
5545
  * default; the dispatcher already keeps live subscriptions fresh). */
4880
5546
  readonly cache: QueryCacheConfig | undefined;
@@ -4950,7 +5616,7 @@ export declare const queryToRpc: <Name extends string, Input extends Schema.Top,
4950
5616
  }>;
4951
5617
  }>, Schema.Struct<{
4952
5618
  readonly _tag: Schema.Literal<"error">;
4953
- readonly error: Schema.Unknown;
5619
+ readonly error: Schema.decodeTo<Schema.Unknown, Schema.Codec<Schema.Json, Schema.Json, never, never>, never, never>;
4954
5620
  readonly revision: Schema.optional<Schema.Number>;
4955
5621
  }>]>, Schema.Top>, Schema.Never, never, never>;
4956
5622
 
@@ -5029,8 +5695,24 @@ export declare type ReactivitySource = TableName | ReactivityChannel;
5029
5695
  */
5030
5696
  export declare type ReactivitySourceValue = string | ReactivityChannel;
5031
5697
 
5698
+ export declare const readFormCaptureSource: (document?: NativeFormCaptureDocument | undefined) => string | undefined;
5699
+
5032
5700
  export declare const readPairing: (storage: PairingStorage) => Promise<Pairing | null>;
5033
5701
 
5702
+ /**
5703
+ * Reauthenticate a live grant through its original strategy chain. Tenant and
5704
+ * principal identity stay bound to the original subscription: changing either
5705
+ * requires a new subscription, never reuse of an already scoped descriptor.
5706
+ * Runtime-owned subjects without a request binding retain their explicit grant.
5707
+ */
5708
+ export declare const reauthorizeSubject: (subject: Subject, prepare?: (fresh: Subject) => Promise<void>) => Promise<Subject>;
5709
+
5710
+ /** Recheck a live grant through fresh authentication and derived authority. */
5711
+ export declare const recheckGuardsEffect: (subject: Subject, guards: ReadonlyArray<AnyCheckSpec> | undefined, input: unknown, options?: GuardCheckOptions) => Effect.Effect<ScopeError | Unauthenticated | RequestedTenantRefused | NoSuchResource | null>;
5712
+
5713
+ /** Refresh derived authority without replaying unrelated RPC plugin hooks. */
5714
+ export declare const refreshEffectiveScopes: (original: Subject, fresh: Subject) => Promise<void>;
5715
+
5034
5716
  /** Every error a plugin registers, as one union (`never` before codegen). */
5035
5717
  export declare type RegisteredPluginError = VoltroPluginErrors[keyof VoltroPluginErrors];
5036
5718
 
@@ -5063,13 +5745,48 @@ export declare const registerPluginAliases: (aliases: Readonly<Record<string, st
5063
5745
  */
5064
5746
  export declare const reportableError: (err: unknown) => Error;
5065
5747
 
5748
+ /** An authenticated request cannot use its requested workspace. This is
5749
+ * scoped access refusal, not evidence that the credential or session ended.
5750
+ * Consumers must keep unrelated workspace sessions intact. */
5751
+ export declare class RequestedTenantRefused extends RequestedTenantRefused_base {
5752
+ }
5753
+
5754
+ declare const RequestedTenantRefused_base: Schema.Class<RequestedTenantRefused, Schema.TaggedStruct<"RequestedTenantRefused", {
5755
+ readonly reason: Schema.Literals<readonly ["target-unavailable", "invalid-target", "memberships-unavailable", "not-a-member", "subject-has-no-tenant"]>;
5756
+ }>, YieldableError>;
5757
+
5758
+ /** A request's tenant hint, read only after a strategy authenticates the caller.
5759
+ * Return null for the credential / stored selection. A non-null target is
5760
+ * checked against fresh memberships on every resolution, including when it
5761
+ * equals the credential's tenant. It never writes a persistent selection.
5762
+ * Do not return a tenant default for an absent hint: that would silently change
5763
+ * the authority of requests which did not select a workspace.
5764
+ */
5765
+ export declare type RequestedTenantSource = (subject: Subject, input: AuthStrategyInput) => Promise<string | null> | string | null;
5766
+
5066
5767
  /** Gate a handler on a scope; fails with a typed `ScopeError` if missing.
5067
5768
  * Checks the EFFECTIVE set so role-derived scopes count. */
5068
5769
  export declare const requireScope: (subject: Subject, scope: string) => Effect.Effect<void, ScopeError>;
5069
5770
 
5771
+ /** Browser-safe descriptor guard for capabilities that require registration. */
5772
+ export declare const requireUpgraded: () => UpgradedGuardSpec;
5773
+
5070
5774
  /** Drop every registration. Tests only — production registers once at module load. */
5071
5775
  export declare const resetPluginAliases: () => void;
5072
5776
 
5777
+ /**
5778
+ * Put authority back onto an IDENTITY that arrived without it — a resumed
5779
+ * durable run, a redeemed Room seat — through the app's own `resolveScopes`
5780
+ * and `subjectGuards`, the same seam a request uses. Credentials never carry
5781
+ * scopes (`subjectIdentity`), so every non-request consumer resolves here
5782
+ * rather than trusting authority frozen at mint time. With no resolver the
5783
+ * identity keeps what it carries (nothing), the fail-closed default.
5784
+ */
5785
+ export declare const resolveIdentityAuthority: (identity: Subject, ctx: AuthSubjectGuardContext, authority: {
5786
+ readonly resolveScopes?: ((subject: Subject, ctx: AuthSubjectGuardContext) => Promise<ScopeResolverResult> | ScopeResolverResult) | undefined;
5787
+ readonly subjectGuards?: ReadonlyArray<AuthSubjectGuard> | undefined;
5788
+ } | undefined) => Promise<IdentityAuthorityOutcome>;
5789
+
5073
5790
  /** Resolve the input-selected action once. Invalid selectors deny rather than
5074
5791
  * widening to any action the subject happens to hold. Also used when an
5075
5792
  * approval persists the resolved guard for its later recheck. */
@@ -5179,6 +5896,16 @@ declare interface RestCache {
5179
5896
  readonly audience?: (output: unknown, input: unknown, subject: Subject) => 'public' | 'private';
5180
5897
  }
5181
5898
 
5899
+ /** Cross-origin browser access for one route. Origins are exact, including opaque `null`. */
5900
+ declare interface RestCors {
5901
+ readonly origins: ReadonlyArray<string>;
5902
+ readonly headers?: ReadonlyArray<string>;
5903
+ readonly methods?: ReadonlyArray<RestMethod>;
5904
+ /** Preflight cache lifetime in seconds. */
5905
+ readonly maxAge?: number;
5906
+ readonly exposeHeaders?: ReadonlyArray<string>;
5907
+ }
5908
+
5182
5909
  export declare type RestDates = 'iso' | 'epoch-ms';
5183
5910
 
5184
5911
  export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
@@ -5256,7 +5983,7 @@ export declare interface RestFormatResponse {
5256
5983
  readonly contentType?: string;
5257
5984
  }
5258
5985
 
5259
- declare type RestGuard = (ctx: RestRouteContext) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
5986
+ declare type RestGuard = (ctx: RestRouteContext, request?: RestGuardRequest) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
5260
5987
 
5261
5988
  /**
5262
5989
  * A guard runs after input decode + ctx resolution, before the handler.
@@ -5268,6 +5995,13 @@ declare interface RestGuardRejection {
5268
5995
  readonly message: string;
5269
5996
  }
5270
5997
 
5998
+ /** Guard input is the decoded execution snapshot. Guards are read-only
5999
+ * admission checks and may run again after awaited admission/storage work. */
6000
+ declare interface RestGuardRequest {
6001
+ readonly input: unknown;
6002
+ readonly defaultDeny: boolean;
6003
+ }
6004
+
5271
6005
  /** What a REST handler may return: the decoded output, a `response(...)` with
5272
6006
  * its own status and headers, an `sse(...)` stream, or `bytes(...)` for a
5273
6007
  * non-JSON body (a file, an export, an XML document). */
@@ -5279,6 +6013,13 @@ export declare type RestNaming = 'camelCase' | 'snake_case';
5279
6013
 
5280
6014
  export declare type RestPagination = 'link-header' | 'body-cursor';
5281
6015
 
6016
+ /** Uses the mounted rate-limit plugin's store, algorithms and dynamic policy. */
6017
+ declare interface RestRateLimit {
6018
+ readonly limit: number;
6019
+ readonly window: string;
6020
+ readonly by?: 'subject' | 'tenant' | 'apiKey' | 'ip' | 'global';
6021
+ }
6022
+
5282
6023
  /** A schema-encoded REST body with an application-selected HTTP status and
5283
6024
  * response headers. The marker keeps ordinary domain objects unambiguous.
5284
6025
  * `output` still validates/encodes `body`; use `Schema.Void` with a 204 when
@@ -5300,6 +6041,8 @@ declare interface RestResponse<O> {
5300
6041
  * stays free of the database dep).
5301
6042
  */
5302
6043
  declare interface RestRouteContext {
6044
+ /** Server-owned invocation trace, stable through readmission and projection. */
6045
+ readonly traceId: string;
5303
6046
  readonly subject: Subject;
5304
6047
  readonly headers: Readonly<Record<string, string>>;
5305
6048
  /** Request metadata and the byte-exact body as received by the HTTP
@@ -5324,6 +6067,9 @@ declare interface RestRouteContext {
5324
6067
  }
5325
6068
 
5326
6069
  declare interface RestRouteDescriptor<I, O> {
6070
+ /** Original schemas for client generation. Metadata only: never mounts a
6071
+ * second handler or selects authentication/idempotency bindings. */
6072
+ readonly publicApiDescriptor?: PublicApiDescriptor;
5327
6073
  readonly method: RestMethod;
5328
6074
  /** Absolute path, e.g. `/v1/customers`. Matches that path exactly. */
5329
6075
  readonly path: string;
@@ -5337,6 +6083,12 @@ declare interface RestRouteDescriptor<I, O> {
5337
6083
  * encodes it to JSON for the `200` response. */
5338
6084
  readonly output: Schema.Codec<O, any, never, never>;
5339
6085
  readonly handler: (input: I, ctx: RestRouteContext) => Promise<RestHandlerResult<O>> | RestHandlerResult<O>;
6086
+ /** Pure synchronous normalization of thrown admission/handler failures.
6087
+ * Return a mapped thrown value (for example RestRouteError), not an HTTP
6088
+ * response; undefined keeps the original failure. Available before authentication,
6089
+ * so only request metadata is given.
6090
+ * Public projections supply their existing typed/profile error mapper here. */
6091
+ readonly mapFailure?: (error: unknown, request: Pick<PluginHttpRouteRequest, 'method' | 'path' | 'query'>) => unknown;
5340
6092
  readonly summary?: string;
5341
6093
  readonly description?: string;
5342
6094
  readonly example?: RestRouteExample;
@@ -5377,6 +6129,10 @@ declare interface RestRouteDescriptor<I, O> {
5377
6129
  */
5378
6130
  readonly version?: `v${number}`;
5379
6131
  readonly guards?: ReadonlyArray<RestGuard>;
6132
+ readonly cors?: RestCors;
6133
+ /** Only an explicit bearer header may authenticate; cookies are removed before auth and context construction. */
6134
+ readonly credentials?: 'bearer';
6135
+ readonly rateLimit?: RestRateLimit;
5380
6136
  /**
5381
6137
  * This route STREAMS (Server-Sent Events) rather than resolving one value — its
5382
6138
  * handler returns `sse(...)`. Declared on the descriptor, not left implicit in
@@ -5436,6 +6192,44 @@ declare interface RestRouteProjection {
5436
6192
  readonly format: RestFormat;
5437
6193
  }
5438
6194
 
6195
+ /**
6196
+ * Default ctx resolver used when the serve pipeline doesn't inject one
6197
+ * (e.g. unit tests). Real mounts pass a resolver that reads the framework's
6198
+ * auth subject + the bound DataStore.
6199
+ */
6200
+ declare interface RestServeBindings {
6201
+ /** Pure scope-refresher registration, shared with native RPC admission. */
6202
+ readonly bindReplayAuthority?: RpcReplayAuthorityBinding;
6203
+ /** Actual host policy for projected procedure guards; raw routes own guards. */
6204
+ readonly defaultDeny?: boolean;
6205
+ /** Full runtime hosts supply their existing trace generator. */
6206
+ readonly traceId?: () => string;
6207
+ /** Bound once from the app's mounted rate-limit plugin. */
6208
+ readonly rateLimit?: (policy: RestRateLimit, ctx: RestRouteContext, route: RestRouteDescriptor<any, any>) => Effect.Effect<void, unknown>;
6209
+ /** Resolve the calling subject and request-local selection before guards. */
6210
+ readonly resolveSubject?: (headers: Readonly<Record<string, string>>, request: {
6211
+ readonly path: string;
6212
+ readonly query: string;
6213
+ }) => Subject | Promise<Subject>;
6214
+ /** The bound framework DataStore. */
6215
+ readonly store?: unknown;
6216
+ /** Build the full per-request app context after authentication. */
6217
+ readonly buildContext?: (subject: Subject, headers: Readonly<Record<string, string>>, request: Pick<RestRouteContext, 'traceId' | 'remoteAddr'>) => unknown | Promise<unknown>;
6218
+ /**
6219
+ * HTTP idempotency — when set, a mutating request (POST/PUT/PATCH/DELETE)
6220
+ * carrying the `header` is deduplicated: the first call runs + caches its
6221
+ * response; a replay within `ttlMs` returns the cached response
6222
+ * (`Idempotency-Replayed: true`); an in-flight duplicate gets `409`. Scoped
6223
+ * per (tenant, method, path). The serve path injects the store + config from
6224
+ * `app.config.ts`'s `idempotency` field.
6225
+ */
6226
+ readonly idempotency?: {
6227
+ readonly store: IdempotencyStore;
6228
+ readonly header: string;
6229
+ readonly ttlMs: number;
6230
+ };
6231
+ }
6232
+
5439
6233
  /**
5440
6234
  * What a REST handler returns to stream its response instead of resolving one
5441
6235
  * value: `sse((emit) => unsubscribe)`. The route's schema `output` is not used
@@ -5584,9 +6378,13 @@ export declare type RpcInterceptor = (next: Effect.Effect<unknown, unknown>, con
5584
6378
  * them automatically.
5585
6379
  */
5586
6380
  export declare interface RpcInterceptorContext {
6381
+ /** Enqueue a settled invocation outcome on the host's root store, outside
6382
+ * the business transaction. Required transactional success capture must use
6383
+ * MutationCommitContext.outbox instead. Older/custom hosts may omit this. */
6384
+ readonly enqueueOutcome?: OutboxEnqueueFacade['enqueue'];
5587
6385
  /** Procedure tag, e.g. `todos.create`. */
5588
6386
  readonly tag: string;
5589
- /** Discriminator — `'mutation' | 'query' | 'action' | 'workflow'`. */
6387
+ /** Procedure kind, including finite stream admission. */
5590
6388
  readonly kind: RpcKind;
5591
6389
  /** The validated input the executor is about to (or already did) receive. */
5592
6390
  readonly input: unknown;
@@ -5622,10 +6420,20 @@ export declare interface RpcInterceptorContext {
5622
6420
  * Which kind of rpc the interceptor is wrapping. The runtime passes
5623
6421
  * `kind: 'mutation'` to interceptors registered as `interceptMutation`,
5624
6422
  * `kind: 'query'` to `interceptQuery`, `kind: 'action'` to
5625
- * `interceptAction`, and `kind: 'workflow'` to `interceptWorkflow` — one
6423
+ * `interceptAction`, `kind: 'workflow'` to `interceptWorkflow`, and
6424
+ * `kind: 'stream'` to `interceptStream` — one
5626
6425
  * plugin can implement any combination.
5627
6426
  */
5628
- export declare type RpcKind = 'mutation' | 'query' | 'action' | 'workflow';
6427
+ export declare type RpcKind = 'mutation' | 'query' | 'action' | 'workflow' | 'stream';
6428
+
6429
+ export declare type RpcReplayAuthorityBinding = (context: RpcReplayAuthorityContext) => undefined;
6430
+
6431
+ /** Pure synchronous authority-refresher binding for RPC and REST admission.
6432
+ * No business work, I/O, grants or side effects belong in this hook. The host
6433
+ * calls the existing guard rechecker after every binding. */
6434
+ export declare interface RpcReplayAuthorityContext extends Pick<RpcInterceptorContext, 'tag' | 'input' | 'subject' | 'traceId' | 'surface' | 'publicApi' | 'remoteAddr'> {
6435
+ readonly kind: RpcKind | 'rest';
6436
+ }
5629
6437
 
5630
6438
  /**
5631
6439
  * Per-firing context passed to schedule-fire interceptors. Shape
@@ -5774,6 +6582,10 @@ declare const ScopeError_base: Schema.Class<ScopeError, Schema.TaggedStruct<"Sco
5774
6582
  * resolver already has. */
5775
6583
  export declare type ScopeResolverResult = ReadonlyArray<string> | ScopeDecision;
5776
6584
 
6585
+ /** Exact tags outrank namespace filters; deeper namespaces outrank broader ones.
6586
+ * Equal specificity is an ambiguity, never an implicit failover/credential hop. */
6587
+ export declare const selectApiRoleEndpoint: (endpoints: ApiRoleEndpoints, tag: string, workload: ApiRoleRpcWorkload) => ApiRoleEndpoint;
6588
+
5777
6589
  /**
5778
6590
  * The subject with `tenantId` selected — or a refusal.
5779
6591
  *
@@ -5977,6 +6789,9 @@ export declare const sessionValueFromHeaders: (headers: Readonly<Record<string,
5977
6789
  */
5978
6790
  export declare const setEffectiveScopes: (subject: Subject, scopes: ReadonlyArray<string>) => void;
5979
6791
 
6792
+ /** Register a request's dynamic role/permission resolver for live delivery. */
6793
+ export declare const setEffectiveScopesRefresher: (subject: Subject, refresh: (fresh: Subject) => Promise<void>) => void;
6794
+
5980
6795
  /** Register (or clear) the process-global policy-guard resolver. Last write
5981
6796
  * wins. The runtime installs one backed by the resource-policy registry and
5982
6797
  * the registered tuple source. */
@@ -6007,6 +6822,33 @@ export declare const shapeDigest: (text: string) => string;
6007
6822
  */
6008
6823
  export declare const sourceKeys: (source: ReactivitySourceValue | ReadonlyArray<ReactivitySourceValue> | undefined) => ReadonlyArray<string>;
6009
6824
 
6825
+ export declare interface SsrfPolicy {
6826
+ /**
6827
+ * Hosts that bypass the guard. An entry may be an exact hostname
6828
+ * (`billing.internal`), a suffix wildcard (`*.svc.cluster.local`), or a
6829
+ * host:port (`127.0.0.1:8787`) when only one port should be reachable.
6830
+ *
6831
+ * This is the one escape hatch, and it is deliberately per-HOST rather than a
6832
+ * boolean: "we call one internal service" and "we do not check URLs" are very
6833
+ * different postures, and a boolean cannot tell them apart six months later.
6834
+ */
6835
+ readonly allowHosts?: ReadonlyArray<string>;
6836
+ /** Max redirect hops to follow. Each one is revalidated. Default 5. */
6837
+ readonly maxRedirects?: number;
6838
+ }
6839
+
6840
+ /**
6841
+ * Decide whether a URL may be fetched. Returns `null` when allowed, else the
6842
+ * reason. Fails CLOSED: a URL that will not parse is blocked.
6843
+ *
6844
+ * DNS is deliberately NOT resolved — this is a synchronous check on the literal
6845
+ * target, so a hostname that RESOLVES to a private address (DNS rebinding) is not
6846
+ * caught here. Blocking the direct-IP vector is the high-severity, cheap half;
6847
+ * rebinding needs network-layer egress control and is called out in the docs
6848
+ * rather than silently implied.
6849
+ */
6850
+ export declare const ssrfReason: (rawUrl: string, policy?: SsrfPolicy) => string | null;
6851
+
6010
6852
  export declare type StorageAccessPolicy = typeof StorageAccessPolicySchema.Type;
6011
6853
 
6012
6854
  export declare const StorageAccessPolicySchema: Schema.$Array<Schema.Struct<{
@@ -6185,6 +7027,9 @@ export declare const strictInput: <S extends Schema.Top>(input: S, procedure?: s
6185
7027
 
6186
7028
  export declare const Subject: Schema.Union<readonly [Schema.Struct<{
6187
7029
  readonly type: Schema.Literal<"user">;
7030
+ /** A guest has a stable identity but has not linked a registered account.
7031
+ * This identity mark survives session signing; it never grants authority. */
7032
+ readonly kind: Schema.optional<Schema.Literal<"guest">>;
6188
7033
  readonly id: Schema.String;
6189
7034
  readonly tenantId: Schema.String;
6190
7035
  readonly scopes: Schema.optional<Schema.$Array<Schema.String>>;
@@ -6251,6 +7096,7 @@ export declare const subjectAuditActorId: (subject: Subject) => string | null;
6251
7096
  export declare const SubjectIdentity: Schema.Union<readonly [Schema.Struct<{
6252
7097
  readonly type: Schema.Literal<"user">;
6253
7098
  readonly id: Schema.String;
7099
+ readonly kind: Schema.optional<Schema.Literal<"guest">>;
6254
7100
  readonly tenantId: Schema.String;
6255
7101
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
6256
7102
  }>, Schema.Struct<{
@@ -6293,9 +7139,9 @@ export declare const SubjectIdentity: Schema.Union<readonly [Schema.Struct<{
6293
7139
  }>, Schema.Struct<{
6294
7140
  readonly type: Schema.Literal<"service">;
6295
7141
  readonly id: Schema.String;
7142
+ readonly service: Schema.String;
6296
7143
  readonly tenantId: Schema.Null;
6297
7144
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
6298
- readonly service: Schema.String;
6299
7145
  }>]>;
6300
7146
 
6301
7147
  export declare type SubjectIdentity = typeof SubjectIdentity.Type;
@@ -6334,6 +7180,7 @@ declare const SubjectService_base: Context.ServiceClass<SubjectService, "@voltro
6334
7180
  readonly type: "user";
6335
7181
  readonly id: string;
6336
7182
  readonly tenantId: string;
7183
+ readonly kind?: "guest" | undefined;
6337
7184
  readonly scopes?: readonly string[] | undefined;
6338
7185
  readonly metadata?: {
6339
7186
  readonly [x: string]: unknown;
@@ -6371,8 +7218,8 @@ declare const SubjectService_base: Context.ServiceClass<SubjectService, "@voltro
6371
7218
  } | {
6372
7219
  readonly type: "service";
6373
7220
  readonly id: string;
6374
- readonly tenantId: null;
6375
7221
  readonly service: string;
7222
+ readonly tenantId: null;
6376
7223
  readonly scopes?: readonly string[] | undefined;
6377
7224
  readonly metadata?: {
6378
7225
  readonly [x: string]: unknown;
@@ -6430,7 +7277,7 @@ export declare const subscriptionEvent: <D extends Schema.Top>(data: D) => Schem
6430
7277
  }>;
6431
7278
  }>, Schema.Struct<{
6432
7279
  readonly _tag: Schema.Literal<"error">;
6433
- readonly error: Schema.Unknown;
7280
+ readonly error: Schema.decodeTo<Schema.Unknown, Schema.Codec<Schema.Json, Schema.Json, never, never>, never, never>;
6434
7281
  readonly revision: Schema.optional<Schema.Number>;
6435
7282
  }>]>;
6436
7283
 
@@ -6710,7 +7557,7 @@ export declare const toRpc: <Name extends string, Input extends Schema.Top, Outp
6710
7557
  }>;
6711
7558
  }>, Schema.Struct<{
6712
7559
  readonly _tag: Schema.Literal<"error">;
6713
- readonly error: Schema.Unknown;
7560
+ readonly error: Schema.decodeTo<Schema.Unknown, Schema.Codec<Schema.Json, Schema.Json, never, never>, never, never>;
6714
7561
  readonly revision: Schema.optional<Schema.Number>;
6715
7562
  }>]>, Schema.Top>, Schema.Never, never, never> | Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Output, Schema.Top, never, never> | Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Output, Schema.Top>, Schema.Never, never, never>;
6716
7563
 
@@ -6865,6 +7712,29 @@ export declare interface UpdateTarget<Input = unknown, Row = unknown, Item = Rec
6865
7712
  readonly shapeItem?: ((input: Input, current: Item) => Item) | undefined;
6866
7713
  }
6867
7714
 
7715
+ /** Runtime form of the registered-human identity requirement. */
7716
+ export declare interface UpgradedCheckSpec {
7717
+ readonly upgraded: true;
7718
+ }
7719
+
7720
+ /** Reject an upgraded marker combined with another guard variant in one entry.
7721
+ * Structural guard unions allow such objects to compile. Keep independent
7722
+ * requirements in separate entries so dispatch and persistence retain both. */
7723
+ export declare const upgradedGuardShapeError: (guard: unknown) => ScopeError | null;
7724
+
7725
+ /** Require a registered human identity. Guest and machine credentials cannot
7726
+ * satisfy this requirement, including credentials holding admin scopes. */
7727
+ export declare interface UpgradedGuardSpec {
7728
+ readonly upgraded: true;
7729
+ }
7730
+
7731
+ /** Validate an optional per-connection SSE lifetime before acquiring resources.
7732
+ * The timer ceiling also prevents millisecond overflow becoming a 1ms timer. */
7733
+ export declare const validateHttpStreamMaxDuration: (value: unknown) => number | undefined;
7734
+
7735
+ /** Refuse malformed runtime contributions before starting a worker. */
7736
+ export declare function validateOutboxHandlerDefinition(value: unknown, owner: string): asserts value is OutboxHandlerDefinition<unknown>;
7737
+
6868
7738
  /**
6869
7739
  * A server-side validation failure pinned to ONE field.
6870
7740
  *
@@ -6908,6 +7778,26 @@ export declare const ValidationIssue: Schema.Struct<{
6908
7778
  export declare type ValidationIssueShape = Schema.Schema.Type<typeof ValidationIssue>;
6909
7779
 
6910
7780
  export declare interface VoltroPlugin {
7781
+ /** Server callbacks for explicitly policy-bound event client streams. */
7782
+ readonly eventClientPolicies?: ReadonlyArray<EventClientPolicy>;
7783
+ /** Declared background/listener work. Omitted means rpc-only for role admission.
7784
+ * Metadata alone does not suppress hooks; hosts must apply the role plan. */
7785
+ readonly workloads?: ReadonlyArray<ApiWorkload>;
7786
+ /** Separately owned API work, started after shared activation. A failed start
7787
+ * must release partial acquisitions before rejecting. Returned resources drain
7788
+ * before shared deactivation; stop failures retain them for retry. */
7789
+ readonly workloadHooks?: Readonly<Partial<Record<ApiWorkload, PluginWorkloadHook>>>;
7790
+ /** App-supplied connection references for this configured plugin instance.
7791
+ * Registry bindings override these selections. Library plugins must not set
7792
+ * topology defaults here; populate only from explicit app options. */
7793
+ readonly connectionBindings?: Readonly<Record<string, ConnectionRef>>;
7794
+ /** Backend-independent requirements. Cross-node HotStore requires Redis; broker roles require their native transport. */
7795
+ readonly needs?: {
7796
+ readonly hot?: 'local' | 'crossNode';
7797
+ readonly connections?: ReadonlyArray<PluginConnectionNeed>;
7798
+ readonly pubsub?: boolean;
7799
+ readonly atomicMulti?: boolean;
7800
+ };
6911
7801
  /**
6912
7802
  * Plugin identifier — printed in `voltro dev` boot logs + surfaced in
6913
7803
  * the inspect manifest. Use a stable string scoped to the package
@@ -7006,6 +7896,16 @@ export declare interface VoltroPlugin {
7006
7896
  readonly declaredEnv?: ReadonlyArray<PluginEnvVar>;
7007
7897
  /** What this plugin collects and uses — see `PluginPrivacyDeclaration`. */
7008
7898
  readonly privacy?: PluginPrivacyDeclaration;
7899
+ /** Subject-data ownership; distinct from the platform privacy disclosure above. */
7900
+ readonly subjectPrivacy?: PluginSubjectPrivacy;
7901
+ /** Idempotent server hook. Identity hooks run after all declared data hooks. */
7902
+ readonly onErase?: PluginEraseHook;
7903
+ /** Server-authorized JSON export. Omit credentials, hashes and session tokens. */
7904
+ readonly onExport?: PluginExportHook;
7905
+ /** Transactional account-data reconciliation; distinct from privacy erasure. */
7906
+ readonly subjectMerge?: PluginSubjectMerge;
7907
+ /** Trusted coordinator must authenticate both accounts and confirmation. */
7908
+ readonly onMergeAccounts?: PluginAccountMergeHook;
7009
7909
  /** Where this plugin sends data outside the installation — see `PluginEgressDeclaration`. Omit when it sends nothing. */
7010
7910
  readonly egress?: ReadonlyArray<PluginEgressDeclaration>;
7011
7911
  /**
@@ -7047,7 +7947,21 @@ export declare interface VoltroPlugin {
7047
7947
  * Absent → the plugin contributes nothing at the mutation boundary
7048
7948
  * (schema-only / lifecycle-only plugins are fine).
7049
7949
  */
7950
+ /** Register pure authority refreshers for native and REST admission,
7951
+ * including queries and raw REST routes, before/after receipt waits. Requires rpc:authority:replay; never run the executor
7952
+ * or normal interceptors to reconstruct an authorization decision. */
7953
+ readonly bindReplayAuthority?: RpcReplayAuthorityBinding;
7050
7954
  readonly interceptMutation?: RpcInterceptor;
7955
+ /** Runs after successful executor and business-rule evaluation, before the
7956
+ * transaction commits. Use only the supplied store for required records or
7957
+ * durable intents. External I/O is prohibited; transaction retries replay
7958
+ * this hook. Requires rpc:intercept:mutation and store:write permissions.
7959
+ * Ordinary RPC interceptors retain their existing outside-transaction scope. */
7960
+ readonly beforeMutationCommit?: MutationCommitHook;
7961
+ /** Post-commit delivery handlers, registered with the shared app worker in
7962
+ * Dev and serve. Requires outbox:handle; effects must not collide with app,
7963
+ * framework or other plugin contributions. Handlers must be idempotent. */
7964
+ readonly outboxHandlers?: ReadonlyArray<OutboxHandlerDefinition<unknown>>;
7051
7965
  /**
7052
7966
  * Wraps every QUERY (streaming subscription handler) call. Same
7053
7967
  * composition semantics as `interceptMutation`. Most observability
@@ -7078,6 +7992,11 @@ export declare interface VoltroPlugin {
7078
7992
  * plugins carry a parity test that fails when one does.
7079
7993
  */
7080
7994
  readonly interceptWorkflow?: RpcInterceptor;
7995
+ /** Wraps finite stream admission and construction, once per subscription,
7996
+ * before guards and executor side effects. The successful value is the
7997
+ * Stream itself; element delivery retains ordinary live guard rechecks.
7998
+ * Requires rpc:intercept:stream. */
7999
+ readonly interceptStream?: RpcInterceptor;
7081
8000
  /**
7082
8001
  * Effect Layer that contributes services into the per-request
7083
8002
  * context. Anything declared here is yieldable from any handler in
@@ -7258,7 +8177,7 @@ export declare interface VoltroPlugin {
7258
8177
  */
7259
8178
  readonly extendSchema?: PluginSchemaContribution;
7260
8179
  /**
7261
- * A directory of APP-SHAPED primitives this plugin brings with it — the same
8180
+ * An absolute directory or named directories of APP-SHAPED primitives this plugin brings with it — the same
7262
8181
  * file conventions an app uses (`*.query.ts` + `.server.ts`, `*.mutation.ts`,
7263
8182
  * `*.workflow.tsx`, `*.cron.tsx`, `*.subscribe.ts`, `*.reaction.ts`,
7264
8183
  * `schema.ts` / `*.entity.ts`, …).
@@ -7277,9 +8196,12 @@ export declare interface VoltroPlugin {
7277
8196
  *
7278
8197
  * The block's procedures keep the names their descriptors declare; a name
7279
8198
  * that collides with the app's own is refused at codegen, as any collision is.
7280
- * An absolute path to a directory; a missing one refuses the boot.
8199
+ * A directory or a plain named map of directories. Named roots use stable
8200
+ * per-plugin module identities independent of declaration order; choose names
8201
+ * explicitly when optional subsystems contribute separate folders. Each value
8202
+ * must be an absolute existing directory; missing/overlapping roots refuse boot.
7281
8203
  */
7282
- readonly sources?: string;
8204
+ readonly sources?: string | Readonly<Record<string, string>>;
7283
8205
  /**
7284
8206
  * Cross-cutting error schemas merged into every procedure's wire error
7285
8207
  * union. Lets a plugin interceptor fail with a typed error (e.g.
@@ -7332,6 +8254,8 @@ export declare interface VoltroPlugin {
7332
8254
  * SDK here). Returns of `undefined`/empty are no-ops.
7333
8255
  */
7334
8256
  readonly contributeObservability?: (ctx: PluginLifecycleContext) => Promise<ObservabilityContribution | undefined> | ObservabilityContribution | undefined;
8257
+ /** Enforce a hand-authored REST route's quota through this plugin. */
8258
+ readonly restRateLimit?: RestServeBindings['rateLimit'];
7335
8259
  /**
7336
8260
  * Told the app's mounted REST surface — hand-authored `restRoutes` AND the
7337
8261
  * routes projected from `publicApi` annotations — once the boot has built it,
@@ -7416,15 +8340,17 @@ export declare interface WebSocketGatewayConnection {
7416
8340
  readonly send: (data: string | Uint8Array) => void;
7417
8341
  /** Await the local transport write, NOT peer receipt. Rejects on close or
7418
8342
  * overflow. Await each send to propagate backpressure to the producer. */
7419
- readonly sendAsync: (data: string | Uint8Array) => Promise<void>;
8343
+ readonly sendAsync: (data: string | Uint8Array) => Promise<WebSocketGatewaySendResult>;
8344
+ /** Active plus queued frame bytes, excluding WebSocket framing overhead. */
8345
+ readonly queuedBytes: number;
7420
8346
  /** Aborted on close, expiry, shutdown or setup failure, including while
7421
8347
  * onConnection is still awaiting an upstream connection. */
7422
8348
  readonly signal: AbortSignal;
7423
8349
  /** Close the connection (application close codes 4000-4999 are yours). */
7424
8350
  readonly close: (code?: number, reason?: string) => void;
7425
8351
  /** Register a message listener (binary-safe; text arrives as bytes). */
7426
- readonly onMessage: (listener: (data: Uint8Array) => void) => void;
7427
- /** The authenticated subject — `null` only on an `auth: 'public'` route. */
8352
+ readonly onMessage: (listener: (data: Uint8Array, receivedAt: number) => void) => void;
8353
+ /** The verified subject — `null` only on an `auth: 'public'` route. */
7428
8354
  readonly subject: Subject | null;
7429
8355
  /** Lowercased request headers of the upgrade. */
7430
8356
  readonly headers: Readonly<Record<string, string>>;
@@ -7445,11 +8371,27 @@ export declare interface WebSocketGatewayRoute {
7445
8371
  * expires.
7446
8372
  * - `'public'` — deliberately unauthenticated (a device fleet with its own
7447
8373
  * protocol-level auth). A decision somebody wrote down, not a default.
7448
- */
7449
- readonly auth: 'subject' | 'public';
8374
+ * - `'token'` — upgrades after the origin check, then verifies the first
8375
+ * assembled frame before starting onConnection. The credential frame
8376
+ * never reaches message listeners. Clients wait for an application
8377
+ * acknowledgement before sending more frames; early frames close 1008.
8378
+ */
8379
+ readonly auth: 'subject' | 'public' | 'token';
8380
+ /** Required only for auth: 'token'. Return null to refuse (1008), or the
8381
+ * verified SubjectResolution (credentialExpiresAt is Unix SECONDS).
8382
+ * Throw/rejection closes 1011 with a generic reason. Respect context.signal:
8383
+ * timeout, disconnect and shutdown abort it, and late results are ignored. */
8384
+ readonly verify?: (firstFrame: Uint8Array, context: WebSocketGatewayVerificationContext) => SubjectResolution | null | Promise<SubjectResolution | null>;
8385
+ /** Only for auth: 'token'. Deadline from socket open, including waiting for
8386
+ * the first frame and verification; default 5000 ms, maximum 2147483647. */
8387
+ readonly verificationTimeoutMs?: number;
7450
8388
  /** Per-connection bounds. Inbound size is checked before app dispatch,
7451
8389
  * not before the platform assembles a WebSocket message. */
7452
8390
  readonly limits?: {
8391
+ /** Optional per-connection token refill rate. Omitted means no rate limit. */
8392
+ readonly maxInboundMessagesPerSecond?: number;
8393
+ /** Initial and maximum token capacity; defaults to the configured rate. */
8394
+ readonly burst?: number;
7453
8395
  /** Maximum inbound/outbound frame bytes; default 1 MiB. */
7454
8396
  readonly maxMessageBytes?: number;
7455
8397
  /** Active plus queued outbound bytes; default 4 MiB. */
@@ -7468,6 +8410,20 @@ export declare interface WebSocketGatewayRoute {
7468
8410
  readonly onConnection: (connection: WebSocketGatewayConnection) => void | (() => void) | Promise<void | (() => void)>;
7469
8411
  }
7470
8412
 
8413
+ /** Local queue pressure after the frame's transport write; not peer receipt. */
8414
+ export declare interface WebSocketGatewaySendResult {
8415
+ readonly queuedBytes: number;
8416
+ }
8417
+
8418
+ /** Cancellation and upgrade metadata for first-frame credential verification. */
8419
+ export declare interface WebSocketGatewayVerificationContext {
8420
+ readonly signal: AbortSignal;
8421
+ readonly headers: Readonly<Record<string, string>>;
8422
+ readonly path: string;
8423
+ /** Process-monotonic reception time in milliseconds, not a Unix timestamp. */
8424
+ readonly receivedAt: number;
8425
+ }
8426
+
7471
8427
  /**
7472
8428
  * Who may call a procedure over the wire: the app's own clients, or only other
7473
8429
  * apps of the project.