@voltro/protocol 0.79.0 → 0.80.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,140 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.80.0] — 2026-09-24
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **The inspect stream's replica selection is decided before the response** — `@voltro/runtime`, `@voltro/voltro`
47
+
48
+ `InspectStream.select` returns `Effect<InspectStreamSelection | undefined>` instead of a `Stream`, where `InspectStreamSelection` is either `{ kind: 'stream', stream }` or `{ kind: 'refused', status, body }`. A stream response is committed as `200` from its first byte, so a refusal can only be expressed before it starts. The option is wired by `voltro dev`, `voltro serve` and `voltro start`; code that passes its own `inspectStream` to `startRpcServer` wraps its stream in `Effect.succeed({ kind: 'stream', stream })`. `voltro update` lists every line that sets `inspectStream`.
49
+ - **`JiraService.listBoards` finds boards by project, name and type** — `@voltro/plugin-atlassian`
50
+
51
+ Every board method of `JiraService` took a board id, so a board picker had nothing to list and people looked ids up in Jira by hand. `listBoards({ projectKeyOrId?, name?, type? })` calls `GET /rest/agile/1.0/board`, follows the pages itself, and returns each board as `{ id, name, type, location? }`, where `location` names the project the board is located in. `name` is a contains match on Jira's side. It runs on the caller's own credentials like every other method, so a board the caller may not see is not listed. The break is for code that provides the service itself: a test stub written against the full `JiraServiceShape` needs the new method.
52
+
53
+ **`voltro update` carries you across this** — codemod `0.80.0/03_jira-service-lists-boards`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.80.0).
54
+ - **A loader has `ctx.query` in the browser, so a clicked-to page keeps its title** — `@voltro/web`, `@voltro/client`, `@voltro/protocol`, `@voltro/cli`
55
+
56
+ `ctx.query` was `undefined` when a loader ran in the browser. A page whose loader resolved an entity's name for `meta` therefore showed it in the title on a fresh load and dropped it after a click inside the app: the same URL, two titles. The browser loader now gets `ctx.query` too. It reads through the page's own api client, so it carries the visitor's session. A query resolves to its first snapshot, as on the server, and an in-band error rejects with the typed error on `voltroError`. A newer navigation that replaces the one in flight now aborts the loader's `signal`, as `LoaderContext.signal` always promised, and that closes the read. What changes for existing code is a check on `query`'s presence: `if (query)`, `query ? … : …` and `query?.(…)` now take the resolving branch in the browser too. Branch on `ctx.isServer` where the check meant "this is the server". `voltro update` lists every such line in your pages and layouts.
57
+
58
+ Bundle size: the browser read path adds 1.6 KB gz to a web app's first load (194.6 → 196.2 KB gz on the framework's zero-procedure fixture). Because the router now imports the api client, the shared chunk that carries the Effect runtime is emitted as `router-*.js` instead of `index-*.js`; the bytes moved between the two files, they did not grow.
59
+ - **No framework loading spinner; useNavigation() exposes navigation state** — `@voltro/web`
60
+
61
+ `FrameworkBoot` mounted a corner `NavigationIndicator` in every web app, in development and production, so a spinner appeared at the bottom right of pages whose design never included one — also in apps that had switched the devtools off. It is no longer mounted, and `NavigationIndicator` is removed from `@voltro/web`. In development the devtools button shows navigation progress as before. To build your own cue, read the new `useNavigation()` hook: `state` is `'loading'` while the router keeps the current page on screen for a target whose page module or loaders are not ready, and `pendingPathname` names that target; a server render is always `'idle'`. For a reconnecting api use `useConnectionStatus(apiName)` from `@voltro/client`. `voltro update` lists every file that still imports `NavigationIndicator`.
62
+ - **DB_MAX_CONNECTIONS is what the whole process holds, not the size of each pool** — `@voltro/cli`, `@voltro/workflow`, `@voltro/database`
63
+
64
+ Every framework user of the database opened its own pool: the store, the plugin bind context, the workflow runtime, aggregates, analytics, and the boot steps that migrate or check the schema. Each was sized by `DB_MAX_CONNECTIONS`, so one process could hold several times the number its boot line printed. Measured on postgres at `DB_MAX_CONNECTIONS=3`: the line promised 4 and the database saw 8 connections from `voltro dev` and from `voltro serve`. Two pods budgeted at 5 against a pooler of 15 both failed to acquire in the same second.
65
+
66
+ The pools were built by dialect-independent code, and the repair is measured the same way on every dialect: under boot and a burst of concurrent requests, postgres, mariadb, mysql and mssql each hold exactly what the line prints.
67
+
68
+ In `voltro dev` and `voltro serve` they now share one pool per database target, so the boot line's number is what the process holds. Short-lived commands such as `voltro data restore` keep their own pool, because they close it to get fresh sessions. A value tuned per pool buys fewer connections in total. Read replicas, a separate migration URL (`DB_DIRECT_URL` / `DB_MIGRATE_URL`), a provisioned tenant's database and pools your own code builds stay separate.
69
+
70
+ The workflow runtime needs 3 connections at once (2 with `VOLTRO_WORKFLOW_RUNNER_STORAGE=memory`). Below that the boot now refuses and names the number; it used to wait out the acquire timeout and exit on `Failed to acquire connection`. `sqlPoolCapacity` is exported from `@voltro/database/sql` beside `registerSqlPoolCapacity`, an addition.
71
+
72
+ **`voltro update` carries you across this** — codemod `0.80.0/01_db-max-connections-is-the-process-budget`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.80.0).
73
+
74
+ ### Added
75
+
76
+ - **security.clientAddressHeader reads a proxy's single-address header** — `@voltro/runtime`, `@voltro/cli`
77
+
78
+ `security.clientAddressHeader` (env `VOLTRO_CLIENT_ADDRESS_HEADER`) names a header a trusted proxy sets to the client's address, such as `cf-connecting-ip` or `x-real-ip`. It is believed only from a peer covered by `security.trustedProxies` and only when it holds one valid IP; otherwise `x-forwarded-for` applies as before. Naming a header without declaring a trusted proxy refuses the boot.
79
+ - **Rate-limit rules can key on the caller's address** — `@voltro/plugin-ratelimit`, `@voltro/protocol`, `@voltro/runtime`, `@voltro/cli`
80
+
81
+ A rule can now use `by: 'ip'`, one bucket per client address. Before sign-in every caller is the same anonymous subject, so `'subject'` put them all in one bucket and one client could use up a sign-in or invite endpoint for everyone. The address is the one the listener resolves through `security.trustedProxies`, and plugin interceptors receive it as `RpcInterceptorContext.remoteAddr` on WebSocket, `POST /rpc` and `publicApi` REST calls; REST handlers get it as `RestRouteContext.remoteAddr`. A client cannot set it. Calls that do not arrive over the network share one `unknown` bucket. `KeyBy` gains the `'ip'` member: every value that type-checked before still does; only code that exhaustively switches over `KeyBy` needs the new case.
82
+
83
+ ### Changed
84
+
85
+ - **Server-side rpc calls carry the visitor's address** — `@voltro/cli`
86
+
87
+ A loader's `ctx.query`, a page's `preload`, and the no-JavaScript form post now send `x-forwarded-for` to the api: the chain the request arrived with plus the address that delivered it to the web server. An api whose `security.trustedProxies` covers the web tier therefore rate-limits and audits these calls per visitor instead of counting every visitor against the web server. An api that trusts no proxy ignores the header, so its behaviour is unchanged. A visitor-written `x-forwarded-for` is never forwarded on its own.
88
+
89
+ ### Fixed
90
+
91
+ - **The boot's database connection line names the dialect in use** — `@voltro/cli`
92
+
93
+ A database configured with `DB_HOST` and friends was logged as `db connection: postgres://…` whatever the dialect, so a SQL Server or MySQL connection read as a postgres one. The line now starts with the dialect's name.
94
+ - **The dashboard's live streams share one browser connection** — `@voltro/cli`
95
+
96
+ The DevTools dashboard opened one event-stream connection per app. Over HTTP/1.1 a browser holds at most six connections per origin across all tabs, so with six apps — or three in two tabs — the logs and overview pages held every connection the dashboard had, and each later request waited indefinitely: a click on another page changed the URL, kept the old page on screen and left the navigation indicator spinning. Every stream a page holds now travels on one connection: the page posts its targets, each with its own app bearer, to `/api/dashboard/proxy/streams`, and `voltro dev` and `voltro start` fan the apps in behind it, each reconnecting on its own backoff. `/api/dashboard/proxy/stream` is removed; `@voltro/dashboard` of the same release uses the new route.
97
+ - **`meta` keeps the rendered language after hydration without a locale cookie** — `@voltro/web`, `@voltro/cli`
98
+
99
+ In the browser, `meta` received the `voltro:locale` cookie's locale and otherwise a fixed `'en'`. A first visit carries no cookie, so the server rendered the title in the language it negotiated (`Accept-Language`, else `defaultLocale`), and after hydration the tab switched to English. `meta` now falls back to the locale the server rendered the document with, which it stamps on `<html lang>`, and the cookie still wins when a language switch has written one. A page `voltro serve` answers with the unrendered shell (a page the prerender did not produce) declared `lang="en"` in every app, and the client took that for the server's choice. The shell now declares the app's `defaultLocale`, or its first declared locale.
100
+ - **The first `voltro dev` on a fresh SQL Server database boots with CDC on** — `@voltro/sql-mssql`
101
+
102
+ With `changeStrategy: 'cdc'`, the default, the Change Tracking reader started before auto-migrate created the schema and enabled tracking on tables that did not exist yet. The boot died on `Failed to execute statement`, so every new project on SQL Server failed its first `voltro dev`. The same database booted once with `CDC=0` worked afterwards.
103
+
104
+ The reader now enables tracking on the tables that exist and picks up the rest on the poll that first finds them, reading each from its own tracking floor. A table the migration creates during the boot is tracked from then on. `ensureChangeTracking` resolves to the tables it could not enable yet.
105
+ - **Replica observations of ended processes are deleted after a day** — `@voltro/cli`, `@voltro/runtime`, `@voltro/database`
106
+
107
+ `_voltro_replica_observations` had no retention. A replica deletes its own rows on a graceful shutdown, but a process that crashed or was killed left them behind, and a replica ID that does not survive a restart — `dev-<pid>` under `voltro dev`, a pod replaced under a new name — never overwrites them. Every such row stayed listed as `departed` in each fleet read indefinitely. The retention sweep now deletes rows not rewritten for 24 hours on both boot paths and every dialect; live replicas rewrite theirs every 30 seconds. `VOLTRO_REPLICA_OBSERVATIONS_TTL_HOURS` changes the window, with a floor of one hour, and the boot line lists the policy with the others.
108
+ - **A live stream for an ended replica is refused instead of reconnecting forever** — `@voltro/cli`, `@voltro/devtools-ui`
109
+
110
+ `/_voltro/inspect/stream?replica=<id>` now resolves the named replica and verifies its handshake before it answers, with the same refusals as a read: `404` for an unknown ID or one without a reachable address (no request leaves the process), `504` for a peer that does not accept the connection, `502` for a peer that answers with another process's feed. Previously the API answered `200 text/event-stream` first and found the missing peer inside the body, so a dashboard still naming a process from before a restart received a stream that closed at once, reconnected on backoff indefinitely, and left a `Fiber terminated with an unhandled error` in the target's log on every attempt. The replica selector in the local and Cloud dashboards now also removes a selected replica once the fleet no longer lists it — the process ended and its ID never answers again — names the removed IDs, and returns an emptied selection to All replicas. A replica that is only unavailable (stale, without a reachable address, or expected by the fleet without an answer) stays selected.
111
+ - **A controlled workflow start without an acknowledgement returns its receipt** — `@voltro/runtime`, `@voltro/voltro`, `@voltro/workflow`, `@voltro/cli`, `@voltro/protocol`
112
+
113
+ When the engine dispatch of a start with flow controls failed without a definite answer, such as an exhausted database pool or an acknowledgement lost after the engine took the run, `start()` threw `Submission … remains unacknowledged`. The submission stayed durable and recovery retried it, so a caller that reported the failure could see the run happen anyway.
114
+
115
+ `start()` now returns `status: 'queued'` with `deferral.mode: 'unacknowledged'`, and `handle.id` is the receipt recovery binds to the resulting run. `start(…, { wait: true })` and `run(…)` follow it. A definite rejection still throws.
116
+
117
+ `WorkflowAdmissionOutcome`'s `accept` may now resolve to a `WorkflowQueuedAdmission` as well as an execution ID. Custom admission adapters only produce that outcome; the framework's facade is what calls `accept`, so adapter code keeps compiling. `isUnacknowledgedSubmission` is exported from `@voltro/workflow`.
118
+
119
+ ---
120
+
121
+ ## [0.79.1] — 2026-09-24
122
+
123
+ ### Changed
124
+
125
+ - **A charset conversion blocked by a soft-drop snapshot names the reclaim** — `@voltro/database`
126
+
127
+ A declared `store.charset` refuses to convert a table that a foreign key ties to a table the plan does not convert. When that other table is a soft-drop snapshot (`<name>__dropped_<stamp>`), the fix line offered three remedies that do not fit one: declaring it, converting it by hand, or leaving the charset undeclared. It now names `voltro db gc-snapshots --before <date>` with the date that covers the newest blocking snapshot, says that the date also removes every older snapshot, and names `voltro db restore-snapshot` for data that is still wanted.
128
+ - **The devtools capsule morphs into a status and back** — `@voltro/devtools`
129
+
130
+ When `voltro dev` reported a status such as "Compiling…", the closed capsule swapped its chips for the status in one frame and jumped to the new width. The swap is now a morph: the incoming content fades up while the capsule glides from its old width to the new one, and a status that changes mid-glide continues from the width reached. Under `prefers-reduced-motion` the swap stays immediate.
131
+ - **The devtools console animates out the way it came in** — `@voltro/devtools`
132
+
133
+ Opening the console slid it in from its dock edge (or scaled a floating window up), but closing removed it in one frame. Closing now plays the entrance backwards: the console slides out towards its edge, or the window scales down and fades, and the capsule returns once it has gone. The console ignores input while it leaves, a page pushed aside by it reflows only after it is gone, and under `prefers-reduced-motion` it still closes at once.
134
+
135
+ ### Fixed
136
+
137
+ - **Probes are answered while a large app's modules load at boot** — `@voltro/cli`
138
+
139
+ The app port answers the health probes from the start of the boot, but the process that answers them is the one booting. Loading the app's modules runs on Node's synchronous loader, so the whole run was one turn of the event loop and a probe waited for all of it: over 800 ms on a generated 1798-module app booting `voltro serve` from the production bundle, and over a second on a loaded machine. Loading now hands the thread back every 25 ms; on the same app, at load 14 on 12 cores, the slowest liveness answer during boot was 89 ms. `voltro dev` loads its modules through the same path.
140
+ - **`voltro update` notes are not silenced by a comment naming the option** — `@voltro/cli`
141
+
142
+ A `manual` codemod decides whether your project is affected by searching its files. In code that search counted comments: an api config that set neither anonymous-tenant option and explained why in a comment read as one that had chosen, and the note meant for it was skipped as "none matched your project". Commented-out code did the same. In `.ts`, `.js` and `.json` files (and their variants) comments no longer count; prose files — runbooks, CI definitions, shell scripts — are still read whole. This applies to every codemod a later `voltro update` runs, including the ones of versions you have not crossed yet.
143
+ - **`voltro update` notes search the whole workspace, not only the app** — `@voltro/cli`
144
+
145
+ `voltro update` runs from an app directory, and the search behind every manual note read only that app. A compose file at the workspace root still setting `VOLTRO_DEV_HEALTH_PORT`, or a tsconfig in another workspace member with a removed TypeScript 7 option, got "nothing in this repository matched" — though the TypeScript 7 note says it checks every tsconfig in the workspace. The search now covers the workspace the app sits in. The TypeScript 7 note also names each tsconfig line it found, and its hand-check `git grep` line matches `"moduleResolution": "node"` and the lower-case spellings (`amd`, `classic`, `es5`); it required `node10` before.
146
+ - **Correction: a compose baseline from before 0.79.0 needs `voltro baseline sync`** — `@voltro/cli`
147
+
148
+ The 0.79.0 note on probes said a compose baseline needs nothing because it regenerates on the next `voltro dev`. It does not: only `voltro baseline sync` rewrites those files, and a baseline synced before 0.79.0 sets `VOLTRO_DEV_HEALTH_PORT` and points its healthchecks at a port nothing listens on any more. `voltro update` now prints a correcting note for every project whose workspace still sets that variable, naming the lines.
149
+ - **`db apply` undo converts tables back and says exactly what still differs** — `@voltro/cli`, `@voltro/database`
150
+
151
+ On MySQL, MariaDB, SQLite and Turso a failed `voltro db apply` is undone rather than rolled back. When the run had converted tables to a declared `store.charset`, the undo planned the way back with that same charset, so no conversion could be planned in reverse — and the check behind "the database is as it was" asked the same charset-blind question, so the line printed over tables that stayed converted. The undo now converts each table back to the character set its string columns had before the run, and verifies it by reading every column back. A column the undo redefines also keeps its default: a string default read from MariaDB was quoted a second time. When an undo does not read as it was, the report now lists each remaining difference instead of asking you to compare by hand.
152
+ - **The compose dev image starts pnpm 11 and later as the non-root user** — `@voltro/cli`
153
+
154
+ The `compose` and `compose-mariadb` baselines' `docker/dev.Dockerfile` activated pnpm as root but never ran it. pnpm 11 and later download a native binary on their first run, into a directory the non-root user compose starts every service as cannot write, so `docker compose … up` stopped at the first service with `EACCES … pnpm-native….tgz`. The image now runs pnpm once while it is built. Run `voltro baseline sync` and rebuild the image to take it.
155
+ - **The closed devtools capsule glows again** — `@voltro/devtools`
156
+
157
+ When the overlay became a docking console, the closed state turned into a status capsule and lost the launcher's animated halo: a bright slice of brand violet circling the rim while a soft glow breathes around it. The capsule carries it again, traced along its pill outline. It steps aside while `voltro dev` reports a status, where the capsule's own pulse in the status colour takes over, and it stands still under `prefers-reduced-motion`.
158
+ - **`db gc-snapshots` drops snapshots that reference each other, and works on MySQL** — `@voltro/cli`
159
+
160
+ `voltro db gc-snapshots` dropped snapshots in catalog order. Two tables one plan soft-dropped keep the foreign key between them, so dropping the parent first failed with `1451` on MySQL and MariaDB (and on Postgres whenever the parent came first), every run, after the snapshots before it were already gone; a soft-dropped reference column failed with `1553`. The keys the snapshots hold are now dropped first, then the columns, then the tables, a child before its parent. A snapshot that a table outside the set still references is refused before anything is dropped, with each constraint named. On MySQL 8 the command, and `voltro db restore-snapshot`, found no snapshot at all, because the catalog answers in upper case there; both read it now.
161
+ - **`voltro update` lists the lines a manual note is about** — `@voltro/cli`
162
+
163
+ A `manual` codemod decides between "found" and "nothing in this repository matched" with its own check, and that check could be narrower than the search its note tells you to run. The note for probes on the app port told readers to grep for `internal/(liveness|readiness|startup)` but looked only for `VOLTRO_DEV_HEALTH_PORT`, so a project whose Helm values, compose healthcheck and smoke script probed app port + 1 directly got "nothing in this repository matched" while every one of them needed moving. A manual note can now list the lines it is about, and `voltro update` prints them under it (`── found in this repository (N lines) — check each:`, `file:line text`). The probe note lists every probe path and variable it finds, with the port beside each probe; the anonymous-tenant note names each api config that sets neither option. A note that lists a line is reported as found.
164
+ - **MariaDB: a charset, type or nullability change keeps the column's CHECK** — `@voltro/database`
165
+
166
+ On MariaDB a CHECK written on the column itself — how a table with `text().oneOf([...])` is created — is part of the column definition, and `MODIFY COLUMN` without it removes it. Every operation that redefines a column did exactly that: a `convert-charset` rewrite, a nullability, type or default change. The re-plan then proposed `add-check` for each, and the apply refused with "the migration did not converge", after every statement had run. Declaring `store.charset` over an existing schema hit it once per converted enum column. The rewrite now restates each column's CHECK, so it stays where it was; an explicit `drop-check` still removes one the declaration no longer has. MySQL keeps such a CHECK on its own and is unchanged. `emitConvertCharsetMysql` takes the CHECKs to keep as an optional last argument, so every existing call compiles as before.
167
+ - **`voltro serve` checks pending file migrations from its bundle, not from source** — `@voltro/cli`, `@voltro/database`
168
+
169
+ Before it boots, `voltro serve` refuses when a `migrations/<timestamp>_<slug>.ts` file has never run. `voltro build` did not bundle those files, so serve imported each from source — and, through it, a second copy of the framework's migration code. Measured on an app with two file migrations: about 290 ms during which the process answered nothing, after the port was already taking health probes. The build now bundles every file migration, and serve reads them from the bundle. `pendingFileMigrationIds` takes an optional module importer as its last argument, and `walkMigrationFiles` is exported; every existing call compiles as before.
170
+ - **`voltro update --wait` says what the registry answered, per package** — `@voltro/cli`
171
+
172
+ `--wait` reported every package it could not confirm as "not on the registry", whether the registry had answered 404 or the request had failed. It now names the answer with each package — `404: not published yet`, or `no answer: <status or error>` — while it waits, at the ceiling, and after a failed install, so a network error is never reported as a missing release. In `pnpm-workspace.yaml`, a `minimumReleaseAgeExclude` entry that pnpm 12.5 and later widened in place to a version union (`'@voltro/ai@0.78.0 || 0.79.0'`) was skipped without a word; a union holding the version being left now moves to the target like any other entry.
173
+
174
+ ---
175
+
42
176
  ## [0.79.0] — 2026-09-23
