@voltro/protocol 0.86.0 → 0.88.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 (78) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/THIRD-PARTY-NOTICES.md +3 -227
  3. package/dist/apiRoleEndpoints.js +107 -0
  4. package/dist/apiRoleObservation.js +67 -0
  5. package/dist/apikey.d.ts +10 -0
  6. package/dist/apikey.js +1 -2
  7. package/dist/approval.js +1 -2
  8. package/dist/approvalDescriptors.js +1 -2
  9. package/dist/auth.js +40 -21
  10. package/dist/boundedRing.js +1 -2
  11. package/dist/businessRule.js +1 -2
  12. package/dist/canonicalJson.js +13 -0
  13. package/dist/connection.js +1 -2
  14. package/dist/devActivity.js +1 -2
  15. package/dist/errorSchema.js +23 -0
  16. package/dist/errorValue.js +1 -2
  17. package/dist/event.js +52 -47
  18. package/dist/formCapture.js +11 -0
  19. package/dist/httpStreamMaxDuration.js +8 -0
  20. package/dist/idempotency.js +89 -35
  21. package/dist/idempotencyInput.js +122 -0
  22. package/dist/identity.js +1 -2
  23. package/dist/index.d.ts +876 -124
  24. package/dist/index.js +51 -40
  25. package/dist/inspect.d.ts +1 -1
  26. package/dist/inspect.js +1 -2
  27. package/dist/inspectBackfill.js +1 -2
  28. package/dist/inspectCommandRejection.js +1 -2
  29. package/dist/inspectProjection.js +1 -2
  30. package/dist/inspectStorageProjection.js +1 -2
  31. package/dist/inspectValueIdentity.js +1 -2
  32. package/dist/inspectWorkflowExecution.js +1 -2
  33. package/dist/inspectWorkflowQueuedStart.js +1 -2
  34. package/dist/jsonSchema.js +1 -2
  35. package/dist/jwt.d.ts +10 -0
  36. package/dist/jwt.js +2 -3
  37. package/dist/outbox.js +1 -2
  38. package/dist/pairing.js +1 -2
  39. package/dist/plugin.js +44 -19
  40. package/dist/pluginAccountMerge.js +87 -0
  41. package/dist/pluginNaming.js +1 -2
  42. package/dist/pluginPrivacy.js +165 -0
  43. package/dist/procedure.js +115 -98
  44. package/dist/publicApi.js +191 -211
  45. package/dist/queryOptionalSources.js +13 -0
  46. package/dist/reactivityChannel.js +1 -2
  47. package/dist/rest.d.ts +227 -64
  48. package/dist/rest.js +296 -194
  49. package/dist/restFormat.js +1 -2
  50. package/dist/restReceipt.js +42 -0
  51. package/dist/revalidate.js +12 -0
  52. package/dist/rowEquality.js +1 -2
  53. package/dist/rowPatch.js +1 -2
  54. package/dist/schemaShape.js +1 -2
  55. package/dist/scopes.js +72 -40
  56. package/dist/secretWindows.js +70 -0
  57. package/dist/serverErrorBus.js +1 -2
  58. package/dist/serviceCall.js +1 -2
  59. package/dist/session.d.ts +89 -6
  60. package/dist/session.js +56 -56
  61. package/dist/sessionHeader.js +1 -2
  62. package/dist/ssrfPolicy.js +29 -0
  63. package/dist/storageAccess.js +1 -2
  64. package/dist/storeErrors.js +1 -2
  65. package/dist/storeScreenshots.js +1 -2
  66. package/dist/strictInput.js +1 -2
  67. package/dist/subscriptionEvent.js +1 -2
  68. package/dist/tenantSelection.js +1 -2
  69. package/dist/time.js +1 -2
  70. package/dist/undo.js +1 -2
  71. package/dist/validation.js +1 -2
  72. package/dist/webSocketGateway.js +8 -3
  73. package/dist/workflow.js +1 -2
  74. package/dist/workflowAccess.js +1 -2
  75. package/dist/workflowRestart.js +1 -2
  76. package/dist/workflowRetry.js +1 -2
  77. package/dist/workflowStartOutcome.js +1 -2
  78. package/package.json +7 -5
package/dist/rest.d.ts CHANGED
@@ -77,7 +77,12 @@ declare interface AnyApprovalPolicy {
77
77
  }
