@voltro/ai 0.88.0 → 0.89.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 +103 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -41,6 +41,109 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
+
## [0.89.0] — 2026-11-05
|
|
45
|
+
|
|
46
|
+
### ⚠ BREAKING
|
|
47
|
+
|
|
48
|
+
- **describeIfReachable moves to @voltro/testing/reachable** — `@voltro/testing`, `@voltro/cli`
|
|
49
|
+
|
|
50
|
+
The root of `@voltro/testing` imported `vitest` for `describeIfReachable` / `describeIfAvailable`. With two vitest snapshots in one lockfile (they can differ by nothing more than an optional peer of vite), every test that imported `@voltro/testing` loaded the second instance, and the app's own `expect(...).rejects.toThrow('…')` then reported `but got ''`. The root entry now imports no test runner. Migration: import `describeIfReachable` and `describeIfAvailable` from `@voltro/testing/reachable`; `voltro update` rewrites the imports. `isTcpReachable`, `ReachableTarget` and `reportEngineVersion` stay on the root.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **Retain exact historical CMS deployment receipts** — `@voltro/cms`
|
|
55
|
+
|
|
56
|
+
Record the original atomic deployment request and result with a signed receipt in its existing activation-history row. The private receipt endpoint can return that exact result after another activation, with the current epochs and whether it is still current. Missing legacy receipts remain unknown; reads never reconstruct history or activate output. Current-only confirmation and public serving admission remain unchanged.
|
|
57
|
+
|
|
58
|
+
Apply the additive nullable activation-history column before starting the updated publisher. Existing custom deployment services can omit the optional receipt port and keep their original routes; existing probe, confirmation and acknowledgement wire formats are unchanged.
|
|
59
|
+
- **Fence unresolved CMS output publication requests** — `@voltro/cms`
|
|
60
|
+
|
|
61
|
+
Native private publication fencing makes an original unresolved request ineligible for future commit and exact-current acknowledgement, while preserving the actual application, policy and activation history. An already-current result is returned unchanged. Fencing never proves an earlier commit did not occur.
|
|
62
|
+
|
|
63
|
+
Apply the normal nullable deployment-head schema plan for `app` and `renderPolicyDigest` before use. Never-bound namespaces gain only an unbound revision; they receive no fabricated application, build or serving authority. Upgrade readers and writers together before creating these heads, and regenerate private clients from the mounted descriptors. Custom services can omit the new port; their mounts expose no fence operation.
|
|
64
|
+
- **Inspect epochs against a retained CMS target** — `@voltro/cms`
|
|
65
|
+
|
|
66
|
+
Private output inspection accepts an optional target ID. Native services first verify that retained target in their fixed namespace before returning current epochs, without reopening source contracts, physical custody or publication. Existing empty-body inspection stays unchanged. The optional service port keeps custom implementations compatible; a custom mount without it refuses the new selector instead of falling back to unrelated epochs.
|
|
67
|
+
- **voltro doctor names two copies of @ai-sdk/provider** — `@voltro/cli`
|
|
68
|
+
|
|
69
|
+
Each `ai` and `@ai-sdk/*` release pins its `@ai-sdk/provider` exactly, and `@voltro/ai` resolves its provider packages by caret, so an app pinning an older `ai` could hold two copies and `tsc` reported `Type 'LanguageModelV4' is not assignable to type 'LanguageModel'`. `voltro doctor` (and `--json`, field `aiProviderSkew`) now lists every package with the provider version it loads when there is more than one, and names the alignment. Measured: `ai` 7.0.156 beside `@voltro/ai` 0.88.0 resolves 4.0.34 and 4.0.35; 7.0.162 resolves one.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **The db pool boot line names the connection the workflow runner holds** — `@voltro/cli`
|
|
74
|
+
|
|
75
|
+
With SQL runner storage on postgres or mysql/mariadb, the workflow runner holds one pool connection for its shard-lock session for the life of the process. The `db pool:` line said `max=5` while the app was served by 4. It now adds `1 of the 5 pooled connections is held for the life of the process (the workflow runner's shard-lock session), so 4 serve the app` after the fleet arithmetic, which is unchanged.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- **voltro add patches removes the patch files of the version left** — `@voltro/cli`
|
|
80
|
+
|
|
81
|
+
Retiring a `patchedDependencies` entry for an earlier Effect version left its file in `patches/`, and a leftover is not inert: patch-package (npm, or a root `postinstall` beside pnpm) applies every file there and reports it as unrecognised. `voltro add patches` now removes the framework's own patch files for another version of the same major once nothing references them, in each package manager's naming. A file still named in `pnpm-workspace.yaml` or `package.json`, a patch for another Effect major and a file of your own naming stay.
|
|
82
|
+
- **voltro agents-md --force keeps symlinks and app guides it never wrote** — `@voltro/cli`
|
|
83
|
+
|
|
84
|
+
A forced refresh followed a symlinked app guide (`apps/api/CLAUDE.md` → `../../AGENTS.md`) and replaced the repo-root core guide it had just refreshed, and on flat layouts it replaced hand-written app `CLAUDE.md` files that carried no keep marker. A symbolic link is now never written through, and a nested guide that does not start with the heading the command seeds (`# api — agent notes`, `# web — agent notes`, `# database — agent notes`) is reported as protected instead of overwritten.
|
|
85
|
+
- **Keep known CMS rollback independent of expired build snapshots** — `@voltro/cms`
|
|
86
|
+
|
|
87
|
+
Roll back a previously activated complete native output using its retained contracts after the private build snapshot expires. Preserve current publisher, region, graph, rights, policy and CAS checks; expired unactivated targets and new forward activation still refuse.
|
|
88
|
+
|
|
89
|
+
The deployment-reader callback gains an optional third mode argument. Existing two-argument readers remain assignable and existing calls remain valid; the returned contract is unchanged.
|
|
90
|
+
- **Keep visible movement labels in accessible names** — `@voltro/cms`
|
|
91
|
+
|
|
92
|
+
CMS movement controls keep the complete visible action together in their contextual accessible names, so speech input can find them by the words shown. Localized controls retain their explicit label callbacks.
|
|
93
|
+
- **Reject Git deletion markers in CMS rollback previews** — `@voltro/cli`
|
|
94
|
+
|
|
95
|
+
Reject the all-zero Git deletion marker before a CMS rollback is queued. The CLI now matches the API's existing commit validation while preserving image rollback previews.
|
|
96
|
+
- **voltro update reviews many test files without running out of memory** — `@voltro/cli`
|
|
97
|
+
|
|
98
|
+
The syntax reviews `voltro update` prints parsed every matching file with its own copy of the TypeScript standard library, so a workspace with a few hundred test files importing `@voltro/testing` exhausted Node's default heap in the request-harness review (`JavaScript heap out of memory`, exit 134), including under `--codemods-only --dry-run`. The reviews read local bindings and imports only and now load no standard library: the same tree completes in seconds with about 140 MB and lists the same call sites.
|
|
99
|
+
- **voltro update --codemods-only names dependencies below the framework's peers** — `@voltro/cli`
|
|
100
|
+
|
|
101
|
+
`--codemods-only` promises not to edit dependencies, so it skipped peer alignment without a word — on the recovery path after a `--no-install` update, an app's own dependency below a framework peer (a direct `ioredis ^5` against `@voltro/kv`'s optional `ioredis ^6.0.0`) surfaced only in `pnpm peers check`. It now prints each one under `Peer review` with its manifest and the framework package requiring it, and still changes nothing.
|
|
102
|
+
- **Scaffolds show the object cache form; comments no longer stop voltro add redis** — `@voltro/cli`
|
|
103
|
+
|
|
104
|
+
The api templates still showed the refused scalar form in commented-out examples (`// cache: 'redis' as const`, `// kv: 'redis' as const`) and in two explanations; they now show `{ backend: 'redis' as const }`. `voltro add redis` treated a `cache:` that appeared only in a comment as an existing declaration and declared nothing, which is every config scaffolded from the api template; it now reads declarations only. The upgrade lists commented-out scalar examples in code so they can be rewritten before someone uncomments one.
|
|
105
|
+
- **voltro dev waits for the api's own drain deadline before SIGKILL** — `@voltro/cli`
|
|
106
|
+
|
|
107
|
+
On a restart the dev supervisor sent SIGKILL 1.5 seconds after SIGTERM. The api child bounds its own drain by `VOLTRO_SHUTDOWN_GRACE_MS` (10 seconds by default), and an app with workflows legitimately takes longer than 1.5 seconds against a remote or pooled database: its runner hands every shard back, two round trips per shard. Such restarts logged `child ignored SIGTERM — escalated to SIGKILL` every time, and the teardown after the cut (CDC detach, trace flush, the startups' `onShutdown`, pool close) never ran. The child now reports its deadline to the supervisor, which waits one second past it. The child logs `drained in …ms`, or `drain deadline exceeded` when it does not finish.
|
|
108
|
+
- **A lazy boundary still loading after hydration keeps its server HTML** — `@voltro/web`
|
|
109
|
+
|
|
110
|
+
A server-rendered page whose layout code-splits part of itself behind `lazy()` + `<Suspense>` blanked once on every first load and mounted that part afresh: its iframes reloaded, inputs and scroll positions inside it started over. Hydration keeps such a boundary's server HTML while its chunk loads, but the framework's own work right after hydration — adopting the server's loader data, mounting the route announcer and the development chrome, the API runtime arriving or changing its version — re-rendered the page as an urgent update, and React discards the server HTML of a boundary an urgent update reaches. That work now runs as transitions, which wait for the boundary. The route announcer also no longer speaks on the first load (it did when the route meta settled after hydration, and in development on StrictMode's second effect run); it announces navigations only, as documented.
|
|
111
|
+
- **Pages and layouts with a loader never render without their data** — `@voltro/web`
|
|
112
|
+
|
|
113
|
+
`useLoaderData()` promises a page that declares a `loader` its result, yet a first load without server data rendered the page once with `undefined` before the loader settled: a client-rendered app, a `renderMode: 'spa'` page with its own loader under a server-rendered layout (it received the server's placeholder), a second `<Router>` in the same process (component tests) and, behind a `Pending` skeleton swap, a layout with a loader that differs from the previous route's. A page reading `data.title` threw into its error boundary, and that boundary stayed up after the data arrived because the pathname had not changed. The router now renders a page or layout only with its own loader result; until then the route's `Pending` skeleton stands in for it and everything inside it, or nothing when the route has none. Hydration of server-rendered pages is unchanged. A page that drew its own loading state from an absent value never sees one now: export `Pending` (or `loading.tsx`) for that.
|
|
114
|
+
- **Avoid duplicate reads when reopening application archives** — `@voltro/cli`
|
|
115
|
+
|
|
116
|
+
Reopening a retained application reuses the complete inventory verified during that call when checking its exact directory contents. Every new open still rehashes all retained source, executable and web bytes and rejects corruption, symlinks and extra files.
|
|
117
|
+
- **Reduce allocation while verifying retained runtime bytes** — `@voltro/cli`
|
|
118
|
+
|
|
119
|
+
Verify the complete Node executable before and after retained execution with one bounded reusable read buffer. Keep the fresh byte hash, no-follow file admission and 256-MiB limit; no runtime pin is cached between calls.
|
|
120
|
+
- **A replica that loses a schedule claim no longer counts a failed insert** — `@voltro/runtime`
|
|
121
|
+
|
|
122
|
+
Replicas that share schedules decide each firing with a claim row. The losing replica found out through a failing INSERT (the primary-key conflict), so every lost race raised `voltro_db_errors_total{op="insert"}` with no log line naming it, and the database logged a duplicate-key error for it. The claim is now an insert that is ignored on conflict. The winner is decided exactly as before; a lost race is no longer a statement failure.
|
|
123
|
+
- **voltro update re-keys the Effect patches for a release on a newer Effect** — `@voltro/cli`
|
|
124
|
+
|
|
125
|
+
pnpm patch entries name an exact version, and pnpm refuses an install whose entry matches nothing. An update to a release on a newer Effect therefore failed its own install with `ERR_PNPM_UNUSED_PATCH` over the driver patch an earlier `voltro add patches` wrote, and the codemod naming the repair only runs after that install. `voltro update` now reads the Effect version the target's patches are keyed to, moves the lockstep overrides to it and retires the earlier CLI's patch entries before installing; afterwards the installed CLI writes the target's patches and the update installs once more. A failed first install restores `pnpm-workspace.yaml` byte for byte, a patch you created yourself keeps its key, and a custom Effect override stops the update before anything is written. `voltro add patches` also accepts the `4.1.0` overrides an earlier release wrote instead of refusing them as custom. An update started by a CLI without this repair still needs the manual order in the update documentation.
|
|
126
|
+
- **voltro update keeps the old release-age exclusion until the install is done** — `@voltro/cli`
|
|
127
|
+
|
|
128
|
+
`voltro update` moved each `minimumReleaseAgeExclude` entry from the release being left to the target before installing. pnpm checks the lockfile's existing entries against its release-age window too, so while the release being left was still inside the window — an update the day after a release — the install refused the lockfile with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`, and the update's message suggested waiting for a registry instead. The entry being left now stays beside the target until the install has replaced the lockfile and is removed afterwards; the preview and the result say so. A failed install still restores the file byte for byte. When the refusal comes from pnpm's default window without exclusions, the message names the window and the two versions to exclude.
|
|
129
|
+
- **Preserve existing rules for generated web files** — `@voltro/cli`
|
|
130
|
+
|
|
131
|
+
Web generation recognizes rooted and unrooted rules for the `.framework` directory and leaves `.gitignore` unchanged when that directory is already covered.
|
|
132
|
+
- **A workflow finalizer observes the terminal exit once, not a success per replay** — `@voltro/cli`, `@voltro/workflow`
|
|
133
|
+
|
|
134
|
+
On Effect 4.1.0 a body that suspended and ran again registered its `addFinalizer` callback twice, and the superseded registration was called with `Exit.succeed(undefined)` whatever the outcome — after a failed or interrupted workflow too, so a finalizer that treats success as commit committed a failed run. The shipped Effect patch now skips finalizers of a run that suspended: the run that reaches the terminal state calls its finalizer once with the real exit; compensations still run once on failure. Run `voltro add patches` and install to pick up the refreshed patch.
|
|
135
|
+
- **A backlog of due workflows no longer takes the whole connection pool** — `@voltro/workflow`, `@voltro/cli`, `@voltro/database`, `@voltro/sql-postgres`, `@voltro/sql-mysql`, `@voltro/sql-mssql`
|
|
136
|
+
|
|
137
|
+
A runner that took over shards with many due executions resumed all of them at once, and their journal writes filled the pool the app shares. With a pool of 5, 1,000 due executions and a 20 ms round trip, a `SELECT 1` waited up to 9.8 s for a connection and the runner's storage sync timed out after 5 s. The engine now admits at most half of the connections the process does not hold for life to its journal at once (at least 1). In the same measurement the longest wait was 1.6 s, no timeout occurred, and the backlog took about 40 % longer to drain. A journal write made inside a SQL transaction is never held back, so the bound cannot deadlock against connections that transactions hold. The `db pool:` boot line states the bound. The postgres, mysql/mariadb and mssql adapters now register their effective pool size, including the driver default of 10 when `DB_MAX_CONNECTIONS` is unset.
|
|
138
|
+
- **Workflow journal rows written before Effect 4 no longer stall the runner** — `@voltro/workflow`
|
|
139
|
+
|
|
140
|
+
A database whose workflow journal holds rows from a release before 0.85.0 carries two shapes Effect 3 wrote and Effect 4's codecs refuse: a void result without its `value` key — every interrupt recorded then has it — and a failure cause as a tree. Effect 4 loads an execution's persisted interrupt whenever the execution activates, so a boot with such an interrupt and a pending message logged `Defect building entity handlers` every few seconds for that execution, its message never received a reply, and other workflows on the same runner did not finish meanwhile. Polling such a run failed on the cause tree, and restart tooling read it as an unrecognized reply. The engine now reads both shapes as their Effect 4 form on every dialect; rows are not rewritten, and rows written today are unchanged. A stray step message of a run that already ended is answered without running the step again.
|
|
141
|
+
- **voltro workflows run commands accept the execution id start prints** — `@voltro/cli`
|
|
142
|
+
|
|
143
|
+
`voltro workflows start` prints the run handle's execution id, and `discard` (like `show`, `retry`, `cancel`, `suspend`, `resume`, `signal` and `update`) answered "Workflow run target not found" for it; only the run id from `list` worked. An id that names a run's execution is now resolved to that run first.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
44
147
|
## [0.88.0] — 2026-11-02
|
|
45
148
|
|
|
46
149
|
### ⚠ BREAKING
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.89.0",
|
|
4
4
|
"description": "Effect-native AI primitives — generateText / generateObject / streamText, tools, agents, media generation, cost tracking + budgets, and provider fallback.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@ai-sdk/anthropic": "^4.0.91",
|
|
57
57
|
"@ai-sdk/openai": "^4.0.104",
|
|
58
|
-
"@voltro/cache": "0.
|
|
59
|
-
"@voltro/database": "0.
|
|
60
|
-
"@voltro/env": "0.
|
|
61
|
-
"@voltro/protocol": "0.
|
|
62
|
-
"@voltro/workflow": "0.
|
|
58
|
+
"@voltro/cache": "0.89.0",
|
|
59
|
+
"@voltro/database": "0.89.0",
|
|
60
|
+
"@voltro/env": "0.89.0",
|
|
61
|
+
"@voltro/protocol": "0.89.0",
|
|
62
|
+
"@voltro/workflow": "0.89.0",
|
|
63
63
|
"ajv": "^8.20.0",
|
|
64
64
|
"ajv-formats": "^3.0.1",
|
|
65
65
|
"ws": "^8.22.0"
|