43
177
 
44
178
  ### ⚠ BREAKING
package/dist/index.d.ts CHANGED
@@ -2302,6 +2302,27 @@ export declare const findAdvisoryResourceGuards: (procedures: ReadonlyArray<{
2302
2302
  /** Record a completed response for replay. */
2303
2303
  export declare const finishIdempotent: (store: IdempotencyStore, scope: string, key: string, response: IdempotencyResponse, now: number) => Promise<void>;
2304
2304
 
2305
+ /**
2306
+ * The value a ONE-SHOT read of an rpc resolves to, given the first value it
2307
+ * delivered.
2308
+ *
2309
+ * A query's success type is the three-member union above, and a one-shot read
2310
+ * takes only its first event: a `snapshot` resolves to its `data`; an `error`
2311
+ * REJECTS — with the typed error on `cause` and `voltroError`, so a caller can
2312
+ * still branch on its `_tag` — exactly as a failed exit would; a `delta` cannot
2313
+ * be first and says so. Anything else is a mutation or action's own result and
2314
+ * passes through, which may legitimately be a tagged object of the app's.
2315
+ *
2316
+ * Shared because there are two such reads — the server loader's `ctx.query`
2317
+ * over `POST /rpc` and the browser loader's over the page's socket — and an
2318
+ * unwrap written twice is how an authorization answer once reached a loader as
2319
+ * its return value on one transport and as a rejection on the other.
2320
+ *
2321
+ * `label` prefixes every message (`[loader.query]`), naming the surface that
2322
+ * made the read.
2323
+ */
2324
+ export declare const firstEventValue: (tag: string, value: unknown, label: string) => unknown;
2325
+
2305
2326
  /** A route rendered for humans — logs, the inspect surface, the dashboard. */
2306
2327
  export declare const formatEventRoute: (route: string) => string;
2307
2328
 
@@ -4847,6 +4868,9 @@ declare interface RestRouteContext {
4847
4868
  * REST handlers on the same capability surface as query/mutation/action
4848
4869
  * handlers without creating a protocol-to-runtime package dependency. */
4849
4870
  readonly app?: unknown;
4871
+ /** The caller's address, resolved through `security.trustedProxies` — the
4872
+ * value the listener's per-IP shield counts. Absent when unknown. */
4873
+ readonly remoteAddr?: string;
4850
4874
  }
4851
4875
 
4852
4876
  declare interface RestRouteDescriptor<I, O> {
@@ -5149,6 +5173,16 @@ export declare interface RpcInterceptorContext {
5149
5173
  readonly surface?: 'rest';
5150
5174
  /** The descriptor's `publicApi` spec, on a projected call. */
5151
5175
  readonly publicApi?: PublicApiSpec;
5176
+ /**
5177
+ * The caller's address — the same value the pre-auth IP shield counts, resolved
5178
+ * by the listener through `security.trustedProxies` (and
5179
+ * `security.clientAddressHeader`). A client cannot set it: the listener
5180
+ * writes it, and any copy the client sends is discarded.
5181
+ *
5182
+ * Absent when the call did not arrive over the network (a workflow step, a
5183
+ * schedule, an in-process invocation) or the socket address is unknown.
5184
+ */
5185
+ readonly remoteAddr?: string;
5152
5186
  }
5153
5187
 
5154
5188
  /**
@@ -7506,7 +7540,12 @@ export declare const WorkflowRunHandleSchema: Schema.Struct<{
7506
7540
  status: Schema.Literal<["running", "succeeded", "queued", "dropped", "skipped", "replayed"]>;
7507
7541
  deferral: Schema.optional<Schema.Struct<{
7508
7542
  /** `debounce` | `batch` | `throttle` | `concurrency` | `paused` |
7509
- * `rateLimit` | `singleton`. */
7543
+ * `rateLimit` | `singleton` | `delayed` | `transaction` | `unacknowledged`.
7544
+ *
7545
+ * `unacknowledged` — the start was dispatched and no acknowledgement came
7546
+ * back. The engine may have it; recovery retries the dispatch and binds the
7547
+ * receipt (`id`) to whatever run results. It is not a failure: do not
7548
+ * report one to a user, follow the receipt. */
7510
7549
  mode: typeof Schema.String;
7511
7550
  /** Epoch ms this may first be admitted. Null for a drop or a skip. */
7512
7551
  dueAt: Schema.NullOr<typeof Schema.Number>;
@@ -7694,7 +7733,12 @@ export declare type WorkflowStartDeferral = Schema.Schema.Type<typeof WorkflowSt
7694
7733
  */
7695
7734
  export declare const WorkflowStartDeferralSchema: Schema.Struct<{
7696
7735
  /** `debounce` | `batch` | `throttle` | `concurrency` | `paused` |
7697
- * `rateLimit` | `singleton`. */
7736
+ * `rateLimit` | `singleton` | `delayed` | `transaction` | `unacknowledged`.
7737
+ *
7738
+ * `unacknowledged` — the start was dispatched and no acknowledgement came
7739
+ * back. The engine may have it; recovery retries the dispatch and binds the
7740
+ * receipt (`id`) to whatever run results. It is not a failure: do not
7741
+ * report one to a user, follow the receipt. */
7698
7742
  mode: typeof Schema.String;
7699
7743
  /** Epoch ms this may first be admitted. Null for a drop or a skip. */
7700
7744
  dueAt: Schema.NullOr<typeof Schema.Number>;
@@ -7812,7 +7856,12 @@ export declare const workflowToRpc: <const Name extends string, Payload extends
7812
7856
  status: Schema.Literal<["running", "succeeded", "queued", "dropped", "skipped", "replayed"]>;
7813
7857
  deferral: Schema.optional<Schema.Struct<{
7814
7858
  /** `debounce` | `batch` | `throttle` | `concurrency` | `paused` |
7815
- * `rateLimit` | `singleton`. */
7859
+ * `rateLimit` | `singleton` | `delayed` | `transaction` | `unacknowledged`.
7860
+ *
7861
+ * `unacknowledged` — the start was dispatched and no acknowledgement came
7862
+ * back. The engine may have it; recovery retries the dispatch and binds the
7863
+ * receipt (`id`) to whatever run results. It is not a failure: do not
7864
+ * report one to a user, follow the receipt. */
7816
7865
  mode: typeof Schema.String;
7817
7866
  /** Epoch ms this may first be admitted. Null for a drop or a skip. */
7818
7867
  dueAt: Schema.NullOr<typeof Schema.Number>;
package/dist/index.js CHANGED
@@ -136,10 +136,22 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
136
136
  _tag: p.Literal("error"),
137
137
  error: p.Unknown,
138
138
  revision: p.optional(p.Number)
139
- })), b = (e, t) => {
139
+ })), wt = (e, t, n) => {
140
+ if (!t || typeof t != "object" || !("_tag" in t)) return t;
141
+ let r = t;
142
+ switch (r._tag) {
143
+ case "snapshot": return r.data;
144
+ case "error": {
145
+ let t = r.error;
146
+ throw Object.assign(Error(`${n} ${e}: ${t?._tag ?? "error"}${t?.message ? ` — ${t.message}` : ""}`, { cause: r.error }), { voltroError: r.error });
147
+ }
148
+ case "delta": throw Error(`${n} ${e}: received a 'delta' as the first event, which should be impossible — a subscription always opens with a snapshot. This is a framework bug.`);
149
+ default: return t;
150
+ }
151
+ }, b = (e, t) => {
140
152
  let n = `${e ?? "this procedure"} input`;
141
153
  return t === !0 ? `${n} (guarded — the guard did NOT run: the payload failed to decode first, so this says nothing about access)` : n;
142
- }, wt = (e) => {
154
+ }, Tt = (e) => {
143
155
  let t = e.ast;
144
156
  return t._tag === "TypeLiteral" && (t.propertySignatures?.length ?? 0) === 0 && (t.indexSignatures?.length ?? 0) === 0;
145
157
  }, x = (e, t, n) => {
@@ -147,8 +159,8 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
147
159
  parseOptions: { onExcessProperty: "error" },
148
160
  parseIssueTitle: () => r
149
161
  });