78
78
 
79
79
  /** A guard is a scope check, a relationship check, or an identity check. */
80
- declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input> | AuthenticatedGuardSpec;
80
+ declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input> | AuthenticatedGuardSpec | UpgradedGuardSpec;
81
+
82
+ /** Work performed by an API process; schema and descriptor loading is separate. */
83
+ declare const API_WORKLOADS: readonly ["rpc", "subscriptions", "events", "presence", "workflows", "schedules", "directors", "drainers", "reapers", "retention", "rest", "webhooks", "inspect", "cdc"];
84
+
85
+ declare type ApiWorkload = typeof API_WORKLOADS[number];
81
86
 
82
87
  /**
83
88
  * "The caller must have an identity" — and nothing narrower.
@@ -209,6 +214,13 @@ export declare const encodeRestError: (format: RestFormat, error: RestFormatErro
209
214
  */
210
215
  export declare const encodeRestSuccess: (format: RestFormat, value: unknown, ctx: RestFormatContext) => RestFormatResponse;
211
216
 
217
+ declare interface EnqueueOptions {
218
+ /** Deduplicate while an earlier intent is still pending, not permanently. */
219
+ readonly idempotencyKey?: string;
220
+ readonly maxAttempts?: number;
221
+ readonly delayMs?: number;
222
+ }
223
+
212
224
  /** The member of an error union that declares `tag`, as a schema of its own —
213
225
  * for the spec's per-status responses. */
214
226
  export declare const errorMemberSchema: (error: Schema.Top | undefined, tag: string) => Schema.Top | undefined;
@@ -239,40 +251,43 @@ declare interface GuardSpec<Input = unknown> {
239
251
  /** How an array of scopes combines. `'all'` (default) = AND (hold every
240
252
  * scope); `'any'` = OR (hold at least one). Ignored for a single scope. */
241
253
  readonly mode?: 'all' | 'any';
242
- /**
243
- * PURE `input → resource id` extractor. Browser-safe (no DB, no server
244
- * import) — exactly like `target.identify`. Omit for a plain subject-scope
245
- * guard.
246
- *
247
- * **On a `GuardSpec` this id is ADVISORY.** A scope guard answers "what may
248
- * this subject do at all", against the subject's global scope set; the id is
249
- * carried for logging and for a future subject-scope resolver that narrows by
250
- * resource. It does not, on its own, make the check per-resource.
251
- *
252
- * **If your authority is per-resource, you want {@link PolicyGuardSpec}, not
253
- * this field** — `guards: [{ action, resourceType, resource }]`, backed by
254
- * `defineResourcePolicy` + a tuple source you register. That is built, wired
255
- * on both boot paths, fail-closed without a resolver, and documented under
256
- * *Authentication → Authorization*. An app whose relationships already live
257
- * in its own tables (a `teamMembers` row, say) registers its own tuple source
258
- * rather than copying data across; see `policyGuardResolver.ts`.
259
- *
260
- * That paragraph is here because its absence cost a deployment their access
261
- * gate. This comment used to describe the resolver as "a future ReBAC /
262
- * `accessPolicy()` resolver" — written before the ReBAC path shipped and
263
- * never updated. They read the type, quoted the sentence, concluded there was
264
- * "nothing in between" declaring an untruth and turning the gate off, and set
265
- * `security: { defaultDeny: false }` on an app with 565 undecided procedures.
266
- * The capability they needed was two fields away. A doc comment that says
267
- * "future" about something shipped is not a small inaccuracy: it is the only
268
- * thing a careful reader has, and it argued them out of a feature.
269
- */
254
+ /** Pure browser-safe input → target resource id selector. With a registered
255
+ * setResourceScopeResolver, scopes can be checked on this resource. Without
256
+ * resourceOnly, an existing global scope or admin:full may satisfy the guard
257
+ * without consulting that resolver. For per-resource authority over relationships use
258
+ * PolicyGuardSpec: `defineResourcePolicy` plus a registered tuple source. That is built, wired
259
+ * on both boot paths and fail-closed without a resolver — an earlier comment called it "future"
260
+ * and argued a deployment out of its access gate. */
270
261
  readonly resource?: (input: Input) => string | undefined;
262
+ /** Require an explicit current resource verdict, including for globally held
263
+ * scopes or admin:full. Missing resolver/resource refuses access. */
264
+ readonly resourceOnly?: boolean;
265
+ }
266
+
267
+ /** Identity of one acquired execution, never reused when a key is reclaimed.
268
+ * This is receipt ownership, not authority to perform a business side effect. */
269
+ declare interface IdempotencyClaim {
270
+ readonly scope: string;
271
+ readonly key: string;
272
+ readonly id: string;
273
+ readonly inputHash: string;
271
274
  }
