@voltro/protocol 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -39,6 +39,27 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.5.0] — 2026-07-18
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **@voltro/runtime** — `.one()` now fails when a query matches MORE than one row, not only when it matches none. It previously probed with `LIMIT 1`, so a filter that quietly stopped being unique returned an arbitrary row while the thrown error still claimed "expected exactly one row". It now probes with `LIMIT 2` and fails with `NoRowFound({ found: 2 })`. Migration: a `.one()` whose filter is not unique and that meant "any match" becomes `.first()` / `.maybeOne()`; one that meant "the unique row" stays as-is and now fails loudly when that assumption breaks. `NoRowFound` and `OptimisticLockError` are now `Schema.TaggedError`s, so they can be declared directly in a descriptor's `error:` union and caught with `Effect.catchTag`. Previously they carried a `_tag` field that looked declarable but did not typecheck there, forcing every caller to catch and re-wrap them in a hand-written tagged error — that wrapper can be deleted. Constructing one directly now takes an object: `new NoRowFound({ table, found })`, `new OptimisticLockError({ table, expected })`. `instanceof` and `_tag` checks are unaffected.
47
+
48
+ ### Added
49
+
50
+ - **@voltro/protocol, @voltro/runtime, @voltro/cli, @voltro/mcp** — Relationship (ReBAC) authorization is now declarative. A descriptor can carry `guards: [{ action, resourceType, resource: (input) => input.id }]` alongside its scope guards; the framework resolves it before the executor (for a mutation, before the transaction opens) and fails with a typed `ScopeError` naming `<resourceType>:<action>`. `defineResourcePolicy` previously enforced nothing on its own. Making it bite required hand-building a map from rpc tag to policy rule and installing an interceptor — undocumented plumbing that nobody wired, and **fail-open by omission**: an rpc missing from the map passed unchecked, with no type error and no boot warning. A guard on the descriptor cannot be forgotten for an rpc that exists, because it is part of the rpc. `setTupleSource` registers where relations are read from — a real registry, not a per-call parameter. Both entrypoints register a default reading `_voltro_rebac_tuples`; an app whose relations already live in its own tables (a `teamMembers` row) registers its own instead of copying data into a framework table. Every unanswerable case DENIES: no tuple source, no policy for the type, an input that does not identify a resource, or a tuple source that throws. An authorization question nobody can answer is a refusal. The capability manifest now carries the declared guards, so a client gates UI on the same declaration the server enforces instead of a hand-kept copy. Only the data crosses the wire — the pure `resource` extractor stays server-side and is reported as `resourceScoped: true`, so a client knows the real answer is per-row and asks rather than assuming. Because guards are re-checked on every subscription delivery, a relationship revoked mid-session now ends the stream rather than continuing to serve it.
51
+ - **@voltro/database, @voltro/runtime, @voltro/cli** — `paginateBy(descriptor, column, cursor, limit, direction?)` generalises `paginateById` to any orderable column. `paginateById` hardcoded `id`, which is right for a sortable key and useless for what feeds actually need — "the next page by `createdAt`" — so apps fell back to hand-rolled `limit + 1` / slice / `hasMore` triples on cursors the helper could not express. `paginateById` is now literally `paginateBy(descriptor, 'id', …)`, so the two cannot drift. `direction` flips the comparison as well as the sort: a `desc` feed pages with `<`. Mismatching those is the classic keyset bug — an ascending comparison under a descending sort returns the same first page forever. The docs example previously demonstrated a related trap (ordering by `createdAt desc` and then calling `paginateById`, which silently re-orders by `id asc`); it now shows the correct form and names the trap. `ctx.load` / `ctx.loadMany` add request-scoped batching. Same-tick reads of one table coalesce into a single `WHERE id IN (...)`, so a breadth-first walk costs one query per LEVEL rather than per node — the assembly shape `relations()` + `.with()` cannot express, because each level's ids come from the level above. Misses are cached too (a repeated dangling reference is fetched once) and a failed batch rejects its waiters without poisoning the cache, so a transient store error does not become "these rows do not exist" for the rest of the request. The cache is request-scoped as a correctness requirement, not a tuning choice: anything longer-lived would serve one subject's rows to another. Workflow steps can now `yield* EffectStore`. Handlers always could; workflow executors could not, so a step reading the store had to lift `ctx.store` with `Effect.tryPromise` — the idiom the rest of the framework tells you to avoid, because it discards the typed `StoreError` channel. The asymmetry was an oversight; the layer is now provided from the workflow context's own store.
52
+ - **@voltro/cli** — `voltro capabilities [--json]` enumerates the framework's export surface by reading the `.d.ts` files in the project's own `node_modules`, so the answer to "what does this framework export" can be verified rather than recalled. The `--json` form is locale-independent and byte-stable, so it can be diffed across upgrades. Symbols that ship but appear nowhere in the project's seeded agent guide are flagged — limited to primitives and hooks, because counting every undocumented export on a real tree gave 1,088 (mostly types and internal Layers), a number too large to act on. `voltro doctor`'s hand-roll detector gained server rules: a hand-written not-found branch on `rows[0]` (→ `.one()`), several sequential `store.query` calls assembling related data (→ `relations()` + `.with()`), `ctx.store` lifted with `Effect.promise` (→ `EffectStore`), an imperative scope check at the top of an executor (→ `guards:`), a credential-shaped column with no `.encrypted()`, a notify/webhook helper at a mutation's tail (→ `defineSubscriber` / `defineReaction`), and hand-rolled cursor pagination (→ `paginateById`). Its scan roots now include the API app directories (`queries/`, `mutations/`, `database/`, …) — without that the server rules could never have fired. The always-loaded agent core now carries a "Pick the SERVER primitive" rubric alongside the client one, and a doc-coverage gate keeps the server surface from drifting out of it.
53
+ - **@voltro/runtime, @voltro/cli** — `ctx.outbox.enqueue(effect, payload, options?)` — a reliable external side effect from a mutation. The enqueue writes through `ctx.store`, which inside a mutation IS the transactional view, so the intent to deliver commits in the same transaction as the domain write or not at all. That closes the window a post-commit tap cannot: `@voltro/plugin-cdc-out` is at-least-once *from enqueue* (its own docs say so — a crash between commit and tap loses the event). Here the enqueue cannot be lost, because losing it means the domain write rolled back too. Delivery after commit remains at-least-once, so handlers must be idempotent; `idempotencyKey` makes that easy to honour. Delivery is declared per effect in a `*.outbox.ts` via `defineOutboxHandler`. The worker is nudged on commit for the fast path and polls every 5s — the poll is the contract, not the optimisation: it recovers rows whose nudge was lost to a crash, rows from another replica, and rows waiting out a backoff. Exponential backoff capped at 5 minutes, configurable `maxAttempts` (default 8), and a dead-letter that stays queryable in `_voltro_outbox` with its `lastError`. An effect with no registered handler is left PENDING rather than dead-lettered — the usual cause is a deploy where the enqueuing code shipped ahead of its handler, and discarding those would turn a rollout ordering detail into permanent loss of a side effect the app believes happened. Two handlers claiming one effect are refused at boot with both filenames. `_voltro_outbox` rides the declarative differ and is created only when the app declares at least one handler. Wired in both `voltro dev` and `voltro serve`.
54
+ - **@voltro/database, @voltro/runtime** — `ctx.store.query()` now returns the row type instead of `Readonly<Record<string, unknown>>`. `QueryDescriptor<R>` carries a phantom row type, so the shape the typed builder already knew survives `.descriptor` into the store: ```ts const rows = await ctx.store.query(database.notes.where(eq('id', id)).descriptor) rows[0].title // string — previously `rows[0]['title'] as string` ``` The type was always available; it was dropped at exactly this boundary, which is why reading a field meant casting. One downstream app accumulated 2,032 of those casts against a surface that could have typed them all along. `EffectStore.query` is typed the same way, so an Effect-form handler keeps both the row type and the `StoreError` channel. A hand-built descriptor still resolves to `Row` — this never types less than before, so it is additive: existing code keeps compiling, and the casts it contains simply become redundant. The driver-level `DataStore.query` stays untyped deliberately. It is the SPI every dialect store and transactional view implements, and those genuinely do return untyped rows off the wire; the type is re-applied one layer up, at the handler-facing `FluentStore`.
55
+
56
+ ### Fixed
57
+
58
+ - **@voltro/runtime, @voltro/cli** — Declarative `guards:` are now re-checked before EVERY subscription delivery, not only when the subscription is opened. A subscription is a long-lived authorization grant, and the scopes that justified it can be withdrawn while it is still open — a role revoked, a resource un-shared, a membership ended. Previously the gate ran once at subscribe and every later delivery re-ran the query and pushed rows out without re-asking, so a revoked subject kept receiving live updates until their socket happened to drop. A denial now ends the subscription with the typed `ScopeError` rather than silently freezing the subscriber on its last authorized value, and the re-check runs BEFORE the read, so a revoked subject's rows are never materialised. Both the descriptor and computed-query paths are covered, in both `voltro dev` and `voltro serve`. Non-authorization failures (a bad predicate, a dropped connection) still leave the subscriber on its last good snapshot as before.
59
+ - **@voltro/testing, @voltro/cli** — - **`@voltro/testing`'s context now provides `ctx.load` / `ctx.loadMany`.** The request-scoped batching helpers were added to the handler context but not to the test harness, so `makeTestContext` no longer satisfied `AppContext` — any suite building a context failed to typecheck, and a handler calling `ctx.load` could not be tested at all. The harness now builds a loader over the same underlying store (one per context, so `withSubject` / `withTenant` can't serve one subject's cached rows to another), mirroring the real builder. - **The `.one()` codemod is declared for 0.5.0, not 0.4.0.** It was authored while 0.4.0 was unreleased; since codemods are selected with `from < version <= to`, leaving it at 0.4.0 would have silently skipped it for everyone upgrading from 0.4.0 — exactly the users who need it.
60
+
61
+ ---
62
+
42
63
  ## [0.4.0] — 2026-07-18
