@voltro/protocol 0.35.0 → 0.37.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,164 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.37.0] — 2026-08-13
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **@voltro/protocol, @voltro/runtime, @voltro/cli** — A field a procedure's input schema does not declare now REJECTS the call. It used to be discarded and the call ran with what was left.
47
+
48
+ The measurement, from a consumer's root layout:
49
+
50
+ ```ts
51
+ query?.('userSettings.list', { employeeId })
52
+ ```
53
+
54
+ That procedure declares `userId` / `userIdIn`. Effect's default `onExcessProperty: 'ignore'` decoded the payload to `{}` — not reasoned, measured:
55
+
56
+ ```ts
57
+ decodeUnknownSync(Struct({ userId: optional(String) }))({ employeeId: 'e' }) // → {}
58
+ ```
59
+
60
+ An empty input to a LIST query is not a narrower filter, it is the ABSENCE of one. Their admin, signed in as `2d0add2c…`, was served the settings row of `4410c2f8…` — another user's language and theme in the first paint, with nothing in any log to say so.
61
+
62
+ **Why refuse rather than warn.** The decoder cannot tell a projection field from a FILTER field, and that asymmetry is the whole risk: dropping an unknown `include` costs a caller some data, dropping an unknown `tenantId` hands them somebody else's. Nothing at decode time distinguishes the two, so the safe direction is the only one available — the same fail-closed reasoning as the row filter's refusal, one layer up. A warning would have to be read by someone, in a log, after the wrong rows were already served.
63
+
64
+ The typed loader query that shipped in 0.36.0 closes the same hole for callers we compile. This closes it for the ones we do not: a plain `fetch`, a curl, a still-cached bundle after a field rename, and every untyped caller.
65
+
66
+ Three things measured rather than assumed, because none follows from the annotation's name: it propagates into NESTED structs, through every member of a UNION, and leaves a non-struct payload (`Schema.Void`, a scalar) alone.
67
+
68
+ **`Schema.Struct({})` needed a filter, and only a real process showed it.** The fixture's `notes.list` declares an empty input; `POST /rpc` with `{ employeeId }` came back `200` with a snapshot, which for twenty minutes read as the whole change having failed. An empty `TypeLiteral` has no property signatures, so Effect has no expected key set for a key to be excess OF — self-consistent, and the wrong answer here, because `input: Schema.Struct({})` is the STRONGEST declaration a procedure can make and it was the one shape that accepted everything. It gets an explicit predicate now; `Schema.Record` keeps its open key set, because there the openness is declared.
69
+
70
+ **Verified against a running `voltro serve`, not only in units.** A declared input succeeds and inserts its row; an undeclared field is refused naming the key and the accepted set. The refusal arrives on the channel a payload decode failure ALREADY used — a missing required field produces the same `Die` with a `ParseError` message — so this adds no new error shape for a client to handle, it moves one case onto the channel the sibling case was always on.
71
+
72
+ `strictInput` lives in one module and every `Rpc.make` payload in `@voltro/protocol` goes through it — query, mutation, action, stream, event, plus the workflow start on both the server lifter and the browser-loaded rpc group. `strictInput.test.ts` asserts that SET by scanning the source, not the five lifters somebody remembered: a rule applied at the sites you can list is the shape that let `bootStoreCodec` be fixed twice and break a third time.
73
+
74
+ **`voltro update` carries you across this** — codemod `0.37.0/01_procedure-input-rejects-undeclared-fields`, a written note. A transform would have to guess which declared field a stray one meant, which is the same guess that produced the defect.
75
+
76
+ ### Added
77
+
78
+ - **@voltro/plugin-audit** — `redactInput` / `redactOutcome` gained `'shape'`, and `redactSubject` gained `'metadata-shape'`: the payload's STRUCTURE survives, no value from it.
79
+
80
+ ```json
81
+ { "__redacted": { "jiraToken": "string(113)", "attempts": "number" } }
82
+ ```
83
+
84
+ Requested by the consumer who had asked for the redaction one round earlier, and both requests were right. They spent a day on a bug their own audit trail could have ended in seconds — a value arrived as 113 characters where 44 were due, and the row that would have said so read `{"__redacted":"all"}`. `'all'` remains the default on every field; this is opt-in.
85
+
86
+ The rules, and the two that are decisions rather than details:
87
+
88
+ - A string reports its LENGTH. Never a prefix, never a hash — `enc:v1:` is a prefix and so is the first byte of a private key, so there is no prefix length that is safe for every credential format. - A number, boolean or date reports its TYPE only. A number can BE the secret. - **A key can be the value.** An object keyed by user data puts a datum where a schema name belongs, so a key is reproduced only when it looks like a declared field — a short plain identifier. The first version truncated long keys and documented the weakness instead; this module's own test caught 62 characters of a secret surviving on the first run. A leak with a footnote is still a leak. - **A string's length is a real disclosure, and a small one.** Stated in the docs rather than buried: for a fixed-format credential it carries nothing, for a human-chosen password it is a weak hint. `'all'` stays the default for anyone that matters to.
89
+
90
+ The `'shape'` outcome describes the payload it REPLACES — the value on success, the error on failure — rather than the event. Describing the event would report `{ kind, value, durationMs }` and hide the field, which is the failure the option exists to end. An error's `_tag` still survives, as it does under `'all'`.
91
+
92
+ ### Fixed
93
+
94
+ - **@voltro/cli** — `voltro db --help` listed fifteen of eighteen subcommands. `adopt`, `scan-credentials` and `encrypt-column` shipped and never joined the hand-written string.
95
+
96
+ A consumer wrote both halves of that gap into a requirements document, as separate items, neither of them about help text:
97
+
98
+ - **`voltro db encrypt-column` "does not exist"** — filed as a feature request, quoting the fifteen names they saw as evidence. It shipped in 0.33.0, and enabling `.encrypted()` on a populated column by hand is exactly the migration they were about to write themselves. - **`scan-credentials` "no longer exists"** — filed as CLOSED, a credential scanner struck off their list as removed. It had not moved.
99
+
100
+ A quoted enumeration is read as exhaustive, and the more careful the reader the more thoroughly they act on the missing entry. Same lesson a boot refusal in `procedureAccessGate` had already taught us, in a place nobody thought of as a message.
101
+
102
+ The usage line is now GENERATED from the dispatch table's key type (`Record<DbSubcommand, Handler>` in `dbCommand.ts`, names in `subcommandNames.ts`), so a handler with no name or a name with no handler fails to compile. `voltro privacy` is keyed the same way. The prose summary beside it cannot be generated — it carries per-command annotations — so a test asserts it mentions every name, because it carried the identical three omissions and it is what `voltro --help` prints first.
103
+
104
+ `subcommandHelpParity.test.ts` also NAMES the six commands whose subcommand menus have no dispatch table behind them (`webhooks`, `evolve`, `new`, `data`, `storage`, `add`). They dispatch through a switch and are unchecked; a silently-unchecked command reads exactly like a checked one.
105
+ - **@voltro/cli** — A 401 or 403 from the inspect surface now names `VOLTRO_INSPECT_TOKEN` and says which side is missing.
106
+
107
+ `voltro db plan --against <url>` printed `remote returned 403` and stopped. A consumer read that as a DATABASE permission problem — the natural reading of a 403 from a command whose entire subject is a database — and went looking at grants. The cause is one unset environment variable, which the command reads four lines above the message.
108
+
109
+ `voltro probe access` had half of it: it named the variable on 401 and not on 403, while classifying both as `refused`. So the two commands somebody needs during an access migration were the two that would not say what was wrong, and one of them said something misleading instead.
110
+
111
+ `inspectGateHint` is shared by both call sites and distinguishes the two statuses, because they call for different actions: a 401 means no credential was sent (set the variable), a 403 means the one sent was not accepted (the two values differ). Both halves of the sentence name the server AND the calling shell — naming one side produces a second failed attempt.
112
+ - **@voltro/cli** — `VOLTRO_TEMPLATES_DIR` is authoritative when set. It used to be a HINT: if the path it named held no `apps/` (or no `baselines/`), both resolvers fell through to the sibling-checkout walk-up and quietly used a different tree — or none.
113
+
114
+ A pointer that silently isn't followed is worse than a wrong one. A CI job aimed at the wrong path scaffolded from whatever it happened to find, and a job whose checkout had failed reported an empty template catalogue with nothing connecting that emptiness to the variable it was given. `scripts/lib/docsSite.mjs` states the same rule for `VOLTRO_DOCS_DIR`, and arrived at it the same way: you said where it is; it is not there.
115
+
116
+ Behaviourally this only changes the misconfigured case — a correct `VOLTRO_TEMPLATES_DIR` resolved to the same place before and after. What changes is that a wrong one now shows up as "not found, here is the path I was told" at the first thing that reads it, instead of as a different tree three steps later.
117
+
118
+ The unbundled resolution order is otherwise untouched: sibling `voltro-templates` → `.voltro-templates` → the bundled `templates/` a published CLI ships.
119
+
120
+ ### Internal (no consumer-facing effect)
121
+
122
+ - **@voltro/plugin-ai-flows** — Two comments in the flow engine cited task records from a plans tracker that has since been deleted. Comment-only; no behavior, no API, nothing a consumer can observe.
123
+
124
+ Worth writing down because of HOW it surfaced. The tracker was retired in the META repo, and the gate that went red was in THIS one — `check-stale-task-comments.mjs` resolves a comment's `task #NN` against `../plans`, so deleting a plan document in one repo can only be half a change, and the other half is in a repo the deleting commit never touched.
125
+
126
+ Neither comment was WRONG, which is the part that makes the rule earn its keep. The first claims `@voltro/ai` has first-class media generation — true: `generateImage`, `generateSpeech`, `generateVideo` all ship in `packages/ai/src/media.ts`. It now names those three instead of a record number, which is checkable without the deleted document. The second only quoted the retired id inside its own account of a defect (a `"not yet wired (task #35)"` message that outlived the shipped HITL park and misled an audit into filing it as unbuilt); the quote lost the number and kept the whole lesson.
127
+
128
+ The check's own failure text is the reasoning: a plan is retired for exactly two reasons — the work shipped, or it was dropped without shipping — and a comment still citing it asserts the second while usually meaning the first.
129
+
130
+ ---
131
+
132
+ ## [0.36.0] — 2026-08-13
133
+
134
+ ### ⚠ BREAKING
135
+
136
+ - **@voltro/integration-http, @voltro/plugin-atlassian** — A 401 from an upstream now produces `code: 'unauthorized'`, not `code: 'session_expired'`. The connection vault's own failure — where we DO know the credential is unusable — becomes `code: 'credential_unusable'`.
137
+
138
+ `session_expired` asserted a cause the status cannot support. A 401 says the credential was not accepted and says nothing about why: expired, revoked, insufficient scope and MALFORMED all produce it. A consumer's plugin sent ciphertext as a bearer token (a separate defect, fixed in the same release), the upstream answered 401, this name called it an expired session, and their health check acted on the name and deleted a valid session. Login loop, with every symptom pointing at a revoked credential.
139
+
140
+ Names get acted on, which is the whole reason to split them:
141
+
142
+ - `'unauthorized'` — the upstream refused. Non-transient, so still never retried; `status` rides along so a caller that knows more about its own upstream can decide for itself. Deciding for them is what this gives up. - `'credential_unusable'` — the connection vault could not produce a credential (no grant, revoked grant, refresh failed). Here the claim is ours to make, because the failure is ours rather than the far end's.
143
+
144
+ The 401 message stopped saying "session expired" too. It now says the credential was refused and that the reason is not in the response — which is the honest sentence and the one that would have saved the day this cost.
145
+
146
+ Its test asserts the CLAIM rather than banning the word: the first version forbade `/expired/i` and went red against the corrected message, which lists expiry as one of several things a 401 can mean. That distinction is the point of the change, so the assertion had to be about `session expired` specifically.
147
+
148
+ **`voltro update` carries you across this** — codemod `0.35.1/01_unauthorized-replaces-session-expired`.
149
+
150
+ ### Added
151
+
152
+ - **@voltro/web** — **`apiSurface: compatible` — why the three altered golden lines cannot break a caller.** `LoaderContext` and `LoaderFn` each gained a type parameter WITH a default, so an unparameterised reference still resolves. The one that needed proving is `query?`, which went from a written-out signature to `LoaderQuery<Procedures>` — and `LoaderQuery` is a conditional whose false branch is character-for-character the previous signature. `unknown` does not extend `ProcedureTypeMap`, so the defaulted instantiation takes that branch.
153
+
154
+ Proved with `tsc` rather than by reading it: a probe asserting mutual assignability between `LoaderQuery<unknown>` and the old signature compiles, and inverting the probe fails — with tsc printing the resolved type as `<T = unknown>(tag: string, input?: Record<string, unknown> | undefined) => Promise<T>`, which is the old signature verbatim.
155
+
156
+ `LoaderContext` takes the app's procedure map, so a loader's `query` infers its input and output from the descriptor instead of returning `unknown`.
157
+
158
+ ```ts
159
+ import type { AppProcedures } from '<your-api>/rpcGroup'
160
+
161
+ export const loader = async ({ query }: LoaderContext<AppProcedures>) => {
162
+ const rows = await query?.('bookmarks.list', { limit: 100 })
163
+ // ^ inferred; an unknown tag or a wrong input shape is a compile error
164
+ }
165
+ ```
166
+
167
+ `AppProcedures` is generated already and has been for a while — it was wired to `createHooks` on the CLIENT and to nothing on the server, so every loader call site spelled its own output type by hand and a typo in a tag compiled. A consumer reported it twice.
168
+
169
+ The extraction reuses `ProcedureInput` / `ProcedureOutput` from `@voltro/client` rather than re-deriving them: a second answer to "what does this tag return" drifts the first time a descriptor field is renamed, and both answers look right in isolation.
170
+
171
+ Opt-in, and non-breaking: with no map named, the signature is the previous `<T = unknown>(tag: string, …)`. The framework cannot import an app's generated file, which is the same reason `createHooks<AppProcedures>` takes it explicitly.
172
+
173
+ Covered by a `.test-d.ts`, because the failure mode is "it compiles when it should not" and no runtime assertion can observe that. Two of its cases exist because the first version was vacuous: an `interface` fixture does not satisfy the map constraint (no implicit index signature — the codegen emits an alias for exactly this reason), so the typed branch fell back silently and every `@ts-expect-error` came back unused.
174
+
175
+ ### Fixed
176
+
177
+ - **@voltro/cli** — The store a plugin receives through `bindDataStore` now carries the storage codec, so an `.encrypted()` column read through it decrypts.
178
+
179
+ A consumer measured both stores inside one request: `ctx.store` gave a 44-character plaintext PAT, and the store their plugin's `credentialsResolver` received gave 113 characters of `enc:v1:…`. Ciphertext is a syntactically valid bearer token, so nothing threw. Jira answered 401, `@voltro/integration-http` named that `session_expired`, their PAT health check did the reasonable thing with that name and deleted the session, and the user got login → dashboard → login forever. A configuration error in the costume of an authentication refusal, where every symptom pointed at the one explanation that was wrong.
180
+
181
+ The part worth recording is that `bootStoreCodec.ts` was written for exactly this, after it happened at two other seams, and its header predicts this consumer's symptom verbatim: "a route reading an `.encrypted()` column got the literal string `enc:v1:…` back … the failure reads as 'wrong credential'". The fix was applied per-seam. `bindDataStore` was not one of the seams anybody listed, so it happened a third time — and a per-seam test stayed green throughout, because it covered the two seams somebody remembered.
182
+
183
+ `bootStoreHandouts.test.ts` asserts the rule instead: no boot path hands a plugin the raw driver, on either boot path, with the wrapper applied before the handout. The codec needs no Subject — it is how a column is spelled on disk versus in JS — so there was never anything a boot-level store could not carry.
184
+
185
+ Also relevant to anyone who followed the 0.28.0 codemod: that codemod told apps to stop carrying a credential on the Subject and look it up in the resolver instead. Doing exactly that is what put an app on this seam, so the instruction and `.encrypted()` were not simultaneously satisfiable through it.
186
+ - **@voltro/runtime, @voltro/cli** — The rpc/WebSocket query and stream arms now resolve row visibility before the executor sees a context. Fixes a 0.35.0 regression that made every read throw for an app with a registered row filter, and the older leak underneath it.
187
+
188
+ 0.35.0 shipped two things for the row filter: the registration moved to `globalThis` (so a duplicate `@voltro/runtime` instance cannot hide it), and a scoped store built without a resolved scope started throwing instead of silently serving unfiltered rows. The first was a real fix for a real hazard. The second was correct in principle and immediately fatal in practice, because the framework itself had a path that did exactly what it now refuses.
189
+
190
+ The consumer who reported the original leak ran the two-line check we asked for and `getRowFilter()` was visible from their request path — so the instance split was NOT their cause, and our hypothesis was wrong. Their measurement is what found the real one: the refusal fired, meaning the registration was FOUND and `ctx.rowFilter` was still undefined at the store. Nothing was missing; a step was.
191
+
192
+ Four arms reach a request context. `makeOneShotQueryRunner` (REST) and `makeQuerySubscriber` (SSE) both `await withRowFilter(...)` and say so in a comment. The rpc query handler and the stream handler — each hand-copied into both boot paths — handed the raw request straight through. So a user's executor received a context whose `ctx.store` applied no row filter, on the two arms that carry the most traffic. It survived because subscriptions are refiltered per DELIVERY, which made a descriptor-returning query look correct end to end while the executor's own reads were not.
193
+
194
+ `withScopedRequest` is the seam that fixes it once: a request that already carries a scope passes through untouched (resolving twice would run the app's `load` twice per request), an app with NO filter stays fully synchronous, and an app with one gets an Effect — which every one of these call sites already accepts. A boot-path parity test pins both stream arms and the shared producer.
195
+
196
+ The refusal also stopped firing for a SYSTEM subject. That is not a softening: `resolveRowFilterScopeFor` returns `NO_ROW_FILTER` for a system subject, so the only correct value was already determined, and several legitimate paths (schedules, resumed workflows, the webhook trigger context) build a context directly with no scope. Demanding a decision there is what took the api down.
197
+
198
+ ---
199
+
42
200
  ## [0.35.0] — 2026-08-13
43
201
 
44
202
  ### ⚠ BREAKING
package/dist/index.d.ts CHANGED
@@ -4055,6 +4055,21 @@ export declare interface StreamProcedureDescriptor<Name extends string, Input ex
4055
4055
 
4056
4056
  export declare const streamToRpc: <Name extends string, Input extends Schema.Schema.Any, Element extends Schema.Schema.Any, Err extends Schema.Schema.All>(descriptor: StreamProcedureDescriptor<Name, Input, Element, Err>, extraErrors?: ExtraErrors) => Rpc.Rpc<Name, Input extends Schema.Struct.Fields ? Schema.Struct<Input> : Input, Stream<Element, Schema.Schema.All>, typeof Schema.Never, never>;
4057
4057
 
4058
+ /**
4059
+ * Annotate a procedure's input schema so an undeclared field fails the decode
4060
+ * instead of vanishing from it.
4061
+ *
4062
+ * Applies to the schema handed to `Rpc.make` — the PAYLOAD only. Not the
4063
+ * success schema, and not `descriptor.input`, which the client still reads
4064
+ * unannotated for schema-driven UI (`normalizeDescriptor`).
4065
+ *
4066
+ * Measured, because none of it follows from the annotation's name: it
4067
+ * propagates into NESTED structs and through a UNION's members, and a
4068
+ * non-struct payload (`Schema.Void`, a scalar) is unaffected — there is no
4069
+ * excess property for it to have.
4070
+ */
4071
+ export declare const strictInput: <S extends Schema.Schema.Any>(input: S) => S;
4072
+
4058
4073
  export declare const Subject: Schema.Union<[Schema.Struct<{
4059
4074
  type: Schema.Literal<["user"]>;
4060
4075
  id: typeof Schema.String;
package/dist/index.js CHANGED
@@ -76,7 +76,13 @@ var We = l.Union(l.String, l.Number), d = l.Record({
76
76
  _tag: l.Literal("error"),
77
77
  error: l.Unknown,
78
78
  revision: l.optional(l.Number)
79
- })), _ = class extends l.TaggedError()("BusinessRuleViolation", {
79
+ })), Xe = (e) => {
80
+ let t = e.ast;
81
+ return t._tag === "TypeLiteral" && (t.propertySignatures?.length ?? 0) === 0 && (t.indexSignatures?.length ?? 0) === 0;
82
+ }, _ = (e) => {
83
+ let t = e.annotations({ parseOptions: { onExcessProperty: "error" } });
84
+ return Xe(e) ? l.filter((e) => typeof e == "object" && e && Object.keys(e).length > 0 ? `this procedure declares no input; received: ${Object.keys(e).join(", ")}` : void 0)(t) : t;
85
+ }, v = class extends l.TaggedError()("BusinessRuleViolation", {
80
86
  rule: l.String,
81
87
  params: l.optional(l.Record({
82
88
  key: l.String,
@@ -85,34 +91,34 @@ var We = l.Union(l.String, l.Number), d = l.Record({
85
91
  field: l.optional(l.String),
86
92
  message: l.optional(l.String),
87
93
  severity: l.Literal("error", "warning")
88
- }) {}, v = class extends l.TaggedError()("ApprovalRequired", {
94
+ }) {}, y = class extends l.TaggedError()("ApprovalRequired", {
89
95
  approvalId: l.String,
90
96
  procedure: l.String,
91
97
  expiresAt: l.String,
92
98
  requiredScopes: l.Array(l.String),
93
99
  reason: l.NullOr(l.String),
94
100
  created: l.Boolean
95
- }) {}, y = class extends l.TaggedError()("ApprovalNotFound", { approvalId: l.String }) {}, b = class extends l.TaggedError()("ApprovalSelfApproval", {
101
+ }) {}, b = class extends l.TaggedError()("ApprovalNotFound", { approvalId: l.String }) {}, x = class extends l.TaggedError()("ApprovalSelfApproval", {
96
102
  approvalId: l.String,
97
103
  subjectId: l.NullOr(l.String)
98
- }) {}, x = class extends l.TaggedError()("ApprovalRejected", {
104
+ }) {}, S = class extends l.TaggedError()("ApprovalRejected", {
99
105
  approvalId: l.String,
100
106
  decidedBy: l.NullOr(l.String),
101
107
  note: l.NullOr(l.String)
102
- }) {}, S = class extends l.TaggedError()("ApprovalExpired", {
108
+ }) {}, C = class extends l.TaggedError()("ApprovalExpired", {
103
109
  approvalId: l.String,
104
110
  expiredAt: l.String
105
- }) {}, C = class extends l.TaggedError()("ApprovalNotPending", {
111
+ }) {}, w = class extends l.TaggedError()("ApprovalNotPending", {
106
112
  approvalId: l.String,
107
113
  status: l.String
108
- }) {}, w = class extends l.TaggedError()("ApprovalForbidden", {
114
+ }) {}, T = class extends l.TaggedError()("ApprovalForbidden", {
109
115
  approvalId: l.String,
110
116
  required: l.NullOr(l.String),
111
117
  message: l.String
112
- }) {}, T = class extends l.TaggedError()("ApprovalUnavailable", {
118
+ }) {}, E = class extends l.TaggedError()("ApprovalUnavailable", {
113
119
  procedure: l.String,
114
120
  message: l.String
115
- }) {}, E = l.Union(v, x, S, T), D = l.Union(y, b, S, C, w, T), O = l.Struct({
121
+ }) {}, D = l.Union(y, S, C, E), O = l.Union(b, x, C, w, T, E), k = l.Struct({
116
122
  id: l.String,
117
123
  procedure: l.String,
118
124
  kind: l.Literal("mutation", "action"),
@@ -126,28 +132,28 @@ var We = l.Union(l.String, l.Number), d = l.Record({
126
132
  decidedAt: l.NullOr(l.String),
127
133
  note: l.NullOr(l.String),
128
134
  relation: l.Literal("to-decide", "requested")
129
- }), k = "channel:", A = Symbol.for("@voltro/protocol/reactivityChannels"), j = globalThis, M = j[A] ?? (j[A] = /* @__PURE__ */ new Map()), Xe = /^[a-z][a-z0-9-]*(\.[a-z0-9-]+)*$/, Ze = (e) => {
130
- if (!Xe.test(e)) throw Error(`reactivityChannel('${e}'): invalid channel name.\n Use lowercase kebab segments separated by dots — \`presence\`, \`job-queue\`,
135
+ }), A = "channel:", j = Symbol.for("@voltro/protocol/reactivityChannels"), M = globalThis, N = M[j] ?? (M[j] = /* @__PURE__ */ new Map()), Ze = /^[a-z][a-z0-9-]*(\.[a-z0-9-]+)*$/, Qe = (e) => {
136
+ if (!Ze.test(e)) throw Error(`reactivityChannel('${e}'): invalid channel name.\n Use lowercase kebab segments separated by dots — \`presence\`, \`job-queue\`,
131
137
  \`billing.usage\`. A \`:\` is refused because it is the namespace separator in
132
- the routing key (\`${k}<name>\`), and an uppercase letter is\n refused because a key that differs only by case reads as one channel and
138
+ the routing key (\`${A}<name>\`), and an uppercase letter is\n refused because a key that differs only by case reads as one channel and
133
139
  routes as two.`);
134
- let t = M.get(e);
140
+ let t = N.get(e);
135
141
  if (t !== void 0) return t;
136
- let n = `${k}${e}`, r = Object.freeze({
142
+ let n = `${A}${e}`, r = Object.freeze({
137
143
  kind: "reactivity-channel",
138
144
  name: e,
139
145
  key: n,
140
146
  toString: () => n
141
147
  });
142
- return M.set(e, r), r;
143
- }, N = (e) => typeof e == "object" && !!e && e.kind === "reactivity-channel", P = (e) => e.startsWith(k), F = () => new Set([...M.values()].map((e) => e.key)), I = (e) => N(e) ? e.key : e, L = (e) => e === void 0 ? [] : Array.isArray(e) ? e.map(I) : [I(e)], Qe = (e) => e === void 0 ? void 0 : Array.isArray(e) ? e.map(I) : I(e), $e = (e) => {
144
- let t = F(), n = [];
145
- for (let r of e) for (let e of L(r.source)) !P(e) || t.has(e) || n.push({
148
+ return N.set(e, r), r;
149
+ }, P = (e) => typeof e == "object" && !!e && e.kind === "reactivity-channel", F = (e) => e.startsWith(A), I = () => new Set([...N.values()].map((e) => e.key)), L = (e) => P(e) ? e.key : e, $e = (e) => e === void 0 ? [] : Array.isArray(e) ? e.map(L) : [L(e)], et = (e) => e === void 0 ? void 0 : Array.isArray(e) ? e.map(L) : L(e), tt = (e) => {
150
+ let t = I(), n = [];
151
+ for (let r of e) for (let e of $e(r.source)) !F(e) || t.has(e) || n.push({
146
152
  procedure: r.name,
147
153
  key: e
148
154
  });
149
155
  return n;
150
- }, et = (e, t) => {
156
+ }, nt = (e, t) => {
151
157
  let n = e?.injectExternalChange;
152
158
  return n === void 0 ? !1 : (n.call(e, {
153
159
  table: t.key,
@@ -156,8 +162,8 @@ var We = l.Union(l.String, l.Number), d = l.Record({
156
162
  old: {},
157
163
  origin: "inline"
158
164
  }), !0);
159
- }, tt = (e) => e.internal !== !0, R = (e) => {
160
- if (nt(e), Array.isArray(e.guards) && e.guards.length === 0) throw Error(`${e.name}: \`guards: []\` is empty, so it enforces nothing — but it reads\n at the call site as if this procedure were protected. Omit the field for an
165
+ }, rt = (e) => e.internal !== !0, R = (e) => {
166
+ if (it(e), Array.isArray(e.guards) && e.guards.length === 0) throw Error(`${e.name}: \`guards: []\` is empty, so it enforces nothing — but it reads\n at the call site as if this procedure were protected. Omit the field for an
161
167
  unguarded procedure, or list the scopes required to call it.`);
162
168
  let t = typeof e.source == "string" ? [e.source] : Array.isArray(e.source) ? e.source : void 0;
163
169
  if (t !== void 0 && (t.length === 0 || t.some((e) => String(e).trim() === ""))) throw Error(`${e.name}: \`source\` is empty, so this query declares reactivity and\n subscribes to nothing — it serves one snapshot and never updates again,
@@ -174,7 +180,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
174
180
  let n = [e.publicApi === void 0 ? void 0 : "publicApi", e.exposeAsTool === void 0 ? void 0 : "exposeAsTool"].filter((e) => e !== void 0);
175
181
  if (n.length === 0) return e;
176
182
  throw Error(`${e.name}: \`internal: true\` cannot be combined with ${n.map((e) => `\`${e}\``).join(" or ")}. \`internal\` takes the procedure OFF the wire; those put it back ON a different one (${n.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 ${n.join(" / ")} annotation if it is not.`);
177
- }, nt = (e) => {
183
+ }, it = (e) => {
178
184
  let t = e.requiresApproval;
179
185
  if (t !== void 0) {
180
186
  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
@@ -184,7 +190,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
184
190
  human from every other, and the second-pair-of-eyes control would enforce nothing.
185
191
  Give the procedure a \`guards:\` decision, or drop \`requiresApproval\`.`);
186
192
  }
187
- }, rt = (e) => e.action !== void 0 && e.resourceType !== void 0, it = (e) => e.openAccess !== void 0 || e.guards !== void 0 && e.guards.length > 0, z = (e, t, n) => {
193
+ }, at = (e) => e.action !== void 0 && e.resourceType !== void 0, ot = (e) => e.openAccess !== void 0 || e.guards !== void 0 && e.guards.length > 0, z = (e, t, n) => {
188
194
  if (n === void 0) return t;
189
195
  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
190
196
  without an authorization check — e.g. \`openAccess: 'public pricing, no caller data'\`.`);
@@ -197,7 +203,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
197
203
  input: e.input,
198
204
  output: e.output,
199
205
  error: e.error ?? l.Never,
200
- source: Qe(e.source),
206
+ source: et(e.source),
201
207
  cache: e.cache,
202
208
  guards: z(e.name, e.guards, e.openAccess),
203
209
  openAccess: e.openAccess,
@@ -234,7 +240,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
234
240
  requiresApproval: e.requiresApproval,
235
241
  internal: e.internal,
236
242
  overridesPlugin: e.overridesPlugin
237
- }), at = (e) => R({
243
+ }), st = (e) => R({
238
244
  kind: "stream",
239
245
  name: e.name,
240
246
  input: e.input,
@@ -244,36 +250,36 @@ var We = l.Union(l.String, l.Number), d = l.Record({
244
250
  overridesPlugin: e.overridesPlugin,
245
251
  guards: z(e.name, e.guards, e.openAccess),
246
252
  openAccess: e.openAccess
247
- }), U = (e, t) => t && t.length > 0 ? l.Union(e, ...t) : e, W = (e, t) => t && t.some((e) => !a(e)) ? l.Union(e, c) : e, ot = (e) => l.Union(e, _), st = (e, t) => t === void 0 ? e : l.Union(e, E), ct = (e, t) => u.make(e.name, {
248
- payload: e.input,
253
+ }), U = (e, t) => t && t.length > 0 ? l.Union(e, ...t) : e, W = (e, t) => t && t.some((e) => !a(e)) ? l.Union(e, c) : e, ct = (e) => l.Union(e, v), lt = (e, t) => t === void 0 ? e : l.Union(e, D), ut = (e, t) => u.make(e.name, {
254
+ payload: _(e.input),
249
255
  success: g(e.output),
250
256
  error: U(W(e.error, e.guards), t),
251
257
  stream: !0
252
- }), lt = (e, t) => u.make(e.name, {
253
- payload: e.input,
258
+ }), dt = (e, t) => u.make(e.name, {
259
+ payload: _(e.input),
254
260
  success: e.output,
255
- error: U(st(ot(W(e.error, e.guards)), e.requiresApproval), t)
256
- }), ut = (e, t) => u.make(e.name, {
257
- payload: e.input,
261
+ error: U(lt(ct(W(e.error, e.guards)), e.requiresApproval), t)
262
+ }), ft = (e, t) => u.make(e.name, {
263
+ payload: _(e.input),
258
264
  success: e.output,
259
- error: U(st(W(e.error, e.guards), e.requiresApproval), t)
260
- }), dt = (e, t) => u.make(e.name, {
261
- payload: e.input,
265
+ error: U(lt(W(e.error, e.guards), e.requiresApproval), t)
266
+ }), pt = (e, t) => u.make(e.name, {
267
+ payload: _(e.input),
262
268
  success: e.element,
263
269
  error: U(e.error, t),
264
270
  stream: !0
265
- }), ft = (e) => {
271
+ }), mt = (e) => {
266
272
  if (typeof e != "object" || !e) return;
267
273
  let t = e._tag;
268
274
  return typeof t == "string" ? t : void 0;
269
- }, pt = (e) => {
275
+ }, ht = (e) => {
270
276
  switch (e.kind) {
271
- case "query": return ct(e);
272
- case "mutation": return lt(e);
273
- case "action": return ut(e);
274
- case "stream": return dt(e);
277
+ case "query": return ut(e);
278
+ case "mutation": return dt(e);
279
+ case "action": return ft(e);
280
+ case "stream": return pt(e);
275
281
  }
276
- }, mt = (e) => {
282
+ }, gt = (e) => {
277
283
  let t = e.input, n = t === void 0 ? {} : { input: t }, r = e.output, i = r === void 0 ? {} : { output: r };
278
284
  if (e.kind === "query") return {
279
285
  kind: "query",
@@ -314,7 +320,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
314
320
  ..."shapeItem" in e && e.shapeItem !== void 0 ? { shapeItem: e.shapeItem } : {}
315
321
  }))
316
322
  };
317
- }, ht = 7500, gt = (e) => typeof e == "object" && !!e && e.kind === "event", _t = (e) => {
323
+ }, _t = 7500, vt = (e) => typeof e == "object" && !!e && e.kind === "event", yt = (e) => {
318
324
  if (e.name.length === 0) throw Error("defineEvent: `name` must not be empty");
319
325
  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
320
326
  whitespace and delivers nothing — silently, and only on that broker, so an
@@ -349,67 +355,67 @@ var We = l.Union(l.String, l.Number), d = l.Record({
349
355
  webhook: e.webhook,
350
356
  delivery: e.delivery
351
357
  };
352
- }, vt = (e) => l.Struct({
358
+ }, bt = (e) => l.Struct({
353
359
  _tag: l.Literal("event"),
354
360
  origin: l.String,
355
361
  n: l.Number,
356
362
  emittedAt: l.Number,
357
363
  payload: e
358
- }), yt = l.Struct({
364
+ }), xt = l.Struct({
359
365
  _tag: l.Literal("gap"),
360
366
  missed: l.Number,
361
367
  reason: l.Literal("buffer", "resume")
362
- }), bt = l.Struct({ _tag: l.Literal("attached") }), xt = (e) => l.Union(bt, vt(e), yt), G = l.Struct({
368
+ }), G = l.Struct({ _tag: l.Literal("attached") }), St = (e) => l.Union(G, bt(e), xt), Ct = l.Struct({
363
369
  origin: l.String,
364
370
  n: l.Number
365
- }), St = (e) => l.Struct({
371
+ }), wt = (e) => l.Struct({
366
372
  key: e,
367
- resume: l.optional(l.Array(G))
368
- }), Ct = (e, t) => {
373
+ resume: l.optional(l.Array(Ct))
374
+ }), Tt = (e, t) => {
369
375
  let n = e.guards !== void 0 && e.guards.some((e) => !a(e)) ? c : l.Never, r = t && t.length > 0 ? l.Union(n, ...t) : n;
370
376
  return u.make(e.name, {
371
- payload: St(e.key),
372
- success: xt(e.payload),
377
+ payload: _(wt(e.key)),
378
+ success: St(e.payload),
373
379
  error: r,
374
380
  stream: !0
375
381
  });
376
- }, wt = class extends l.TaggedError()("EventPayloadInvalid", {
382
+ }, Et = class extends l.TaggedError()("EventPayloadInvalid", {
377
383
  event: l.String,
378
384
  message: l.String
379
- }) {}, Tt = class extends l.TaggedError()("EventKeyInvalid", {
385
+ }) {}, Dt = class extends l.TaggedError()("EventKeyInvalid", {
380
386
  event: l.String,
381
387
  message: l.String
382
- }) {}, Et = class extends l.TaggedError()("EventPayloadTooLarge", {
388
+ }) {}, Ot = class extends l.TaggedError()("EventPayloadTooLarge", {
383
389
  event: l.String,
384
390
  bytes: l.Number,
385
391
  limit: l.Number
386
- }) {}, Dt = (e) => {
392
+ }) {}, kt = (e) => {
387
393
  if (typeof e != "object" || !e) return JSON.stringify(e) ?? "null";
388
394
  let t = Object.entries(e).filter(([, e]) => e !== void 0).sort(([e], [t]) => e < t ? -1 : +(e > t));
389
395
  return JSON.stringify(t);
390
- }, Ot = "\0", kt = (e, t, n) => [
396
+ }, At = "\0", jt = (e, t, n) => [
391
397
  e ?? "~",
392
398
  t,
393
- Dt(n)
394
- ].join("\0"), At = (e) => {
399
+ kt(n)
400
+ ].join("\0"), Mt = (e) => {
395
401
  let [t = "~", n = "", r = ""] = e.split("\0");
396
402
  return {
397
403
  tenantId: t === "~" ? null : t,
398
404
  event: n,
399
405
  key: r
400
406
  };
401
- }, jt = (e) => e.split("\0").join(" · "), Mt = (e) => e, Nt = (e) => e, Pt = (e) => {
407
+ }, Nt = (e) => e.split("\0").join(" · "), Pt = (e) => e, Ft = (e) => e, It = (e) => {
402
408
  let t = e.alias?.trim(), n = e.instance?.trim(), r = t !== void 0 && t !== "" ? t : e.base;
403
409
  return n !== void 0 && n !== "" ? `${r}#${n}` : r;
404
- }, Ft = (e) => {
410
+ }, Lt = (e) => {
405
411
  if (e.length !== 0) return (t, n) => e.reduceRight((e, t) => t(e, n), t);
406
- }, It = (e, t) => {
412
+ }, Rt = (e, t) => {
407
413
  let n = He.GenericTag(e);
408
414
  return {
409
415
  Tag: n,
410
416
  Live: Ue.succeed(n, t)
411
417
  };
412
- }, Lt = (e, t, n) => {
418
+ }, zt = (e, t, n) => {
413
419
  if (!t) return { ok: !0 };
414
420
  let r = K(n);
415
421
  if (!r) return {
@@ -419,7 +425,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
419
425
  let i = t.trim();
420
426
  if (i === "*" || i === "") return { ok: !0 };
421
427
  let a = i.split(/\s+/).filter((e) => e.length > 0);
422
- for (let i of a) if (!Rt(i, r)) return {
428
+ for (let i of a) if (!Bt(i, r)) return {
423
429
  ok: !1,
424
430
  reason: `plugin "${e}" requires framework ${t}, running ${n}`
425
431
  };
@@ -432,7 +438,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
432
438
  patch: Number(t[3]),
433
439
  pre: t[4] ?? ""
434
440
  } : null;
435
- }, q = (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, Rt = (e, t) => {
441
+ }, q = (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, Bt = (e, t) => {
436
442
  if (e === "*") return !0;
437
443
  if (e.startsWith("^")) {
438
444
  let n = K(e.slice(1));
@@ -454,17 +460,17 @@ var We = l.Union(l.String, l.Number), d = l.Record({
454
460
  }
455
461
  let r = K(e);
456
462
  return r ? q(t, r) === 0 : !1;
457
- }, J = l.Literal("running", "succeeded", "failed", "cancelled", "suspended"), zt = l.Literal("cancel", "terminate", "abandon"), Bt = l.Struct({
463
+ }, J = l.Literal("running", "succeeded", "failed", "cancelled", "suspended"), Vt = l.Literal("cancel", "terminate", "abandon"), Ht = l.Struct({
458
464
  mode: l.String,
459
465
  dueAt: l.NullOr(l.Number),
460
466
  retryAfterMs: l.NullOr(l.Number),
461
467
  intentId: l.NullOr(l.String)
462
- }), Vt = l.Struct({
468
+ }), Ut = l.Struct({
463
469
  id: l.String,
464
470
  workflowName: l.String,
465
471
  executionId: l.NullOr(l.String),
466
472
  status: l.Literal("running", "queued", "dropped", "skipped"),
467
- deferral: l.optional(Bt)
473
+ deferral: l.optional(Ht)
468
474
  }), Y = l.Struct({
469
475
  id: l.String,
470
476
  tag: l.String,
@@ -484,12 +490,12 @@ var We = l.Union(l.String, l.Number), d = l.Record({
484
490
  durationMs: l.NullOr(l.Number),
485
491
  traceId: l.NullOr(l.String),
486
492
  parentExecutionId: l.NullOr(l.String),
487
- parentClosePolicy: l.NullOr(zt)
488
- }), Ht = l.Struct({
493
+ parentClosePolicy: l.NullOr(Vt)
494
+ }), Wt = l.Struct({
489
495
  tag: l.optional(l.String),
490
496
  status: l.optional(J),
491
497
  limit: l.optional(l.Number)
492
- }), Ut = l.Struct({
498
+ }), Gt = l.Struct({
493
499
  id: l.String,
494
500
  runId: l.String,
495
501
  stepName: l.String,
@@ -504,7 +510,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
504
510
  startedAt: l.Date,
505
511
  completedAt: l.NullOr(l.Date),
506
512
  durationMs: l.NullOr(l.Number)
507
- }), Wt = l.Struct({
513
+ }), Kt = l.Struct({
508
514
  id: l.String,
509
515
  runId: l.String,
510
516
  eventType: l.String,
@@ -512,7 +518,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
512
518
  occurredAt: l.Date,
513
519
  stepName: l.NullOr(l.String),
514
520
  attempt: l.NullOr(l.Number)
515
- }), Gt = l.Struct({
521
+ }), qt = l.Struct({
516
522
  id: l.String,
517
523
  name: l.String,
518
524
  payload: l.Unknown,
@@ -520,7 +526,7 @@ var We = l.Union(l.String, l.Number), d = l.Record({
520
526
  subject: l.NullOr(l.Unknown),
521
527
  traceId: l.NullOr(l.String),
522
528
  occurredAt: l.Date
523
- }), Kt = l.Struct({
529
+ }), Jt = l.Struct({
524
530
  id: l.String,
525
531
  eventId: l.String,
526
532
  eventName: l.String,
@@ -533,108 +539,108 @@ var We = l.Union(l.String, l.Number), d = l.Record({
533
539
  errorMessage: l.NullOr(l.String),
534
540
  createdAt: l.Date,
535
541
  completedAt: l.NullOr(l.Date)
536
- }), qt = l.Struct({ id: l.String }), X = l.Struct({ runId: l.String }), Jt = l.Struct({
542
+ }), Yt = l.Struct({ id: l.String }), X = l.Struct({ runId: l.String }), Xt = l.Struct({
537
543
  name: l.optional(l.String),
538
544
  limit: l.optional(l.Number)
539
- }), Yt = l.Struct({ eventId: l.String }), Z = l.Struct({
545
+ }), Zt = l.Struct({ eventId: l.String }), Z = l.Struct({
540
546
  workflowName: l.String,
541
547
  executionId: l.String
542
- }), Xt = l.Struct({
548
+ }), Qt = l.Struct({
543
549
  id: l.String,
544
550
  signalName: l.String,
545
551
  payload: l.optional(l.Unknown)
546
- }), Zt = l.Struct({
552
+ }), $t = l.Struct({
547
553
  id: l.String,
548
554
  updateName: l.String,
549
555
  payload: l.optional(l.Unknown),
550
556
  timeoutMs: l.optional(l.Number)
551
- }), Qt = l.Struct({
557
+ }), en = l.Struct({
552
558
  eventId: l.String,
553
559
  updateId: l.String,
554
560
  completedEventId: l.String,
555
561
  result: l.Unknown
556
- }), $t = B({
562
+ }), tn = B({
557
563
  name: "__voltro.workflow.run",
558
564
  source: "_voltro_workflow_runs",
559
- input: qt,
565
+ input: Yt,
560
566
  output: l.Array(Y)
561
- }), en = B({
567
+ }), nn = B({
562
568
  name: "__voltro.workflow.runs",
563
569
  source: "_voltro_workflow_runs",
564
- input: Ht,
570
+ input: Wt,
565
571
  output: l.Array(Y)
566
- }), tn = B({
572
+ }), rn = B({
567
573
  name: "__voltro.workflow.run.steps",
568
574
  source: "_voltro_workflow_run_steps",
569
575
  input: X,
570
- output: l.Array(Ut)
571
- }), nn = B({
576
+ output: l.Array(Gt)
577
+ }), an = B({
572
578
  name: "__voltro.workflow.run.events",
573
579
  source: "_voltro_workflow_run_events",
574
580
  input: X,
575
- output: l.Array(Wt)
576
- }), rn = B({
581
+ output: l.Array(Kt)
582
+ }), on = B({
577
583
  name: "__voltro.workflow.domainEvents",
578
584
  source: "_voltro_workflow_events",
579
- input: Jt,
580
- output: l.Array(Gt)
581
- }), an = B({
585
+ input: Xt,
586
+ output: l.Array(qt)
587
+ }), sn = B({
582
588
  name: "__voltro.workflow.event.deliveries",
583
589
  source: "_voltro_workflow_event_deliveries",
584
- input: Yt,
585
- output: l.Array(Kt)
586
- }), on = H({
590
+ input: Zt,
591
+ output: l.Array(Jt)
592
+ }), cn = H({
587
593
  name: "__voltro.workflow.cancel",
588
594
  input: Z,
589
595
  output: l.Struct({ ok: l.Boolean })
590
- }), sn = H({
596
+ }), ln = H({
591
597
  name: "__voltro.workflow.resume",
592
598
  input: Z,
593
599
  output: l.Struct({ ok: l.Boolean })
594
- }), cn = H({
600
+ }), un = H({
595
601
  name: "__voltro.workflow.signal",
596
- input: Xt,
602
+ input: Qt,
597
603
  output: l.Struct({ eventId: l.String })
598
- }), ln = H({
604
+ }), dn = H({
599
605
  name: "__voltro.workflow.update",
600
- input: Zt,
601
- output: Qt
602
- }), un = "__voltro.undo.log", dn = "__voltro.undo.apply", fn = "__voltro.undo.redo", pn = l.Struct({
606
+ input: $t,
607
+ output: en
608
+ }), fn = "__voltro.undo.log", pn = "__voltro.undo.apply", mn = "__voltro.undo.redo", hn = l.Struct({
603
609
  id: l.String,
604
610
  tag: l.String,
605
611
  label: l.NullOr(l.String),
606
612
  undone: l.Boolean,
607
613
  crossesAction: l.Boolean,
608
614
  createdAt: l.String
609
- }), mn = class extends l.TaggedError()("UndoNotFound", { invocationId: l.String }) {}, hn = class extends l.TaggedError()("UndoForbidden", { invocationId: l.String }) {}, gn = class extends l.TaggedError()("UndoConflict", {
615
+ }), gn = class extends l.TaggedError()("UndoNotFound", { invocationId: l.String }) {}, _n = class extends l.TaggedError()("UndoForbidden", { invocationId: l.String }) {}, vn = class extends l.TaggedError()("UndoConflict", {
610
616
  invocationId: l.String,
611
617
  reason: l.Literal("conflict", "action")
612
- }) {}, _n = l.Union(mn, hn, gn), vn = B({
613
- name: un,
618
+ }) {}, yn = l.Union(gn, _n, vn), bn = B({
619
+ name: fn,
614
620
  source: "_voltro_undo_log",
615
621
  input: l.Struct({ limit: l.optional(l.Number) }),
616
- output: l.Array(pn),
622
+ output: l.Array(hn),
617
623
  openAccess: "subject-scoped by construction: lists only the calling subject's own undoable actions"
618
- }), yn = V({
619
- name: dn,
624
+ }), xn = V({
625
+ name: pn,
620
626
  input: l.Struct({ invocationId: l.String }),
621
627
  output: l.Struct({ ok: l.Boolean }),
622
- error: _n,
628
+ error: yn,
623
629
  openAccess: "subject-scoped by construction: undo is per-actor — another subject's invocation fails typed with UndoForbidden"
624
- }), bn = V({
625
- name: fn,
630
+ }), Sn = V({
631
+ name: mn,
626
632
  input: l.Struct({ invocationId: l.String }),
627
633
  output: l.Struct({ ok: l.Boolean }),
628
- error: _n,
634
+ error: yn,
629
635
  openAccess: "subject-scoped by construction: redo is per-actor — another subject's invocation fails typed with UndoForbidden"
630
- }), xn = "__voltro.approvals.pending", Sn = "__voltro.approvals.decide", Cn = B({
631
- name: xn,
636
+ }), Cn = "__voltro.approvals.pending", wn = "__voltro.approvals.decide", Tn = B({
637
+ name: Cn,
632
638
  source: "_voltro_approvals",
633
639
  input: l.Struct({ limit: l.optional(l.Number) }),
634
- output: l.Array(O),
640
+ output: l.Array(k),
635
641
  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"
636
- }), wn = V({
637
- name: Sn,
642
+ }), En = V({
643
+ name: wn,
638
644
  input: l.Struct({
639
645
  approvalId: l.String,
640
646
  decision: l.Literal("approve", "reject"),
@@ -644,35 +650,35 @@ var We = l.Union(l.String, l.Number), d = l.Record({
644
650
  approvalId: l.String,
645
651
  status: l.Literal("approved", "rejected")
646
652
  }),
647
- error: D,
653
+ error: O,
648
654
  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"
649
- }), Tn = "__voltro.connections.list", En = "__voltro.connections.start", Dn = "__voltro.connections.submitToken", On = "__voltro.connections.disconnect", Q = l.Literal("oauth2", "pat"), kn = l.Literal("disconnected", "connected", "expired", "revoked", "error"), An = l.Struct({
655
+ }), Dn = "__voltro.connections.list", On = "__voltro.connections.start", kn = "__voltro.connections.submitToken", An = "__voltro.connections.disconnect", Q = l.Literal("oauth2", "pat"), jn = l.Literal("disconnected", "connected", "expired", "revoked", "error"), Mn = l.Struct({
650
656
  connectionId: l.String,
651
657
  kind: Q,
652
658
  label: l.String,
653
- status: kn,
659
+ status: jn,
654
660
  accountId: l.NullOr(l.String),
655
661
  accountLabel: l.NullOr(l.String),
656
662
  scopes: l.Array(l.String),
657
663
  expiresAt: l.NullOr(l.String),
658
664
  lastError: l.NullOr(l.String),
659
665
  connectedAt: l.NullOr(l.String)
660
- }), jn = class extends l.TaggedError()("ConnectionNotDeclared", { connectionId: l.String }) {}, Mn = class extends l.TaggedError()("ConnectionSubjectRequired", { connectionId: l.String }) {}, Nn = class extends l.TaggedError()("ConnectionKindMismatch", {
666
+ }), Nn = class extends l.TaggedError()("ConnectionNotDeclared", { connectionId: l.String }) {}, Pn = class extends l.TaggedError()("ConnectionSubjectRequired", { connectionId: l.String }) {}, Fn = class extends l.TaggedError()("ConnectionKindMismatch", {
661
667
  connectionId: l.String,
662
668
  expected: Q,
663
669
  actual: Q
664
- }) {}, Pn = class extends l.TaggedError()("ConnectionHandshakeFailed", {
670
+ }) {}, In = class extends l.TaggedError()("ConnectionHandshakeFailed", {
665
671
  connectionId: l.String,
666
672
  reason: l.String,
667
673
  transient: l.Boolean
668
- }) {}, $ = l.Union(jn, Mn, Nn, Pn), Fn = B({
669
- name: Tn,
674
+ }) {}, $ = l.Union(Nn, Pn, Fn, In), Ln = B({
675
+ name: Dn,
670
676
  source: "_voltro_connections",
671
677
  input: l.Struct({}),
672
- output: l.Array(An),
678
+ output: l.Array(Mn),
673
679
  openAccess: "self-scoped read: projects the declared connections for the calling subject only (its own connect/disconnect state)"
674
- }), In = H({
675
- name: En,
680
+ }), Rn = H({
681
+ name: On,
676
682
  input: l.Struct({
677
683
  connectionId: l.String,
678
684
  redirectTo: l.optional(l.String)
@@ -683,8 +689,8 @@ var We = l.Union(l.String, l.Number), d = l.Record({
683
689
  }),
684
690
  error: $,
685
691
  openAccess: "self-service: begins an oauth handshake that stores a credential for the calling subject only; anonymous callers fail typed with ConnectionSubjectRequired"
686
- }), Ln = V({
687
- name: Dn,
692
+ }), zn = V({
693
+ name: kn,
688
694
  input: l.Struct({
689
695
  connectionId: l.String,
690
696
  token: l.String
@@ -692,15 +698,15 @@ var We = l.Union(l.String, l.Number), d = l.Record({
692
698
  output: l.Struct({ ok: l.Boolean }),
693
699
  error: $,
694
700
  openAccess: "self-service: stores a pasted token as the calling subject's own credential; anonymous callers fail typed with ConnectionSubjectRequired"
695
- }), Rn = V({
696
- name: On,
701
+ }), Bn = V({
702
+ name: An,
697
703
  input: l.Struct({ connectionId: l.String }),
698
704
  output: l.Struct({ ok: l.Boolean }),
699
705
  error: $,
700
706
  openAccess: "self-service: deletes only the calling subject's own credential row for this connection"
701
- }), zn = (e) => {
707
+ }), Vn = (e) => {
702
708
  let t = e instanceof Date ? e.getTime() : typeof e == "number" ? e : typeof e == "string" ? new Date(e).getTime() : 0;
703
709
  return Number.isNaN(t) ? 0 : t;
704
- }, Bn = 1;
710
+ }, Hn = 1;
705
711
  //#endregion
706
- export { je as ADMIN_SCOPE, oe as APIKEY_ISSUE_ORG_SCOPE, n as APIKEY_ISSUE_OTHER_SCOPE, Ae as APIKEY_ISSUE_SELF_SCOPE, Sn as APPROVALS_DECIDE_TAG, xn as APPROVALS_PENDING_TAG, D as ApprovalDecisionErrors, E as ApprovalErrors, S as ApprovalExpired, w as ApprovalForbidden, y as ApprovalNotFound, C as ApprovalNotPending, x as ApprovalRejected, v as ApprovalRequired, b as ApprovalSelfApproval, T as ApprovalUnavailable, De as AuthMiddleware, _ as BusinessRuleViolation, Tn as CONNECTIONS_LIST_TAG, On as CONNECTION_DISCONNECT_TAG, En as CONNECTION_START_TAG, Dn as CONNECTION_SUBMIT_TOKEN_TAG, Pn as ConnectionHandshakeFailed, Se as ConnectionInfo, Te as ConnectionInfoMiddleware, Q as ConnectionKind, Nn as ConnectionKindMismatch, jn as ConnectionNotDeclared, An as ConnectionState, kn as ConnectionStatus, Mn as ConnectionSubjectRequired, _e as DEFAULT_SCOPE_CACHE_MAX_ENTRIES, ue as DEFAULT_SCOPE_CACHE_TTL_MS, Ot as EVENT_ROUTE_SEP, Tt as EventKeyInvalid, wt as EventPayloadInvalid, Et as EventPayloadTooLarge, ht as MAX_EVENT_ENVELOPE_BYTES, Bn as PROTOCOL_VERSION, O as PendingApproval, k as REACTIVITY_CHANNEL_PREFIX, c as ScopeError, Ce as Subject, Ee as SubjectIdentity, fe as SubjectService, dn as UNDO_APPLY_TAG, un as UNDO_LOG_TAG, fn as UNDO_REDO_TAG, be as Unauthenticated, gn as UndoConflict, hn as UndoForbidden, pn as UndoLogEntry, mn as UndoNotFound, Z as WorkflowControlInputSchema, Gt as WorkflowDomainEventRowSchema, Jt as WorkflowDomainEventsInputSchema, Yt as WorkflowEventDeliveriesInputSchema, Kt as WorkflowEventDeliveryRowSchema, zt as WorkflowParentClosePolicySchema, Wt as WorkflowRunEventRowSchema, Vt as WorkflowRunHandleSchema, qt as WorkflowRunRefSchema, Y as WorkflowRunRowSchema, J as WorkflowRunStatusSchema, Ut as WorkflowRunStepRowSchema, X as WorkflowRunTableRefSchema, Ht as WorkflowRunsInputSchema, Xt as WorkflowSignalInputSchema, Bt as WorkflowStartDeferralSchema, Zt as WorkflowUpdateInputSchema, Qt as WorkflowUpdateResultSchema, ut as actionToRpc, i as advisoryResourceGuardWarning, Oe as anonymousSubject, Ye as applyRowPatch, pe as applyScopeDecision, wn as approvalsDecideDescriptor, Cn as approvalsPendingQueryDescriptor, me as assertAuthenticated, ze as beginIdempotent, Lt as checkFrameworkCompat, r as checkGuards, ie as checkGuardsEffect, we as composeAuthStrategies, Ft as composeRpcInterceptors, Rn as connectionDisconnectDescriptor, In as connectionStartDescriptor, Ln as connectionSubmitTokenDescriptor, Fn as connectionsListQueryDescriptor, F as declaredReactivityChannelKeys, H as defineAction, _t as defineEvent, V as defineMutation, Mt as definePlugin, Nt as definePluginRoute, It as definePluginService, B as defineQuery, at as defineStream, qe as diffRows, ye as effectiveScopes, Dt as encodeEventKey, ft as errorTag, bt as eventAttached, vt as eventEnvelope, yt as eventGap, G as eventResumePoint, kt as eventRoute, xt as eventStreamEvent, St as eventSubscribeInput, Ct as eventToRpc, Ie as failIdempotent, e as findAdvisoryResourceGuards, Pe as finishIdempotent, jt as formatEventRoute, ve as getPolicyGuardResolver, ne as getResourceScopeResolver, it as hasAccessDecision, xe as hasCallbackRoutes, re as hasEffectiveScope, ae as hasScope, m as idToPath, Re as idempotencyScope, gt as isEventDescriptor, Je as isIdKeyed, a as isOpenAccess, ee as isPolicyCheck, rt as isPolicyGuard, N as isReactivityChannel, P as isReactivityChannelKey, ge as isSystemSubject, tt as isWireReachable, he as makeScopeCache, Be as memoryIdempotencyStore, te as missingAccessDecision, lt as mutationToRpc, mt as normalizeDescriptor, Qe as normalizeSource, s as openAccessSpec, At as parseEventRoute, Ge as pathToId, Pt as pluginInstanceName, et as publishReactivity, Ve as publishServerError, ct as queryToRpc, Ze as reactivityChannel, Ne as requireScope, f as rowPatchOpSchema, p as rowPatchSchema, le as scopeCacheKey, t as setEffectiveScopes, ce as setPolicyGuardResolver, o as setResourceScopeResolver, L as sourceKeys, dt as streamToRpc, ke as subjectIdentity, se as subjectScopes, Le as subscribeServerErrors, g as subscriptionEvent, Me as systemSubject, de as tenantScopedSubject, pt as toRpc, zn as tsMs, $e as undeclaredChannelKeys, yn as undoApplyDescriptor, vn as undoLogQueryDescriptor, bn as undoRedoDescriptor, on as workflowCancelDescriptor, rn as workflowDomainEventsQueryDescriptor, an as workflowEventDeliveriesQueryDescriptor, sn as workflowResumeDescriptor, nn as workflowRunEventsQueryDescriptor, $t as workflowRunQueryDescriptor, tn as workflowRunStepsQueryDescriptor, en as workflowRunsQueryDescriptor, cn as workflowSignalDescriptor, ln as workflowUpdateDescriptor, Fe as wsMutationIdempotencyScope };
712
+ export { je as ADMIN_SCOPE, oe as APIKEY_ISSUE_ORG_SCOPE, n as APIKEY_ISSUE_OTHER_SCOPE, Ae as APIKEY_ISSUE_SELF_SCOPE, wn as APPROVALS_DECIDE_TAG, Cn as APPROVALS_PENDING_TAG, O as ApprovalDecisionErrors, D as ApprovalErrors, C as ApprovalExpired, T as ApprovalForbidden, b as ApprovalNotFound, w as ApprovalNotPending, S as ApprovalRejected, y as ApprovalRequired, x as ApprovalSelfApproval, E as ApprovalUnavailable, De as AuthMiddleware, v as BusinessRuleViolation, Dn as CONNECTIONS_LIST_TAG, An as CONNECTION_DISCONNECT_TAG, On as CONNECTION_START_TAG, kn as CONNECTION_SUBMIT_TOKEN_TAG, In as ConnectionHandshakeFailed, Se as ConnectionInfo, Te as ConnectionInfoMiddleware, Q as ConnectionKind, Fn as ConnectionKindMismatch, Nn as ConnectionNotDeclared, Mn as ConnectionState, jn as ConnectionStatus, Pn as ConnectionSubjectRequired, _e as DEFAULT_SCOPE_CACHE_MAX_ENTRIES, ue as DEFAULT_SCOPE_CACHE_TTL_MS, At as EVENT_ROUTE_SEP, Dt as EventKeyInvalid, Et as EventPayloadInvalid, Ot as EventPayloadTooLarge, _t as MAX_EVENT_ENVELOPE_BYTES, Hn as PROTOCOL_VERSION, k as PendingApproval, A as REACTIVITY_CHANNEL_PREFIX, c as ScopeError, Ce as Subject, Ee as SubjectIdentity, fe as SubjectService, pn as UNDO_APPLY_TAG, fn as UNDO_LOG_TAG, mn as UNDO_REDO_TAG, be as Unauthenticated, vn as UndoConflict, _n as UndoForbidden, hn as UndoLogEntry, gn as UndoNotFound, Z as WorkflowControlInputSchema, qt as WorkflowDomainEventRowSchema, Xt as WorkflowDomainEventsInputSchema, Zt as WorkflowEventDeliveriesInputSchema, Jt as WorkflowEventDeliveryRowSchema, Vt as WorkflowParentClosePolicySchema, Kt as WorkflowRunEventRowSchema, Ut as WorkflowRunHandleSchema, Yt as WorkflowRunRefSchema, Y as WorkflowRunRowSchema, J as WorkflowRunStatusSchema, Gt as WorkflowRunStepRowSchema, X as WorkflowRunTableRefSchema, Wt as WorkflowRunsInputSchema, Qt as WorkflowSignalInputSchema, Ht as WorkflowStartDeferralSchema, $t as WorkflowUpdateInputSchema, en as WorkflowUpdateResultSchema, ft as actionToRpc, i as advisoryResourceGuardWarning, Oe as anonymousSubject, Ye as applyRowPatch, pe as applyScopeDecision, En as approvalsDecideDescriptor, Tn as approvalsPendingQueryDescriptor, me as assertAuthenticated, ze as beginIdempotent, zt as checkFrameworkCompat, r as checkGuards, ie as checkGuardsEffect, we as composeAuthStrategies, Lt as composeRpcInterceptors, Bn as connectionDisconnectDescriptor, Rn as connectionStartDescriptor, zn as connectionSubmitTokenDescriptor, Ln as connectionsListQueryDescriptor, I as declaredReactivityChannelKeys, H as defineAction, yt as defineEvent, V as defineMutation, Pt as definePlugin, Ft as definePluginRoute, Rt as definePluginService, B as defineQuery, st as defineStream, qe as diffRows, ye as effectiveScopes, kt as encodeEventKey, mt as errorTag, G as eventAttached, bt as eventEnvelope, xt as eventGap, Ct as eventResumePoint, jt as eventRoute, St as eventStreamEvent, wt as eventSubscribeInput, Tt as eventToRpc, Ie as failIdempotent, e as findAdvisoryResourceGuards, Pe as finishIdempotent, Nt as formatEventRoute, ve as getPolicyGuardResolver, ne as getResourceScopeResolver, ot as hasAccessDecision, xe as hasCallbackRoutes, re as hasEffectiveScope, ae as hasScope, m as idToPath, Re as idempotencyScope, vt as isEventDescriptor, Je as isIdKeyed, a as isOpenAccess, ee as isPolicyCheck, at as isPolicyGuard, P as isReactivityChannel, F as isReactivityChannelKey, ge as isSystemSubject, rt as isWireReachable, he as makeScopeCache, Be as memoryIdempotencyStore, te as missingAccessDecision, dt as mutationToRpc, gt as normalizeDescriptor, et as normalizeSource, s as openAccessSpec, Mt as parseEventRoute, Ge as pathToId, It as pluginInstanceName, nt as publishReactivity, Ve as publishServerError, ut as queryToRpc, Qe as reactivityChannel, Ne as requireScope, f as rowPatchOpSchema, p as rowPatchSchema, le as scopeCacheKey, t as setEffectiveScopes, ce as setPolicyGuardResolver, o as setResourceScopeResolver, $e as sourceKeys, pt as streamToRpc, _ as strictInput, ke as subjectIdentity, se as subjectScopes, Le as subscribeServerErrors, g as subscriptionEvent, Me as systemSubject, de as tenantScopedSubject, ht as toRpc, Vn as tsMs, tt as undeclaredChannelKeys, xn as undoApplyDescriptor, bn as undoLogQueryDescriptor, Sn as undoRedoDescriptor, cn as workflowCancelDescriptor, on as workflowDomainEventsQueryDescriptor, sn as workflowEventDeliveriesQueryDescriptor, ln as workflowResumeDescriptor, an as workflowRunEventsQueryDescriptor, tn as workflowRunQueryDescriptor, rn as workflowRunStepsQueryDescriptor, nn as workflowRunsQueryDescriptor, un as workflowSignalDescriptor, dn as workflowUpdateDescriptor, Fe as wsMutationIdempotencyScope };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/protocol",
3
- "version": "0.35.0",
3
+ "version": "0.37.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",
@@ -54,8 +54,8 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@effect/sql": "^0.52.0",
57
- "@voltro/database": "0.35.0",
58
- "@voltro/logger": "0.35.0",
57
+ "@voltro/database": "0.37.0",
58
+ "@voltro/logger": "0.37.0",
59
59
  "jose": "^6.2.4"
60
60
  },
61
61
  "peerDependencies": {