150
- return wt(e) ? p.filter((e) => typeof e == "object" && e && Object.keys(e).length > 0 ? `this procedure declares no input; received: ${Object.keys(e).join(", ")}` : void 0)(i).annotations({ identifier: r }) : i;
151
- }, Tt = class extends p.TaggedError()("BusinessRuleViolation", {
162
+ return Tt(e) ? p.filter((e) => typeof e == "object" && e && Object.keys(e).length > 0 ? `this procedure declares no input; received: ${Object.keys(e).join(", ")}` : void 0)(i).annotations({ identifier: r }) : i;
163
+ }, Et = class extends p.TaggedError()("BusinessRuleViolation", {
152
164
  rule: p.String,
153
165
  params: p.optional(p.Record({
154
166
  key: p.String,
@@ -157,21 +169,21 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
157
169
  field: p.optional(p.String),
158
170
  message: p.optional(p.String),
159
171
  severity: p.Literal("error", "warning")
160
- }) {}, Et = p.Struct({
172
+ }) {}, Dt = p.Struct({
161
173
  field: p.String,
162
174
  message: p.String,
163
175
  params: p.optional(p.Record({
164
176
  key: p.String,
165
177
  value: p.Unknown
166
178
  }))
167
- }), Dt = class extends p.TaggedError()("ValidationError", {
179
+ }), Ot = class extends p.TaggedError()("ValidationError", {
168
180
  field: p.String,
169
181
  message: p.String,
170
182
  params: p.optional(p.Record({
171
183
  key: p.String,
172
184
  value: p.Unknown
173
185
  }))
174
- }) {}, Ot = class extends p.TaggedError()("ValidationErrors", { issues: p.Array(Et).pipe(p.minItems(1)) }) {}, kt = (e) => {
186
+ }) {}, kt = class extends p.TaggedError()("ValidationErrors", { issues: p.Array(Dt).pipe(p.minItems(1)) }) {}, At = (e) => {
175
187
  if (typeof e != "object" || !e) return [];
176
188
  let t = e._tag;
177
189
  if (t === "ValidationError") {
@@ -197,34 +209,34 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
197
209
  }];
198
210
  }
199
211
  return [];
200
- }, At = class extends p.TaggedError()("ApprovalRequired", {
212
+ }, jt = class extends p.TaggedError()("ApprovalRequired", {
201
213
  approvalId: p.String,
202
214
  procedure: p.String,
203
215
  expiresAt: p.String,
204
216
  requiredScopes: p.Array(p.String),
205
217
  reason: p.NullOr(p.String),
206
218
  created: p.Boolean
207
- }) {}, jt = class extends p.TaggedError()("ApprovalNotFound", { approvalId: p.String }) {}, Mt = class extends p.TaggedError()("ApprovalSelfApproval", {
219
+ }) {}, Mt = class extends p.TaggedError()("ApprovalNotFound", { approvalId: p.String }) {}, Nt = class extends p.TaggedError()("ApprovalSelfApproval", {
208
220
  approvalId: p.String,
209
221
  subjectId: p.NullOr(p.String)
210
- }) {}, Nt = class extends p.TaggedError()("ApprovalRejected", {
222
+ }) {}, Pt = class extends p.TaggedError()("ApprovalRejected", {
211
223
  approvalId: p.String,
212
224
  decidedBy: p.NullOr(p.String),
213
225
  note: p.NullOr(p.String)
214
226
  }) {}, S = class extends p.TaggedError()("ApprovalExpired", {
215
227
  approvalId: p.String,
216
228
  expiredAt: p.String
217
- }) {}, Pt = class extends p.TaggedError()("ApprovalNotPending", {
229
+ }) {}, Ft = class extends p.TaggedError()("ApprovalNotPending", {
218
230
  approvalId: p.String,
219
231
  status: p.String
220
- }) {}, Ft = class extends p.TaggedError()("ApprovalForbidden", {
232
+ }) {}, It = class extends p.TaggedError()("ApprovalForbidden", {
221
233
  approvalId: p.String,
222
234
  required: p.NullOr(p.String),
223
235
  message: p.String
224
236
  }) {}, C = class extends p.TaggedError()("ApprovalUnavailable", {
225
237
  procedure: p.String,
226
238
  message: p.String
227
- }) {}, It = p.Union(At, Nt, S, C), Lt = p.Union(jt, Mt, S, Pt, Ft, C), Rt = p.Struct({
239
+ }) {}, Lt = p.Union(jt, Pt, S, C), Rt = p.Union(Mt, Nt, S, Ft, It, C), zt = p.Struct({
228
240
  id: p.String,
229
241
  procedure: p.String,
230
242
  kind: p.Literal("mutation", "action"),
@@ -238,8 +250,8 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
238
250
  decidedAt: p.NullOr(p.String),
239
251
  note: p.NullOr(p.String),
240
252
  relation: p.Literal("to-decide", "requested")
241
- }), w = "channel:", zt = Symbol.for("@voltro/protocol/reactivityChannels"), Bt = globalThis, T = Bt[zt] ?? (Bt[zt] = /* @__PURE__ */ new Map()), Vt = /^[a-z][a-z0-9-]*(\.[a-z0-9-]+)*$/, Ht = (e) => {
242
- if (!Vt.test(e)) throw Error(`reactivityChannel('${e}'): invalid channel name.\n Use lowercase kebab segments separated by dots — \`presence\`, \`job-queue\`,
253
+ }), w = "channel:", Bt = Symbol.for("@voltro/protocol/reactivityChannels"), Vt = globalThis, T = Vt[Bt] ?? (Vt[Bt] = /* @__PURE__ */ new Map()), Ht = /^[a-z][a-z0-9-]*(\.[a-z0-9-]+)*$/, Ut = (e) => {
254
+ if (!Ht.test(e)) throw Error(`reactivityChannel('${e}'): invalid channel name.\n Use lowercase kebab segments separated by dots — \`presence\`, \`job-queue\`,
243
255
  \`billing.usage\`. A \`:\` is refused because it is the namespace separator in
244
256
  the routing key (\`${w}<name>\`), and an uppercase letter is\n refused because a key that differs only by case reads as one channel and
245
257
  routes as two.`);
@@ -252,14 +264,14 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
252
264
  toString: () => n
253
265
  });
254
266
  return T.set(e, r), r;
255
- }, Ut = (e) => typeof e == "object" && !!e && e.kind === "reactivity-channel", Wt = (e) => e.startsWith(w), Gt = () => new Set([...T.values()].map((e) => e.key)), E = (e) => Ut(e) ? e.key : e, Kt = (e) => e === void 0 ? [] : Array.isArray(e) ? e.map(E) : [E(e)], qt = (e) => e === void 0 ? void 0 : Array.isArray(e) ? e.map(E) : E(e), Jt = (e) => {
256
- let t = Gt(), n = [];
257
- for (let r of e) for (let e of Kt(r.source)) Wt(e) && !t.has(e) && n.push({
267
+ }, Wt = (e) => typeof e == "object" && !!e && e.kind === "reactivity-channel", Gt = (e) => e.startsWith(w), Kt = () => new Set([...T.values()].map((e) => e.key)), E = (e) => Wt(e) ? e.key : e, qt = (e) => e === void 0 ? [] : Array.isArray(e) ? e.map(E) : [E(e)], Jt = (e) => e === void 0 ? void 0 : Array.isArray(e) ? e.map(E) : E(e), Yt = (e) => {
268
+ let t = Kt(), n = [];
269
+ for (let r of e) for (let e of qt(r.source)) Gt(e) && !t.has(e) && n.push({
258
270
  procedure: r.name,
259
271
  key: e
260
272
  });
261
273
  return n;
262
- }, Yt = (e, t) => {
274
+ }, Xt = (e, t) => {
263
275
  let n = e?.injectExternalChange;
264
276
  return n !== void 0 && (n.call(e, {
265
277
  table: t.key,
@@ -268,8 +280,8 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
268
280
  old: {},
269
281
  origin: "inline"
270
282
  }), !0);
271
- }, Xt = (e) => e.internal !== !0, D = (e) => {
272
- if (Zt(e), e.idempotencyScope !== void 0 && e.idempotencyScope !== "subject" && e.idempotencyScope !== "connection") throw Error(`${e.name}: idempotencyScope must be 'subject' or 'connection'`);
283
+ }, Zt = (e) => e.internal !== !0, D = (e) => {
284
+ if (Qt(e), e.idempotencyScope !== void 0 && e.idempotencyScope !== "subject" && e.idempotencyScope !== "connection") throw Error(`${e.name}: idempotencyScope must be 'subject' or 'connection'`);
273
285
  let t = e.serialize;
274
286
  if (t !== void 0) {
275
287
  if (typeof t.key != "function") throw Error(`${e.name}: serialize.key must be a function of the input`);
@@ -293,7 +305,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
293
305
  let r = [e.publicApi === void 0 ? void 0 : "publicApi", e.exposeAsTool === void 0 ? void 0 : "exposeAsTool"].filter((e) => e !== void 0);
294
306
  if (r.length === 0) return e;
295
307
  throw Error(`${e.name}: \`internal: true\` cannot be combined with ${r.map((e) => `\`${e}\``).join(" or ")}. \`internal\` takes the procedure OFF the wire; those put it back ON a different one (${r.includes("publicApi") ? "a REST route" : "an agent tool"}), and that surface is projected without consulting the flag — so the procedure would be unreachable from your client and reachable from the internet. Drop \`internal: true\` if the wider surface is intended, or remove the ${r.join(" / ")} annotation if it is not.`);
296
- }, Zt = (e) => {
308
+ }, Qt = (e) => {
297
309
  let t = e.requiresApproval;
298
310
  if (t !== void 0) {
299
311
  if (!Array.isArray(t.approvers) || t.approvers.length === 0) throw Error(`${e.name}: \`requiresApproval.approvers\` is empty, so NOBODY can approve this\n procedure and every call would park forever. Name the scope(s) an approver must hold
@@ -303,7 +315,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
303
315
  human from every other, and the second-pair-of-eyes control would enforce nothing.
304
316
  Give the procedure a \`guards:\` decision, or drop \`requiresApproval\`.`);
305
317
  }
306
- }, Qt = (e) => e.action !== void 0 && e.resourceType !== void 0, $t = (e) => e.authenticated === !0, en = (e) => e.openAccess !== void 0 || e.guards !== void 0 && e.guards.length > 0, O = (e, t, n) => {
318
+ }, $t = (e) => e.action !== void 0 && e.resourceType !== void 0, en = (e) => e.authenticated === !0, tn = (e) => e.openAccess !== void 0 || e.guards !== void 0 && e.guards.length > 0, O = (e, t, n) => {
307
319
  if (n === void 0) return t;
308
320
  if (n.trim() === "") throw Error(`${e}: \`openAccess\` needs a REASON, not an empty string. It is the sentence a\n reviewer reads to decide whether this procedure should really be callable
309
321
  without an authorization check — e.g. \`openAccess: 'public pricing, no caller data'\`.`);
@@ -316,7 +328,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
316
328
  input: e.input,
317
329
  output: e.output,
318
330
  error: e.error ?? p.Never,
319
- source: qt(e.source),
331
+ source: Jt(e.source),
320
332
  cache: e.cache,
321
333
  guards: O(e.name, e.guards, e.openAccess),
322
334
  openAccess: e.openAccess,
@@ -355,7 +367,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
355
367
  requiresApproval: e.requiresApproval,
356
368
  internal: e.internal,
357
369
  overridesPlugin: e.overridesPlugin
358
- }), tn = (e) => D({
370
+ }), nn = (e) => D({
359
371
  kind: "stream",
360
372
  name: e.name,
361
373
  input: e.input,
@@ -365,44 +377,44 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
365
377
  overridesPlugin: e.overridesPlugin,
366
378
  guards: O(e.name, e.guards, e.openAccess),
367
379
  openAccess: e.openAccess
368
- }), M = (e, t) => t && t.length > 0 ? p.Union(e, ...t) : e, N = (e) => e !== void 0 && e.some((e) => !u(e)), nn = (e) => p.Union(e, f, l), rn = (e) => p.Union(e, Tt), an = (e) => p.Union(e, Dt, Ot), on = (e, t) => t === void 0 ? e : p.Union(e, It), P = (e, t) => {
369
- let n = nn(e.error);
380
+ }), M = (e, t) => t && t.length > 0 ? p.Union(e, ...t) : e, N = (e) => e !== void 0 && e.some((e) => !u(e)), rn = (e) => p.Union(e, f, l), an = (e) => p.Union(e, Et), on = (e) => p.Union(e, Ot, kt), sn = (e, t) => t === void 0 ? e : p.Union(e, Lt), P = (e, t) => {
381
+ let n = rn(e.error);
370
382
  if (t === "stream" || t === "query") return n;
371
- let r = t === "mutation" ? p.Union(rn(n), dt, ht) : n;
372
- return on(an(t === "mutation" && e.idempotencyScope === "connection" ? p.Union(r, qe) : r), e.requiresApproval);
373
- }, sn = (e, t) => M(nn(e.errorSchema), t), cn = (e, t) => ({
383
+ let r = t === "mutation" ? p.Union(an(n), dt, ht) : n;
384
+ return sn(on(t === "mutation" && e.idempotencyScope === "connection" ? p.Union(r, qe) : r), e.requiresApproval);
385
+ }, cn = (e, t) => M(rn(e.errorSchema), t), ln = (e, t) => ({
374
386
  ...e,
375
387
  name: t
376
- }), ln = (e, t) => m.make(e.name, {
388
+ }), un = (e, t) => m.make(e.name, {
377
389
  payload: x(e.input, e.name, N(e.guards)),
378
390
  success: y(e.output),
379
391
  error: M(P(e, "query"), t),
380
392
  stream: !0
381
- }), un = (e, t) => m.make(e.name, {
393
+ }), dn = (e, t) => m.make(e.name, {
382
394
  payload: x(e.input, e.name, N(e.guards)),
383
395
  success: e.output,
384
396
  error: M(P(e, "mutation"), t)
385
- }), dn = (e, t) => m.make(e.name, {
397
+ }), fn = (e, t) => m.make(e.name, {
386
398
  payload: x(e.input, e.name, N(e.guards)),
387
399
  success: e.output,
388
400
  error: M(P(e, "action"), t)
389
- }), fn = (e, t) => m.make(e.name, {
401
+ }), pn = (e, t) => m.make(e.name, {
390
402
  payload: x(e.input, e.name, N(e.guards)),
391
403
  success: e.element,
392
404
  error: M(e.error, t),
393
405
  stream: !0
394
- }), pn = (e) => {
406
+ }), mn = (e) => {
395
407
  if (typeof e != "object" || !e) return;
396
408
  let t = e._tag;
397
409
  return typeof t == "string" ? t : void 0;
398
- }, mn = (e) => {
410
+ }, hn = (e) => {
399
411
  switch (e.kind) {
400
- case "query": return ln(e);
401
- case "mutation": return un(e);
402
- case "action": return dn(e);
403
- case "stream": return fn(e);
412
+ case "query": return un(e);
413
+ case "mutation": return dn(e);
414
+ case "action": return fn(e);
415
+ case "stream": return pn(e);
404
416
  }
405
- }, hn = (e) => {
417
+ }, gn = (e) => {
406
418
  let t = e.input, n = t === void 0 ? {} : { input: t }, r = e.output, i = r === void 0 ? {} : { output: r };
407
419
  if (e.kind === "query") return {
408
420
  kind: "query",
@@ -448,7 +460,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
448
460
  ..."relations" in e && e.relations !== void 0 ? { relations: e.relations } : {}
449
461
  }))