43
64
 
44
65
  ### ⚠ BREAKING
package/dist/index.d.ts CHANGED
@@ -29,6 +29,12 @@ export declare const ADMIN_SCOPE = "admin:full";
29
29
 
30
30
  export declare const anonymousSubject: (tenantId: string | null) => Subject;
31
31
 
32
+ /** A guard entry is either a scope check or a relationship check. */
33
+ export declare type AnyCheckSpec = GuardCheckSpec | PolicyCheckSpec;
34
+
35
+ /** A guard is either a scope check or a relationship check. */
36
+ export declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input>;
37
+
32
38
  /**
33
39
  * Apply a `RowPatch` to `prev`, producing `next`. Exact inverse of
34
40
  * `diffRows`: `applyRowPatch(prev, diffRows(prev, next))` deep-equals
@@ -171,7 +177,7 @@ export declare const checkFrameworkCompat: (pluginName: string, range: string |
171
177
  * mutations, before the transaction opens). The `admin:full` bypass passes
172
178
  * everything.
173
179
  */
174
- export declare const checkGuards: (subject: Subject, guards: ReadonlyArray<GuardCheckSpec> | undefined) => ScopeError | null;
180
+ export declare const checkGuards: (subject: Subject, guards: ReadonlyArray<AnyCheckSpec> | undefined) => ScopeError | null;
175
181
 
176
182
  /**
177
183
  * The resource-aware counterpart of `checkGuards`. Identical semantics when no
@@ -188,7 +194,7 @@ export declare const checkGuards: (subject: Subject, guards: ReadonlyArray<Guard
188
194
  * every guard passes. The runtime calls this in the dispatch spine BEFORE the
189
195
  * executor (mutations: before the transaction opens).
190
196
  */
191
- export declare const checkGuardsEffect: (subject: Subject, guards: ReadonlyArray<GuardCheckSpec> | undefined, input: unknown) => Effect.Effect<ScopeError | null>;
197
+ export declare const checkGuardsEffect: (subject: Subject, guards: ReadonlyArray<AnyCheckSpec> | undefined, input: unknown) => Effect.Effect<ScopeError | null>;
192
198
 
