@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/rest.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as AST from 'effect/SchemaAST';
2
2
  import { DataStore } from '@voltro/database';
3
+ import { Effect } from 'effect';
3
4
  import { Schema } from 'effect';
4
5
 
5
6
  declare interface ActionProcedureDescriptor<Name extends string, Input extends Schema.Top, Output extends Schema.Top, Error extends Schema.Top> {
@@ -76,7 +77,12 @@ declare interface AnyApprovalPolicy {
76
77
  }
77
78
 
78
79
  /** A guard is a scope check, a relationship check, or an identity check. */
79
- 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];
80
86
 
81
87
  /**
82
88
  * "The caller must have an identity" — and nothing narrower.
@@ -208,6 +214,13 @@ export declare const encodeRestError: (format: RestFormat, error: RestFormatErro
208
214
  */
209
215
  export declare const encodeRestSuccess: (format: RestFormat, value: unknown, ctx: RestFormatContext) => RestFormatResponse;
210
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
+
211
224
  /** The member of an error union that declares `tag`, as a schema of its own —
212
225
  * for the spec's per-status responses. */
213
226
  export declare const errorMemberSchema: (error: Schema.Top | undefined, tag: string) => Schema.Top | undefined;
@@ -238,40 +251,43 @@ declare interface GuardSpec<Input = unknown> {
238
251
  /** How an array of scopes combines. `'all'` (default) = AND (hold every
239
252
  * scope); `'any'` = OR (hold at least one). Ignored for a single scope. */
240
253
  readonly mode?: 'all' | 'any';
241
- /**
242
- * PURE `input → resource id` extractor. Browser-safe (no DB, no server
243
- * import) — exactly like `target.identify`. Omit for a plain subject-scope
244
- * guard.
245
- *
246
- * **On a `GuardSpec` this id is ADVISORY.** A scope guard answers "what may
247
- * this subject do at all", against the subject's global scope set; the id is
248
- * carried for logging and for a future subject-scope resolver that narrows by
249
- * resource. It does not, on its own, make the check per-resource.
250
- *
251
- * **If your authority is per-resource, you want {@link PolicyGuardSpec}, not
252
- * this field** — `guards: [{ action, resourceType, resource }]`, backed by
253
- * `defineResourcePolicy` + a tuple source you register. That is built, wired
254
- * on both boot paths, fail-closed without a resolver, and documented under
255
- * *Authentication → Authorization*. An app whose relationships already live
256
- * in its own tables (a `teamMembers` row, say) registers its own tuple source
257
- * rather than copying data across; see `policyGuardResolver.ts`.
258
- *
259
- * That paragraph is here because its absence cost a deployment their access
260
- * gate. This comment used to describe the resolver as "a future ReBAC /
261
- * `accessPolicy()` resolver" — written before the ReBAC path shipped and
262
- * never updated. They read the type, quoted the sentence, concluded there was
263
- * "nothing in between" declaring an untruth and turning the gate off, and set
264
- * `security: { defaultDeny: false }` on an app with 565 undecided procedures.
265
- * The capability they needed was two fields away. A doc comment that says
266
- * "future" about something shipped is not a small inaccuracy: it is the only
267
- * thing a careful reader has, and it argued them out of a feature.
268
- */
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. */
269
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;
270
265
  }