272
275
 
276
+ declare type IdempotencyClaimResult = {
277
+ readonly kind: 'claimed';
278
+ readonly claim: IdempotencyClaim;
279
+ } | {
280
+ readonly kind: 'existing';
281
+ readonly record: IdempotencyRecord;
282
+ } | {
283
+ readonly kind: 'conflict';
284
+ };
285
+
273
286
  declare interface IdempotencyRecord {
274
287
  readonly scope: string;
275
288
  readonly key: string;
289
+ /** Null means a retained receipt predates declared-input binding. */
290
+ readonly inputHash: string | null;
276
291
  readonly status: 'in_flight' | 'completed';
277
292
  readonly response: IdempotencyResponse | null;
278
293
  /** epoch ms */
@@ -282,34 +297,24 @@ declare interface IdempotencyRecord {
282
297
  declare interface IdempotencyResponse {
283
298
  readonly status: number;
284
299
  readonly body: unknown;
300
+ /** Framework-owned REST qualification metadata. Custom stores preserve it
301
+ * unchanged, separately from the arbitrary application output in body. */
302
+ readonly rest?: unknown;
285
303
  }
286
304
 
287
305
  declare interface IdempotencyStore {
288
- /** Current record for (scope, key), or null. Read-side (inspect/dashboard). */
289
306
  readonly get: (scope: string, key: string) => Promise<IdempotencyRecord | null>;
290
- /**
291
- * Atomically claim (scope, key) IF absent OR stale — a COMPLETED record older
292
- * than `ttlMs` (the replay window), or an IN-FLIGHT one whose lease
293
- * (`inFlightLeaseMs`, renewed by the live handler) lapsed:
294
- * write a fresh `in_flight` row and return `'claimed'`. Otherwise return the
295
- * EXISTING (still-fresh) record. The atomicity here is the whole game — two
296
- * concurrent same-key requests both hit this, exactly one gets `'claimed'`,
297
- * the loser reads the winner's record. In SQL this is one
298
- * `INSERT … ON CONFLICT DO UPDATE … WHERE existing.createdAt < now - ttl`.
299
- */
300
- readonly claim: (scope: string, key: string, now: number, ttlMs: number, inFlightLeaseMs: number) => Promise<'claimed' | IdempotencyRecord>;
301
- /**
302
- * Extend a RUNNING claim's lease: bump its timestamp while it is still
303
- * `in_flight` (a completed or released claim is left alone). The server calls
304
- * it while the handler runs; a process that dies stops calling it, and its
305
- * claim goes stale after `inFlightLeaseMs` instead of after the whole replay
306
- * window.
307
- */
308
- readonly renew: (scope: string, key: string, now: number) => Promise<void>;
309
- /** Flip the claim to `completed` and store the response. */
310
- readonly complete: (scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
311
- /** Drop the claim (handler errored → a retry may re-process). */
312
- readonly release: (scope: string, key: string) => Promise<void>;
307
+ /** Atomically acquire an absent/stale key with a new, never-reused identity.
308
+ * Otherwise return the existing receipt, or conflict if contention prevents
309
+ * observing a stable record. Expiry does not prove an old executor stopped. */
310
+ readonly claim: (scope: string, key: string, inputHash: string, now: number, ttlMs: number, inFlightLeaseMs: number) => Promise<IdempotencyClaimResult>;
311
+ /** Atomically match original scope/key/id AND in_flight status. False means
312
+ * this execution no longer owns a running receipt; never touch its successor. */
313
+ readonly renew: (claim: IdempotencyClaim, now: number) => Promise<boolean>;
314
+ /** Complete only the original running claim and start its replay window now. */
315
+ readonly complete: (claim: IdempotencyClaim, response: IdempotencyResponse, now: number) => Promise<boolean>;
316
+ /** Release only the original running claim. Completed receipts are retained. */
317
+ readonly release: (claim: IdempotencyClaim) => Promise<boolean>;
313
318
  }
314
319
 
315
320
  declare interface InsertTarget<Input = unknown, Row = unknown, Item = Record<string, unknown>> extends NestedTargetFields<Input> {
@@ -372,6 +377,12 @@ declare interface MutationProcedureDescriptor<Name extends string, Input extends
372
377
  readonly idempotencyScope?: MutationIdempotencyScope | undefined;
373
378
  /** Same-key calls run in invocation order on the client — see `defineMutation`. */
374
379
  readonly serialize?: MutationSerialization<Schema.Schema.Type<Input>> | undefined;
380
+ /** Exclude this invocation from automatic row-image undo capture, with an
381
+ * explicit domain reason. Does not disable transactions, guards or audit. */
382
+ readonly undo?: {
383
+ readonly capture: false;
384
+ readonly reason: string;
385
+ } | undefined;
375
386
  /** Declarative target(s) — drives auto-optimistic on the client AND
376
387
  * surfaces which tables this mutation touches (debug, future
377
388
  * query-invalidation analytics). Mutations without a target run
@@ -479,6 +490,12 @@ declare interface OpenAccessSpec {
479
490
  /** The rpc tag as an `operationId`: `teams.list` → `teamsList`. */
480
491
  export declare const operationIdOf: (tag: string) => string;
481
492
 
493
+ /** Enqueue-only capability bound to the caller's actual transaction store. */
494
+ declare interface OutboxEnqueueFacade {
495
+ /** Returns the stable delivery identity. Delivery happens after commit. */
496
+ enqueue(effect: string, payload: Record<string, unknown>, options?: EnqueueOptions): Promise<string>;
497
+ }
498
+
482
499
  /** The parameter names a route pattern binds, in order. */
483
500
  export declare const pathParamNames: (path: string) => ReadonlyArray<string>;
484
501
 
@@ -522,6 +539,12 @@ declare interface PluginErrorSchema {
522
539
 
523
540
  /** A public raw-HTTP route a plugin serves on the framework listener. */
524
541
  declare interface PluginHttpRoute {
542
+ /** Executable API workload owning this route. Omitted means `rpc`.
543
+ * Discovery and descriptor generation remain independent of role selection. */
544
+ readonly workload?: ApiWorkload;
545
+ /** The already-mounted REST projection, retained for SDK/schema inspection.
546
+ * Do not remount it through generic REST auth or idempotency bindings. */
547
+ readonly restContract?: RestRouteDescriptor<any, any>;
525
548
  /** HTTP method, or `'*'` for any (the handler decides). The server mounts the
526
549
  * path for exactly the methods its routes declare; `'*'` mounts it for every
527
550
  * method the router knows (34), so keep that for a route that really serves
@@ -550,6 +573,13 @@ declare interface PluginHttpRoute {
550
573
  */
551
574
  readonly match?: 'prefix' | 'exact';
552
575
  readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
576
+ /** Lazy, backpressured input instead of a buffered rawBody on real HTTP.
577
+ * Requires match: 'exact' and exclusive ownership of the router-equivalent
578
+ * path (including other methods). The required handle remains the buffered
579
+ * in-process entry. Both entries must apply the same authorization/codec.
580
+ * maxBodyBytes counts consumed bytes; overflow overrides the handler's
581
+ * response with 413 even when the handler catches the read error. */
582
+ readonly handleStream?: (req: PluginHttpRouteStreamRequest) => Promise<PluginHttpRouteResult>;
553
583
  /** Per-route body cap override (bytes) — wins over the listener's shared
554
584
  * `maxBodyBytes`. NOTE: routes SHARING a path share one body read, so the
555
585
  * widest override on the path's group applies to the whole group. */
@@ -752,11 +782,23 @@ declare interface PluginHttpRouteResult {
752
782
  */
753
783
  declare interface PluginHttpRouteStream {
754
784
  readonly subscribe: (emit: (chunk: string) => void) => () => void;
785
+ /** Per-connection lifetime in milliseconds (1..2147483647). Ending the
786
+ * response releases the subscription and keep-alive timer. Absent: no cap. */
787
+ readonly maxDurationMs?: number;
755
788
  /** Keep-alive comment interval in ms (default 15000; `0` disables). Without
756
789
  * it an idle SSE connection is dropped by proxies after ~30–60s. */
757
790
  readonly keepAliveMs?: number;
758
791
  }
759
792
 
793
+ /** A lazy binary request on the framework listener. No body bytes are read
794
+ * before the handler runs. Consume once, through EOF, before accepting an
795
+ * upload; returning early drains the unread body without validating it.
796
+ * The signal ends on disconnect, interruption or handler completion. */
797
+ declare interface PluginHttpRouteStreamRequest extends Omit<PluginHttpRouteRequest, 'rawBody'> {
798
+ readonly body: AsyncIterable<Uint8Array>;
799
+ readonly signal: AbortSignal;
800
+ }
801
+
760
802
  /**
761
803
  * A relationship (ReBAC) guard — "may this subject perform ACTION on THIS row?"
762
804
  *
@@ -949,6 +991,10 @@ declare interface PublicApiSpec {
949
991
  /** Streaming queries over request/response: 'snapshot' (default — first
950
992
  * snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
951
993
  readonly stream?: 'snapshot' | 'sse';
994
+ /** Per-connection SSE lifetime in milliseconds (1..2147483647). Requires
995
+ * an SSE query projection. Clients may reconnect for a freshly authorized
996
+ * snapshot; expiry does not cancel a domain operation. */
997
+ readonly streamMaxDurationMs?: number;
952
998
  /**
953
999
  * HTTP status per DECLARED typed error tag, for the projection. A typed
954
1000
  * error the handler fails with — one of the descriptor's `error:` union —
@@ -960,6 +1006,11 @@ declare interface PublicApiSpec {
960
1006
  * publicApi: { errorStatus: { AccessDeniedError: 403, NotFoundError: 404 } }
961
1007
  */
962
1008
  readonly errorStatus?: Readonly<Record<string, number>>;
1009
+ /** Additional HTTP failure statuses for the same declared error schema.
1010
+ * Documents dedicated handlers that choose a status from the error's fields;
1011
+ * does not change `errorStatus` or the generic projection's runtime mapping.
1012
+ * Values must be integer HTTP error statuses (400–599); duplicates are omitted. */
1013
+ readonly additionalErrorStatuses?: Readonly<Record<string, ReadonlyArray<number>>>;
963
1014
  /**
964
1015
  * The spec's tag group(s) for this operation. Default: the rpc tag's prefix
965
1016
  * (`teams.list` → `teams`) — the first path segment, what a consumer looks
@@ -1063,6 +1114,16 @@ declare interface QueryCacheConfig {
1063
1114
  * cardinality of a cache the author sized per org.
1064
1115
  */
1065
1116
  readonly scope: 'subject' | 'tenant' | 'global';
1117
+ /**
1118
+ * The handler's result also depends on something no table write
1119
+ * invalidates — the clock, randomness, `ctx.connections`, an outgoing HTTP
1120
+ * call, `ctx.kv`. Applies to a COMPUTED query (a handler that returns a
1121
+ * value): without it such a result is computed but NOT kept, and the dev log
1122
+ * names what was used. With it, `ttl` is the bound on how stale that part may
1123
+ * be. `Date.now()`, `Math.random()` and a `fetch` outside the framework's
1124
+ * HTTP client are not observable — a handler that uses them must say so here.
1125
+ */
1126
+ readonly volatile?: boolean | undefined;
1066
1127
  }
1067
1128
 
1068
1129
  declare interface QueryProcedureDescriptor<Name extends string, Input extends Schema.Top, Output extends Schema.Top, Error extends Schema.Top> {
@@ -1077,6 +1138,9 @@ declare interface QueryProcedureDescriptor<Name extends string, Input extends Sc
1077
1138
  * listed table changes (pass an array to depend on several). Optional:
1078
1139
  * queries without a source never receive auto-patches (joins, aggregates). */
1079
1140
  readonly source: string | ReadonlyArray<string> | undefined;
1141
+ /** Optional integration observers, all also present in source. They keep
1142
+ * invalidation active when installed; absence is a visible graph warning. */
1143
+ readonly optionalSource?: ReadonlyArray<string>;
1080
1144
  /** Server-side snapshot cache config. Absent → never cached (the
1081
1145
  * default; the dispatcher already keeps live subscriptions fresh). */
1082
1146
  readonly cache: QueryCacheConfig | undefined;
@@ -1138,13 +1202,9 @@ export declare const requireAnyScope: (scopes: ReadonlyArray<string>) => RestGua
1138
1202
  * guards remain authorization failures (403). */
1139
1203
  export declare const requireAuthenticated: () => RestGuard;
1140
1204
 
1141
- /**
1142
- * Guard that requires the resolved subject to carry `scope`. Anonymous /
1143
- * scope-less subjects are rejected with `403`. The blanket admin scope
1144
- * (`ADMIN_SCOPE` = `'admin:full'`) satisfies any required scope — the SAME
1145
- * bypass the rpc-handler `requireScope`/`hasScope` (`./scopes`) honours, so
1146
- * a subject granted admin is admin on both surfaces.
1147
- */
1205
+ /** Require an effective scope, including derived roles. Callers without it
1206
+ * receive 403; add requireAuthenticated when authenticated identity is required.
1207
+ * The admin:full bypass is the same one used by native scope guards. */
1148
1208
  export declare const requireScope: (scope: string) => RestGuard;
1149
1209
 
1150
1210
  /**
@@ -1324,7 +1384,7 @@ export declare interface RestFormatResponse {
1324
1384
  readonly contentType?: string;
1325
1385
  }
1326
1386
 
1327
- export declare type RestGuard = (ctx: RestRouteContext) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
1387
+ export declare type RestGuard = (ctx: RestRouteContext, request?: RestGuardRequest) => RestGuardRejection | undefined | Promise<RestGuardRejection | undefined>;
1328
1388
 
1329
1389
  /**
1330
1390
  * A guard runs after input decode + ctx resolution, before the handler.
@@ -1336,6 +1396,13 @@ export declare interface RestGuardRejection {
1336
1396
  readonly message: string;
1337
1397
  }
1338
1398
 
1399
+ /** Guard input is the decoded execution snapshot. Guards are read-only
1400
+ * admission checks and may run again after awaited admission/storage work. */
1401
+ export declare interface RestGuardRequest {
1402
+ readonly input: unknown;
1403
+ readonly defaultDeny: boolean;
1404
+ }
1405
+
1339
1406
  /** What a REST handler may return: the decoded output, a `response(...)` with
1340
1407
  * its own status and headers, an `sse(...)` stream, or `bytes(...)` for a
1341
1408
  * non-JSON body (a file, an export, an XML document). */
@@ -1375,6 +1442,8 @@ export declare interface RestResponse<O> {
1375
1442
  * stays free of the database dep).
1376
1443
  */
1377
1444
  export declare interface RestRouteContext {
1445
+ /** Server-owned invocation trace, stable through readmission and projection. */
1446
+ readonly traceId: string;
1378
1447
  readonly subject: Subject;
1379
1448
  readonly headers: Readonly<Record<string, string>>;
1380
1449
  /** Request metadata and the byte-exact body as received by the HTTP
@@ -1399,6 +1468,9 @@ export declare interface RestRouteContext {
1399
1468
  }
1400
1469
 
1401
1470
  export declare interface RestRouteDescriptor<I, O> {
1471
+ /** Original schemas for client generation. Metadata only: never mounts a
1472
+ * second handler or selects authentication/idempotency bindings. */
1473
+ readonly publicApiDescriptor?: PublicApiDescriptor;
1402
1474
  readonly method: RestMethod;
1403
1475
  /** Absolute path, e.g. `/v1/customers`. Matches that path exactly. */
1404
1476
  readonly path: string;
@@ -1412,6 +1484,12 @@ export declare interface RestRouteDescriptor<I, O> {
1412
1484
  * encodes it to JSON for the `200` response. */
1413
1485
  readonly output: Schema.Codec<O, any, never, never>;
1414
1486
  readonly handler: (input: I, ctx: RestRouteContext) => Promise<RestHandlerResult<O>> | RestHandlerResult<O>;
1487
+ /** Pure synchronous normalization of thrown admission/handler failures.
1488
+ * Return a mapped thrown value (for example RestRouteError), not an HTTP
1489
+ * response; undefined keeps the original failure. Available before authentication,
1490
+ * so only request metadata is given.
1491
+ * Public projections supply their existing typed/profile error mapper here. */
1492
+ readonly mapFailure?: (error: unknown, request: Pick<PluginHttpRouteRequest, 'method' | 'path' | 'query'>) => unknown;
1415
1493
  readonly summary?: string;
1416
1494
  readonly description?: string;
1417
1495
  readonly example?: RestRouteExample;
@@ -1541,14 +1619,23 @@ export declare const restRoutesToHttpRoutes: (routes: ReadonlyArray<RestRouteDes
1541
1619
  * auth subject + the bound DataStore.
1542
1620
  */
1543
1621
  export declare interface RestServeBindings {
1622
+ /** Pure scope-refresher registration, shared with native RPC admission. */
1623
+ readonly bindReplayAuthority?: RpcReplayAuthorityBinding;
1624
+ /** Actual host policy for projected procedure guards; raw routes own guards. */
1625
+ readonly defaultDeny?: boolean;
1626
+ /** Full runtime hosts supply their existing trace generator. */
1627
+ readonly traceId?: () => string;
1544
1628
  /** Bound once from the app's mounted rate-limit plugin. */
1545
1629
  readonly rateLimit?: (policy: RestRateLimit, ctx: RestRouteContext, route: RestRouteDescriptor<any, any>) => Effect.Effect<void, unknown>;
1546
- /** Resolve the calling subject from the request headers. */
1547
- readonly resolveSubject?: (headers: Readonly<Record<string, string>>) => Subject | Promise<Subject>;
1630
+ /** Resolve the calling subject and request-local selection before guards. */
1631
+ readonly resolveSubject?: (headers: Readonly<Record<string, string>>, request: {
1632
+ readonly path: string;
1633
+ readonly query: string;
1634
+ }) => Subject | Promise<Subject>;
1548
1635
  /** The bound framework DataStore. */
1549
1636
  readonly store?: unknown;
1550
1637
  /** Build the full per-request app context after authentication. */
1551
- readonly buildContext?: (subject: Subject, headers: Readonly<Record<string, string>>) => unknown | Promise<unknown>;
1638
+ readonly buildContext?: (subject: Subject, headers: Readonly<Record<string, string>>, request: Pick<RestRouteContext, 'traceId' | 'remoteAddr'>) => unknown | Promise<unknown>;
1552
1639
  /**
1553
1640
  * HTTP idempotency — when set, a mutating request (POST/PUT/PATCH/DELETE)
1554
1641
  * carrying the `header` is deduplicated: the first call runs + caches its
@@ -1603,6 +1690,72 @@ export declare const routeAcceptsPath: (route: {
1603
1690
  readonly match?: "prefix" | "exact" | undefined;
1604
1691
  }, path: string) => boolean;
1605
1692
 
1693
+ /**
1694
+ * Per-call context passed to every rpc interceptor. The fields are
1695
+ * deliberately shallow — plugins shouldn't need the full AppContext +
1696
+ * DataStore to do their job. For runtime services (DataStore, custom
1697
+ * Tags, etc.) declare them via `definePlugin({ services })` and read
1698
+ * via `yield* MyTag` in the interceptor body; the framework provides
1699
+ * them automatically.
1700
+ */
1701
+ declare interface RpcInterceptorContext {
1702
+ /** Enqueue a settled invocation outcome on the host's root store, outside
1703
+ * the business transaction. Required transactional success capture must use
1704
+ * MutationCommitContext.outbox instead. Older/custom hosts may omit this. */
1705
+ readonly enqueueOutcome?: OutboxEnqueueFacade['enqueue'];
1706
+ /** Procedure tag, e.g. `todos.create`. */
1707
+ readonly tag: string;
1708
+ /** Procedure kind, including finite stream admission. */
1709
+ readonly kind: RpcKind;
1710
+ /** The validated input the executor is about to (or already did) receive. */
1711
+ readonly input: unknown;
1712
+ /** The resolved subject (user / apiKey / serviceAccount / anonymous). */
1713
+ readonly subject: Subject;
1714
+ /** Per-call trace id (matches OTel spans + inspect events). */
1715
+ readonly traceId: string;
1716
+ /** Active OTel span id (16-hex), when tracing is on. Lets observability
1717
+ * plugins (Sentry/Datadog) attach an error to the exact span, not just
1718
+ * the trace. Absent when no tracer is installed. */
1719
+ readonly spanId?: string;
1720
+ /**
1721
+ * `'rest'` when the call came through the `publicApi` projection (`/v1`);
1722
+ * absent on the rpc wire. A per-endpoint policy on the projection — the
1723
+ * descriptor's `publicApi.rateLimit` — applies to these calls only.
1724
+ */
1725
+ readonly surface?: 'rest';
1726
+ /** The descriptor's `publicApi` spec, on a projected call. */
1727
+ readonly publicApi?: PublicApiSpec;
1728
+ /**
1729
+ * The caller's address — the same value the pre-auth IP shield counts, resolved
1730
+ * by the listener through `security.trustedProxies` (and
1731
+ * `security.clientAddressHeader`). A client cannot set it: the listener
1732
+ * writes it, and any copy the client sends is discarded.
1733
+ *
1734
+ * Absent when the call did not arrive over the network (a workflow step, a
1735
+ * schedule, an in-process invocation) or the socket address is unknown.
1736
+ */
1737
+ readonly remoteAddr?: string;
1738
+ }
1739
+
1740
+ /**
1741
+ * Which kind of rpc the interceptor is wrapping. The runtime passes
1742
+ * `kind: 'mutation'` to interceptors registered as `interceptMutation`,
1743
+ * `kind: 'query'` to `interceptQuery`, `kind: 'action'` to
1744
+ * `interceptAction`, `kind: 'workflow'` to `interceptWorkflow`, and
1745
+ * `kind: 'stream'` to `interceptStream` — one
1746
+ * plugin can implement any combination.
1747
+ */
1748
+ declare type RpcKind = 'mutation' | 'query' | 'action' | 'workflow' | 'stream';
1749
+
1750
+ declare type RpcReplayAuthorityBinding = (context: RpcReplayAuthorityContext) => undefined;
1751
+
1752
+ /** Pure synchronous authority-refresher binding for RPC and REST admission.
1753
+ * No business work, I/O, grants or side effects belong in this hook. The host
1754
+ * calls the existing guard rechecker after every binding. */
1755
+ declare interface RpcReplayAuthorityContext extends Pick<RpcInterceptorContext, 'tag' | 'input' | 'subject' | 'traceId' | 'surface' | 'publicApi' | 'remoteAddr'> {
1756
+ readonly kind: RpcKind | 'rest';
1757
+ }
1758
+
1606
1759
  /**
1607
1760
  * The runtime-erased form of a procedure's `service: { callers }` — "only these
1608
1761
  * apps of the project may call this, and nobody else".
@@ -1646,6 +1799,7 @@ declare interface ServiceSpec {
1646
1799
  */
1647
1800
  export declare const sse: (subscribe: (emit: (chunk: string) => void) => () => void, options?: {
1648
1801
  readonly keepAliveMs?: number;
1802
+ readonly maxDurationMs?: number;
1649
1803
  }) => RestStreamResponse;
1650
1804
 
1651
1805
  /** Frame ONE SSE event. Newlines in `data` are split across `data:` lines, as
@@ -1654,6 +1808,9 @@ export declare const sseFrame: (event: string, data: unknown) => string;
1654
1808
 
1655
1809
  declare const Subject: Schema.Union<readonly [Schema.Struct<{
1656
1810
  readonly type: Schema.Literal<"user">;
1811
+ /** A guest has a stable identity but has not linked a registered account.
1812
+ * This identity mark survives session signing; it never grants authority. */
1813
+ readonly kind: Schema.optional<Schema.Literal<"guest">>;
1657
1814
  readonly id: Schema.String;
1658
1815
  readonly tenantId: Schema.String;
1659
1816
  readonly scopes: Schema.optional<Schema.$Array<Schema.String>>;
@@ -1782,6 +1939,12 @@ declare interface UpdateTarget<Input = unknown, Row = unknown, Item = Record<str
1782
1939
  readonly shapeItem?: ((input: Input, current: Item) => Item) | undefined;
1783
1940
  }
1784
1941
 
1942
+ /** Require a registered human identity. Guest and machine credentials cannot
1943
+ * satisfy this requirement, including credentials holding admin scopes. */
1944
+ declare interface UpgradedGuardSpec {
1945
+ readonly upgraded: true;
1946
+ }
1947
+
1785
1948
  export declare const wireExample: (example: {
1786
1949
  readonly request?: unknown;
1787
1950
  readonly response?: unknown;