193
199
  export declare interface ClientDescriptor {
194
200
  readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'workflow';
@@ -499,6 +505,9 @@ export declare const failIdempotent: (store: IdempotencyStore, scope: string, ke
499
505
  /** Record a completed response for replay. */
500
506
  export declare const finishIdempotent: (store: IdempotencyStore, scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
501
507
 
508
+ /** The currently-registered policy-guard resolver, or `undefined`. */
509
+ export declare const getPolicyGuardResolver: () => PolicyGuardResolver | undefined;
510
+
502
511
  /** The currently-registered resource-scope resolver, or `undefined`. */
503
512
  export declare const getResourceScopeResolver: () => ResourceScopeResolver | undefined;
504
513
 
@@ -517,7 +526,7 @@ export declare interface GuardCheckSpec {
517
526
 
518
527
  /** One or more declarative guards on a descriptor. All must pass (AND across
519
528
  * entries; `mode` controls AND/OR WITHIN one entry's scope array). */
520
- export declare type Guards<Input = unknown> = ReadonlyArray<GuardSpec<Input>>;
529
+ export declare type Guards<Input = unknown> = ReadonlyArray<AnyGuardSpec<Input>>;
521
530
 
522
531
  export declare interface GuardSpec<Input = unknown> {
523
532
  /** Required permission scope(s). A single string, or an array combined by
@@ -683,6 +692,12 @@ export declare interface InsertTarget<Input = unknown, Row = unknown, Item = Rec
683
692
  * diffed by id and falls back to shipping the full data. */
684
693
  export declare const isIdKeyed: (rows: ReadonlyArray<Readonly<Record<string, unknown>>>) => rows is ReadonlyArray<PatchRow>;
685
694
 
695
+ /** Runtime narrowing for the relationship variant. */
696
+ export declare const isPolicyCheck: (g: AnyCheckSpec) => g is PolicyCheckSpec;
697
+
698
+ /** Narrow a guard entry to the relationship variant. */
699
+ export declare const isPolicyGuard: <I>(g: AnyGuardSpec<I>) => g is PolicyGuardSpec<I>;
700
+
686
701
  export declare const isSystemSubject: (subject: Subject) => boolean;
687
702
 
688
703
  /** In-memory store — the default for single-process dev + the test double. */
@@ -1341,6 +1356,61 @@ export declare interface PluginTemplate {
1341
1356
  */
1342
1357
  export declare type PluginUninstallHook = (ctx: PluginLifecycleContext) => Effect.Effect<void, unknown> | Promise<void> | void;
1343
1358
 
1359
+ /** The runtime-erased form of `PolicyGuardSpec` — a relationship check. */
1360
+ export declare interface PolicyCheckSpec {
1361
+ readonly action: string;
1362
+ readonly resourceType: string;
1363
+ readonly resource: (input: unknown) => string | undefined;
1364
+ }
1365
+
1366
+ /** The question the runtime asks a registered policy-guard resolver. */
1367
+ export declare interface PolicyGuardRequest {
1368
+ readonly subject: Subject;
1369
+ /** The action named in the resource policy's `actions` map. */
1370
+ readonly action: string;
1371
+ readonly resourceType: string;
1372
+ /** The id the guard's pure `resource` extractor produced. */
1373
+ readonly resourceId: string;
1374
+ }
1375
+
1376
+ /**
1377
+ * Answers "does `subject` have `action` on `<resourceType>:<resourceId>`".
1378
+ * Registered once at boot via `setPolicyGuardResolver`. A failed Effect is
1379
+ * treated as a DENIAL — a resolver wanting another posture catches its own
1380
+ * errors.
1381
+ */
1382
+ export declare type PolicyGuardResolver = (req: PolicyGuardRequest) => Effect.Effect<boolean, unknown>;
1383
+
1384
+ /**
1385
+ * A relationship (ReBAC) guard — "may this subject perform ACTION on THIS row?"
1386
+ *
1387
+ * The scope guard above answers "what may this subject do at all"; this answers
1388
+ * "on which row". Both live in the same `guards:` array on purpose, because the
1389
+ * alternative is what apps actually built: a hand-maintained map from rpc tag →
1390
+ * policy rule, installed as an interceptor. That map is FAIL-OPEN BY OMISSION —
1391
+ * add an rpc, forget the entry, and it is silently unguarded. A declaration on
1392
+ * the descriptor cannot be forgotten for an rpc that exists, because it IS the
1393
+ * rpc.
1394
+ *
1395
+ * Browser-safe by the same construction as `GuardSpec`: strings plus a PURE
1396
+ * `resource` extractor. Resolution — reading relationship tuples, applying the
1397
+ * policy's `implies` closure — happens server-side through the registered
1398
+ * policy resolver. With no resolver registered the check FAILS CLOSED: an
1399
+ * unanswerable authorization question is a denial, never a pass.
1400
+ *
1401
+ * Because it is data, the same declaration compiles into the capability
1402
+ * manifest the client reads, so a UI gate and the server check cannot drift.
1403
+ */
1404
+ export declare interface PolicyGuardSpec<Input = unknown> {
1405
+ /** The action to authorize, as named in the resource policy's `actions`. */
1406
+ readonly action: string;
1407
+ /** Which registered resource policy governs the check. */
1408
+ readonly resourceType: string;
1409
+ /** PURE `input → resource id`. Returning undefined DENIES — an unidentifiable
1410
+ * resource is not a reason to skip the check. */
1411
+ readonly resource: (input: Input) => string | undefined;
1412
+ }
1413
+
1344
1414
  export declare type ProcedureDescriptor = QueryProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | MutationProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | ActionProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | StreamProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All>;
1345
1415
 
1346
1416
  export declare const PROTOCOL_VERSION: 1;
@@ -1736,6 +1806,11 @@ export declare type ServerErrorSource = 'rest' | 'aggregate' | 'subscriber' | 'r
1736
1806
  */
1737
1807
  export declare const setEffectiveScopes: (subject: Subject, scopes: ReadonlyArray<string>) => void;
1738
1808
 
1809
+ /** Register (or clear) the process-global policy-guard resolver. Last write
1810
+ * wins. The runtime installs one backed by the resource-policy registry and
1811
+ * the registered tuple source. */
1812
+ export declare const setPolicyGuardResolver: (resolver: PolicyGuardResolver | undefined) => void;
1813
+
1739
1814
  /**
1740
1815
  * Register (or clear, with `undefined`) the process-global resource-scope
1741
1816
  * resolver. Call once at boot — from `@voltro/plugin-rbac`'s wiring, or directly
package/dist/index.js CHANGED
@@ -1,30 +1,30 @@
1
- import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as ee, l as s, m as te, n as ne, o as re, p as ie, r as ae, s as oe, t as c, u as se, v as ce, y as le } from "./serverErrorBus-0wjvKwGO.js";
2
- import { a as ue, c as de, d as fe, f as pe, i as me, l as he, n as ge, o as _e, r as ve, s as ye, t as be, u as l } from "./auth-DCE6m7Bo.js";
3
- import { Context as u, Layer as xe, Schema as d } from "effect";
4
- import { Rpc as f } from "@effect/rpc";
1
+ import { S as e, _ as t, a as n, b as r, c as i, d as a, f as o, g as ee, h as te, i as ne, l as s, m as c, n as re, o as ie, p as ae, r as oe, s as se, t as ce, u as le, v as ue, x as de, y as fe } from "./serverErrorBus-XDdIJk0c.js";
2
+ import { a as pe, c as me, d as he, f as ge, i as _e, l as ve, n as ye, o as be, r as xe, s as Se, t as Ce, u as we } from "./auth-DCE6m7Bo.js";
3
+ import { Context as Te, Layer as Ee, Schema as l } from "effect";
4
+ import { Rpc as u } from "@effect/rpc";
5
5
  //#region src/rowPatch.ts
6
- var Se = d.Union(d.String, d.Number), p = d.Record({
7
- key: d.String,
8
- value: d.Unknown
9
- }).pipe(d.filter((e) => "id" in e, { message: () => "patch row must carry an id" })), m = d.Union(d.Struct({
10
- op: d.Literal("add"),
11
- path: d.String,
12
- value: p
13
- }), d.Struct({
14
- op: d.Literal("replace"),
15
- path: d.String,
16
- value: p
17
- }), d.Struct({
18
- op: d.Literal("remove"),
19
- path: d.String
20
- })), h = d.Struct({
21
- ops: d.Array(m),
22
- order: d.Array(Se)
23
- }), g = (e) => `/${String(e).replace(/~/g, "~0").replace(/\//g, "~1")}`, Ce = (e) => (e.startsWith("/") ? e.slice(1) : e).replace(/~1/g, "/").replace(/~0/g, "~"), _ = (e) => {
6
+ var De = l.Union(l.String, l.Number), d = l.Record({
7
+ key: l.String,
8
+ value: l.Unknown
9
+ }).pipe(l.filter((e) => "id" in e, { message: () => "patch row must carry an id" })), f = l.Union(l.Struct({
10
+ op: l.Literal("add"),
11
+ path: l.String,
12
+ value: d
13
+ }), l.Struct({
14
+ op: l.Literal("replace"),
15
+ path: l.String,
16
+ value: d
17
+ }), l.Struct({
18
+ op: l.Literal("remove"),
19
+ path: l.String
20
+ })), p = l.Struct({
21
+ ops: l.Array(f),
22
+ order: l.Array(De)
23
+ }), m = (e) => `/${String(e).replace(/~/g, "~0").replace(/\//g, "~1")}`, Oe = (e) => (e.startsWith("/") ? e.slice(1) : e).replace(/~1/g, "/").replace(/~0/g, "~"), h = (e) => {
24
24
  let t = Object.keys(e).sort(), n = {};
25
25
  for (let r of t) n[r] = e[r];
26
26
  return JSON.stringify(n);
27
- }, we = (e, t) => _(e) === _(t), Te = (e, t) => {
27
+ }, ke = (e, t) => h(e) === h(t), Ae = (e, t) => {
28
28
  let n = /* @__PURE__ */ new Map();
29
29
  for (let t of e) n.set(t.id, t);
30
30
  let r = [], i = [], a = /* @__PURE__ */ new Set();
@@ -33,26 +33,26 @@ var Se = d.Union(d.String, d.Number), p = d.Record({
33
33
  let t = n.get(e.id);
34
34
  t === void 0 ? r.push({
35
35
  op: "add",
36
- path: g(e.id),
36
+ path: m(e.id),
37
37
  value: e
38
- }) : we(t, e) || r.push({
38
+ }) : ke(t, e) || r.push({
39
39
  op: "replace",
40
- path: g(e.id),
40
+ path: m(e.id),
41
41
  value: e
42
42
  });
43
43
  }
44
44
  for (let t of e) a.has(t.id) || r.push({
45
45
  op: "remove",
46
- path: g(t.id)
46
+ path: m(t.id)
47
47
  });
48
48
  return {
49
49
  ops: r,
50
50
  order: i
51
51
  };
52
- }, Ee = (e) => e.every((e) => {
52
+ }, je = (e) => e.every((e) => {
53
53
  let t = e.id;
54
54
  return typeof t == "string" || typeof t == "number";
55
- }), De = (e, t) => {
55
+ }), Me = (e, t) => {
56
56
  let n = /* @__PURE__ */ new Map();
57
57
  for (let t of e) n.set(t.id, t);
58
58
  for (let e of t.ops) (e.op === "add" || e.op === "replace") && n.set(e.value.id, e.value);
@@ -62,82 +62,82 @@ var Se = d.Union(d.String, d.Number), p = d.Record({
62
62
  t !== void 0 && r.push(t);
63
63
  }
64
64
  return r;
65
- }, v = (e) => d.Union(d.Struct({
66
- _tag: d.Literal("snapshot"),
67
- revision: d.Number,
65
+ }, g = (e) => l.Union(l.Struct({
66
+ _tag: l.Literal("snapshot"),
67
+ revision: l.Number,
68
68
  data: e,
69
- computed: d.optional(d.Boolean)
70
- }), d.Struct({
71
- _tag: d.Literal("delta"),
72
- revision: d.Number,
73
- emittedAt: d.Number,
74
- patch: h
75
- }), d.Struct({
76
- _tag: d.Literal("error"),
77
- error: d.Unknown,
78
- revision: d.optional(d.Number)
79
- })), y = (e) => ({
69
+ computed: l.optional(l.Boolean)
70
+ }), l.Struct({
71
+ _tag: l.Literal("delta"),
72
+ revision: l.Number,
73
+ emittedAt: l.Number,
74
+ patch: p
75
+ }), l.Struct({
76
+ _tag: l.Literal("error"),
77
+ error: l.Unknown,
78
+ revision: l.optional(l.Number)
79
+ })), Ne = (e) => e.action !== void 0 && e.resourceType !== void 0, _ = (e) => ({
80
80
  kind: "query",
81
81
  name: e.name,
82
82
  input: e.input,
83
83
  output: e.output,
84
- error: e.error ?? d.Never,
84
+ error: e.error ?? l.Never,
85
85
  source: e.source,
86
86
  cache: e.cache,
87
87
  guards: e.guards,
88
88
  publicApi: e.publicApi,
89
89
  exposeAsTool: e.exposeAsTool
90
- }), b = (e) => ({
90
+ }), v = (e) => ({
91
91
  kind: "mutation",
92
92
  name: e.name,
93
93
  input: e.input,
94
94
  output: e.output,
95
- error: e.error ?? d.Never,
95
+ error: e.error ?? l.Never,
96
96
  target: e.target,
97
97
  guards: e.guards,
98
98
  publicApi: e.publicApi,
99
99
  exposeAsTool: e.exposeAsTool
100
- }), x = (e) => ({
100
+ }), y = (e) => ({
101
101
  kind: "action",
102
102
  name: e.name,
103
103
  input: e.input,
104
104
  output: e.output,
105
- error: e.error ?? d.Never,
105
+ error: e.error ?? l.Never,
106
106
  guards: e.guards,
107
107
  publicApi: e.publicApi,
108
108
  exposeAsTool: e.exposeAsTool
109
- }), Oe = (e) => ({
109
+ }), b = (e) => ({
110
110
  kind: "stream",
111
111
  name: e.name,
112
112
  input: e.input,
113
113
  element: e.element,
114
- error: e.error ?? d.Never
115
- }), S = (e, t) => t && t.length > 0 ? d.Union(e, ...t) : e, C = (e, t) => t && t.length > 0 ? d.Union(e, s) : e, w = (e, t) => f.make(e.name, {
114
+ error: e.error ?? l.Never
115
+ }), x = (e, t) => t && t.length > 0 ? l.Union(e, ...t) : e, S = (e, t) => t && t.length > 0 ? l.Union(e, s) : e, C = (e, t) => u.make(e.name, {
116
116
  payload: e.input,
117
- success: v(e.output),
118
- error: S(C(e.error, e.guards), t),
117
+ success: g(e.output),
118
+ error: x(S(e.error, e.guards), t),
119
119
  stream: !0
120
- }), T = (e, t) => f.make(e.name, {
120
+ }), w = (e, t) => u.make(e.name, {
121
121
  payload: e.input,
122
122
  success: e.output,
123
- error: S(C(e.error, e.guards), t)
124
- }), E = (e, t) => f.make(e.name, {
123
+ error: x(S(e.error, e.guards), t)
124
+ }), T = (e, t) => u.make(e.name, {
125
125
  payload: e.input,
126
126
  success: e.output,
127
- error: S(C(e.error, e.guards), t)
128
- }), D = (e, t) => f.make(e.name, {
127
+ error: x(S(e.error, e.guards), t)
128
+ }), E = (e, t) => u.make(e.name, {
129
129
  payload: e.input,
130
130
  success: e.element,
131
- error: S(e.error, t),
131
+ error: x(e.error, t),
132
132
  stream: !0
133
- }), ke = (e) => {
133
+ }), Pe = (e) => {
134
134
  switch (e.kind) {
135
- case "query": return w(e);
136
- case "mutation": return T(e);
137
- case "action": return E(e);
138
- case "stream": return D(e);
135
+ case "query": return C(e);
136
+ case "mutation": return w(e);
137
+ case "action": return T(e);
138
+ case "stream": return E(e);
139
139
  }
140
- }, Ae = (e) => {
140
+ }, Fe = (e) => {
141
141
  let t = e.input, n = t === void 0 ? {} : { input: t }, r = e.output, i = r === void 0 ? {} : { output: r };
142
142
  if (e.kind === "query") return {
143
143
  kind: "query",
@@ -178,15 +178,15 @@ var Se = d.Union(d.String, d.Number), p = d.Record({
178
178
  ..."shapeItem" in e && e.shapeItem !== void 0 ? { shapeItem: e.shapeItem } : {}
179
179
  }))
180
180
  };
181
- }, je = (e) => e, Me = (e) => e, Ne = (e) => {
181
+ }, Ie = (e) => e, Le = (e) => e, Re = (e) => {
182
182
  if (e.length !== 0) return (t, n) => e.reduceRight((e, t) => t(e, n), t);
183
- }, Pe = (e, t) => {
184
- let n = u.GenericTag(e);
183
+ }, ze = (e, t) => {
184
+ let n = Te.GenericTag(e);
185
185
  return {
186
186
  Tag: n,
187
- Live: xe.succeed(n, t)
187
+ Live: Ee.succeed(n, t)
188
188
  };
189
- }, Fe = (e, t, n) => {
189
+ }, D = (e, t, n) => {
190
190
  if (!t) return { ok: !0 };
191
191
  let r = O(n);
192
192
  if (!r) return {
@@ -196,7 +196,7 @@ var Se = d.Union(d.String, d.Number), p = d.Record({
196
196
  let i = t.trim();
197
197
  if (i === "*" || i === "") return { ok: !0 };
198
198
  let a = i.split(/\s+/).filter((e) => e.length > 0);
199
- for (let i of a) if (!Ie(i, r)) return {
199
+ for (let i of a) if (!Be(i, r)) return {
200
200
  ok: !1,
201
201
  reason: `plugin "${e}" requires framework ${t}, running ${n}`
202
202
  };
@@ -209,7 +209,7 @@ var Se = d.Union(d.String, d.Number), p = d.Record({
209
209
  patch: Number(t[3]),
210
210
  pre: t[4] ?? ""
211
211
  } : null;
212
- }, k = (e, t) => e.major === t.major ? e.minor === t.minor ? e.patch === t.patch ? e.pre && !t.pre ? -1 : !e.pre && t.pre ? 1 : e.pre && t.pre ? e.pre < t.pre ? -1 : +(e.pre > t.pre) : 0 : e.patch - t.patch : e.minor - t.minor : e.major - t.major, Ie = (e, t) => {
212
+ }, k = (e, t) => e.major === t.major ? e.minor === t.minor ? e.patch === t.patch ? e.pre && !t.pre ? -1 : !e.pre && t.pre ? 1 : e.pre && t.pre ? e.pre < t.pre ? -1 : +(e.pre > t.pre) : 0 : e.patch - t.patch : e.minor - t.minor : e.major - t.major, Be = (e, t) => {
213
213
  if (e === "*") return !0;
214
214
  if (e.startsWith("^")) {
215
215
  let n = O(e.slice(1));
@@ -231,173 +231,173 @@ var Se = d.Union(d.String, d.Number), p = d.Record({
231
231
  }
232
232
  let r = O(e);
233
233
  return r ? k(t, r) === 0 : !1;
234
- }, A = d.Literal("running", "succeeded", "failed", "cancelled", "suspended"), j = d.Literal("cancel", "terminate", "abandon"), Le = d.Struct({
235
- id: d.String,
236
- workflowName: d.String,
237
- executionId: d.String,
238
- status: d.Literal("running")
239
- }), M = d.Struct({
240
- id: d.String,
241
- tag: d.String,
242
- executionId: d.String,
234
+ }, A = l.Literal("running", "succeeded", "failed", "cancelled", "suspended"), j = l.Literal("cancel", "terminate", "abandon"), Ve = l.Struct({
235
+ id: l.String,
236
+ workflowName: l.String,
237
+ executionId: l.String,
238
+ status: l.Literal("running")
239
+ }), M = l.Struct({
240
+ id: l.String,
241
+ tag: l.String,
242
+ executionId: l.String,
243
243
  status: A,
244
- payload: d.Unknown,
245
- workflowVersion: d.NullOr(d.String),
246
- workflowPatches: d.NullOr(d.Unknown),
247
- output: d.NullOr(d.Unknown),
248
- subject: d.NullOr(d.Unknown),
249
- source: d.NullOr(d.String),
250
- errorTag: d.NullOr(d.String),
251
- errorMessage: d.NullOr(d.String),
252
- cancelled: d.Boolean,
253
- startedAt: d.Date,
254
- completedAt: d.NullOr(d.Date),
255
- durationMs: d.NullOr(d.Number),
256
- traceId: d.NullOr(d.String),
257
- parentExecutionId: d.NullOr(d.String),
258
- parentClosePolicy: d.NullOr(j)
259
- }), N = d.Struct({
260
- tag: d.optional(d.String),
261
- status: d.optional(A),
262
- limit: d.optional(d.Number)
263
- }), P = d.Struct({
264
- id: d.String,
265
- runId: d.String,
266
- stepName: d.String,
267
- attempt: d.Number,
268
- status: d.Literal("running", "succeeded", "failed"),
269
- input: d.NullOr(d.Unknown),
270
- retryPolicy: d.NullOr(d.Unknown),
271
- output: d.NullOr(d.Unknown),
272
- errorTag: d.NullOr(d.String),
273
- errorMessage: d.NullOr(d.String),
274
- errorCause: d.NullOr(d.Unknown),
275
- startedAt: d.Date,
276
- completedAt: d.NullOr(d.Date),
277
- durationMs: d.NullOr(d.Number)
278
- }), F = d.Struct({
279
- id: d.String,
280
- runId: d.String,
281
- eventType: d.String,
282
- payload: d.NullOr(d.Unknown),
283
- occurredAt: d.Date,
284
- stepName: d.NullOr(d.String),
285
- attempt: d.NullOr(d.Number)
286
- }), I = d.Struct({
287
- id: d.String,
288
- name: d.String,
289
- payload: d.Unknown,
290
- source: d.String,
291
- subject: d.NullOr(d.Unknown),
292
- traceId: d.NullOr(d.String),
293
- occurredAt: d.Date
294
- }), L = d.Struct({
295
- id: d.String,
296
- eventId: d.String,
297
- eventName: d.String,
298
- triggerId: d.String,
299
- workflowName: d.String,
300
- executionId: d.NullOr(d.String),
301
- status: d.Literal("starting", "started", "skipped", "failed"),
302
- idempotencyKey: d.String,
303
- skipped: d.Boolean,
304
- errorMessage: d.NullOr(d.String),
305
- createdAt: d.Date,
306
- completedAt: d.NullOr(d.Date)
307
- }), R = d.Struct({ id: d.String }), z = d.Struct({ runId: d.String }), B = d.Struct({
308
- name: d.optional(d.String),
309
- limit: d.optional(d.Number)
310
- }), V = d.Struct({ eventId: d.String }), H = d.Struct({
311
- workflowName: d.String,
312
- executionId: d.String
313
- }), U = d.Struct({
314
- id: d.String,
315
- signalName: d.String,
316
- payload: d.optional(d.Unknown)
317
- }), W = d.Struct({
318
- id: d.String,
319
- updateName: d.String,
320
- payload: d.optional(d.Unknown),
321
- timeoutMs: d.optional(d.Number)
322
- }), G = d.Struct({
323
- eventId: d.String,
324
- updateId: d.String,
325
- completedEventId: d.String,
326
- result: d.Unknown
327
- }), Re = y({
244
+ payload: l.Unknown,
245
+ workflowVersion: l.NullOr(l.String),
246
+ workflowPatches: l.NullOr(l.Unknown),
247
+ output: l.NullOr(l.Unknown),
248
+ subject: l.NullOr(l.Unknown),
249
+ source: l.NullOr(l.String),
250
+ errorTag: l.NullOr(l.String),
251
+ errorMessage: l.NullOr(l.String),
252
+ cancelled: l.Boolean,
253
+ startedAt: l.Date,
254
+ completedAt: l.NullOr(l.Date),
255
+ durationMs: l.NullOr(l.Number),
256
+ traceId: l.NullOr(l.String),
257
+ parentExecutionId: l.NullOr(l.String),
258
+ parentClosePolicy: l.NullOr(j)
259
+ }), N = l.Struct({
260
+ tag: l.optional(l.String),
261
+ status: l.optional(A),
262
+ limit: l.optional(l.Number)
263
+ }), P = l.Struct({
264
+ id: l.String,
265
+ runId: l.String,
266
+ stepName: l.String,
267
+ attempt: l.Number,
268
+ status: l.Literal("running", "succeeded", "failed"),
269
+ input: l.NullOr(l.Unknown),
270
+ retryPolicy: l.NullOr(l.Unknown),
271
+ output: l.NullOr(l.Unknown),
272
+ errorTag: l.NullOr(l.String),
273
+ errorMessage: l.NullOr(l.String),
274
+ errorCause: l.NullOr(l.Unknown),
275
+ startedAt: l.Date,
276
+ completedAt: l.NullOr(l.Date),
277
+ durationMs: l.NullOr(l.Number)
278
+ }), F = l.Struct({
279
+ id: l.String,
280
+ runId: l.String,
281
+ eventType: l.String,
282
+ payload: l.NullOr(l.Unknown),
283
+ occurredAt: l.Date,
284
+ stepName: l.NullOr(l.String),
285
+ attempt: l.NullOr(l.Number)
286
+ }), I = l.Struct({
287
+ id: l.String,
288
+ name: l.String,
289
+ payload: l.Unknown,
290
+ source: l.String,
291
+ subject: l.NullOr(l.Unknown),
292
+ traceId: l.NullOr(l.String),
293
+ occurredAt: l.Date
294
+ }), L = l.Struct({
295
+ id: l.String,
296
+ eventId: l.String,
297
+ eventName: l.String,
298
+ triggerId: l.String,
299
+ workflowName: l.String,
300
+ executionId: l.NullOr(l.String),
301
+ status: l.Literal("starting", "started", "skipped", "failed"),
302
+ idempotencyKey: l.String,
303
+ skipped: l.Boolean,
304
+ errorMessage: l.NullOr(l.String),
305
+ createdAt: l.Date,
306
+ completedAt: l.NullOr(l.Date)
307
+ }), R = l.Struct({ id: l.String }), z = l.Struct({ runId: l.String }), B = l.Struct({
308
+ name: l.optional(l.String),
309
+ limit: l.optional(l.Number)
310
+ }), V = l.Struct({ eventId: l.String }), H = l.Struct({
311
+ workflowName: l.String,
312
+ executionId: l.String
313
+ }), U = l.Struct({
314
+ id: l.String,
315
+ signalName: l.String,
316
+ payload: l.optional(l.Unknown)
317
+ }), W = l.Struct({
318
+ id: l.String,
319
+ updateName: l.String,
320
+ payload: l.optional(l.Unknown),
321
+ timeoutMs: l.optional(l.Number)
322
+ }), G = l.Struct({
323
+ eventId: l.String,
324
+ updateId: l.String,
325
+ completedEventId: l.String,
326
+ result: l.Unknown
327
+ }), He = _({
328
328
  name: "__voltro.workflow.run",
329
329
  source: "_voltro_workflow_runs",
330
330
  input: R,
331
- output: d.Array(M)
332
- }), ze = y({
331
+ output: l.Array(M)
332
+ }), Ue = _({
333
333
  name: "__voltro.workflow.runs",
334
334
  source: "_voltro_workflow_runs",
335
335
  input: N,
336
- output: d.Array(M)
337
- }), Be = y({
336
+ output: l.Array(M)
337
+ }), We = _({
338
338
  name: "__voltro.workflow.run.steps",
339
339
  source: "_voltro_workflow_run_steps",
340
340
  input: z,
341
- output: d.Array(P)
342
- }), Ve = y({
341
+ output: l.Array(P)
342
+ }), Ge = _({
343
343
  name: "__voltro.workflow.run.events",
344
344
  source: "_voltro_workflow_run_events",
345
345
  input: z,
346
- output: d.Array(F)
347
- }), He = y({
346
+ output: l.Array(F)
347
+ }), Ke = _({
348
348
  name: "__voltro.workflow.domainEvents",
349
349
  source: "_voltro_workflow_events",
350
350
  input: B,
351
- output: d.Array(I)
352
- }), Ue = y({
351
+ output: l.Array(I)
352
+ }), qe = _({
353
353
  name: "__voltro.workflow.event.deliveries",
354
354
  source: "_voltro_workflow_event_deliveries",
355
355
  input: V,
356
- output: d.Array(L)
357
- }), We = x({
356
+ output: l.Array(L)
357
+ }), Je = y({
358
358
  name: "__voltro.workflow.cancel",
359
359
  input: H,
360
- output: d.Struct({ ok: d.Boolean })
361
- }), Ge = x({
360
+ output: l.Struct({ ok: l.Boolean })
361
+ }), Ye = y({
362
362
  name: "__voltro.workflow.resume",
363
363
  input: H,
364
- output: d.Struct({ ok: d.Boolean })
365
- }), Ke = x({
364
+ output: l.Struct({ ok: l.Boolean })
365
+ }), Xe = y({
366
366
  name: "__voltro.workflow.signal",
367
367
  input: U,
368
- output: d.Struct({ eventId: d.String })
369
- }), qe = x({
368
+ output: l.Struct({ eventId: l.String })
369
+ }), Ze = y({
370
370
  name: "__voltro.workflow.update",
371
371
  input: W,
372
372
  output: G
373
- }), K = "__voltro.undo.log", q = "__voltro.undo.apply", J = "__voltro.undo.redo", Y = d.Struct({
374
- id: d.String,
375
- tag: d.String,
376
- label: d.NullOr(d.String),
377
- undone: d.Boolean,
378
- crossesAction: d.Boolean,
379
- createdAt: d.String
380
- }), X = class extends d.TaggedError()("UndoNotFound", { invocationId: d.String }) {}, Z = class extends d.TaggedError()("UndoForbidden", { invocationId: d.String }) {}, Q = class extends d.TaggedError()("UndoConflict", {
381
- invocationId: d.String,
382
- reason: d.Literal("conflict", "action")
383
- }) {}, $ = d.Union(X, Z, Q), Je = y({
373
+ }), K = "__voltro.undo.log", q = "__voltro.undo.apply", J = "__voltro.undo.redo", Y = l.Struct({
374
+ id: l.String,
375
+ tag: l.String,
376
+ label: l.NullOr(l.String),
377
+ undone: l.Boolean,
378
+ crossesAction: l.Boolean,
379
+ createdAt: l.String
380
+ }), X = class extends l.TaggedError()("UndoNotFound", { invocationId: l.String }) {}, Z = class extends l.TaggedError()("UndoForbidden", { invocationId: l.String }) {}, Q = class extends l.TaggedError()("UndoConflict", {
381
+ invocationId: l.String,
382
+ reason: l.Literal("conflict", "action")
383
+ }) {}, $ = l.Union(X, Z, Q), Qe = _({
384
384
  name: K,
385
385
  source: "_voltro_undo_log",
386
- input: d.Struct({ limit: d.optional(d.Number) }),
387
- output: d.Array(Y)
388
- }), Ye = b({
386
+ input: l.Struct({ limit: l.optional(l.Number) }),
387
+ output: l.Array(Y)
388
+ }), $e = v({
389
389
  name: q,
390
- input: d.Struct({ invocationId: d.String }),
391
- output: d.Struct({ ok: d.Boolean }),
390
+ input: l.Struct({ invocationId: l.String }),
391
+ output: l.Struct({ ok: l.Boolean }),
392
392
  error: $
393
- }), Xe = b({
393
+ }), et = v({
394
394
  name: J,
395
- input: d.Struct({ invocationId: d.String }),
396
- output: d.Struct({ ok: d.Boolean }),
395
+ input: l.Struct({ invocationId: l.String }),
396
+ output: l.Struct({ ok: l.Boolean }),
397
397
  error: $
398
- }), Ze = (e) => {
398
+ }), tt = (e) => {
399
399
  let t = e instanceof Date ? e.getTime() : typeof e == "number" ? e : typeof e == "string" ? new Date(e).getTime() : 0;
400
400
  return Number.isNaN(t) ? 0 : t;
401
- }, Qe = 1;
401
+ }, nt = 1;
402
402
  //#endregion
403
- export { n as ADMIN_SCOPE, be as AuthMiddleware, ge as ConnectionInfo, ve as ConnectionInfoMiddleware, Qe as PROTOCOL_VERSION, s as ScopeError, me as Subject, ue as SubjectService, q as UNDO_APPLY_TAG, K as UNDO_LOG_TAG, J as UNDO_REDO_TAG, _e as Unauthenticated, Q as UndoConflict, Z as UndoForbidden, Y as UndoLogEntry, X as UndoNotFound, H as WorkflowControlInputSchema, I as WorkflowDomainEventRowSchema, B as WorkflowDomainEventsInputSchema, V as WorkflowEventDeliveriesInputSchema, L as WorkflowEventDeliveryRowSchema, j as WorkflowParentClosePolicySchema, F as WorkflowRunEventRowSchema, Le as WorkflowRunHandleSchema, R as WorkflowRunRefSchema, M as WorkflowRunRowSchema, A as WorkflowRunStatusSchema, P as WorkflowRunStepRowSchema, z as WorkflowRunTableRefSchema, N as WorkflowRunsInputSchema, U as WorkflowSignalInputSchema, W as WorkflowUpdateInputSchema, G as WorkflowUpdateResultSchema, E as actionToRpc, ye as anonymousSubject, De as applyRowPatch, de as assertAuthenticated, ae as beginIdempotent, Fe as checkFrameworkCompat, se as checkGuards, r as checkGuardsEffect, he as composeAuthStrategies, Ne as composeRpcInterceptors, x as defineAction, b as defineMutation, je as definePlugin, Me as definePluginRoute, Pe as definePluginService, y as defineQuery, Oe as defineStream, Te as diffRows, i as effectiveScopes, ee as failIdempotent, t as finishIdempotent, ie as getResourceScopeResolver, l as hasCallbackRoutes, te as hasEffectiveScope, o as hasScope, g as idToPath, re as idempotencyScope, Ee as isIdKeyed, fe as isSystemSubject, oe as memoryIdempotencyStore, T as mutationToRpc, Ae as normalizeDescriptor, Ce as pathToId, c as publishServerError, w as queryToRpc, a as requireScope, m as rowPatchOpSchema, h as rowPatchSchema, e as setEffectiveScopes, ce as setResourceScopeResolver, D as streamToRpc, le as subjectScopes, ne as subscribeServerErrors, v as subscriptionEvent, pe as systemSubject, ke as toRpc, Ze as tsMs, Ye as undoApplyDescriptor, Je as undoLogQueryDescriptor, Xe as undoRedoDescriptor, We as workflowCancelDescriptor, He as workflowDomainEventsQueryDescriptor, Ue as workflowEventDeliveriesQueryDescriptor, Ge as workflowResumeDescriptor, Ve as workflowRunEventsQueryDescriptor, Re as workflowRunQueryDescriptor, Be as workflowRunStepsQueryDescriptor, ze as workflowRunsQueryDescriptor, Ke as workflowSignalDescriptor, qe as workflowUpdateDescriptor };
403
+ export { i as ADMIN_SCOPE, Ce as AuthMiddleware, ye as ConnectionInfo, xe as ConnectionInfoMiddleware, nt as PROTOCOL_VERSION, s as ScopeError, _e as Subject, pe as SubjectService, q as UNDO_APPLY_TAG, K as UNDO_LOG_TAG, J as UNDO_REDO_TAG, be as Unauthenticated, Q as UndoConflict, Z as UndoForbidden, Y as UndoLogEntry, X as UndoNotFound, H as WorkflowControlInputSchema, I as WorkflowDomainEventRowSchema, B as WorkflowDomainEventsInputSchema, V as WorkflowEventDeliveriesInputSchema, L as WorkflowEventDeliveryRowSchema, j as WorkflowParentClosePolicySchema, F as WorkflowRunEventRowSchema, Ve as WorkflowRunHandleSchema, R as WorkflowRunRefSchema, M as WorkflowRunRowSchema, A as WorkflowRunStatusSchema, P as WorkflowRunStepRowSchema, z as WorkflowRunTableRefSchema, N as WorkflowRunsInputSchema, U as WorkflowSignalInputSchema, W as WorkflowUpdateInputSchema, G as WorkflowUpdateResultSchema, T as actionToRpc, Se as anonymousSubject, Me as applyRowPatch, me as assertAuthenticated, oe as beginIdempotent, D as checkFrameworkCompat, le as checkGuards, a as checkGuardsEffect, ve as composeAuthStrategies, Re as composeRpcInterceptors, y as defineAction, v as defineMutation, Ie as definePlugin, Le as definePluginRoute, ze as definePluginService, _ as defineQuery, b as defineStream, Ae as diffRows, o as effectiveScopes, ne as failIdempotent, n as finishIdempotent, ae as getPolicyGuardResolver, c as getResourceScopeResolver, we as hasCallbackRoutes, te as hasEffectiveScope, ee as hasScope, m as idToPath, ie as idempotencyScope, je as isIdKeyed, t as isPolicyCheck, Ne as isPolicyGuard, he as isSystemSubject, se as memoryIdempotencyStore, w as mutationToRpc, Fe as normalizeDescriptor, Oe as pathToId, ce as publishServerError, C as queryToRpc, ue as requireScope, f as rowPatchOpSchema, p as rowPatchSchema, fe as setEffectiveScopes, r as setPolicyGuardResolver, de as setResourceScopeResolver, E as streamToRpc, e as subjectScopes, re as subscribeServerErrors, g as subscriptionEvent, ge as systemSubject, Pe as toRpc, tt as tsMs, $e as undoApplyDescriptor, Qe as undoLogQueryDescriptor, et as undoRedoDescriptor, Je as workflowCancelDescriptor, Ke as workflowDomainEventsQueryDescriptor, qe as workflowEventDeliveriesQueryDescriptor, Ye as workflowResumeDescriptor, Ge as workflowRunEventsQueryDescriptor, He as workflowRunQueryDescriptor, We as workflowRunStepsQueryDescriptor, Ue as workflowRunsQueryDescriptor, Xe as workflowSignalDescriptor, Ze as workflowUpdateDescriptor };
package/dist/rest.d.ts CHANGED
@@ -15,6 +15,9 @@ declare interface ActionProcedureDescriptor<Name extends string, Input extends S
15
15
  readonly exposeAsTool: ExposeAsTool | undefined;
16
16
  }
17
17
 
18
+ /** A guard is either a scope check or a relationship check. */
19
+ declare type AnyGuardSpec<Input = unknown> = GuardSpec<Input> | PolicyGuardSpec<Input>;
20
+
18
21
  /**
19
22
  * Project every descriptor carrying a `publicApi` annotation into a REST
20
23
  * route, in stable (path) order. The boot layer feeds these straight into
@@ -77,7 +80,7 @@ declare interface ExposeAsToolSpec {
77
80
 
78
81
  /** One or more declarative guards on a descriptor. All must pass (AND across
79
82
  * entries; `mode` controls AND/OR WITHIN one entry's scope array). */
80
- declare type Guards<Input = unknown> = ReadonlyArray<GuardSpec<Input>>;
83
+ declare type Guards<Input = unknown> = ReadonlyArray<AnyGuardSpec<Input>>;
81
84
 
82
85
  declare interface GuardSpec<Input = unknown> {
83
86
  /** Required permission scope(s). A single string, or an array combined by
@@ -253,6 +256,36 @@ declare interface PluginHttpRouteResult {
253
256
  readonly headers?: Record<string, string>;
254
257
  }
255
258
 
259
+ /**
260
+ * A relationship (ReBAC) guard — "may this subject perform ACTION on THIS row?"
261
+ *
262
+ * The scope guard above answers "what may this subject do at all"; this answers
263
+ * "on which row". Both live in the same `guards:` array on purpose, because the
264
+ * alternative is what apps actually built: a hand-maintained map from rpc tag →
265
+ * policy rule, installed as an interceptor. That map is FAIL-OPEN BY OMISSION —
266
+ * add an rpc, forget the entry, and it is silently unguarded. A declaration on
267
+ * the descriptor cannot be forgotten for an rpc that exists, because it IS the
268
+ * rpc.
269
+ *
270
+ * Browser-safe by the same construction as `GuardSpec`: strings plus a PURE
271
+ * `resource` extractor. Resolution — reading relationship tuples, applying the
272
+ * policy's `implies` closure — happens server-side through the registered
273
+ * policy resolver. With no resolver registered the check FAILS CLOSED: an
274
+ * unanswerable authorization question is a denial, never a pass.
275
+ *
276
+ * Because it is data, the same declaration compiles into the capability
277
+ * manifest the client reads, so a UI gate and the server check cannot drift.
278
+ */
279
+ declare interface PolicyGuardSpec<Input = unknown> {
280
+ /** The action to authorize, as named in the resource policy's `actions`. */
281
+ readonly action: string;
282
+ /** Which registered resource policy governs the check. */
283
+ readonly resourceType: string;
284
+ /** PURE `input → resource id`. Returning undefined DENIES — an unidentifiable
285
+ * resource is not a reason to skip the check. */
286
+ readonly resource: (input: Input) => string | undefined;
287
+ }
288
+
256
289
  /** A descriptor kind that can be projected to REST (streams are not, in v1). */
257
290
  export declare type PublicApiDescriptor = QueryProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | MutationProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All> | ActionProcedureDescriptor<string, Schema.Schema.Any, Schema.Schema.Any, Schema.Schema.All>;
258
291
 
package/dist/rest.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, o as n, r, t as i } from "./serverErrorBus-0wjvKwGO.js";
1
+ import { a as e, i as t, o as n, r, t as i } from "./serverErrorBus-XDdIJk0c.js";
2
2
  import { s as a } from "./auth-DCE6m7Bo.js";
3
3
  import { Effect as o, Schema as s } from "effect";
4
4
  //#region src/publicApi.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/protocol",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "The Voltro wire + plugin contract — defineQuery/Mutation/Action/Stream, definePlugin, sessions / JWT / API-keys, and the RPC protocol.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@effect/sql": "^0.51.1",
56
- "@voltro/database": "0.4.0",
56
+ "@voltro/database": "0.5.0",
57
57
  "jose": "^6.2.3"
58
58
  },
59
59
  "peerDependencies": {