@voltro/protocol 0.80.0 → 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). */
@@ -2326,6 +2418,10 @@ export declare const firstEventValue: (tag: string, value: unknown, label: strin
2326
2418
  /** A route rendered for humans — logs, the inspect surface, the dashboard. */
2327
2419
  export declare const formatEventRoute: (route: string) => string;
2328
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
+
2329
2425
  /** Close code a gateway connection receives when its credential expires —
2330
2426
  * the same session-expiry contract the rpc socket has (SEC-16), spelled as
2331
2427
  * an application close code so foreign clients can reauth + reconnect. */
@@ -2448,6 +2544,9 @@ export declare const hasEnforcedGuard: (guards: DeclaredAccess | undefined) => b
2448
2544
  * the RAW subject scopes — use `hasEffectiveScope` to include rbac roles. */
2449
2545
  export declare const hasScope: (subject: Subject, scope: string) => boolean;
2450
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
+
2451
2550
  /**
2452
2551
  * Short-circuit response shape. Returning a `HttpInterceptResponse`
2453
2552
  * from the interceptor halts the pipeline — the framework sends the
@@ -2547,14 +2646,24 @@ export declare interface IdempotencyStore {
2547
2646
  /** Current record for (scope, key), or null. Read-side (inspect/dashboard). */
2548
2647
  readonly get: (scope: string, key: string) => Promise<IdempotencyRecord | null>;
2549
2648
  /**
2550
- * 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:
2551
2652
  * write a fresh `in_flight` row and return `'claimed'`. Otherwise return the
2552
2653
  * EXISTING (still-fresh) record. The atomicity here is the whole game — two
2553
2654
  * concurrent same-key requests both hit this, exactly one gets `'claimed'`,
2554
2655
  * the loser reads the winner's record. In SQL this is one
2555
2656
  * `INSERT … ON CONFLICT DO UPDATE … WHERE existing.createdAt < now - ttl`.
2556
2657
  */
2557
- 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>;
2558
2667
  /** Flip the claim to `completed` and store the response. */
2559
2668
  readonly complete: (scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
2560
2669
  /** Drop the claim (handler errored → a retry may re-process). */
@@ -2637,12 +2746,21 @@ export declare interface InsertTarget<Input = unknown, Row = unknown, Item = Rec
2637
2746
  readonly shapeItem?: ((input: Input, optimisticId: string) => Item) | undefined;
2638
2747
  }
2639
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
+
2640
2752
  /** Runtime narrowing for the identity variant. */
2641
2753
  export declare const isAuthenticatedCheck: (g: AnyCheckSpec) => g is AuthenticatedCheckSpec;
2642
2754
 
2643
2755
  /** Narrow a guard entry to the identity variant. */
2644
2756
  export declare const isAuthenticatedGuard: <I>(g: AnyGuardSpec<I>) => g is AuthenticatedGuardSpec;
2645
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
+
2646
2764
  export declare const isEventDescriptor: (value: unknown) => value is AnyEventDescriptor;
2647
2765
 
2648
2766
  /** True when every row in the set carries an `id`. The dispatcher uses
@@ -2651,6 +2769,17 @@ export declare const isEventDescriptor: (value: unknown) => value is AnyEventDes
2651
2769
  * diffed by id and falls back to shipping the full data. */
2652
2770
  export declare const isIdKeyed: (rows: ReadonlyArray<Readonly<Record<string, unknown>>>) => rows is ReadonlyArray<PatchRow>;
2653
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
+
2654
2783
  /** Runtime narrowing for the declared-open variant. */
2655
2784
  export declare const isOpenAccess: (g: AnyCheckSpec) => g is OpenAccessSpec;
2656
2785
 
@@ -2676,6 +2805,20 @@ export declare const isReactivityChannel: (value: unknown) => value is Reactivit
2676
2805
  */
2677
2806
  export declare const isReactivityChannelKey: (key: string) => boolean;
2678
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
+
2679
2822
  export declare const isSystemSubject: (subject: Subject) => boolean;
2680
2823
 
2681
2824
  export declare const isTenantSelectable: (subject: Subject) => subject is TenantSelectableSubject;
@@ -2807,6 +2950,9 @@ export declare interface MutationProcedureDescriptor<Name extends string, Input
2807
2950
  * `openAccess: '<why>'`. Mutually exclusive with `guards`; together they are
2808
2951
  * the only two shapes `security.defaultDeny` accepts. */
2809
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;
2810
2956
  /** Opt this mutation into a public REST endpoint (innovation/11). */
2811
2957
  readonly publicApi: PublicApiSpec | undefined;
2812
2958
  /** Opt this mutation into the auto-synthesized agent toolset (innovation/07). */
@@ -3452,11 +3598,17 @@ export declare interface PluginErrorSchema {
3452
3598
 
3453
3599
  /** A public raw-HTTP route a plugin serves on the framework listener. */
3454
3600
  export declare interface PluginHttpRoute {
3455
- /** HTTP method, or `'*'` for any (the handler decides). PATCH/HEAD/OPTIONS
3456
- * are first-class — the REST desugar used to mount `'*'` partly BECAUSE
3457
- * this union lacked PATCH; that reason is gone (the `'*'` mount remains
3458
- * 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. */
3459
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;
3460
3612
  /** Absolute path, e.g. `/_voltro/storage`. Matched byte for byte: the
3461
3613
  * router folds case and a trailing slash to FIND the route, the dispatcher
3462
3614
  * then asks whether the request's path is one the route declared — so
@@ -4512,6 +4664,9 @@ export declare interface QueryProcedureDescriptor<Name extends string, Input ext
4512
4664
  * `openAccess: '<why>'`. Mutually exclusive with `guards`; together they are
4513
4665
  * the only two shapes `security.defaultDeny` accepts. */
4514
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;
4515
4670
  /** Opt this query into a public REST endpoint (innovation/11). */
4516
4671
  readonly publicApi: PublicApiSpec | undefined;
4517
4672
  /** Opt this query into the auto-synthesized agent toolset (innovation/07). */
@@ -5405,6 +5560,118 @@ declare const ServerOnlyColumnWrite_base: Schema.TaggedErrorClass<ServerOnlyColu
5405
5560
  columns: Schema.Array$<typeof Schema.String>;
5406
5561
  }>;
5407
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
+
5408
5675
  /**
5409
5676
  * Publish the caller's fully-resolved scope set for this request (raw subject
5410
5677
  * scopes ∪ role-derived scopes ∪ any extra grants). Called by the rbac
@@ -5617,6 +5884,14 @@ export declare const Subject: Schema.Union<[Schema.Struct<{
5617
5884
  tenantId: typeof Schema.Null;
5618
5885
  scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
5619
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>>;
5620
5895
  }>]>;
5621
5896
 
5622
5897
  export declare type Subject = typeof Subject.Type;
@@ -5670,6 +5945,12 @@ export declare const SubjectIdentity: Schema.Union<[Schema.Struct<{
5670
5945
  id: typeof Schema.String;
5671
5946
  tenantId: typeof Schema.Null;
5672
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;
5673
5954
  }>]>;
5674
5955
 
5675
5956
  export declare type SubjectIdentity = typeof SubjectIdentity.Type;
@@ -5742,6 +6023,15 @@ declare const SubjectService_base: Context.TagClass<SubjectService, "@voltro/Sub
5742
6023
  readonly metadata?: {
5743
6024
  readonly [x: string]: unknown;
5744
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;
5745
6035
  }>;
5746
6036
 
5747
6037
  /** Subscribe to server-primitive errors. Returns an unsubscribe fn. */
@@ -6666,6 +6956,29 @@ export declare interface VoltroPlugin {
6666
6956
  * specific permission name in the error.
6667
6957
  */
6668
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;
6669
6982
  /**
6670
6983
  * Cross-cutting error schemas merged into every procedure's wire error
6671
6984
  * union. Lets a plugin interceptor fail with a typed error (e.g.
@@ -6834,6 +7147,24 @@ export declare interface WebSocketGatewayRoute {
6834
7147
  readonly onConnection: (connection: WebSocketGatewayConnection) => void | (() => void) | Promise<void | (() => void)>;
6835
7148
  }
6836
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
+
6837
7168
  /**
6838
7169
  * The error union a procedure ACTUALLY puts on the wire — `descriptor.error`
6839
7170
  * plus everything the framework can produce for it before or around the