@voltro/protocol 0.79.1 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -37,6 +37,9 @@ export declare interface ActionProcedureDescriptor<Name extends string, Input ex
37
37
  * `openAccess: '<why>'`. Mutually exclusive with `guards`; together they are
38
38
  * the only two shapes `security.defaultDeny` accepts. */
39
39
  readonly openAccess: string | undefined;
40
+ /** Only these apps of the project may call this procedure — see `service` on
41
+ * the definer's options. Absent → the app's own clients call it. */
42
+ readonly service: ServiceSpec | undefined;
40
43
  /** Opt this action into a public REST endpoint (innovation/11). */
41
44
  readonly publicApi: PublicApiSpec | undefined;
42
45
  /** Opt this action into the auto-synthesized agent toolset (innovation/07). */
@@ -89,9 +92,9 @@ export declare interface AnyApprovalPolicy {
89
92
  readonly reason?: string;
90
93
  }
91
94
 
92
- /** A guard entry is a scope check, a relationship check, an identity check, or
93
- * a declared no-check-needed decision. */
94
- export declare type AnyCheckSpec = GuardCheckSpec | PolicyCheckSpec | AuthenticatedCheckSpec | OpenAccessSpec;
95
+ /** A guard entry is a scope check, a relationship check, an identity check, a
96
+ * declared no-check-needed decision, or a service-only decision. */
97
+ export declare type AnyCheckSpec = GuardCheckSpec | PolicyCheckSpec | AuthenticatedCheckSpec | OpenAccessSpec | ServiceAccessSpec;
95
98
 
96
99
  /** Any declared event, with the generics erased — for registries and audits. */
97
100
  export declare type AnyEventDescriptor = EventDescriptor<string, Schema.Schema.Any, Schema.Schema.Any>;
@@ -130,6 +133,21 @@ export declare const APIKEY_ISSUE_OTHER_SCOPE = "apikeys:issue:other";
130
133
  */
131
134
  export declare const APIKEY_ISSUE_SELF_SCOPE = "apikeys:issue:self";
132
135
 
136
+ export declare class ApiUnavailable extends ApiUnavailable_base {
137
+ }
138
+
139
+ declare const ApiUnavailable_base: Schema.TaggedErrorClass<ApiUnavailable, "ApiUnavailable", {
140
+ readonly _tag: Schema.tag<"ApiUnavailable">;
141
+ } & {
142
+ /** The binding name on the caller (`apis.<name>`). */
143
+ api: typeof Schema.String;
144
+ /** The rpc tag that was called. */
145
+ tag: typeof Schema.String;
146
+ /** Why no answer arrived. */
147
+ reason: Schema.Literal<["timeout", "transport", "closed"]>;
148
+ message: typeof Schema.String;
149
+ }>;
150
+
133
151
  /** The subject a decision produces, plus what the decision took away. `removed`
134
152
  * is never non-empty for a `grant`. */