271
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;
274
+ }
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
+
272
286
  declare interface IdempotencyRecord {
273
287
  readonly scope: string;
274
288
  readonly key: string;
289
+ /** Null means a retained receipt predates declared-input binding. */
290
+ readonly inputHash: string | null;
275
291
  readonly status: 'in_flight' | 'completed';
276
292
  readonly response: IdempotencyResponse | null;
277
293
  /** epoch ms */
@@ -281,34 +297,24 @@ declare interface IdempotencyRecord {
281
297
  declare interface IdempotencyResponse {
282
298
  readonly status: number;
283
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;
284
303
  }
285
304
 
286
305
  declare interface IdempotencyStore {
287
- /** Current record for (scope, key), or null. Read-side (inspect/dashboard). */
288
306
  readonly get: (scope: string, key: string) => Promise<IdempotencyRecord | null>;
289
- /**
290
- * Atomically claim (scope, key) IF absent OR stale — a COMPLETED record older
291
- * than `ttlMs` (the replay window), or an IN-FLIGHT one whose lease
292
- * (`inFlightLeaseMs`, renewed by the live handler) lapsed:
293
- * write a fresh `in_flight` row and return `'claimed'`. Otherwise return the
294
- * EXISTING (still-fresh) record. The atomicity here is the whole game — two
295
- * concurrent same-key requests both hit this, exactly one gets `'claimed'`,
296
- * the loser reads the winner's record. In SQL this is one
297
- * `INSERT … ON CONFLICT DO UPDATE … WHERE existing.createdAt < now - ttl`.
298
- */
299
- readonly claim: (scope: string, key: string, now: number, ttlMs: number, inFlightLeaseMs: number) => Promise<'claimed' | IdempotencyRecord>;
300
- /**
301
- * Extend a RUNNING claim's lease: bump its timestamp while it is still
302
- * `in_flight` (a completed or released claim is left alone). The server calls
303
- * it while the handler runs; a process that dies stops calling it, and its
304
- * claim goes stale after `inFlightLeaseMs` instead of after the whole replay
305
- * window.
306
- */
307
- readonly renew: (scope: string, key: string, now: number) => Promise<void>;
308
- /** Flip the claim to `completed` and store the response. */
309
- readonly complete: (scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
310
- /** Drop the claim (handler errored → a retry may re-process). */
311
- 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>;
312
318
  }
313
319
 
314
320
  declare interface InsertTarget<Input = unknown, Row = unknown, Item = Record<string, unknown>> extends NestedTargetFields<Input> {
@@ -371,6 +377,12 @@ declare interface MutationProcedureDescriptor<Name extends string, Input extends
371
377
  readonly idempotencyScope?: MutationIdempotencyScope | undefined;
372
378
  /** Same-key calls run in invocation order on the client — see `defineMutation`. */
373
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;
374
386
  /** Declarative target(s) — drives auto-optimistic on the client AND
375
387
  * surfaces which tables this mutation touches (debug, future
376
388
  * query-invalidation analytics). Mutations without a target run
@@ -478,6 +490,12 @@ declare interface OpenAccessSpec {
478
490
  /** The rpc tag as an `operationId`: `teams.list` → `teamsList`. */
479
491
  export declare const operationIdOf: (tag: string) => string;
480
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
+
481
499
  /** The parameter names a route pattern binds, in order. */
482
500
  export declare const pathParamNames: (path: string) => ReadonlyArray<string>;
483
501
 
@@ -521,6 +539,12 @@ declare interface PluginErrorSchema {
521
539
 
522
540
  /** A public raw-HTTP route a plugin serves on the framework listener. */
523
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>;
524
548
  /** HTTP method, or `'*'` for any (the handler decides). The server mounts the
525
549
  * path for exactly the methods its routes declare; `'*'` mounts it for every
526
550
  * method the router knows (34), so keep that for a route that really serves
@@ -549,6 +573,13 @@ declare interface PluginHttpRoute {
549
573
  */
550
574
  readonly match?: 'prefix' | 'exact';
551
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>;
552
583
  /** Per-route body cap override (bytes) — wins over the listener's shared
553
584
  * `maxBodyBytes`. NOTE: routes SHARING a path share one body read, so the
554
585
  * widest override on the path's group applies to the whole group. */
@@ -589,6 +620,9 @@ declare interface PluginHttpRoute {
589
620
  * handler branches on: exempting `/saml` exempts `POST /saml/anything`.
590
621
  */
591
622
  readonly originGuard?: 'exempt';
623
+ readonly cors?: RestCors;
624
+ /** Bearer-only routes must discard cookie authority before authentication. */
625
+ readonly credentials?: 'bearer';
592
626
  }
593
627
 
594
628
  /**
@@ -748,11 +782,23 @@ declare interface PluginHttpRouteResult {
748
782
  */
749
783
  declare interface PluginHttpRouteStream {
750
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;
751
788
  /** Keep-alive comment interval in ms (default 15000; `0` disables). Without
752
789
  * it an idle SSE connection is dropped by proxies after ~30–60s. */
753
790
  readonly keepAliveMs?: number;
754
791
  }
755
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
+
756
802
  /**
757
803
  * A relationship (ReBAC) guard — "may this subject perform ACTION on THIS row?"
758
804
  *
@@ -945,6 +991,10 @@ declare interface PublicApiSpec {
945
991
  /** Streaming queries over request/response: 'snapshot' (default — first
946
992
  * snapshot, like POST /rpc) | 'sse' (Server-Sent Events of snapshot+deltas). */
947
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;
948
998
  /**
949
999
  * HTTP status per DECLARED typed error tag, for the projection. A typed
950
1000
  * error the handler fails with — one of the descriptor's `error:` union —
@@ -956,6 +1006,11 @@ declare interface PublicApiSpec {
956
1006
  * publicApi: { errorStatus: { AccessDeniedError: 403, NotFoundError: 404 } }
957
1007
  */
958
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>>>;
959
1014
  /**
960
1015
  * The spec's tag group(s) for this operation. Default: the rpc tag's prefix
961
1016
  * (`teams.list` → `teams`) — the first path segment, what a consumer looks
@@ -1059,6 +1114,16 @@ declare interface QueryCacheConfig {
1059
1114
  * cardinality of a cache the author sized per org.
1060
1115
  */
1061
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;
1062
1127
  }
1063
1128
 
1064
1129
  declare interface QueryProcedureDescriptor<Name extends string, Input extends Schema.Top, Output extends Schema.Top, Error extends Schema.Top> {
@@ -1073,6 +1138,9 @@ declare interface QueryProcedureDescriptor<Name extends string, Input extends Sc
1073
1138
  * listed table changes (pass an array to depend on several). Optional:
1074
1139
  * queries without a source never receive auto-patches (joins, aggregates). */
1075
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>;
1076
1144
  /** Server-side snapshot cache config. Absent → never cached (the
1077
1145
  * default; the dispatcher already keeps live subscriptions fresh). */
1078
1146
  readonly cache: QueryCacheConfig | undefined;
@@ -1134,13 +1202,9 @@ export declare const requireAnyScope: (scopes: ReadonlyArray<string>) => RestGua
1134
1202
  * guards remain authorization failures (403). */
1135
1203
  export declare const requireAuthenticated: () => RestGuard;
1136
1204
 
1137
- /**
1138
- * Guard that requires the resolved subject to carry `scope`. Anonymous /
1139
- * scope-less subjects are rejected with `403`. The blanket admin scope
1140
- * (`ADMIN_SCOPE` = `'admin:full'`) satisfies any required scope — the SAME
1141
- * bypass the rpc-handler `requireScope`/`hasScope` (`./scopes`) honours, so
1142
- * a subject granted admin is admin on both surfaces.
1143
- */
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. */
1144
1208
  export declare const requireScope: (scope: string) => RestGuard;
1145
1209
 
1146
1210
  /**
@@ -1225,6 +1289,22 @@ export declare interface RestCache {
1225
1289
  readonly audience?: (output: unknown, input: unknown, subject: Subject) => 'public' | 'private';
1226
1290
  }
1227
1291
 
1292
+ /** Cross-origin browser access for one route. Origins are exact, including opaque `null`. */
1293
+ export declare interface RestCors {
1294
+ readonly origins: ReadonlyArray<string>;
1295
+ readonly headers?: ReadonlyArray<string>;
1296
+ readonly methods?: ReadonlyArray<RestMethod>;
1297
+ /** Preflight cache lifetime in seconds. */
1298
+ readonly maxAge?: number;
1299
+ readonly exposeHeaders?: ReadonlyArray<string>;
1300
+ }
1301
+
1302
+ /** Shared by the descriptor adapter and listener, including early transport refusals. */
1303
+ export declare const restCorsHeaders: (cors: RestCors | undefined, origin: string | undefined) => Record<string, string>;
1304
+
1305
+ /** Preflight never authenticates, consumes quota, reads a body or executes a handler. */
1306
+ export declare const restCorsPreflight: (cors: RestCors, method: string, headers: Readonly<Record<string, string>>) => PluginHttpRouteResult;
1307
+
1228
1308
  export declare type RestDates = 'iso' | 'epoch-ms';
1229
1309
 
1230
1310
  export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
@@ -1304,7 +1384,7 @@ export declare interface RestFormatResponse {
1304
1384
  readonly contentType?: string;
1305
1385
  }
1306
1386
 
1307
- 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>;
1308
1388
 
1309
1389
  /**
1310
1390
  * A guard runs after input decode + ctx resolution, before the handler.
@@ -1316,6 +1396,13 @@ export declare interface RestGuardRejection {
1316
1396
  readonly message: string;
1317
1397
  }
1318
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
+
1319
1406
  /** What a REST handler may return: the decoded output, a `response(...)` with
1320
1407
  * its own status and headers, an `sse(...)` stream, or `bytes(...)` for a
1321
1408
  * non-JSON body (a file, an export, an XML document). */
@@ -1327,6 +1414,13 @@ export declare type RestNaming = 'camelCase' | 'snake_case';
1327
1414
 
1328
1415
  export declare type RestPagination = 'link-header' | 'body-cursor';
1329
1416
 
1417
+ /** Uses the mounted rate-limit plugin's store, algorithms and dynamic policy. */
1418
+ export declare interface RestRateLimit {
1419
+ readonly limit: number;
1420
+ readonly window: string;
1421
+ readonly by?: 'subject' | 'tenant' | 'apiKey' | 'ip' | 'global';
1422
+ }
1423
+
1330
1424
  /** A schema-encoded REST body with an application-selected HTTP status and
1331
1425
  * response headers. The marker keeps ordinary domain objects unambiguous.
1332
1426
  * `output` still validates/encodes `body`; use `Schema.Void` with a 204 when
@@ -1348,6 +1442,8 @@ export declare interface RestResponse<O> {
1348
1442
  * stays free of the database dep).
1349
1443
  */
1350
1444
  export declare interface RestRouteContext {
1445
+ /** Server-owned invocation trace, stable through readmission and projection. */
1446
+ readonly traceId: string;
1351
1447
  readonly subject: Subject;
1352
1448
  readonly headers: Readonly<Record<string, string>>;
1353
1449
  /** Request metadata and the byte-exact body as received by the HTTP
@@ -1372,6 +1468,9 @@ export declare interface RestRouteContext {
1372
1468
  }
1373
1469
 
1374
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;
1375
1474
  readonly method: RestMethod;
1376
1475
  /** Absolute path, e.g. `/v1/customers`. Matches that path exactly. */
1377
1476
  readonly path: string;
@@ -1385,6 +1484,12 @@ export declare interface RestRouteDescriptor<I, O> {
1385
1484
  * encodes it to JSON for the `200` response. */
1386
1485
  readonly output: Schema.Codec<O, any, never, never>;
1387
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;
1388
1493
  readonly summary?: string;
1389
1494
  readonly description?: string;
1390
1495
  readonly example?: RestRouteExample;
@@ -1425,6 +1530,10 @@ export declare interface RestRouteDescriptor<I, O> {
1425
1530
  */
1426
1531
  readonly version?: `v${number}`;
1427
1532
  readonly guards?: ReadonlyArray<RestGuard>;
1533
+ readonly cors?: RestCors;
1534
+ /** Only an explicit bearer header may authenticate; cookies are removed before auth and context construction. */
1535
+ readonly credentials?: 'bearer';
1536
+ readonly rateLimit?: RestRateLimit;
1428
1537
  /**
1429
1538
  * This route STREAMS (Server-Sent Events) rather than resolving one value — its
1430
1539
  * handler returns `sse(...)`. Declared on the descriptor, not left implicit in
@@ -1510,12 +1619,23 @@ export declare const restRoutesToHttpRoutes: (routes: ReadonlyArray<RestRouteDes
1510
1619
  * auth subject + the bound DataStore.
1511
1620
  */
1512
1621
  export declare interface RestServeBindings {
1513
- /** Resolve the calling subject from the request headers. */
1514
- readonly resolveSubject?: (headers: Readonly<Record<string, string>>) => Subject | Promise<Subject>;
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;
1628
+ /** Bound once from the app's mounted rate-limit plugin. */
1629
+ readonly rateLimit?: (policy: RestRateLimit, ctx: RestRouteContext, route: RestRouteDescriptor<any, any>) => Effect.Effect<void, unknown>;
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>;
1515
1635
  /** The bound framework DataStore. */
1516
1636
  readonly store?: unknown;
1517
1637
  /** Build the full per-request app context after authentication. */
1518
- 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>;
1519
1639
  /**
1520
1640
  * HTTP idempotency — when set, a mutating request (POST/PUT/PATCH/DELETE)
1521
1641
  * carrying the `header` is deduplicated: the first call runs + caches its
@@ -1570,6 +1690,72 @@ export declare const routeAcceptsPath: (route: {
1570
1690
  readonly match?: "prefix" | "exact" | undefined;
1571
1691
  }, path: string) => boolean;
1572
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
+
1573
1759
  /**
1574
1760
  * The runtime-erased form of a procedure's `service: { callers }` — "only these
1575
1761
  * apps of the project may call this, and nobody else".
@@ -1613,6 +1799,7 @@ declare interface ServiceSpec {
1613
1799
  */
1614
1800
  export declare const sse: (subscribe: (emit: (chunk: string) => void) => () => void, options?: {
1615
1801
  readonly keepAliveMs?: number;
1802
+ readonly maxDurationMs?: number;
1616
1803
  }) => RestStreamResponse;
1617
1804
 
1618
1805
  /** Frame ONE SSE event. Newlines in `data` are split across `data:` lines, as
@@ -1621,6 +1808,9 @@ export declare const sseFrame: (event: string, data: unknown) => string;
1621
1808
 
1622
1809
  declare const Subject: Schema.Union<readonly [Schema.Struct<{
1623
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">>;
1624
1814
  readonly id: Schema.String;
1625
1815
  readonly tenantId: Schema.String;
1626
1816
  readonly scopes: Schema.optional<Schema.$Array<Schema.String>>;
@@ -1749,6 +1939,12 @@ declare interface UpdateTarget<Input = unknown, Row = unknown, Item = Record<str
1749
1939
  readonly shapeItem?: ((input: Input, current: Item) => Item) | undefined;
1750
1940
  }
1751
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
+
1752
1948
  export declare const wireExample: (example: {
1753
1949
  readonly request?: unknown;
1754
1950
  readonly response?: unknown;