@voltro/ui-shadcn 0.64.0 → 0.66.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 +161 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,167 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.66.0] — 2026-09-06
|
|
43
|
+
|
|
44
|
+
### ⚠ BREAKING
|
|
45
|
+
|
|
46
|
+
- **@voltro/client, @voltro/cli** — Socket reconnects retry immediately, then after 500 ms with factor 1.5 and a 5 s cap; configure `web.api.recovery.retry: { initialMs, thenMs, factor, maxMs }` beside `graceMs` and rebuild the web app. Web and React Native share the policy, reset it on protocol readiness, preserve subscription bases/revisions and report every scheduled retry in client diagnostics. One transport loop owns retries, preventing competing supervisor rebuilds. Migration for custom `startApiSupervisor`/`BuildClient` adapters: own socket retries inside the transport (or use `buildApiRuntime`); `onSocketIssue` now reports health/backstop signals only, and `retryDelayMs` overrides construction failures only. Header resolvers are evaluated per runtime build, so do not rely on socket cuts to refresh a rotated token: the web host retains bounded auth-error refresh, and custom hosts can call `SupervisorHandle.refreshAuth()`. Explicit `refreshAuth()`/`reconnect()` still rebuild. Ordinary web/RN mounts require no adapter changes.
|
|
47
|
+
|
|
48
|
+
**`voltro update` carries you across this** — codemod `0.66.0/02_socket-retry-owner`. 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.66.0).
|
|
49
|
+
- **@voltro/database, @voltro/runtime, @voltro/voltro** — `id({ scheme: 'numeric' })` declares the numeric row key it always emitted. The builder was typed `ColumnBuilder<string, 'id'>` for every scheme and the row decoder used `Schema.String` for every id, so a BIGSERIAL / AUTO_INCREMENT / IDENTITY column arrived in application code as a decimal string. It is now `number`, decoded as `Schema.Number`, and `InMemoryDataStore` allocates numbers so the in-memory store and a SQL store agree.
|
|
50
|
+
|
|
51
|
+
Migration — the codemod prints these, and they are ordered by how the failures surface. String operations on such an id stop compiling and the compiler names every one; wrap with `String(id)` at whatever boundary wants text. Equality against a quoted literal (`row.id === '4'`) is the half that keeps compiling and is now permanently false — drop the quotes. Seeds and fixtures spelling the id as a string fail to decode; write them as numbers. Template interpolation needs no change.
|
|
52
|
+
|
|
53
|
+
`reference(target, { storage: 'numeric' })` carries the same numeric type.
|
|
54
|
+
|
|
55
|
+
**`voltro update` carries you across this** — codemod `0.66.0/01_numeric-id-is-a-number`. 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.66.0).
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **@voltro/client, @voltro/protocol** — Add typed `useConnectionSetup(tag)` and `useMutation(tag).withConnectionSetup(handle)` for acknowledged connection-local setup before initial writes and replay. The Effect-scoped barrier follows physical sockets, restores acknowledged intent, shares concurrent setup attempts and clears on leave, unmount or subject/tenant change. Setup descriptors must explicitly declare connection-scoped idempotency; normal writes keep their keys and replay limits. This is not a durable outbox or an authorization grant. Existing mutation identity helpers retain their signatures.
|
|
60
|
+
- **@voltro/cli** — Generated production Compose stacks run declared boot-lifecycle seeds in the migration job, after the reviewed schema plan has applied. Seeding is therefore one pre-deploy operation rather than something every serving replica repeats on boot.
|
|
61
|
+
- **@voltro/protocol, @voltro/runtime, @voltro/cli, @voltro/voltro** — Mutations can declare `idempotencyScope: 'connection'` for native WebSocket side effects: repeat keys deduplicate within that server-owned connection but execute again on a replacement connection. HTTP RPCs fail with typed `MutationConnectionRequired`, including when dedup is disabled; internal, public REST and agent-tool projections are refused. The default subject-scoped replay behavior of ordinary business mutations is unchanged. This does not replace application authorization, reconnect ordering, durable pending writes or disconnect cleanup.
|
|
62
|
+
- **@voltro/runtime, @voltro/cli, @voltro/testing** — `ctx.connectionState` keeps server-owned, structured-cloneable values for one native RPC connection: mutations defer writes/deletes to the existing commit queue, rollback/retry discards them, and transport close clears the state without allowing late commits to resurrect it. Dev, serve and `makeTestContext({ connection })` share the implementation; `withRpcConnection` selects another connection against the same test store and services. This is neither authentication nor durable membership storage.
|
|
63
|
+
- **@voltro/database, @voltro/runtime, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql, @voltro/testing, @voltro/voltro** — `store.update(table, key, patch)`, `delete`, `patchJson` and `hardDelete` take EITHER an id string or the columns that identify the row:
|
|
64
|
+
|
|
65
|
+
await store.update('memberships', { userId: u, orgId: o }, { role: 'member' }) await store.delete('memberships', { userId: u, orgId: o })
|
|
66
|
+
|
|
67
|
+
A table declared `.primaryKey(['userId','orgId'])` — or with one explicit key column, which is how a one-to-one shares its parent's key — has no `id`, so it could not be reached by the keyed forms at all. Measured before this, against the in-memory store: `update('memberships', 'u1', …)` returned `null`, `delete('memberships', 'u1')` returned `false`, and the row was untouched. Those are the SAME values a missing row returns.
|
|
68
|
+
|
|
69
|
+
The five store implementations did not agree about it either. The SQL stores emitted `WHERE id = ?` literally — 3 sites in postgres, 8 in mysql, 3 in sqlite, 3 in mssql — so a dialect answered `column "id" does not exist`, while the in-memory store keys rows internally and simply missed. A suite on `makeTestApp` runs the quiet one, so it went green over a call that fails in production.
|
|
70
|
+
|
|
71
|
+
A key is resolved ONCE, by `rowKeyColumns` in `@voltro/database`, so the five implementations cannot drift about what a key is. A bare string still means the `id` column, unchanged; on a table that has none it is refused with the key columns named, rather than answering `null`.
|
|
72
|
+
|
|
73
|
+
Two further defects surfaced when a composite-key table joined the dialect parity harness, both of them older than this change and invisible while the feature had no users:
|
|
74
|
+
|
|
75
|
+
- **MySQL could not INSERT such a row.** A row arriving without an `id` was assumed to be an AUTO_INCREMENT table, so the store recovered the key via `LAST_INSERT_ID()` and failed with "is the primary key AUTO_INCREMENT?" — a question about a column the table never declared. It now reads the post-image back by the values just written when nothing was generated. - **MSSQL could not CREATE one over `text()` columns.** `text()` renders as `NVARCHAR(MAX)` there, which SQL Server refuses as a key column. The emitter already bounds indexed text to `NVARCHAR(450)` for exactly this reason; the primary key's own columns were missing from the set that drives it.
|
|
76
|
+
|
|
77
|
+
`runDialectParity` gained three composite-key scenarios, so all five dialects assert it — the harness that found both of the above.
|
|
78
|
+
|
|
79
|
+
`apiSurface: compatible` — the golden lines that are REPLACED rather than added are the three keyed signatures widening `primaryKey: string` to `primaryKey: RowKey`. A widened parameter accepts everything it did before, so no call site can fail; `@voltro/voltro` is named because the two aggregate goldens carry the same surface and a source package's own report does not pull them along.
|
|
80
|
+
- **@voltro/cli** — `voltro create-project` can add multiple independently cloud-bound projects to one workspace. `add-app --to` and `cloud project link --project` select a target explicitly when needed, and default project port ranges never overlap. API apps now receive manifest/config ports alongside web apps. While the `compose` baseline is active, project/app changes and `voltro baseline sync` regenerate every service plus per-project PostgreSQL databases from those manifests. The generated stack pins PostgreSQL 18.6 and lets containerized web dev resolve its API proxy through `VOLTRO_API_ORIGIN` service DNS.
|
|
81
|
+
- **@voltro/database** — `.primaryKey(['memberId'])` accepts a single explicit natural or reference column as well as a composite set. It previously refused one field and pointed the caller at `id()`, which made a one-to-one shared primary-key/foreign-key impossible to declare without inventing a second key column.
|
|
82
|
+
- **@voltro/database** — `reference(target, { storage: 'numeric' })` declares a foreign key to a table whose primary key is numeric, without disguising it as a plain integer column: row schemas decode it as a number, every SQL renderer emits the dialect's 64-bit integer form, snapshots and introspection round-trip the storage kind, and the migration planner no longer churns it back to text.
|
|
83
|
+
|
|
84
|
+
`reference(target, { column: 'key' })` targets a unique alternate key instead of assuming `id`. Deferred cyclic constraints, subject graphs and test factories carry that target column through. The default remains text-and-`id`.
|
|
85
|
+
- **@voltro/protocol, @voltro/cli, @voltro/testing** — Custom REST handlers receive the per-request application context as `ctx.app`. Development, production `serve` and `makeTestApp` bind the same surface, so a REST webhook can publish a typed event or reach cache, KV, workflows and webhooks exactly as an RPC handler does — without a protocol-to-runtime dependency and without stepping outside the framework to do it.
|
|
86
|
+
- **@voltro/protocol** — Typed REST handlers can shape their own response. `response(body, { status, headers })` selects a status and headers, including repeated values such as the several `Set-Cookie` lines a login emits; `restError(status, body, headers)` preserves an application-defined JSON error contract instead of flattening it to the framework's shape; binary responses can pick status and headers the same way. `RestRouteContext` exposes the byte-exact request body alongside method, path and query, so a signed webhook or a multipart protocol no longer has to drop down to a raw plugin route to see the bytes it must verify.
|
|
87
|
+
|
|
88
|
+
`requireAuthenticated()` gives a protected REST route one consistent 401 path, distinct from the 403 a scope check produces.
|
|
89
|
+
|
|
90
|
+
`apiSurface: compatible` — the golden's one replaced line is the handler's return type widening from `Promise<O> | O` to `Promise<O | RestResponse<O>> | O | RestResponse<O>`. A handler that returns `O` is still assignable, so no declaration site breaks. The one place it is observable is a consumer who reads the descriptor's `handler` type and calls it directly, whose result is now the wider union; the supported way to exercise a route is `makeTestApp`, which is unaffected.
|
|
91
|
+
- **@voltro/database, @voltro/runtime, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-mssql, @voltro/sql-sqlite, @voltro/sql-turso, @voltro/testing, @voltro/voltro** — Upserts accept `updateValues` for additional conflict-only assignments without modifying the INSERT row or changing native column-list upserts into read/compute callbacks. Defined values override the selected update projection; undefined is omitted and identity rewrites are refused. The typed, Effect, replication, transaction and codec surfaces preserve the option, including row-bound encryption. Covered by the shared live-dialect parity suite, including both Turso backends. Existing option fields and calls remain compatible; this is the persistence foundation, not activation of column `.onUpdate()` callbacks.
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- **@voltro/client** — Keep a transport-interrupted mutation pending through repeated native `MutationInFlight` replies while its original replay window still permits it. Preserve one key, pending slot and optimistic patch; recheck identity, capability, transport and connection setup before each attempt. Do not renew the window, silently accept a failed write, retry other handler failures or busy-loop on malformed non-positive server delays.
|
|
96
|
+
- **@voltro/client** — `useOutbox` drains online enqueues and restored writes, joins concurrent replay calls, includes writes appended during an acknowledgement, and stops at every failed predecessor without a render-driven retry loop. A subsequent explicit retry or reconnection can retry transient failures; conflicts still require resolution. This scheduling fix does not add a durable-save acknowledgement or cross-tab ownership.
|
|
97
|
+
- **@voltro/client** — Keep late failures from ended physical mutation attempts from marking a healed replacement socket disconnected. Native mutation error events record `affectsTransport: false` when their lifetime evidence proves this distinction; the original failure, commit uncertainty and raw error reporting remain intact. Emitters without native lifetime evidence retain the conservative fallback.
|
|
98
|
+
- **@voltro/database, @voltro/runtime, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-mssql, @voltro/sql-sqlite, @voltro/testing, voltro** — Request-scoped stores evaluate column `.onUpdate()` before validation and encryption for keyed/bulk updates, soft deletes, upsert conflicts and JSON patches. `patchJson` accepts optional companion assignments in the same database UPDATE; object merges are shallow and retain JSON nulls. SQLite-family escaped object keys are compared and preserved after exactly one decoding step, based on a constant in-statement engine probe. Raw stores do not run application callbacks. Scalar JSON roots on SQL Server remain a separate schema-validation limitation.
|
|
99
|
+
- **@voltro/cli** — A generated multi-project Compose stack probes each API's declared port. The generator added one but probed the neighbouring web app's port instead, so an API could be marked healthy without ever being tested — and a web service that correctly waited for API readiness deadlocked behind a check that was never going to describe it.
|
|
100
|
+
- **@voltro/cli** — Production serve/start and SSR bundles now resolve dependencies with the bundler's actual import/require conditions. Installed ESM-only transitive packages no longer become bare external imports that fail after strict-pnpm deployment or pruning. Missing required dependencies and broken installed exports fail the build instead of being reported as optional peers; only actually absent packages declared optional by their importer use that fallback. Native runtime shims remain intact. If this exposes a previously hidden missing dependency, install/fix it in its importing package rather than labelling a required import optional. Real esbuild and Vite tests cover conditional exports, strict transitive resolution, optional and required failures, and execution after relocation without node_modules.
|
|
101
|
+
- **@voltro/cli** — `voltro doctor`'s `cron/bucket-on-wall-clock` follows value references into relative app helpers, including renamed imports, namespace members and re-exports. A `new Date()` in a called `midnightUtcForToday()` is now attributed to its cron in text and `--json` output. Unused helper exports, comments, strings and types do not supply either the wall clock or a clearing `scheduledAt` read. This remains a static advisory, not bucket-key data-flow proof; package imports, tsconfig aliases and dynamic imports are not followed.
|
|
102
|
+
- **@voltro/i18n** — `placeholdersOf` is now exported from `@voltro/i18n`'s public entry, including the built JavaScript and declarations. The 0.64.0 changelog promised that export, but it existed only in the internal catalog module; its tests imported that module and missed the absent package export. Those tests now exercise the public entry. No application change is needed unless you want to use the helper.
|
|
103
|
+
- **@voltro/runtime** — `InMemoryDataStore` keeps seeded rows from tables that deliberately have no `id` column — natural and composite primary keys — and inserts into those tables no longer fabricate one. The store assigns an internal map identity instead, which is what a SQL store does, rather than leaking an undeclared field into application rows.
|
|
104
|
+
|
|
105
|
+
Its numeric sequence now starts above the highest seeded id, so the first generated insert can no longer land on top of a seed row.
|
|
106
|
+
- **@voltro/sql-turso** — File-backed libsql connections enable WAL at startup so readers from completed interactive transactions do not block subsequent writers' commits. Remote transports retain server-owned journal configuration. Local-file concurrent-transaction tests require no Turso Cloud account; network and replica synchronization still need separate live validation.
|
|
107
|
+
- **@voltro/runtime** — The in-memory store now applies registered schema literal defaults (including timestamp `now`, JSON and arrays) and implicit nullable values on insert, including transactional, bulk, upsert-insert and insert-ignore-insert paths. Returned rows and change events contain the populated values; explicit values, seeded rows and conflict-update fields are preserved. Mutable defaults are copied per row. App-side factories/computed values remain owned by the shared mutation wrapper; this does not emulate generated SQL expressions or certify database-dialect parity.
|
|
108
|
+
- **@voltro/sql-mssql** — Bind Date parameters as `DateTime2` and strings as `NVarChar`, matching the schema before values reach SQL Server. This preserves milliseconds, dates before 1753, Unicode text and JSON keys/values across row writes, predicates and raw SQL. Explicit NULL remains `NVarChar`. The direct `tedious` dependency uses the version already locked for the SQL client.
|
|
109
|
+
- **@voltro/runtime, @voltro/cli, @voltro/protocol, @voltro/plugin-audit** — Every trace id now comes from one function, `newTraceId()` in `@voltro/runtime` — 128 random bits as 32 hex, the shape a `traceparent` parser, a collector and `voltro traces` all accept.
|
|
110
|
+
|
|
111
|
+
Seventeen call sites minted their own, and three of the shapes were not ids. `incoming:<webhookId>`, `reaction:<workflow>`, `schedule:<name>-<ms>` and the literal `'webhooks:trigger'` were CONSTANTS — five of them wrote the identical string into `traceId` and `source`, so the trace id was the caller's label wearing a second hat and every delivery a process handled shared one trace, with the waterfall merging unrelated work. Six more carried `public-`/`grpc-`/`rest-` plus eight base36 characters, about 41 bits, which collides well inside a busy day. One passed `randomUUID()` unstripped, 36 characters with dashes, which no `traceparent` parser accepts — including this framework's own.
|
|
112
|
+
|
|
113
|
+
The entry-point kind is not lost: it lives in the `source` field beside the id, which is where it was already being written.
|
|
114
|
+
|
|
115
|
+
Plugin HTTP routes now carry a real one too. `PluginHttpRouteEvent` gains `traceId` — the caller's `traceparent` where one arrived, freshly minted otherwise — and the audit trail records it instead of the empty string it wrote before, so an incident review can join a login to what it caused. `apiSurface: compatible`: an observer that ignores the new field is unaffected, and the framework is the only producer of the event.
|
|
116
|
+
|
|
117
|
+
A guard in `@voltro/runtime` now asks the question over the whole tree rather than per call site, because the previous test pinned exactly one of them and the same defect went on living at sixteen others.
|
|
118
|
+
- **@voltro/database, @voltro/runtime** — Declarative migrations now retain partial-index predicates and partial uniqueness on new and existing tables, introspect native filters on PostgreSQL/SQLite/SQL Server, detect same-name predicate changes and invalid PostgreSQL online indexes, and include predicates in drift fingerprints. A partial unique index is never emitted as a full UNIQUE constraint. Existing qualifying duplicates refuse the apply without automatic data repair; a pre-DDL conflict check protects replacements when the current column shape supports it. MySQL/MariaDB uniqueActive lowering remains; unlowered partial indexes are refused by the declarative path. SQL normalization is conservative, not a general expression-equivalence prover.
|
|
119
|
+
|
|
120
|
+
Memory upsert now addresses matched rows through their actual conflict-column key instead of assuming an id column, both directly and transactionally. Natural/composite primary-key rows preserve independent fields, rollback and post-commit events. The schema/API spelling is unchanged and needs no source codemod; projects upgrading must review and apply the newly visible missing constraints, resolving conflicting historical data explicitly rather than deleting it automatically.
|
|
121
|
+
- **@voltro/cli** — Production workspace WebSocket proxies preserve the browser's Host header, so the native same-origin guard accepts legitimate requests behind internal Docker or cluster hostnames while continuing to reject foreign and opaque origins.
|
|
122
|
+
- **@voltro/protocol** — REST descriptors reject undeclared descendant paths before decoding, resolving identity or executing their handler, instead of accidentally serving parent data or performing parent writes. Named parameters, trailing slashes, explicit splats and raw plugin HTTP prefix routes keep their declared behavior.
|
|
123
|
+
- **@voltro/runtime** — Isolate connection credentials across HTTP/WebSocket protocols and listeners, and deliver every disconnect to both runtime cleanup and the RPC core. Executors receive the server-owned `ctx.request.connection` identity, transport and close signal; numeric client IDs remain opaque process-local handles. Connection state is shared across bundled and external module copies.
|
|
124
|
+
- **@voltro/cli** — `voltro prune-runtime` now preserves package-resolution metadata for rooted native dependencies, so packages such as `argon2` remain importable in pruned production images.
|
|
125
|
+
- **@voltro/sql-sqlite, @voltro/sql-turso, @voltro/testing** — SQLite-family raw stores now encode registered JSON and array columns on every row-write path, including insertMany, updateMany, callback upserts and conflict-only updateValues. Previously objects could fail binding or reach Turso as invalid JSON when a request codec had not already serialized them. Shared live-dialect tests cover direct and transactional writes over SQLite and both Turso clients as well as the other SQL stores.
|
|
126
|
+
- **@voltro/testing** — Declared events published by a mutation under `invoke` now use the transaction's commit queue, including subject/tenant re-scopes; rollback and deadlock replay discard failed-attempt notifications, while untransacted actions still publish immediately.
|
|
127
|
+
- **@voltro/cli** — Apply the shared page-safe security-header policy to both web listeners, including streaming, assets and errors, with `security.headers` configuration and route-owned overrides. Do not derive HSTS from untrusted forwarded headers. Proxy native runtime capability discovery in production as well as dev: one public GET per configured workspace API, not the development inspect surface. Named and custom socket paths reach the actual server's replay capability; ambiguous custom socket parents fail explicitly instead of advertising the wrong API. No replay support or successful writes are synthesized by the proxy.
|
|
128
|
+
|
|
129
|
+
### Internal (no consumer-facing effect)
|
|
130
|
+
|
|
131
|
+
- **@voltro/cli** — Building the CLI package now gives its declaration-rollup process a bounded 6144 MiB heap, and packing delegates to the same build script. The seven-entry rollup exhausted the default roughly 4 GiB heap even in an isolated Node 24 build; the measured build succeeds with the scoped allowance. This does not change the memory settings of application builds or serving processes, and does not disable declaration validation or increase build concurrency.
|
|
132
|
+
- The local release gate now executes the CI test planner with all packages and both CLI projects, rather than skipping its GitHub-specific preparation and reading missing shard files. Missing filters or project selectors fail before tests start; the coverage verifier also refuses a missing expected-package plan. Selftests execute the workflow's actual producer/consumer handoff. MySQL test fixtures check authenticated TCP readiness and distinguish cold initialization from failed steady-state healthchecks. The Postgres partial-index contract creates and cleans up its own database on the regular fixture server, so all seven assertions run without an additional opt-in flag in both local and CI gates. Row-history test files run serially because their live fixtures install and write the same fixed-name Postgres table; parallel setup could deadlock before the history assertions ran when the schema was cold.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## [0.65.0] — 2026-09-04
|
|
137
|
+
|
|
138
|
+
### ⚠ BREAKING
|
|
139
|
+
|
|
140
|
+
- **@voltro/plugin-ai-flows** — `@voltro/plugin-ai-flows` declares `internal: true` on its `flow.run` workflow, so it no longer becomes a start rpc. Every start is server-side and always was: the plugin's own guarded procedures, the cadence tick, and `chainTo`'s `workflows.child`. There is no meaningful client start — the payload carries `runId`, the id of a row the caller must already have created.
|
|
141
|
+
|
|
142
|
+
Leaving the decision off was not a lax default. An app installs the plugin by re-exporting the descriptor from its own `flow.run.workflow.tsx`, which makes it one of the app's DISCOVERED workflows — exactly the set the boot gate judges — so under `security.defaultDeny` (on by default) the app's boot was REFUSED, naming a workflow the app had no way to decide about. A first-party descriptor must satisfy the framework's own gate, and a test now holds every workflow any first-party package ships to that.
|
|
143
|
+
|
|
144
|
+
Migration: start a flow through the plugin's guarded procedures. Server code is unaffected.
|
|
145
|
+
|
|
146
|
+
**`voltro update` carries you across this** — codemod `0.65.0/01_ai-flows-run-is-internal`. 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.65.0).
|
|
147
|
+
- **@voltro/runtime, @voltro/protocol, @voltro/cli, @voltro/voltro** — `EffectStore` gains `one`, `maybeOne` and `first` — the same single-row reads `ctx.store` offers, taking a builder or a descriptor.
|
|
148
|
+
|
|
149
|
+
Their absence made two `voltro doctor` rules mutually unsatisfiable: `row-not-found` prescribes `.maybeOne(builder)`, `store-effect-lift` prescribes `yield* EffectStore`, and the Effect surface had no `.maybeOne`. Following both produced `Property 'maybeOne' does not exist on type 'EffectStoreOps'`. Nothing was hard about it — the object the service closes over has always been the full fluent store, and every call site was already casting to say so; only the declared type was narrower. `transactional` inherits the wider surface.
|
|
150
|
+
|
|
151
|
+
`one` fails with `NoRowFound` on its own channel rather than collapsing into `StoreOperationFailed`, for the reason the catch boundary already gives about `TenantRowNotFound`: stringifying a deliberate, catchable refusal is how it stops being one. There is no `onMissing` on the Effect surface — `Effect.catchTag('NoRowFound', …)` says the same thing and keeps the channel exact.
|
|
152
|
+
|
|
153
|
+
`NoRowFound` moves to `@voltro/protocol` (re-exported from `@voltro/runtime`, so nothing that imported it moves). The agent guide already said it was declarable in a descriptor's `error:` union, and a descriptor is browser-loaded while `@voltro/runtime` is on the browser-safety guard's server-only list — so following that sentence produced a refused boot. The package it sat in was the only thing making the guidance false.
|
|
154
|
+
|
|
155
|
+
**`voltro update` carries you across this** — codemod `0.65.0/02_effect-store-and-guard-unions`. 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.65.0).
|
|
156
|
+
|
|
157
|
+
### Added
|
|
158
|
+
|
|
159
|
+
- **@voltro/protocol, @voltro/runtime, @voltro/voltro** — `guards: [{ authenticated: true }]` — the access decision that says "the caller must have an identity", and nothing narrower.
|
|
160
|
+
|
|
161
|
+
It was the third case the vocabulary could not express, and every substitute was wrong in a different way. `{ scope: … }` refuses every real user in an app that issues no raw scopes, which is what authority resolved per team in the executor looks like. A relationship guard has no tuple to match on the first call, when the resource does not exist yet. And `openAccess` is untrue wherever the auth fallback is a deliberate anonymous subject — the honest reason would read "whoever can open the socket may spend our model budget". Apps were writing a resource policy whose entire content was "you are logged in".
|
|
162
|
+
|
|
163
|
+
`checkGuards` already distinguished this internally: its return type has always been `ScopeError | Unauthenticated | null`. The guard makes the distinction sayable. It ENFORCES, so it satisfies `security.defaultDeny` — unlike `openAccess`, which declares that nothing is checked — and it composes: every entry in `guards:` must pass.
|
|
164
|
+
|
|
165
|
+
Both enforcement loops carry it, the synchronous one and the Effect one that runs whenever a policy or resource-scoped guard is present. The approval serialiser stores it too: a variant it dropped would be an identity requirement that quietly stopped applying the moment a decision went through an approval.
|
|
166
|
+
|
|
167
|
+
The golden churn here is one member added to `AnyGuardSpec` / `AnyCheckSpec`. Nothing already written changes meaning; only a switch that is EXHAUSTIVE over the union needs an arm, and that case is carried by the manual codemod `0.65.0/02_effect-store-and-guard-unions` alongside the store-layer change it ships with.
|
|
168
|
+
|
|
169
|
+
### Changed
|
|
170
|
+
|
|
171
|
+
- **@voltro/cli** — Four `voltro doctor` corrections, each where the rule's premise was narrower than the code it read:
|
|
172
|
+
|
|
173
|
+
- `n-plus-one` follows a dependent chain through ORDINARY bindings. It compared the next read's text against the names earlier reads were DIRECTLY assigned to, so `const row = rows[0]` between two reads made the second look independent — and a finite chain whose next key comes out of the previous row was reported as an N+1 you are writing, which `Effect.all` cannot fix. - `input/uncapped-array` reads the `input:` expression alone, plus the schemas it names. It scanned the whole file, so a descriptor whose input holds no array and whose output returns `items: Schema.Array(Item)` was reported — and a `maxItems` on a RESPONSE bounds nothing a request can ask for. - `mutation/target-op-mismatch` treats `target: [...]` as the set of allowed ops per table. Comparing each declared target ALONE asked "does the executor do anything but insert", answered yes because delete was also declared, and reported a complete declaration. - `bulk-write-in-loop` recommends only APIs that exist. It named `insertManyIgnore` and `upsertMany`, neither of which the store has; `insertIgnore` and `upsert` resolve a conflict per row and have no set-based form, so the advice is now to keep the loop inside one transaction and bound the input.
|
|
174
|
+
- **@voltro/cli** — `voltro update`'s codemod preview splits the count in the summary line: `6 in range for 0.63.0 → 0.64.0 — 6 print written steps for you to apply`. The per-entry markers were already there and the legend explained them, but both sit BELOW the count, so "6 codemods" formed the expectation of six automatic rewrites before the reader reached either. Six manual codemods is six pieces of hand work, and that is the number somebody schedules an afternoon around.
|
|
175
|
+
|
|
176
|
+
### Fixed
|
|
177
|
+
|
|
178
|
+
- **@voltro/cli** — `open-reason-unverified` had three defects that between them let it be silenced by rewording while missing the case it exists for:
|
|
179
|
+
|
|
180
|
+
- The reason literal was matched with one quote class that forbade every quote character INSIDE the string, so a reason naming its guard in backticks did not parse and the rule returned without opening the executor at all. - `can` is a framework guard name AND an ordinary English word, so any reason containing "anyone can call it" named a guard the executor did not have. A lowercase word now counts only when written as code — `can()` or in backticks. - The executor test was token PRESENCE, which the import line alone satisfies. It requires a CALL, the same shape the sibling rule already used.
|
|
181
|
+
|
|
182
|
+
`input/uncapped-array` covers `*.query.ts` as well. A read does not write, which is what the old wording was about, but that does not make it bounded: a hundred thousand ids in one `getByIds` is the caller choosing the size of the query, and the body limit bounds bytes rather than entries.
|
|
183
|
+
|
|
184
|
+
`store-effect-lift` names both preconditions its promise depends on — that a typed error only reaches the caller once the descriptor DECLARES it, and that the class comes from `@voltro/protocol`. Naming a benefit and neither precondition is how a rule earns a large diff with nothing observable at the end of it.
|
|
185
|
+
- **@voltro/runtime** — An `.encrypted()` column written through `upsert` is bound to the row that RECEIVES it. On the conflict path that row is the existing one, whose id is not the id the caller offered, so the ciphertext went to disk authenticated against an id that row does not have — and AES-GCM then refused it: `FieldDecryptionError … Unsupported state or unable to authenticate data`.
|
|
186
|
+
|
|
187
|
+
The damage was not the failed call. The row keeps the mis-bound value, so every later read of it throws too, and the write reports success on the way in. The conflict is now resolved before the encrypt (`upsertDestinationId`, shared by both codec wrappers), which costs one extra read and only for a call that actually writes a secret.
|
|
188
|
+
|
|
189
|
+
It bites wherever the row carries an id at encrypt time — always on the memory store, and on every SQL dialect when the id is the caller's rather than the database's, since `EXCLUDED.<col>` / `VALUES(<col>)` / `MERGE` all carry the offered bytes onto the existing row.
|
|
190
|
+
|
|
191
|
+
`updateMany` through the BOOT store now refuses an `.encrypted()` column instead of writing a binding nothing can authenticate: a set-based write has no row, and the request-scoped store is the one that expands it per row.
|
|
192
|
+
- **@voltro/cli** — `.framework/dist/server/` — the SSR bundle and the precompiled app config — is marked `{"type":"module"}` like the api and start bundles already were, so a production `voltro start` no longer opens with `MODULE_TYPELESS_PACKAGE_JSON` for an artefact the app did not write. All three directories go through one `writeEsmPackageMarker`, and a guard now asks every module that runs a bundler whether it marks what it wrote.
|
|
193
|
+
- **@voltro/cli** — `voltro new workflow` scaffolds a descriptor that declares `internal: true`, with the three options in a comment above it. The template emitted no access decision at all, so the file it wrote refused the app's own boot under `security.defaultDeny` — a scaffold whose first act is to break the build.
|
|
194
|
+
- **@voltro/database, @voltro/runtime, @voltro/protocol, @voltro/voltro** — `StoreOperationFailed.cause` no longer carries the driver's sentence or a stack trace. It was `String(error)` on a `FiberFailure`, which is the message AND the stack — so an absolute server path reached every caller that declared the error, and every subscriber on a stream carrying it.
|
|
195
|
+
|
|
196
|
+
The words were the worse half. `@voltro/database`'s own header already states why a driver message may not be forwarded: postgres attaches `Failing row contains (…)` — the entire row, `.sensitive()` columns included — to a not-null and a check violation, mysql echoes the duplicate value, mssql the truncated one. That rule was written for `ConstraintViolation` and the error beside it broke it.
|
|
197
|
+
|
|
198
|
+
`wireSafeCause` builds the field now: the constraint KIND and its constraint/column NAME, or the driver's code, and nothing that was ever a value. The full detail is logged server-side instead, so diagnosis is not traded away for the fix.
|
|
199
|
+
- **@voltro/cli** — The inspect action behind the dashboard's workflow "resume" refuses a run that is not suspended, instead of reporting a resume that did not happen. The engine resumes only a run carrying a terminal `Suspended` reply; the action called it, ignored the answer, flipped the row to `running` and recorded a `run-resumed` event regardless. For a run already stuck in `running` — the state somebody presses that button in — every visible sign said it had worked, including a fabricated entry in the timeline of the run being investigated.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
42
203
|
## [0.64.0] — 2026-09-04
|
|
43
204
|
|
|
44
205
|
### ⚠ BREAKING
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/ui-shadcn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Voltro's first-party shadcn/ui kit: Tailwind v4 design tokens (light + dark), 30+ primitives, layout compositions, styled widgets for the @voltro/ui seam, and the canonical theme/language preference-cookie helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@radix-ui/react-dropdown-menu": "^2.1.24",
|
|
50
50
|
"@radix-ui/react-toggle": "^1.1.18",
|
|
51
51
|
"@radix-ui/react-toggle-group": "^1.1.19",
|
|
52
|
-
"@voltro/ui": "0.
|
|
52
|
+
"@voltro/ui": "0.66.0",
|
|
53
53
|
"class-variance-authority": "^0.7.1",
|
|
54
54
|
"clsx": "^2.1.1",
|
|
55
55
|
"shiki": "^4.4.3",
|