450
462
  };
451
- }, gn = 7500, _n = (e) => typeof e == "object" && !!e && e.kind === "event", vn = (e) => {
463
+ }, _n = 7500, vn = (e) => typeof e == "object" && !!e && e.kind === "event", yn = (e) => {
452
464
  if (e.name.length === 0) throw Error("defineEvent: `name` must not be empty");
453
465
  if (/\s/.test(e.name)) throw Error(`defineEvent("${e.name}"): \`name\` must not contain whitespace.\n The name is used as a broker subject segment. NATS refuses a subject with
454
466
  whitespace and delivers nothing — silently, and only on that broker, so an
@@ -483,62 +495,62 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
483
495
  webhook: e.webhook,
484
496
  delivery: e.delivery
485
497
  };
486
- }, yn = (e) => p.Struct({
498
+ }, bn = (e) => p.Struct({
487
499
  _tag: p.Literal("event"),
488
500
  origin: p.String,
489
501
  n: p.Number,
490
502
  emittedAt: p.Number,
491
503
  payload: e
492
- }), bn = p.Struct({
504
+ }), xn = p.Struct({
493
505
  _tag: p.Literal("gap"),
494
506
  missed: p.Number,
495
507
  reason: p.Literal("buffer", "resume")
496
- }), xn = p.Struct({ _tag: p.Literal("attached") }), Sn = (e) => p.Union(xn, yn(e), bn), Cn = p.Struct({
508
+ }), Sn = p.Struct({ _tag: p.Literal("attached") }), Cn = (e) => p.Union(Sn, bn(e), xn), wn = p.Struct({
497
509
  origin: p.String,
498
510
  n: p.Number
499
- }), wn = (e) => p.Struct({
511
+ }), Tn = (e) => p.Struct({
500
512
  key: e,
501
- resume: p.optional(p.Array(Cn))
502
- }), Tn = (e, t) => {
513
+ resume: p.optional(p.Array(wn))
514
+ }), En = (e, t) => {
503
515
  let n = e.guards !== void 0 && e.guards.some((e) => !u(e)) ? p.Union(f, l) : l, r = t && t.length > 0 ? p.Union(n, ...t) : n;
504
516
  return m.make(e.name, {
505
- payload: x(wn(e.key), e.name, N(e.guards)),
506
- success: Sn(e.payload),
517
+ payload: x(Tn(e.key), e.name, N(e.guards)),
518
+ success: Cn(e.payload),
507
519
  error: r,
508
520
  stream: !0
509
521
  });
510
- }, En = class extends p.TaggedError()("EventPayloadInvalid", {
522
+ }, Dn = class extends p.TaggedError()("EventPayloadInvalid", {
511
523
  event: p.String,
512
524
  message: p.String
513
- }) {}, Dn = class extends p.TaggedError()("EventKeyInvalid", {
525
+ }) {}, On = class extends p.TaggedError()("EventKeyInvalid", {
514
526
  event: p.String,
515
527
  message: p.String
516
- }) {}, On = class extends p.TaggedError()("EventPayloadTooLarge", {
528
+ }) {}, kn = class extends p.TaggedError()("EventPayloadTooLarge", {
517
529
  event: p.String,
518
530
  bytes: p.Number,
519
531
  limit: p.Number
520
- }) {}, kn = (e) => {
532
+ }) {}, An = (e) => {
521
533
  if (typeof e != "object" || !e) return JSON.stringify(e) ?? "null";
522
534
  let t = Object.entries(e).filter(([, e]) => e !== void 0).sort(([e], [t]) => e < t ? -1 : +(e > t));
523
535
  return JSON.stringify(t);
524
- }, An = "\0", jn = (e, t, n) => [
536
+ }, jn = "\0", Mn = (e, t, n) => [
525
537
  e ?? "~",
526
538
  t,
527
- kn(n)
528
- ].join("\0"), Mn = (e) => {
539
+ An(n)
540
+ ].join("\0"), Nn = (e) => {
529
541
  let [t = "~", n = "", r = ""] = e.split("\0");
530
542
  return {
531
543
  tenantId: t === "~" ? null : t,
532
544
  event: n,
533
545
  key: r
534
546
  };
535
- }, Nn = (e) => e.split("\0").join(" · "), F = Symbol.for("@voltro/protocol/workflow-access"), Pn = {
547
+ }, Pn = (e) => e.split("\0").join(" · "), F = Symbol.for("@voltro/protocol/workflow-access"), Fn = {
536
548
  guards: void 0,
537
549
  openAccess: void 0,
538
550
  internal: !1,
539
551
  controls: void 0,
540
552
  controlsOpenAccess: void 0
541
- }, Fn = (e, t) => {
553
+ }, In = (e, t) => {
542
554
  let n = t.guards;
543
555
  if (n !== void 0 && n.length === 0) throw Error(`workflow("${e}"): \`guards: []\` is empty, so it enforces nothing —\n but it reads at the call site as if the start were protected. Omit the field
544
556
  and decide with \`openAccess\` or \`internal\`, or list the scopes that may start it.`);
@@ -570,16 +582,16 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
570
582
  controls: i === void 0 ? r : [d(i.trim())],
571
583
  controlsOpenAccess: i?.trim()
572
584
  };