135
153
  export declare interface AppliedScopes {
@@ -677,7 +695,7 @@ export declare type AuthSubjectGuardVerdict = {
677
695
  * `fresh` the caller runs the handler then calls `finish`/`fail`; on
678
696
  * `replay`/`conflict` it returns immediately without touching the handler.
679
697
  */
680
- export declare const beginIdempotent: (store: IdempotencyStore, scope: string, key: string, ttlMs: number, now: number) => Promise<IdempotencyOutcome>;
698
+ export declare const beginIdempotent: (store: IdempotencyStore, scope: string, key: string, ttlMs: number, now: number, inFlightLeaseMs?: number) => Promise<IdempotencyOutcome>;
681
699
 
682
700
  /**
683
701
  * A cross-table business rule was violated inside a mutation's transaction; the
@@ -1379,11 +1397,25 @@ export declare interface CoordinatedTickOutcome {
1379
1397
  * because that is where the normalised decision lands, and because every
1380
1398
  * enforcement path reads the descriptor's array and nothing else.
1381
1399
  */
1382
- export declare type DeclaredAccess<Input = unknown> = ReadonlyArray<AnyGuardSpec<Input> | OpenAccessSpec>;
1400
+ export declare type DeclaredAccess<Input = unknown> = ReadonlyArray<AnyGuardSpec<Input> | OpenAccessSpec | ServiceAccessSpec>;
1383
1401
 
1384
1402
  /** Every channel declared in this process, by routing key. */
1385
1403
  export declare const declaredReactivityChannelKeys: () => ReadonlySet<string>;
1386
1404
 
1405
+ /**
1406
+ * How long a RUNNING claim holds without renewal.
1407
+ *
1408
+ * A claim used to go stale only after the replay window — 24 hours by default —
1409
+ * so a process that died inside a handler left its key `in_flight` for a day:
1410
+ * every retry was refused as "still in flight", including the retry of a
1411
+ * workflow step whose callee had simply restarted. A live handler renews its
1412
+ * claim every third of this (`holdIdempotentClaim`); a dead one stops, and the
1413
+ * next retry takes over after at most this long. A handler that blocks its event
1414
+ * loop longer than this can be taken over while still running — the same bound
1415
+ * every lease has.
1416
+ */
1417
+ export declare const DEFAULT_IN_FLIGHT_LEASE_MS = 30000;
1418
+
1387
1419
  /** Default cap on cached subjects before the cache sweeps + trims. */
1388
1420
  export declare const DEFAULT_SCOPE_CACHE_MAX_ENTRIES = 10000;
1389
1421
 
@@ -1418,6 +1450,26 @@ export declare const defineAction: <const Name extends string, Input extends Sch
1418
1450
  * openAccess: 'public pricing page — reads no caller data'
1419
1451
  */
1420
1452
  readonly openAccess?: string;
1453
+ /**
1454
+ * Make this a SERVICE procedure: callable only by the listed apps of this
1455
+ * project, and by nobody else — no browser session, no API key, no admin.
1456
+ *
1457
+ * service: { callers: ['worker'] }
1458
+ *
1459
+ * The caller is another app that binds this one under `apis:` and calls it as
1460
+ * `ctx.apis.<name>.<tag>(input)`; it authenticates with the service credential
1461
+ * this app accepts under `serviceCallers`. It is its own access decision, so it
1462
+ * satisfies `security.defaultDeny` and cannot sit beside `guards:` or
1463
+ * `openAccess:`.
1464
+ *
1465
+ * The handler runs as the calling SERVICE: it reads across tenants and must
1466
+ * name a tenant to write into one — `ctx.storeForTenant(input.tenantId)` is the
1467
+ * shape for per-tenant work.
1468
+ *
1469
+ * Codegen emits it into `serviceGroup.generated.ts`, never into the browser's
1470
+ * `rpcGroup.generated.ts`.
1471
+ */
1472
+ readonly service?: ServiceSpec;
1421
1473
  /** Table(s) this action READS. Declaring it is what lets `voltro check` know
1422
1474
  * the table is alive — without it, a table only an action touches reads as
1423
1475
  * an orphan. See `ActionProcedureDescriptor.source`. */
@@ -1631,6 +1683,26 @@ export declare const defineMutation: <const Name extends string, Input extends S
1631
1683
  * openAccess: 'public pricing page — reads no caller data'
1632
1684
  */
1633
1685
  readonly openAccess?: string;
1686
+ /**
1687
+ * Make this a SERVICE procedure: callable only by the listed apps of this
1688
+ * project, and by nobody else — no browser session, no API key, no admin.
1689
+ *
1690
+ * service: { callers: ['worker'] }
1691
+ *
1692
+ * The caller is another app that binds this one under `apis:` and calls it as
1693
+ * `ctx.apis.<name>.<tag>(input)`; it authenticates with the service credential
1694
+ * this app accepts under `serviceCallers`. It is its own access decision, so it
1695
+ * satisfies `security.defaultDeny` and cannot sit beside `guards:` or
1696
+ * `openAccess:`.
1697
+ *
1698
+ * The handler runs as the calling SERVICE: it reads across tenants and must
1699
+ * name a tenant to write into one — `ctx.storeForTenant(input.tenantId)` is the
1700
+ * shape for per-tenant work.
1701
+ *
1702
+ * Codegen emits it into `serviceGroup.generated.ts`, never into the browser's
1703
+ * `rpcGroup.generated.ts`.
1704
+ */
1705
+ readonly service?: ServiceSpec;
1634
1706
  /** Project this mutation as a public REST endpoint (innovation/11). */
1635
1707
  readonly publicApi?: PublicApiSpec;
1636
1708
  /** Expose this mutation as an agent tool (innovation/07). */
@@ -1764,6 +1836,26 @@ export declare const defineQuery: <const Name extends string, Input extends Sche
1764
1836
  * openAccess: 'public pricing page — reads no caller data'
1765
1837
  */
1766
1838
  readonly openAccess?: string;
1839
+ /**
1840
+ * Make this a SERVICE procedure: callable only by the listed apps of this
1841
+ * project, and by nobody else — no browser session, no API key, no admin.
1842
+ *
1843
+ * service: { callers: ['worker'] }
1844
+ *
1845
+ * The caller is another app that binds this one under `apis:` and calls it as
1846
+ * `ctx.apis.<name>.<tag>(input)`; it authenticates with the service credential
1847
+ * this app accepts under `serviceCallers`. It is its own access decision, so it
1848
+ * satisfies `security.defaultDeny` and cannot sit beside `guards:` or
1849
+ * `openAccess:`.
1850
+ *
1851
+ * The handler runs as the calling SERVICE: it reads across tenants and must
1852
+ * name a tenant to write into one — `ctx.storeForTenant(input.tenantId)` is the
1853
+ * shape for per-tenant work.
1854
+ *
1855
+ * Codegen emits it into `serviceGroup.generated.ts`, never into the browser's
1856
+ * `rpcGroup.generated.ts`.
1857
+ */
1858
+ readonly service?: ServiceSpec;
1767
1859
  /** Project this query as a public REST endpoint (innovation/11). */
1768
1860
  readonly publicApi?: PublicApiSpec;
1769
1861
  /** Expose this query as an agent tool (innovation/07). */
@@ -2302,9 +2394,34 @@ export declare const findAdvisoryResourceGuards: (procedures: ReadonlyArray<{
2302
2394
  /** Record a completed response for replay. */
2303
2395
  export declare const finishIdempotent: (store: IdempotencyStore, scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
2304
2396
 
2397
+ /**
2398
+ * The value a ONE-SHOT read of an rpc resolves to, given the first value it
2399
+ * delivered.
2400
+ *
2401
+ * A query's success type is the three-member union above, and a one-shot read
2402
+ * takes only its first event: a `snapshot` resolves to its `data`; an `error`
2403
+ * REJECTS — with the typed error on `cause` and `voltroError`, so a caller can
2404
+ * still branch on its `_tag` — exactly as a failed exit would; a `delta` cannot
2405
+ * be first and says so. Anything else is a mutation or action's own result and
2406
+ * passes through, which may legitimately be a tagged object of the app's.
2407
+ *
2408
+ * Shared because there are two such reads — the server loader's `ctx.query`
2409
+ * over `POST /rpc` and the browser loader's over the page's socket — and an
2410
+ * unwrap written twice is how an authorization answer once reached a loader as
2411
+ * its return value on one transport and as a rejection on the other.
2412
+ *
2413
+ * `label` prefixes every message (`[loader.query]`), naming the surface that
2414
+ * made the read.
2415
+ */
2416
+ export declare const firstEventValue: (tag: string, value: unknown, label: string) => unknown;
2417
+
2305
2418
  /** A route rendered for humans — logs, the inspect surface, the dashboard. */
2306
2419
  export declare const formatEventRoute: (route: string) => string;
2307
2420
 
2421
+ /** How long a record of this status stays fresh — the replay window for a
2422
+ * completed response, the lease for a running one (never longer than the window). */
2423
+ export declare const freshFor: (status: IdempotencyRecord["status"], ttlMs: number, inFlightLeaseMs: number) => number;
2424
+
2308
2425
  /** Close code a gateway connection receives when its credential expires —
2309
2426
  * the same session-expiry contract the rpc socket has (SEC-16), spelled as
2310
2427
  * an application close code so foreign clients can reauth + reconnect. */
@@ -2427,6 +2544,9 @@ export declare const hasEnforcedGuard: (guards: DeclaredAccess | undefined) => b
2427
2544
  * the RAW subject scopes — use `hasEffectiveScope` to include rbac roles. */
2428
2545
  export declare const hasScope: (subject: Subject, scope: string) => boolean;
2429
2546
 
2547
+ /** Renew a fresh claim's lease while its handler runs; returns the stop. */
2548
+ export declare const holdIdempotentClaim: (store: IdempotencyStore, scope: string, key: string, inFlightLeaseMs?: number, now?: () => number) => (() => void);
2549
+
2430
2550
  /**
2431
2551
  * Short-circuit response shape. Returning a `HttpInterceptResponse`
2432
2552
  * from the interceptor halts the pipeline — the framework sends the
@@ -2526,14 +2646,24 @@ export declare interface IdempotencyStore {
2526
2646
  /** Current record for (scope, key), or null. Read-side (inspect/dashboard). */
2527
2647
  readonly get: (scope: string, key: string) => Promise<IdempotencyRecord | null>;
2528
2648
  /**
2529
- * Atomically claim (scope, key) IF absent OR stale (older than `ttlMs`):
2649
+ * Atomically claim (scope, key) IF absent OR stale — a COMPLETED record older
2650
+ * than `ttlMs` (the replay window), or an IN-FLIGHT one whose lease
2651
+ * (`inFlightLeaseMs`, renewed by the live handler) lapsed:
2530
2652
  * write a fresh `in_flight` row and return `'claimed'`. Otherwise return the
2531
2653
  * EXISTING (still-fresh) record. The atomicity here is the whole game — two
2532
2654
  * concurrent same-key requests both hit this, exactly one gets `'claimed'`,
2533
2655
  * the loser reads the winner's record. In SQL this is one
2534
2656
  * `INSERT … ON CONFLICT DO UPDATE … WHERE existing.createdAt < now - ttl`.
2535
2657
  */
2536
- readonly claim: (scope: string, key: string, now: number, ttlMs: number) => Promise<'claimed' | IdempotencyRecord>;
2658
+ readonly claim: (scope: string, key: string, now: number, ttlMs: number, inFlightLeaseMs: number) => Promise<'claimed' | IdempotencyRecord>;
2659
+ /**
2660
+ * Extend a RUNNING claim's lease: bump its timestamp while it is still
2661
+ * `in_flight` (a completed or released claim is left alone). The server calls
2662
+ * it while the handler runs; a process that dies stops calling it, and its
2663
+ * claim goes stale after `inFlightLeaseMs` instead of after the whole replay
2664
+ * window.
2665
+ */
2666
+ readonly renew: (scope: string, key: string, now: number) => Promise<void>;
2537
2667
  /** Flip the claim to `completed` and store the response. */
2538
2668
  readonly complete: (scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
2539
2669
  /** Drop the claim (handler errored → a retry may re-process). */
@@ -2616,12 +2746,21 @@ export declare interface InsertTarget<Input = unknown, Row = unknown, Item = Rec
2616
2746
  readonly shapeItem?: ((input: Input, optimisticId: string) => Item) | undefined;
2617
2747
  }
2618
2748
 
2749
+ /** Is this failure a missing answer (retry it) rather than an answer (do not)? */
2750
+ export declare const isApiUnavailable: (error: unknown) => error is ApiUnavailable;
2751
+
2619
2752
  /** Runtime narrowing for the identity variant. */
2620
2753
  export declare const isAuthenticatedCheck: (g: AnyCheckSpec) => g is AuthenticatedCheckSpec;
2621
2754
 
2622
2755
  /** Narrow a guard entry to the identity variant. */
2623
2756
  export declare const isAuthenticatedGuard: <I>(g: AnyGuardSpec<I>) => g is AuthenticatedGuardSpec;
2624
2757
 
2758
+ /** Belongs in the app's own client group (`rpcGroup.generated.ts`). */
2759
+ export declare const isClientReachable: (descriptor: {
2760
+ readonly internal?: boolean | undefined;
2761
+ readonly service?: ServiceSpec | undefined;
2762
+ }) => boolean;
2763
+
2625
2764
  export declare const isEventDescriptor: (value: unknown) => value is AnyEventDescriptor;
2626
2765
 
2627
2766
  /** True when every row in the set carries an `id`. The dispatcher uses
@@ -2630,6 +2769,17 @@ export declare const isEventDescriptor: (value: unknown) => value is AnyEventDes
2630
2769
  * diffed by id and falls back to shipping the full data. */
2631
2770
  export declare const isIdKeyed: (rows: ReadonlyArray<Readonly<Record<string, unknown>>>) => rows is ReadonlyArray<PatchRow>;
2632
2771
 
2772
+ /**
2773
+ * A machine actor that works ACROSS tenants: the framework acting as itself
2774
+ * (`system`) or another app of the project calling in (`service`).
2775
+ *
2776
+ * This is a statement about DATA placement only — no row filter, no implicit
2777
+ * tenant, an explicit tenant for writes. It says nothing about authority: a
2778
+ * service holds no scopes, and every place that lets `system` bypass a check
2779
+ * keeps asking `isSystemSubject`.
2780
+ */
2781
+ export declare const isMachineSubject: (subject: Subject) => boolean;
2782
+
2633
2783
  /** Runtime narrowing for the declared-open variant. */
2634
2784
  export declare const isOpenAccess: (g: AnyCheckSpec) => g is OpenAccessSpec;
2635
2785
 
@@ -2655,6 +2805,20 @@ export declare const isReactivityChannel: (value: unknown) => value is Reactivit
2655
2805
  */
2656
2806
  export declare const isReactivityChannelKey: (key: string) => boolean;
2657
2807
 
2808
+ /** Runtime narrowing for the service-only variant. */
2809
+ export declare const isServiceAccess: (g: AnyCheckSpec) => g is ServiceAccessSpec;
2810
+
2811
+ /** Belongs in the group other apps call (`serviceGroup.generated.ts`). */
2812
+ export declare const isServiceReachable: (descriptor: {
2813
+ readonly internal?: boolean | undefined;
2814
+ readonly service?: ServiceSpec | undefined;
2815
+ }) => boolean;
2816
+
2817
+ /** Is this caller another app of the project, and which one? */
2818
+ export declare const isServiceSubject: (subject: Subject) => subject is Extract<Subject, {
2819
+ readonly type: "service";
2820
+ }>;
2821
+
2658
2822
  export declare const isSystemSubject: (subject: Subject) => boolean;
2659
2823
 
2660
2824
  export declare const isTenantSelectable: (subject: Subject) => subject is TenantSelectableSubject;
@@ -2786,6 +2950,9 @@ export declare interface MutationProcedureDescriptor<Name extends string, Input
2786
2950
  * `openAccess: '<why>'`. Mutually exclusive with `guards`; together they are
2787
2951
  * the only two shapes `security.defaultDeny` accepts. */
2788
2952
  readonly openAccess: string | undefined;
2953
+ /** Only these apps of the project may call this procedure — see `service` on
2954
+ * the definer's options. Absent → the app's own clients call it. */
2955
+ readonly service: ServiceSpec | undefined;
2789
2956
  /** Opt this mutation into a public REST endpoint (innovation/11). */
2790
2957
  readonly publicApi: PublicApiSpec | undefined;
2791
2958
  /** Opt this mutation into the auto-synthesized agent toolset (innovation/07). */
@@ -3431,11 +3598,17 @@ export declare interface PluginErrorSchema {
3431
3598
 
3432
3599
  /** A public raw-HTTP route a plugin serves on the framework listener. */
3433
3600
  export declare interface PluginHttpRoute {
3434
- /** HTTP method, or `'*'` for any (the handler decides). PATCH/HEAD/OPTIONS
3435
- * are first-class — the REST desugar used to mount `'*'` partly BECAUSE
3436
- * this union lacked PATCH; that reason is gone (the `'*'` mount remains
3437
- * for its other job: one dispatcher per shared path + a precise 405). */
3601
+ /** HTTP method, or `'*'` for any (the handler decides). The server mounts the
3602
+ * path for exactly the methods its routes declare; `'*'` mounts it for every
3603
+ * method the router knows (34), so keep that for a route that really serves
3604
+ * them — see `answersOtherMethods` for one that only wants to refuse them. */
3438
3605
  readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
3606
+ /** `true`: the handler gates its own method, so a request with a method the
3607
+ * path does not declare is still handed to it — to answer with a precise
3608
+ * `405` and `Allow` instead of the router's `404`. The path is still mounted
3609
+ * only for `method`. A REST route sets it. Without it, a route that declares
3610
+ * one method never sees another. */
3611
+ readonly answersOtherMethods?: boolean | undefined;
3439
3612
  /** Absolute path, e.g. `/_voltro/storage`. Matched byte for byte: the
3440
3613
  * router folds case and a trailing slash to FIND the route, the dispatcher
3441
3614
  * then asks whether the request's path is one the route declared — so
@@ -4491,6 +4664,9 @@ export declare interface QueryProcedureDescriptor<Name extends string, Input ext
4491
4664
  * `openAccess: '<why>'`. Mutually exclusive with `guards`; together they are
4492
4665
  * the only two shapes `security.defaultDeny` accepts. */
4493
4666
  readonly openAccess: string | undefined;
4667
+ /** Only these apps of the project may call this procedure — see `service` on
4668
+ * the definer's options. Absent → the app's own clients call it. */
4669
+ readonly service: ServiceSpec | undefined;
4494
4670
  /** Opt this query into a public REST endpoint (innovation/11). */
4495
4671
  readonly publicApi: PublicApiSpec | undefined;
4496
4672
  /** Opt this query into the auto-synthesized agent toolset (innovation/07). */
@@ -4847,6 +5023,9 @@ declare interface RestRouteContext {
4847
5023
  * REST handlers on the same capability surface as query/mutation/action
4848
5024
  * handlers without creating a protocol-to-runtime package dependency. */
4849
5025
  readonly app?: unknown;
5026
+ /** The caller's address, resolved through `security.trustedProxies` — the
5027
+ * value the listener's per-IP shield counts. Absent when unknown. */
5028
+ readonly remoteAddr?: string;
4850
5029
  }
4851
5030
 
4852
5031
  declare interface RestRouteDescriptor<I, O> {
@@ -5149,6 +5328,16 @@ export declare interface RpcInterceptorContext {
5149
5328
  readonly surface?: 'rest';
5150
5329
  /** The descriptor's `publicApi` spec, on a projected call. */
5151
5330
  readonly publicApi?: PublicApiSpec;
5331
+ /**
5332
+ * The caller's address — the same value the pre-auth IP shield counts, resolved
5333
+ * by the listener through `security.trustedProxies` (and
5334
+ * `security.clientAddressHeader`). A client cannot set it: the listener
5335
+ * writes it, and any copy the client sends is discarded.
5336
+ *
5337
+ * Absent when the call did not arrive over the network (a workflow step, a
5338
+ * schedule, an in-process invocation) or the socket address is unknown.
5339
+ */
5340
+ readonly remoteAddr?: string;
5152
5341
  }
5153
5342
 
5154
5343
  /**
@@ -5371,6 +5560,118 @@ declare const ServerOnlyColumnWrite_base: Schema.TaggedErrorClass<ServerOnlyColu
5371
5560
  columns: Schema.Array$<typeof Schema.String>;
5372
5561
  }>;
5373
5562
 
5563
+ /** A service (app) name: what `serviceCallers`, `service.callers` and a service
5564
+ * credential all spell the same way. At most 56 characters: the caller writes
5565
+ * as `service:<name>`, which is an `actors.id` — an `id()` column, VARCHAR(64)
5566
+ * on three dialects — so a longer name would fail its first audited write. */
5567
+ export declare const SERVICE_NAME_PATTERN: RegExp;
5568
+
5569
+ /**
5570
+ * The runtime-erased form of a procedure's `service: { callers }` — "only these
5571
+ * apps of the project may call this, and nobody else".
5572
+ *
5573
+ * A guard entry for the reason `OpenAccessSpec` is one: every enforcement path
5574
+ * is handed the `guards` array and nothing else, so a decision that lived on a
5575
+ * separate descriptor field would be enforced by the paths somebody remembered
5576
+ * and skipped by the rest.
5577
+ *
5578
+ * It admits exactly a `service` subject whose name is listed. `admin:full` does
5579
+ * NOT bypass it: a service procedure is a machine contract between two apps,
5580
+ * and a browser session holding the admin scope is not one of the parties.
5581
+ */
5582
+ export declare interface ServiceAccessSpec {
5583
+ /** The calling apps' names, as the callee declares them in `serviceCallers`.
5584
+ * Non-empty by construction — the definers refuse an empty list. */
5585
+ readonly service: ReadonlyArray<string>;
5586
+ }
5587
+
5588
+ /** Build the erased `service:` entry. The definers call this. */
5589
+ export declare const serviceAccessSpec: (callers: ReadonlyArray<string>) => ServiceAccessSpec;
5590
+
5591
+ /**
5592
+ * A procedure map (`ServiceProcedures`) turned into the nested object a caller
5593
+ * writes: `'deployments.activate'` becomes `.deployments.activate(...)`. A tag
5594
+ * that is both a procedure and a namespace prefix is both.
5595
+ */
5596
+ export declare type ServiceApi<P> = 0 extends (1 & P) ? any : ServiceApiOf<P>;
5597
+
5598
+ declare type ServiceApiOf<P> = {
5599
+ readonly [H in ServiceTagHead<keyof P & string>]: (H extends keyof P ? ServiceCall<P[H]> : unknown) & ServiceApiOf<{
5600
+ [K in keyof P & string as K extends `${H}.${infer R}` ? R : never]: P[K];
5601
+ }>;
5602
+ };
5603
+
5604
+ /** One procedure as the caller sees it — callable, `.effect`, and `.subscribe` for a query. */
5605
+ export declare type ServiceCall<D> = D extends ServiceDescriptorShape ? ((input: ServiceDecoded<D['input']>, options?: ServiceCallOptions) => Promise<ServiceDecoded<D['output']>>) & {
5606
+ /** The same call as an Effect. Inside a workflow step its write key is
5607
+ * derived from the step, so a re-sent step replays instead of writing twice. */
5608
+ readonly effect: (input: ServiceDecoded<D['input']>, options?: ServiceCallOptions) => Effect.Effect<ServiceDecoded<D['output']>, unknown>;
5609
+ } & (D extends {
5610
+ readonly kind: 'query';
5611
+ } ? {
5612
+ /** Hold the live query open; `onValue` gets each materialized value. */
5613
+ readonly subscribe: (input: ServiceDecoded<D['input']>, handlers: ServiceSubscribeHandlers<ServiceDecoded<D['output']>>) => ServiceSubscription;
5614
+ } : unknown) : never;
5615
+
5616
+ /** The apps a guard array admits as service callers, or `undefined` when the
5617
+ * procedure is not a service procedure. */
5618
+ export declare const serviceCallersOf: (guards: ReadonlyArray<AnyCheckSpec> | undefined) => ReadonlyArray<string> | undefined;
5619
+
5620
+ export declare interface ServiceCallOptions {
5621
+ /** Deadline for this call. Default 10 seconds. */
5622
+ readonly timeoutMs?: number;
5623
+ /** Explicit idempotency key for a write. Overrides the ambient and the fresh one. */
5624
+ readonly idempotencyKey?: string;
5625
+ /** Interrupts the call when aborted. */
5626
+ readonly signal?: AbortSignal;
5627
+ }
5628
+
5629
+ /** The decoded (application-side) type of a schema slot. */
5630
+ export declare type ServiceDecoded<S> = S extends {
5631
+ readonly Type: infer T;
5632
+ } ? T : never;
5633
+
5634
+ /** What a service procedure's descriptor type carries that the facade reads. */
5635
+ export declare interface ServiceDescriptorShape {
5636
+ readonly kind: string;
5637
+ readonly input: unknown;
5638
+ readonly output: unknown;
5639
+ }
5640
+
5641
+ /**
5642
+ * A service procedure's declaration: which apps of the project may call it.
5643
+ *
5644
+ * service: { callers: ['worker'] }
5645
+ *
5646
+ * The names are the CALLING apps' names as this app lists them in
5647
+ * `serviceCallers` — the same name the caller's credential resolves to.
5648
+ */
5649
+ export declare interface ServiceSpec {
5650
+ readonly callers: ReadonlyArray<string>;
5651
+ }
5652
+
5653
+ /**
5654
+ * The subject a verified service credential resolves to. Built by the service
5655
+ * strategy only — an app never constructs one, and nothing grants it scopes.
5656
+ */
5657
+ export declare const serviceSubject: (service: string) => Extract<Subject, {
5658
+ readonly type: "service";
5659
+ }>;
5660
+
5661
+ export declare interface ServiceSubscribeHandlers<T> {
5662
+ /** Every materialized value of the live query, starting with the first snapshot. */
5663
+ readonly onValue: (value: T) => void;
5664
+ /** The subscription's own in-band error (e.g. an access refusal). */
5665
+ readonly onError?: (error: unknown) => void;
5666
+ }
5667
+
5668
+ export declare interface ServiceSubscription {
5669
+ readonly unsubscribe: () => void;
5670
+ }
5671
+
5672
+ /** The first segment of a dotted rpc tag. */
5673
+ export declare type ServiceTagHead<K extends string> = K extends `${infer H}.${string}` ? H : K;
5674
+
5374
5675
  /**
5375
5676
  * Publish the caller's fully-resolved scope set for this request (raw subject
5376
5677
  * scopes ∪ role-derived scopes ∪ any extra grants). Called by the rbac
@@ -5583,6 +5884,14 @@ export declare const Subject: Schema.Union<[Schema.Struct<{
5583
5884
  tenantId: typeof Schema.Null;
5584
5885
  scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
5585
5886
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
5887
+ }>, Schema.Struct<{
5888
+ type: Schema.Literal<["service"]>;
5889
+ id: typeof Schema.String;
5890
+ /** The calling app's name, as the callee lists it in `serviceCallers`. */
5891
+ service: typeof Schema.String;
5892
+ tenantId: typeof Schema.Null;
5893
+ scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
5894
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
5586
5895
  }>]>;
5587
5896
 
5588
5897
  export declare type Subject = typeof Subject.Type;
@@ -5636,6 +5945,12 @@ export declare const SubjectIdentity: Schema.Union<[Schema.Struct<{
5636
5945
  id: typeof Schema.String;
5637
5946
  tenantId: typeof Schema.Null;
5638
5947
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
5948
+ }>, Schema.Struct<{
5949
+ type: Schema.Literal<["service"]>;
5950
+ id: typeof Schema.String;
5951
+ tenantId: typeof Schema.Null;
5952
+ metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
5953
+ service: typeof Schema.String;
5639
5954
  }>]>;
5640
5955
 
5641
5956
  export declare type SubjectIdentity = typeof SubjectIdentity.Type;
@@ -5708,6 +6023,15 @@ declare const SubjectService_base: Context.TagClass<SubjectService, "@voltro/Sub
5708
6023
  readonly metadata?: {
5709
6024
  readonly [x: string]: unknown;
5710
6025
  } | undefined;
6026
+ } | {
6027
+ readonly type: "service";
6028
+ readonly id: string;
6029
+ readonly tenantId: null;
6030
+ readonly scopes?: readonly string[] | undefined;
6031
+ readonly metadata?: {
6032
+ readonly [x: string]: unknown;
6033
+ } | undefined;
6034
+ readonly service: string;
5711
6035
  }>;
5712
6036
 
5713
6037
  /** Subscribe to server-primitive errors. Returns an unsubscribe fn. */
@@ -6632,6 +6956,29 @@ export declare interface VoltroPlugin {
6632
6956
  * specific permission name in the error.
6633
6957
  */
6634
6958
  readonly extendSchema?: PluginSchemaContribution;
6959
+ /**
6960
+ * A directory of APP-SHAPED primitives this plugin brings with it — the same
6961
+ * file conventions an app uses (`*.query.ts` + `.server.ts`, `*.mutation.ts`,
6962
+ * `*.workflow.tsx`, `*.cron.tsx`, `*.subscribe.ts`, `*.reaction.ts`,
6963
+ * `schema.ts` / `*.entity.ts`, …).
6964
+ *
6965
+ * sources: fileURLToPath(new URL('./app', import.meta.url))
6966
+ *
6967
+ * Every app that installs the plugin discovers those files exactly as it
6968
+ * discovers its own: the same codegen, browser-safety guard, access gate,
6969
+ * migrations and boot paths. That is what makes a reusable BUILDING BLOCK — a
6970
+ * ledger, an approval flow, an audit trail — shareable between apps of a
6971
+ * project without copying it.
6972
+ *
6973
+ * It shares CODE, never data: each installing app gets its own copy of the
6974
+ * block's tables, in its own database. Two apps that need each other's data
6975
+ * call each other (`apis:` + `service:` procedures).
6976
+ *
6977
+ * The block's procedures keep the names their descriptors declare; a name
6978
+ * that collides with the app's own is refused at codegen, as any collision is.
6979
+ * An absolute path to a directory; a missing one refuses the boot.
6980
+ */
6981
+ readonly sources?: string;
6635
6982
  /**
6636
6983
  * Cross-cutting error schemas merged into every procedure's wire error
6637
6984
  * union. Lets a plugin interceptor fail with a typed error (e.g.
@@ -6800,6 +7147,24 @@ export declare interface WebSocketGatewayRoute {
6800
7147
  readonly onConnection: (connection: WebSocketGatewayConnection) => void | (() => void) | Promise<void | (() => void)>;
6801
7148
  }
6802
7149
 
7150
+ /**
7151
+ * Who may call a procedure over the wire: the app's own clients, or only other
7152
+ * apps of the project.
7153
+ *
7154
+ * A `service:` procedure IS on the wire — the server routes it on the same
7155
+ * socket — but it belongs to a different client group. Codegen emits it into
7156
+ * `serviceGroup.generated.ts` (imported by server processes of other apps) and
7157
+ * never into `rpcGroup.generated.ts` (imported by browsers), so no browser
7158
+ * bundle names a machine contract. The server refuses every non-service caller
7159
+ * regardless; keeping it out of the browser group is what keeps it out of the
7160
+ * code people ship.
7161
+ */
7162
+ export declare type WireAudience = 'client' | 'service';
7163
+
7164
+ export declare const wireAudienceOf: (descriptor: {
7165
+ readonly service?: ServiceSpec | undefined;
7166
+ }) => WireAudience;
7167
+
6803
7168
  /**
6804
7169
  * The error union a procedure ACTUALLY puts on the wire — `descriptor.error`
6805
7170
  * plus everything the framework can produce for it before or around the
@@ -7506,7 +7871,12 @@ export declare const WorkflowRunHandleSchema: Schema.Struct<{
7506
7871
  status: Schema.Literal<["running", "succeeded", "queued", "dropped", "skipped", "replayed"]>;
7507
7872
  deferral: Schema.optional<Schema.Struct<{
7508
7873
  /** `debounce` | `batch` | `throttle` | `concurrency` | `paused` |
7509
- * `rateLimit` | `singleton`. */
7874
+ * `rateLimit` | `singleton` | `delayed` | `transaction` | `unacknowledged`.
7875
+ *
7876
+ * `unacknowledged` — the start was dispatched and no acknowledgement came
7877
+ * back. The engine may have it; recovery retries the dispatch and binds the
7878
+ * receipt (`id`) to whatever run results. It is not a failure: do not
7879
+ * report one to a user, follow the receipt. */
7510
7880
  mode: typeof Schema.String;
7511
7881
  /** Epoch ms this may first be admitted. Null for a drop or a skip. */
7512
7882
  dueAt: Schema.NullOr<typeof Schema.Number>;
@@ -7694,7 +8064,12 @@ export declare type WorkflowStartDeferral = Schema.Schema.Type<typeof WorkflowSt
7694
8064
  */
7695
8065
  export declare const WorkflowStartDeferralSchema: Schema.Struct<{
7696
8066
  /** `debounce` | `batch` | `throttle` | `concurrency` | `paused` |
7697
- * `rateLimit` | `singleton`. */
8067
+ * `rateLimit` | `singleton` | `delayed` | `transaction` | `unacknowledged`.
8068
+ *
8069
+ * `unacknowledged` — the start was dispatched and no acknowledgement came
8070
+ * back. The engine may have it; recovery retries the dispatch and binds the
8071
+ * receipt (`id`) to whatever run results. It is not a failure: do not
8072
+ * report one to a user, follow the receipt. */
7698
8073
  mode: typeof Schema.String;
7699
8074
  /** Epoch ms this may first be admitted. Null for a drop or a skip. */
7700
8075
  dueAt: Schema.NullOr<typeof Schema.Number>;
@@ -7812,7 +8187,12 @@ export declare const workflowToRpc: <const Name extends string, Payload extends
7812
8187
  status: Schema.Literal<["running", "succeeded", "queued", "dropped", "skipped", "replayed"]>;
7813
8188
  deferral: Schema.optional<Schema.Struct<{
7814
8189
  /** `debounce` | `batch` | `throttle` | `concurrency` | `paused` |
7815
- * `rateLimit` | `singleton`. */
8190
+ * `rateLimit` | `singleton` | `delayed` | `transaction` | `unacknowledged`.
8191
+ *
8192
+ * `unacknowledged` — the start was dispatched and no acknowledgement came
8193
+ * back. The engine may have it; recovery retries the dispatch and binds the
8194
+ * receipt (`id`) to whatever run results. It is not a failure: do not
8195
+ * report one to a user, follow the receipt. */
7816
8196
  mode: typeof Schema.String;
7817
8197
  /** Epoch ms this may first be admitted. Null for a drop or a skip. */
7818
8198
  dueAt: Schema.NullOr<typeof Schema.Number>;