573
- }, In = (e, t) => {
585
+ }, Ln = (e, t) => {
574
586
  Object.defineProperty(e, F, {
575
587
  value: t,
576
588
  enumerable: !1
577
589
  });
578
590
  }, I = (e) => {
579
- if (typeof e != "object" || !e) return Pn;
591
+ if (typeof e != "object" || !e) return Fn;
580
592
  let t = e[F];
581
- return t === void 0 ? Pn : t;
582
- }, Ln = (e) => {
593
+ return t === void 0 ? Fn : t;
594
+ }, Rn = (e) => {
583
595
  let t = I(e);
584
596
  return {
585
597
  name: e.name,
@@ -588,18 +600,18 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
588
600
  openAccess: t.openAccess,
589
601
  controls: t.controls
590
602
  };
591
- }, Rn = (e) => e, zn = (e) => e, Bn = (e) => {
603
+ }, zn = (e) => e, Bn = (e) => e, Vn = (e) => {
592
604
  let t = e.alias?.trim(), n = e.instance?.trim(), r = t !== void 0 && t !== "" ? t : e.base;
593
605
  return n !== void 0 && n !== "" ? `${r}#${n}` : r;
594
- }, Vn = (e) => {
606
+ }, Hn = (e) => {
595
607
  if (e.length !== 0) return (t, n) => e.reduceRight((e, t) => t(e, n), t);
596
- }, Hn = (e, t) => {
608
+ }, Un = (e, t) => {
597
609
  let n = pt.GenericTag(e);
598
610
  return {
599
611
  Tag: n,
600
612
  Live: mt.succeed(n, t)
601
613
  };
602
- }, Un = (e, t, n) => {
614
+ }, Wn = (e, t, n) => {
603
615
  if (!t) return { ok: !0 };
604
616
  let r = n === void 0 ? null : L(n);
605
617
  if (!r) return {
@@ -609,7 +621,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
609
621
  let i = t.trim();
610
622
  if (i === "*" || i === "") return { ok: !0 };
611
623
  let a = i.split(/\s+/).filter((e) => e.length > 0);
612
- for (let i of a) if (!Wn(i, r)) return {
624
+ for (let i of a) if (!Gn(i, r)) return {
613
625
  ok: !1,
614
626
  reason: `plugin "${e}" requires framework ${t}, running ${n}`
615
627
  };
@@ -622,7 +634,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
622
634
  patch: Number(t[3]),
623
635
  pre: t[4] ?? ""
624
636
  } : null;
625
- }, R = (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, Wn = (e, t) => {
637
+ }, R = (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, Gn = (e, t) => {
626
638
  if (e === "*") return !0;
627
639
  if (e.startsWith("^")) {
628
640
  let n = L(e.slice(1));
@@ -644,29 +656,29 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
644
656
  }
645
657
  let r = L(e);
646
658
  return r ? R(t, r) === 0 : !1;
647
- }, Gn = (e) => {
659
+ }, Kn = (e) => {
648
660
  let t = e.startsWith("@") ? e.replace(/^@[^/]+\//, "") : e;
649
661
  return (t.startsWith("plugin-") ? t.slice(7) : t).replace(/-([a-zA-Z0-9])/g, (e, t) => t.toUpperCase());
650
- }, Kn = (e) => {
662
+ }, qn = (e) => {
651
663
  let t = e.startsWith("@") ? e.replace(/^@[^/]+\//, "") : e;
652
664
  return (t.startsWith("plugin-") ? t.slice(7) : t).replace(/#/g, "--").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
653
- }, qn = Symbol.for("@voltro/protocol/pluginAliases"), z = () => {
654
- let e = globalThis, t = e[qn];
665
+ }, Jn = Symbol.for("@voltro/protocol/pluginAliases"), z = () => {
666
+ let e = globalThis, t = e[Jn];
655
667
  if (t instanceof Map) return t;
656
668
  let n = /* @__PURE__ */ new Map();
657
- return e[qn] = n, n;
658
- }, Jn = (e) => {
669
+ return e[Jn] = n, n;
670
+ }, Yn = (e) => {
659
671
  let t = z();
660
672
  for (let [n, r] of Object.entries(e)) {
661
673
  if (r === "") continue;
662
674
  let e = t.get(n);
663
675
  e === void 0 ? t.set(n, /* @__PURE__ */ new Set([r])) : e.add(r);
664
676
  }
665
- }, Yn = () => {
677
+ }, Xn = () => {
666
678
  z().clear();
667
- }, Xn = (e, t) => {
679
+ }, Zn = (e, t) => {
668
680
  let n = z().get(e);
669
- if (n === void 0 || n.size === 0) return `${Gn(e)}.${t}`;
681
+ if (n === void 0 || n.size === 0) return `${Kn(e)}.${t}`;
670
682
  if (n.size === 1) return `${[...n][0]}.${t}`;
671
683
  let r = [...n].filter((e) => !e.includes("#"));
672
684
  if (r.length === 1) return `${r[0]}.${t}`;
@@ -732,23 +744,23 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
732
744
  if (t.kind === "admission") return e.kind === (t.outcome.kind === "cancelled" ? "confirmed" : "already-terminal");
733
745
  let n = t.snapshot;
734
746
  return n.workflowName !== e.workflowName || n.id.length === 0 || n.executionId.length === 0 ? !1 : e.kind === "accepted" ? n.status === "running" || n.status === "suspended" : e.kind === "confirmed" ? n.status === "cancelled" : e.kind === "already-terminal" && (n.status === "succeeded" || n.status === "failed" || n.status === "cancelled");
735
- }, { message: () => "Queued cancellation acknowledgement contradicts its receipt or observation" })), Zn = p.Literal("cancel", "terminate", "abandon"), Qn = p.Struct({
747
+ }, { message: () => "Queued cancellation acknowledgement contradicts its receipt or observation" })), Qn = p.Literal("cancel", "terminate", "abandon"), $n = p.Struct({
736
748
  mode: p.String,
737
749
  dueAt: p.NullOr(p.Number),
738
750
  retryAfterMs: p.NullOr(p.Number),
739
751
  intentId: p.NullOr(p.String)
740
- }), $n = p.Struct({
752
+ }), er = p.Struct({
741
753
  id: p.String,
742
754
  workflowName: p.String,
743
755
  executionId: p.NullOr(p.String),
744
756
  status: p.Literal("running", "succeeded", "queued", "dropped", "skipped", "replayed"),
745
- deferral: p.optional(Qn)
746
- }), er = (e, t) => {
757
+ deferral: p.optional($n)
758
+ }), tr = (e, t) => {
747
759
  let n = I(e);
748
760
  return m.make(e.name, {
749
761
  payload: x(e.payloadSchema, e.name, N(n.guards)),
750
- success: $n,
751
- error: sn({
762
+ success: er,
763
+ error: cn({
752
764
  errorSchema: e.errorSchema,
753
765
  guards: n.guards
754
766
  }, t)
@@ -772,12 +784,12 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
772
784
  durationMs: p.NullOr(p.Number),
773
785
  traceId: p.NullOr(p.String),
774
786
  parentExecutionId: p.NullOr(p.String),
775
- parentClosePolicy: p.NullOr(Zn)
776
- }), tr = p.Struct({
787
+ parentClosePolicy: p.NullOr(Qn)
788
+ }), nr = p.Struct({
777
789
  tag: p.optional(p.String),
778
790
  status: p.optional(V),
779
791
  limit: p.optional(p.Number)
780
- }), nr = p.Struct({
792
+ }), rr = p.Struct({
781
793
  id: p.String,
782
794
  runId: p.String,
783
795
  stepName: p.String,
@@ -792,7 +804,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
792
804
  startedAt: p.Date,
793
805
  completedAt: p.NullOr(p.Date),
794
806
  durationMs: p.NullOr(p.Number)
795
- }), rr = p.Struct({
807
+ }), ir = p.Struct({
796
808
  id: p.String,
797
809
  runId: p.String,
798
810
  eventType: p.String,
@@ -800,7 +812,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
800
812
  occurredAt: p.Date,
801
813
  stepName: p.NullOr(p.String),
802
814
  attempt: p.NullOr(p.Number)
803
- }), ir = p.Struct({
815
+ }), ar = p.Struct({
804
816
  id: p.String,
805
817
  name: p.String,
806
818
  payload: p.Unknown,
@@ -808,7 +820,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
808
820
  subject: p.NullOr(p.Unknown),
809
821
  traceId: p.NullOr(p.String),
810
822
  occurredAt: p.Date
811
- }), ar = p.Struct({
823
+ }), or = p.Struct({
812
824
  id: p.String,
813
825
  eventId: p.String,
814
826
  eventName: p.String,
@@ -821,10 +833,10 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
821
833
  errorMessage: p.NullOr(p.String),
822
834
  createdAt: p.Date,
823
835
  completedAt: p.NullOr(p.Date)
824
- }), or = p.Struct({ id: p.String }), K = p.Struct({ runId: p.String }), sr = p.Struct({
836
+ }), sr = p.Struct({ id: p.String }), K = p.Struct({ runId: p.String }), cr = p.Struct({
825
837
  name: p.optional(p.String),
826
838
  limit: p.optional(p.Number)
827
- }), cr = p.Struct({ eventId: p.String }), q = p.Struct({
839
+ }), lr = p.Struct({ eventId: p.String }), q = p.Struct({
828
840
  requestId: p.NonEmptyString,
829
841
  cause: p.optionalWith(p.Struct({
830
842
  reason: p.NonEmptyString,
@@ -837,7 +849,7 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
837
849
  workflowName: p.String,
838
850
  executionId: p.String,
839
851
  control: p.optional(q)
840
- }), lr = p.Struct({
852
+ }), ur = p.Struct({
841
853
  workflowName: p.NonEmptyString,
842
854
  executionId: p.NonEmptyString,
843
855
  operation: p.Literal("cancel", "resume"),
@@ -846,106 +858,106 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
846
858
  workflowName: p.NonEmptyString,
847
859
  receiptId: p.NonEmptyString,
848
860
  requestId: p.NonEmptyString
849
- }), ur = p.Struct({
861
+ }), dr = p.Struct({
850
862
  ...Y.fields,
851
863
  cause: q.fields.cause
852
- }), dr = j({
864
+ }), fr = j({
853
865
  name: "__voltro.workflow.queued.cancel",
854
- input: ur,
866
+ input: dr,
855
867
  output: W,
856
868
  openAccess: "authorization happens in the executor against the target run — the caller must be authenticated and belong to the run's tenant, and a missing run answers exactly like a foreign one"
857
- }), fr = k({
869
+ }), pr = k({
858
870
  name: "__voltro.workflow.queued.control",
859
871
  input: Y,
860
872
  output: W,
861
873
  openAccess: "authorization happens in the executor against the target run — the caller must be authenticated and belong to the run's tenant, and a missing run answers exactly like a foreign one"
862
- }), pr = k({
874
+ }), mr = k({
863
875
  name: "__voltro.workflow.control",
864
- input: lr,
876
+ input: ur,
865
877
  output: U,
866
878
  openAccess: "authorization happens in the executor against the target run — the caller must be authenticated and belong to the run's tenant, and a missing run answers exactly like a foreign one"
867
- }), mr = p.Struct({
879
+ }), hr = p.Struct({
868
880
  id: p.String,
869
881
  signalName: p.String,
870
882
  payload: p.optional(p.Unknown)
871
- }), hr = p.Struct({
883
+ }), gr = p.Struct({
872
884
  id: p.String,
873
885
  updateName: p.String,
874
886
  payload: p.optional(p.Unknown),
875
887
  timeoutMs: p.optional(p.Number)
876
- }), gr = p.Struct({
888
+ }), _r = p.Struct({
877
889
  eventId: p.String,
878
890
  updateId: p.String,
879
891
  completedEventId: p.String,
880
892
  result: p.Unknown
881
- }), _r = k({
893
+ }), vr = k({
882
894
  name: "__voltro.workflow.run",
883
895
  source: "_voltro_workflow_runs",
884
- input: or,
896
+ input: sr,
885
897
  output: p.Array(G),
886
898
  openAccess: "the answer is scoped to the caller in the executor — a system or admin:full subject sees every row, an authenticated subject the rows of its own tenant, an anonymous caller none"
887
- }), vr = k({
899
+ }), yr = k({
888
900
  name: "__voltro.workflow.runs",
889
901
  source: "_voltro_workflow_runs",
890
- input: tr,
902
+ input: nr,
891
903
  output: p.Array(G),
892
904
  openAccess: "the answer is scoped to the caller in the executor — a system or admin:full subject sees every row, an authenticated subject the rows of its own tenant, an anonymous caller none"
893
- }), yr = k({
905
+ }), br = k({
894
906
  name: "__voltro.workflow.run.steps",
895
907
  source: "_voltro_workflow_run_steps",
896
908
  input: K,
897
- output: p.Array(nr),
909
+ output: p.Array(rr),
898
910
  openAccess: "the answer is scoped to the caller in the executor — a system or admin:full subject sees every row, an authenticated subject the rows of its own tenant, an anonymous caller none"
899
- }), br = k({
911
+ }), xr = k({
900
912
  name: "__voltro.workflow.run.events",
901
913
  source: "_voltro_workflow_run_events",
902
914
  input: K,
903
- output: p.Array(rr),
915
+ output: p.Array(ir),
904
916
  openAccess: "the answer is scoped to the caller in the executor — a system or admin:full subject sees every row, an authenticated subject the rows of its own tenant, an anonymous caller none"
905
- }), xr = k({
917
+ }), Sr = k({
906
918
  name: "__voltro.workflow.domainEvents",
907
919
  source: "_voltro_workflow_events",
908
- input: sr,
909
- output: p.Array(ir),
920
+ input: cr,
921
+ output: p.Array(ar),
910
922
  openAccess: "the answer is scoped to the caller in the executor — a system or admin:full subject sees every row, an authenticated subject the rows of its own tenant, an anonymous caller none"
911
- }), Sr = k({
923
+ }), Cr = k({
912
924
  name: "__voltro.workflow.event.deliveries",
913
925
  source: "_voltro_workflow_event_deliveries",
914
- input: cr,
915
- output: p.Array(ar),
926
+ input: lr,
927
+ output: p.Array(or),
916
928
  openAccess: "the answer is scoped to the caller in the executor — a system or admin:full subject sees every row, an authenticated subject the rows of its own tenant, an anonymous caller none"
917
- }), Cr = j({
929
+ }), wr = j({
918
930
  name: "__voltro.workflow.cancel",
919
931
  input: J,
920
932
  output: U,
921
933
  openAccess: "authorization happens in the executor against the target run — the caller must be authenticated and belong to the run's tenant, and a missing run answers exactly like a foreign one"
922
- }), wr = j({
934
+ }), Tr = j({
923
935
  name: "__voltro.workflow.resume",
924
936
  input: J,
925
937
  output: U,
926
938
  openAccess: "authorization happens in the executor against the target run — the caller must be authenticated and belong to the run's tenant, and a missing run answers exactly like a foreign one"
927
- }), Tr = j({
939
+ }), Er = j({
928
940
  name: "__voltro.workflow.signal",
929
- input: mr,
941
+ input: hr,
930
942
  output: p.Struct({ eventId: p.String }),
931
943
  openAccess: "authorization happens in the executor against the target run — the caller must be authenticated and belong to the run's tenant, and a missing run answers exactly like a foreign one"
932
- }), Er = j({
944
+ }), Dr = j({
933
945
  name: "__voltro.workflow.update",
934
- input: hr,
935
- output: gr,
946
+ input: gr,
947
+ output: _r,
936
948
  openAccess: "authorization happens in the executor against the target run — the caller must be authenticated and belong to the run's tenant, and a missing run answers exactly like a foreign one"
937
- }), Dr = p.NonEmptyString.pipe(p.filter((e) => e.trim().length > 0, { message: () => "A workflow retry identity must not be blank" })), Or = p.Struct({
938
- runId: Dr,
939
- requestId: Dr
940
- }), kr = p.Struct({
941
- ...Or.fields,
949
+ }), Or = p.NonEmptyString.pipe(p.filter((e) => e.trim().length > 0, { message: () => "A workflow retry identity must not be blank" })), kr = p.Struct({
950
+ runId: Or,
951
+ requestId: Or
952
+ }), Ar = p.Struct({
953
+ ...kr.fields,
942
954
  payloadOverride: p.optional(p.Unknown)
943
955
  }), X = {
944
956
  requestId: p.NonEmptyString,
945
957
  originalRunId: p.NonEmptyString,
946
958
  originalExecutionId: p.NonEmptyString,
947
959
  workflowName: p.NonEmptyString
948
- }, Ar = p.Union(p.Struct({
960
+ }, jr = p.Union(p.Struct({
949
961
  ...X,
950
962
  kind: p.Literal("queued"),
951
963
  nextAttemptAt: p.NullOr(p.Number)
@@ -966,38 +978,38 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
966
978
  ...X,
967
979
  kind: p.Literal("rejected"),
968
980
  failureId: p.NonEmptyString
969
- })), jr = "__voltro.undo.log", Mr = "__voltro.undo.apply", Nr = "__voltro.undo.redo", Pr = p.Struct({
981
+ })), Mr = "__voltro.undo.log", Nr = "__voltro.undo.apply", Pr = "__voltro.undo.redo", Fr = p.Struct({
970
982
  id: p.String,
971
983
  tag: p.String,
972
984
  label: p.NullOr(p.String),
973
985
  undone: p.Boolean,
974
986
  crossesAction: p.Boolean,
975
987
  createdAt: p.String
976
- }), Fr = class extends p.TaggedError()("UndoNotFound", { invocationId: p.String }) {}, Ir = class extends p.TaggedError()("UndoForbidden", { invocationId: p.String }) {}, Lr = class extends p.TaggedError()("UndoConflict", {
988
+ }), Ir = class extends p.TaggedError()("UndoNotFound", { invocationId: p.String }) {}, Lr = class extends p.TaggedError()("UndoForbidden", { invocationId: p.String }) {}, Rr = class extends p.TaggedError()("UndoConflict", {
977
989
  invocationId: p.String,
978
990
  reason: p.Literal("conflict", "action")
979
- }) {}, Rr = p.Union(Fr, Ir, Lr), zr = k({
980
- name: jr,
991
+ }) {}, zr = p.Union(Ir, Lr, Rr), Br = k({
992
+ name: Mr,
981
993
  source: "_voltro_undo_log",
982
994
  input: p.Struct({ limit: p.optional(p.Number) }),
983
- output: p.Array(Pr),
995
+ output: p.Array(Fr),
984
996
  openAccess: "subject-scoped by construction: lists only the calling subject's own undoable actions"
985
- }), Br = A({
986
- name: Mr,
997
+ }), Vr = A({
998
+ name: Nr,
987
999
  input: p.Struct({ invocationId: p.String }),
988
1000
  output: p.Struct({ ok: p.Boolean }),
989
- error: Rr,
1001
+ error: zr,
990
1002
  openAccess: "subject-scoped by construction: undo is per-actor — another subject's invocation fails typed with UndoForbidden"
991
- }), Vr = A({
992
- name: Nr,
1003
+ }), Hr = A({
1004
+ name: Pr,
993
1005
  input: p.Struct({ invocationId: p.String }),
994
1006
  output: p.Struct({ ok: p.Boolean }),
995
- error: Rr,
1007
+ error: zr,
996
1008
  openAccess: "subject-scoped by construction: redo is per-actor — another subject's invocation fails typed with UndoForbidden"
997
- }), Hr = class extends p.TaggedError()("TenantScopeViolation", {
1009
+ }), Ur = class extends p.TaggedError()("TenantScopeViolation", {
998
1010
  table: p.String,
999
1011
  reason: p.String
1000
- }) {}, Ur = class extends p.TaggedError()("StoreOperationFailed", {
1012
+ }) {}, Wr = class extends p.TaggedError()("StoreOperationFailed", {
1001
1013
  operation: p.String,
1002
1014
  table: p.String,
1003
1015
  cause: p.String
@@ -1005,28 +1017,28 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
1005
1017
  get message() {
1006
1018
  return `${this.operation} on '${this.table}' failed: ${this.cause}`;
1007
1019
  }
1008
- }, Wr = class extends p.TaggedError()("NoRowFound", {
1020
+ }, Gr = class extends p.TaggedError()("NoRowFound", {
1009
1021
  table: p.String,
1010
1022
  found: p.Number
1011
1023
  }) {
1012
1024
  get message() {
1013
1025
  return this.found === 0 ? `expected exactly one row in '${this.table}', found none` : `expected exactly one row in '${this.table}', found more than one`;
1014
1026
  }
1015
- }, Gr = class extends p.TaggedError()("TableValidationFailed", {
1027
+ }, Kr = class extends p.TaggedError()("TableValidationFailed", {
1016
1028
  table: p.String,
1017
1029
  summary: p.String,
1018
1030
  issues: p.Array(p.Struct({
1019
1031
  path: p.String,
1020
1032
  message: p.String
1021
1033
  }))
1022
- }) {}, Kr = class extends p.TaggedError()("TenantRowNotFound", {
1034
+ }) {}, qr = class extends p.TaggedError()("TenantRowNotFound", {
1023
1035
  table: p.String,
1024
1036
  id: p.String,
1025
1037
  reason: p.String
1026
- }) {}, qr = class extends p.TaggedError()("ServerOnlyColumnWrite", {
1038
+ }) {}, Jr = class extends p.TaggedError()("ServerOnlyColumnWrite", {
1027
1039
  table: p.String,
1028
1040
  columns: p.Array(p.String)
1029
- }) {}, Jr = class extends p.TaggedError()("ConstraintViolation", {
1041
+ }) {}, Yr = class extends p.TaggedError()("ConstraintViolation", {
1030
1042
  kind: p.Literal("foreignKey", "foreignKeyInUse", "unique", "notNull", "check"),
1031
1043
  table: p.String,
1032
1044
  operation: p.String,
@@ -1045,14 +1057,14 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
1045
1057
  case "check": return `check constraint ${t}: the row does not satisfy it`;
1046
1058
  }
1047
1059
  }
1048
- }, Yr = "__voltro.approvals.pending", Xr = "__voltro.approvals.decide", Zr = k({
1049
- name: Yr,
1060
+ }, Xr = "__voltro.approvals.pending", Zr = "__voltro.approvals.decide", Qr = k({
1061
+ name: Xr,
1050
1062
  source: "_voltro_approvals",
1051
1063
  input: p.Struct({ limit: p.optional(p.Number) }),
1052
- output: p.Array(Rt),
1064
+ output: p.Array(zt),
1053
1065
  openAccess: "the answer is scoped to the caller — a row appears only if they requested it or hold its recorded approver scopes, so an anonymous caller sees nothing"
1054
- }), Qr = A({
1055
- name: Xr,
1066
+ }), $r = A({
1067
+ name: Zr,
1056
1068
  input: p.Struct({
1057
1069
  approvalId: p.String,
1058
1070
  decision: p.Literal("approve", "reject"),
@@ -1062,35 +1074,35 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
1062
1074
  approvalId: p.String,
1063
1075
  status: p.Literal("approved", "rejected")
1064
1076
  }),
1065
- error: Lt,
1077
+ error: Rt,
1066
1078
  openAccess: "the approver authority is the PENDING ROW's own recorded scopes (plus an unconditional self-approval refusal), checked in-handler — a descriptor-level scope would have to be the union of every approval-requiring procedure in the app"
1067
- }), Z = "__voltro.connections.list", $r = "__voltro.connections.start", ei = "__voltro.connections.submitToken", ti = "__voltro.connections.disconnect", Q = p.Literal("oauth2", "pat"), ni = p.Literal("disconnected", "connected", "expired", "revoked", "error"), ri = p.Struct({
1079
+ }), Z = "__voltro.connections.list", ei = "__voltro.connections.start", ti = "__voltro.connections.submitToken", ni = "__voltro.connections.disconnect", Q = p.Literal("oauth2", "pat"), ri = p.Literal("disconnected", "connected", "expired", "revoked", "error"), ii = p.Struct({
1068
1080
  connectionId: p.String,
1069
1081
  kind: Q,
1070
1082
  label: p.String,
1071
- status: ni,
1083
+ status: ri,
1072
1084
  accountId: p.NullOr(p.String),
1073
1085
  accountLabel: p.NullOr(p.String),
1074
1086
  scopes: p.Array(p.String),
1075
1087
  expiresAt: p.NullOr(p.String),
1076
1088
  lastError: p.NullOr(p.String),
1077
1089
  connectedAt: p.NullOr(p.String)
1078
- }), ii = class extends p.TaggedError()("ConnectionNotDeclared", { connectionId: p.String }) {}, ai = class extends p.TaggedError()("ConnectionSubjectRequired", { connectionId: p.String }) {}, oi = class extends p.TaggedError()("ConnectionKindMismatch", {
1090
+ }), ai = class extends p.TaggedError()("ConnectionNotDeclared", { connectionId: p.String }) {}, oi = class extends p.TaggedError()("ConnectionSubjectRequired", { connectionId: p.String }) {}, si = class extends p.TaggedError()("ConnectionKindMismatch", {
1079
1091
  connectionId: p.String,
1080
1092
  expected: Q,
1081
1093
  actual: Q
1082
- }) {}, si = class extends p.TaggedError()("ConnectionHandshakeFailed", {
1094
+ }) {}, ci = class extends p.TaggedError()("ConnectionHandshakeFailed", {
1083
1095
  connectionId: p.String,
1084
1096
  reason: p.String,
1085
1097
  transient: p.Boolean
1086
- }) {}, $ = p.Union(ii, ai, oi, si), ci = k({
1098
+ }) {}, $ = p.Union(ai, oi, si, ci), li = k({
1087
1099
  name: Z,
1088
1100
  source: "_voltro_connections",
1089
1101
  input: p.Struct({}),
1090
- output: p.Array(ri),
1102
+ output: p.Array(ii),
1091
1103
  openAccess: "self-scoped read: projects the declared connections for the calling subject only (its own connect/disconnect state)"
1092
- }), li = j({
1093
- name: $r,
1104
+ }), ui = j({
1105
+ name: ei,
1094
1106
  input: p.Struct({
1095
1107
  connectionId: p.String,
1096
1108
  redirectTo: p.optional(p.String)
@@ -1101,8 +1113,8 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
1101
1113
  }),
1102
1114
  error: $,
1103
1115
  openAccess: "self-service: begins an oauth handshake that stores a credential for the calling subject only; anonymous callers fail typed with ConnectionSubjectRequired"
1104
- }), ui = A({
1105
- name: ei,
1116
+ }), di = A({
1117
+ name: ti,
1106
1118
  input: p.Struct({
1107
1119
  connectionId: p.String,
1108
1120
  token: p.String
@@ -1110,21 +1122,21 @@ var gt = p.Union(p.String, p.Number), h = p.Record({
1110
1122
  output: p.Struct({ ok: p.Boolean }),
1111
1123
  error: $,
1112
1124
  openAccess: "self-service: stores a pasted token as the calling subject's own credential; anonymous callers fail typed with ConnectionSubjectRequired"
1113
- }), di = A({
1114
- name: ti,
1125
+ }), fi = A({
1126
+ name: ni,
1115
1127
  input: p.Struct({ connectionId: p.String }),
1116
1128
  output: p.Struct({ ok: p.Boolean }),
1117
1129
  error: $,
1118
1130
  openAccess: "self-service: deletes only the calling subject's own credential row for this connection"
1119
- }), fi = (e) => {
1131
+ }), pi = (e) => {
1120
1132
  let t = e instanceof Date ? e.getTime() : typeof e == "number" ? e : typeof e == "string" ? new Date(e).getTime() : 0;
1121
1133
  return Number.isNaN(t) ? 0 : t;
1122
- }, pi = 4001, mi = /* @__PURE__ */ new Set(["/rpc", "/ws"]), hi = (e) => {
1134
+ }, mi = 4001, hi = /* @__PURE__ */ new Set(["/rpc", "/ws"]), gi = (e) => {
1123
1135
  for (let [t, n] of Object.entries(e.limits ?? {})) if (n !== void 0 && (!Number.isSafeInteger(n) || n <= 0)) throw Error(`defineWebSocket: limits.${t} must be a positive safe integer`);
1124
1136
  if (!e.path.startsWith("/")) throw Error(`defineWebSocket: path must be absolute, got '${e.path}'`);
1125
- if (mi.has(e.path)) throw Error(`defineWebSocket: '${e.path}' is the framework's own socket surface — mount the gateway elsewhere`);
1137
+ if (hi.has(e.path)) throw Error(`defineWebSocket: '${e.path}' is the framework's own socket surface — mount the gateway elsewhere`);
1126
1138
  if (e.auth !== "subject" && e.auth !== "public") throw Error(`defineWebSocket: auth must be 'subject' or 'public' (explicitly — an unauthenticated socket has to be a decision somebody wrote down), got '${String(e.auth)}'`);
1127
1139
  return e;
1128
- }, gi = 1;
1140
+ }, _i = 1;
1129
1141
  //#endregion
1130
- export { Ne as ADMIN_SCOPE, Oe as APIKEY_ISSUE_ORG_SCOPE, je as APIKEY_ISSUE_OTHER_SCOPE, Ce as APIKEY_ISSUE_SELF_SCOPE, Xr as APPROVALS_DECIDE_TAG, Yr as APPROVALS_PENDING_TAG, Lt as ApprovalDecisionErrors, It as ApprovalErrors, S as ApprovalExpired, Ft as ApprovalForbidden, jt as ApprovalNotFound, Pt as ApprovalNotPending, Nt as ApprovalRejected, At as ApprovalRequired, Mt as ApprovalSelfApproval, C as ApprovalUnavailable, i as AuthMiddleware, Tt as BusinessRuleViolation, Z as CONNECTIONS_LIST_TAG, ti as CONNECTION_DISCONNECT_TAG, $r as CONNECTION_START_TAG, ei as CONNECTION_SUBMIT_TOKEN_TAG, si as ConnectionHandshakeFailed, ae as ConnectionInfo, Te as ConnectionInfoMiddleware, Q as ConnectionKind, oi as ConnectionKindMismatch, ii as ConnectionNotDeclared, ri as ConnectionState, ni as ConnectionStatus, ai as ConnectionSubjectRequired, Jr as ConstraintViolation, t as DEFAULT_SCOPE_CACHE_MAX_ENTRIES, we as DEFAULT_SCOPE_CACHE_TTL_MS, _e as DEVICES_MANAGE_SCOPE, An as EVENT_ROUTE_SEP, Dn as EventKeyInvalid, En as EventPayloadInvalid, On as EventPayloadTooLarge, pi as GATEWAY_CREDENTIAL_EXPIRED_CLOSE_CODE, re as IMPERSONATION_METADATA_KEY, st as JSONAPI_MEDIA_TYPE, gn as MAX_EVENT_ENVELOPE_BYTES, ft as MUTATION_IN_FLIGHT_RETRY_AFTER_MS, qe as MutationConnectionRequired, dt as MutationInFlight, Wr as NoRowFound, nt as PROBLEM_JSON, gi as PROTOCOL_VERSION, Rt as PendingApproval, w as REACTIVITY_CHANNEL_PREFIX, at as REST_PROFILE_NAMES, f as ScopeError, qr as ServerOnlyColumnWrite, Ur as StoreOperationFailed, ie as Subject, oe as SubjectIdentity, o as SubjectService, Gr as TableValidationFailed, Kr as TenantRowNotFound, Hr as TenantScopeViolation, ge as TenantSelectionRefused, Mr as UNDO_APPLY_TAG, jr as UNDO_LOG_TAG, Nr as UNDO_REDO_TAG, l as Unauthenticated, Lr as UndoConflict, Ir as UndoForbidden, Pr as UndoLogEntry, Fr as UndoNotFound, Dt as ValidationError, Ot as ValidationErrors, Et as ValidationIssue, F as WorkflowAccessProperty, J as WorkflowControlInputSchema, q as WorkflowControlIntentSchema, lr as WorkflowControlReadInputSchema, U as WorkflowControlResultSchema, ir as WorkflowDomainEventRowSchema, sr as WorkflowDomainEventsInputSchema, cr as WorkflowEventDeliveriesInputSchema, ar as WorkflowEventDeliveryRowSchema, Zn as WorkflowParentClosePolicySchema, ur as WorkflowQueuedCancelInputSchema, W as WorkflowQueuedCancellationResultSchema, Y as WorkflowQueuedControlTargetSchema, kr as WorkflowRetryCommandSchema, Ar as WorkflowRetryObservationSchema, Or as WorkflowRetryTargetSchema, rr as WorkflowRunEventRowSchema, $n as WorkflowRunHandleSchema, or as WorkflowRunRefSchema, G as WorkflowRunRowSchema, H as WorkflowRunSnapshotSchema, V as WorkflowRunStatusSchema, nr as WorkflowRunStepRowSchema, K as WorkflowRunTableRefSchema, tr as WorkflowRunsInputSchema, mr as WorkflowSignalInputSchema, Qn as WorkflowStartDeferralSchema, B as WorkflowStartTerminalOutcomeSchema, hr as WorkflowUpdateInputSchema, gr as WorkflowUpdateResultSchema, dn as actionToRpc, Me as advisoryResourceGuardWarning, ne as anonymousSubject, Ct as applyRowPatch, ce as applyScopeDecision, Qr as approvalsDecideDescriptor, Zr as approvalsPendingQueryDescriptor, ze as assertAuthenticated, In as attachWorkflowAccess, Ue as beginIdempotent, Un as checkFrameworkCompat, ve as checkGuards, Ee as checkGuardsEffect, tt as compareSurfaces, n as composeAuthStrategies, Vn as composeRpcInterceptors, di as connectionDisconnectDescriptor, li as connectionStartDescriptor, ui as connectionSubmitTokenDescriptor, ci as connectionsListQueryDescriptor, Gt as declaredReactivityChannelKeys, j as defineAction, vn as defineEvent, A as defineMutation, Rn as definePlugin, zn as definePluginRoute, Hn as definePluginService, k as defineQuery, tn as defineStream, hi as defineWebSocket, xt as diffRows, Pe as effectiveScopes, kn as encodeEventKey, $e as encodeRestError, Ke as encodeRestSuccess, pn as errorTag, xn as eventAttached, yn as eventEnvelope, bn as eventGap, Cn as eventResumePoint, jn as eventRoute, Sn as eventStreamEvent, wn as eventSubscribeInput, Tn as eventToRpc, Be as failIdempotent, kt as fieldIssuesOf, ye as findAdvisoryResourceGuards, ut as finishIdempotent, Nn as formatEventRoute, be as getPolicyGuardResolver, ke as getResourceScopeResolver, I as getWorkflowAccess, Qe as groupShapeDigests, en as hasAccessDecision, fe as hasCallbackRoutes, De as hasEffectiveScope, N as hasEnforcedGuard, Se as hasScope, g as idToPath, We as idempotencyScope, b as inputLabel, xe as isAuthenticatedCheck, $t as isAuthenticatedGuard, _n as isEventDescriptor, St as isIdKeyed, u as isOpenAccess, Fe as isPolicyCheck, Qt as isPolicyGuard, Ut as isReactivityChannel, Wt as isReactivityChannelKey, c as isSystemSubject, se as isTenantSelectable, Xt as isWireReachable, de as makeScopeCache, He as memoryIdempotencyStore, Re as missingAccessDecision, un as mutationToRpc, rt as nextLink, hn as normalizeDescriptor, qt as normalizeSource, d as openAccessSpec, pe as orderByBearerPrefix, Mn as parseEventRoute, yt as pathToId, Gn as pluginAlias, Bn as pluginInstanceName, Kn as pluginSlug, Xn as pluginTag, ot as problemDetails, Je as problemTypeOf, Ze as procedureShapeDigest, Yt as publishReactivity, Xe as publishServerError, ln as queryToRpc, s as rawImpersonationMark, Ht as reactivityChannel, Jn as registerPluginAliases, Le as requireScope, Yn as resetPluginAliases, le as resolvePolicyAction, et as resolveRestFormat, Fn as resolveWorkflowAccess, ct as restViewSchema, _t as rowPatchOpSchema, vt as rowPatchSchema, Ge as schemaShape, te as scopeCacheKey, e as selectSubjectTenant, r as setEffectiveScopes, Ie as setPolicyGuardResolver, ue as setResourceScopeResolver, lt as shapeDigest, Kt as sourceKeys, fn as streamToRpc, x as strictInput, Ae as subjectAuditActorId, ee as subjectIdentity, a as subjectScopes, it as subscribeServerErrors, y as subscriptionEvent, he as systemSubject, me as tenantScopedSubject, mn as toRpc, Ye as toSnakeCase, fi as tsMs, Jt as undeclaredChannelKeys, Br as undoApplyDescriptor, zr as undoLogQueryDescriptor, Vr as undoRedoDescriptor, P as wireErrorUnion, cn as withRpcTag, Ln as workflowAccessDescriptor, Cr as workflowCancelDescriptor, pr as workflowControlQueryDescriptor, xr as workflowDomainEventsQueryDescriptor, Sr as workflowEventDeliveriesQueryDescriptor, dr as workflowQueuedCancelDescriptor, fr as workflowQueuedControlQueryDescriptor, wr as workflowResumeDescriptor, br as workflowRunEventsQueryDescriptor, _r as workflowRunQueryDescriptor, yr as workflowRunStepsQueryDescriptor, vr as workflowRunsQueryDescriptor, Tr as workflowSignalDescriptor, er as workflowToRpc, Er as workflowUpdateDescriptor, sn as workflowWireErrorUnion, Ve as wsMutationIdempotencyScope };
1142
+ export { Ne as ADMIN_SCOPE, Oe as APIKEY_ISSUE_ORG_SCOPE, je as APIKEY_ISSUE_OTHER_SCOPE, Ce as APIKEY_ISSUE_SELF_SCOPE, Zr as APPROVALS_DECIDE_TAG, Xr as APPROVALS_PENDING_TAG, Rt as ApprovalDecisionErrors, Lt as ApprovalErrors, S as ApprovalExpired, It as ApprovalForbidden, Mt as ApprovalNotFound, Ft as ApprovalNotPending, Pt as ApprovalRejected, jt as ApprovalRequired, Nt as ApprovalSelfApproval, C as ApprovalUnavailable, i as AuthMiddleware, Et as BusinessRuleViolation, Z as CONNECTIONS_LIST_TAG, ni as CONNECTION_DISCONNECT_TAG, ei as CONNECTION_START_TAG, ti as CONNECTION_SUBMIT_TOKEN_TAG, ci as ConnectionHandshakeFailed, ae as ConnectionInfo, Te as ConnectionInfoMiddleware, Q as ConnectionKind, si as ConnectionKindMismatch, ai as ConnectionNotDeclared, ii as ConnectionState, ri as ConnectionStatus, oi as ConnectionSubjectRequired, Yr as ConstraintViolation, t as DEFAULT_SCOPE_CACHE_MAX_ENTRIES, we as DEFAULT_SCOPE_CACHE_TTL_MS, _e as DEVICES_MANAGE_SCOPE, jn as EVENT_ROUTE_SEP, On as EventKeyInvalid, Dn as EventPayloadInvalid, kn as EventPayloadTooLarge, mi as GATEWAY_CREDENTIAL_EXPIRED_CLOSE_CODE, re as IMPERSONATION_METADATA_KEY, st as JSONAPI_MEDIA_TYPE, _n as MAX_EVENT_ENVELOPE_BYTES, ft as MUTATION_IN_FLIGHT_RETRY_AFTER_MS, qe as MutationConnectionRequired, dt as MutationInFlight, Gr as NoRowFound, nt as PROBLEM_JSON, _i as PROTOCOL_VERSION, zt as PendingApproval, w as REACTIVITY_CHANNEL_PREFIX, at as REST_PROFILE_NAMES, f as ScopeError, Jr as ServerOnlyColumnWrite, Wr as StoreOperationFailed, ie as Subject, oe as SubjectIdentity, o as SubjectService, Kr as TableValidationFailed, qr as TenantRowNotFound, Ur as TenantScopeViolation, ge as TenantSelectionRefused, Nr as UNDO_APPLY_TAG, Mr as UNDO_LOG_TAG, Pr as UNDO_REDO_TAG, l as Unauthenticated, Rr as UndoConflict, Lr as UndoForbidden, Fr as UndoLogEntry, Ir as UndoNotFound, Ot as ValidationError, kt as ValidationErrors, Dt as ValidationIssue, F as WorkflowAccessProperty, J as WorkflowControlInputSchema, q as WorkflowControlIntentSchema, ur as WorkflowControlReadInputSchema, U as WorkflowControlResultSchema, ar as WorkflowDomainEventRowSchema, cr as WorkflowDomainEventsInputSchema, lr as WorkflowEventDeliveriesInputSchema, or as WorkflowEventDeliveryRowSchema, Qn as WorkflowParentClosePolicySchema, dr as WorkflowQueuedCancelInputSchema, W as WorkflowQueuedCancellationResultSchema, Y as WorkflowQueuedControlTargetSchema, Ar as WorkflowRetryCommandSchema, jr as WorkflowRetryObservationSchema, kr as WorkflowRetryTargetSchema, ir as WorkflowRunEventRowSchema, er as WorkflowRunHandleSchema, sr as WorkflowRunRefSchema, G as WorkflowRunRowSchema, H as WorkflowRunSnapshotSchema, V as WorkflowRunStatusSchema, rr as WorkflowRunStepRowSchema, K as WorkflowRunTableRefSchema, nr as WorkflowRunsInputSchema, hr as WorkflowSignalInputSchema, $n as WorkflowStartDeferralSchema, B as WorkflowStartTerminalOutcomeSchema, gr as WorkflowUpdateInputSchema, _r as WorkflowUpdateResultSchema, fn as actionToRpc, Me as advisoryResourceGuardWarning, ne as anonymousSubject, Ct as applyRowPatch, ce as applyScopeDecision, $r as approvalsDecideDescriptor, Qr as approvalsPendingQueryDescriptor, ze as assertAuthenticated, Ln as attachWorkflowAccess, Ue as beginIdempotent, Wn as checkFrameworkCompat, ve as checkGuards, Ee as checkGuardsEffect, tt as compareSurfaces, n as composeAuthStrategies, Hn as composeRpcInterceptors, fi as connectionDisconnectDescriptor, ui as connectionStartDescriptor, di as connectionSubmitTokenDescriptor, li as connectionsListQueryDescriptor, Kt as declaredReactivityChannelKeys, j as defineAction, yn as defineEvent, A as defineMutation, zn as definePlugin, Bn as definePluginRoute, Un as definePluginService, k as defineQuery, nn as defineStream, gi as defineWebSocket, xt as diffRows, Pe as effectiveScopes, An as encodeEventKey, $e as encodeRestError, Ke as encodeRestSuccess, mn as errorTag, Sn as eventAttached, bn as eventEnvelope, xn as eventGap, wn as eventResumePoint, Mn as eventRoute, Cn as eventStreamEvent, Tn as eventSubscribeInput, En as eventToRpc, Be as failIdempotent, At as fieldIssuesOf, ye as findAdvisoryResourceGuards, ut as finishIdempotent, wt as firstEventValue, Pn as formatEventRoute, be as getPolicyGuardResolver, ke as getResourceScopeResolver, I as getWorkflowAccess, Qe as groupShapeDigests, tn as hasAccessDecision, fe as hasCallbackRoutes, De as hasEffectiveScope, N as hasEnforcedGuard, Se as hasScope, g as idToPath, We as idempotencyScope, b as inputLabel, xe as isAuthenticatedCheck, en as isAuthenticatedGuard, vn as isEventDescriptor, St as isIdKeyed, u as isOpenAccess, Fe as isPolicyCheck, $t as isPolicyGuard, Wt as isReactivityChannel, Gt as isReactivityChannelKey, c as isSystemSubject, se as isTenantSelectable, Zt as isWireReachable, de as makeScopeCache, He as memoryIdempotencyStore, Re as missingAccessDecision, dn as mutationToRpc, rt as nextLink, gn as normalizeDescriptor, Jt as normalizeSource, d as openAccessSpec, pe as orderByBearerPrefix, Nn as parseEventRoute, yt as pathToId, Kn as pluginAlias, Vn as pluginInstanceName, qn as pluginSlug, Zn as pluginTag, ot as problemDetails, Je as problemTypeOf, Ze as procedureShapeDigest, Xt as publishReactivity, Xe as publishServerError, un as queryToRpc, s as rawImpersonationMark, Ut as reactivityChannel, Yn as registerPluginAliases, Le as requireScope, Xn as resetPluginAliases, le as resolvePolicyAction, et as resolveRestFormat, In as resolveWorkflowAccess, ct as restViewSchema, _t as rowPatchOpSchema, vt as rowPatchSchema, Ge as schemaShape, te as scopeCacheKey, e as selectSubjectTenant, r as setEffectiveScopes, Ie as setPolicyGuardResolver, ue as setResourceScopeResolver, lt as shapeDigest, qt as sourceKeys, pn as streamToRpc, x as strictInput, Ae as subjectAuditActorId, ee as subjectIdentity, a as subjectScopes, it as subscribeServerErrors, y as subscriptionEvent, he as systemSubject, me as tenantScopedSubject, hn as toRpc, Ye as toSnakeCase, pi as tsMs, Yt as undeclaredChannelKeys, Vr as undoApplyDescriptor, Br as undoLogQueryDescriptor, Hr as undoRedoDescriptor, P as wireErrorUnion, ln as withRpcTag, Rn as workflowAccessDescriptor, wr as workflowCancelDescriptor, mr as workflowControlQueryDescriptor, Sr as workflowDomainEventsQueryDescriptor, Cr as workflowEventDeliveriesQueryDescriptor, fr as workflowQueuedCancelDescriptor, pr as workflowQueuedControlQueryDescriptor, Tr as workflowResumeDescriptor, xr as workflowRunEventsQueryDescriptor, vr as workflowRunQueryDescriptor, br as workflowRunStepsQueryDescriptor, yr as workflowRunsQueryDescriptor, Er as workflowSignalDescriptor, tr as workflowToRpc, Dr as workflowUpdateDescriptor, cn as workflowWireErrorUnion, Ve as wsMutationIdempotencyScope };
package/dist/rest.d.ts CHANGED
@@ -1289,6 +1289,9 @@ export declare interface RestRouteContext {
1289
1289
  * REST handlers on the same capability surface as query/mutation/action
1290
1290
  * handlers without creating a protocol-to-runtime package dependency. */
1291
1291
  readonly app?: unknown;
1292
+ /** The caller's address, resolved through `security.trustedProxies` — the
1293
+ * value the listener's per-IP shield counts. Absent when unknown. */
1294
+ readonly remoteAddr?: string;
1292
1295
  }
1293
1296
 
1294
1297
  export declare interface RestRouteDescriptor<I, O> {
package/dist/rest.js CHANGED
@@ -519,7 +519,8 @@ var w = (e, t, n) => t.method ?? (t.resource !== void 0 && n !== void 0 ? D("",
519
519
  path: l.path,
520
520
  query: l.query,
521
521
  rawBody: l.rawBody,
522
- ..._ === void 0 ? {} : { app: _ }
522
+ ..._ === void 0 ? {} : { app: _ },
523
+ ...l.remoteAddr === void 0 ? {} : { remoteAddr: l.remoteAddr }
523
524
  };
524
525
  if (a.guards) for (let e of a.guards) {
525
526
  let t = await e(x);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/protocol",
3
- "version": "0.79.0",
3
+ "version": "0.80.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",
@@ -65,8 +65,8 @@
65
65
  "dependencies": {
66
66
  "@effect/experimental": "^0.61.1",
67
67
  "@effect/sql": "^0.52.1",
68
- "@voltro/database": "0.79.0",
69
- "@voltro/logger": "0.79.0",
68
+ "@voltro/database": "0.80.0",
69
+ "@voltro/logger": "0.80.0",
70
70
  "jose": "^6.2.12"
71
71
  },
72
72
  "peerDependencies": {