Releases: punarduttrajput/wovyr
Release list
v0.4.1
A patch release covering the release pipeline itself. No library, server, or
CLI behavior changed.
Fixed
- Releases publish to crates.io. The release workflow had no
cargo publish
step at all, so the registry behind the README's own first quickstart command
(cargo install wovyr-cli) only ever advanced when somebody ran it by hand —
it sat at0.3.2while the repo, the README badge, and/healthzall
reported0.4.0. A newcratesjob publishes every publishable workspace
member withcargo publish --workspace, which derives the upload order from
the dependency graph rather than a hand-maintained list, behind a
sparse-index pre-flight that excludes anything already published at its
current version — so a partial run is recovered by re-running the job instead
of by hand. - The npm and PyPI publish steps can run at all. Both bound their token to
the step's ownenv:and then tested it in that same step'sif:— a
condition that can never be true, since secrets cannot be referenced from
if:and a step'senv:is not populated when itsif:is evaluated. Both
steps reportedskippedon every release run that has ever existed (v0.3.1,
v0.3.2, v0.4.0) while the job and the workflow still went green, which is why
every version currently live on npm and PyPI was in fact published manually.
The token check moved inside each script, where a skip leaves an explanatory
log line, and both publishes are now idempotent
(twine upload --skip-existing, plus annpm viewpre-check since npm has no
equivalent flag). @wovyr/angular's version tests no longer assert the wrong generation.
They hardcoded0.3.xas the matching series and0.4.0as a mismatch, so
both inverted and began failing the momentTARGET_SERVER_VERSIONmoved to
0.4.x— undetected, because that package has no CI job. The fixtures now
derive fromTARGET_SERVER_VERSION, so they stay correct across future bumps.
v0.4.0
A minor bump rather than a patch: the Wasmtime upgrade below changes the
wovyr-tools wasi feature's public surface, and this project follows semver
from 0.3.0 onward.
Fixed
-
A quota breach reports figures at the scale they actually occur. The
llm_cost_per_day_usdmessage formatted every amount{:.4}, so a breach at real
per-call scale — agpt-4o-minireply costs on the order of$0.000008— read
llm_cost_per_day_usd: 0.0000 + 0.0000 exceeds limit 0.0000: it named the metric
and then told the operator nothing about what was spent, what was requested, or
what the ceiling was. A newusd_precisionpicks the decimal places needed to show
three significant digits (two for human-scale money, capped at eight), and the
three figures in one message share that precision so the comparison lines up
digit for digit — the admission check runs before the call, so its delta is0.0,
and a bare0.00beside eight-decimal siblings reads like a different unit
(crates/wovyr-tenancy/src/quota.rs). Live:
llm_cost_per_day_usd: 0.00000420 + 0.00000000 exceeds limit 0.00000100, with a
dollar-scale budget still reading in plain dollars. -
image_generateis available toworkflows run --local, not just
agents run --local. The tool was registered inline in the agent command's own
body, so a workflowtoolactivity naming it failed with a bareunknown tool
even with a provider key configured — while the same activity worked against the
server, whose one shared registry has it. Registration moved into
local_registry, the constructor both CLI paths already funnel through, so the
asymmetry is now unrepresentable rather than merely fixed — the argument HLTH-901
made for the executor, applied to the registry
(apps/wovyr-cli/src/main.rs,
workflow.rs). It stays conditional on a
configured key (it needs a real, billed API), and the workflow runner now builds
one gateway shared by its registry and executor, soimage_generateroutes
through the same retry/failover/breaker pipeline as every other call. Guarded by
tests asserting both paths produce identical tool sets at each privilege level,
and that the tool's presence follows the key rather than the command. -
The test suite no longer reads or writes the developer's real
~/.wovyr.
cargo test --workspaceresolved durable stores through
wovyr_config::paths::*, which readHOME/USERPROFILE— so every run left
test tenancy quotas (prj-quota-test,prj-async-block) sitting in live local
state, wrote workflow checkpoints and event logs (test-exec-1.*) into the real
workflow store, and appended test entries to the real tamper-evident audit
chain (found at ~380 KB and growing on the machine this was fixed on). Beyond
the pollution, it meant a test run and awovyr devon the same machine shared
files, and pre-existing local state could influence results.AppState::for_test
already isolated the two stores whose contents tests assert on, but everything
elsefrom_envbuilds — the audit chain, tenancy, the workflow store, the KMS
root key, secrets, the async-run and webhook-outbox files — still landed in the
real directory.wovyr_config::rootgained a process-wide root override
(set_root_override/redirect_to_scratch/root_override), which
AppState::for_testand theauthz_matrixintegration test call before building
anything (root.rs,
state.rs). Overriding one function covers
every resource directory, because they all already resolve through
root::wovyr_dir()— the previous comment onfor_testcalled this "a larger
change that would have to thread a state-root parameter through every
crate::config::*_dir()helper", which was not the case; that comment is
corrected. Redirection is per process, not per test: tests in one binary
share a scratch root exactly as they previously shared the real one, isolated
from the user rather than from each other, so the two per-AppStatestore swaps
stay. Guarded by a test asserting the suite's resolved root is under the temp dir
and is not the home-derived path, pluswovyr-configunit tests for the
override's first-call-wins semantics and its reach across everypaths::*
helper. -
wovyr plugin build <project>works with a relative path. The documented
flow —wovyr plugin new hello-toolthenwovyr plugin build hello-tool— failed
for every relative argument with "no build output at hello-tool\target\…".
build_cmdderived a relative--target-dirfrom the project argument and then
ran cargo withcurrent_dir(project), so cargo resolved that path a second time
against the new cwd and wrote the module tohello-tool/hello-tool/target/…while
the command looked for it one level up. An absolute argument happened to work,
which is why the round-trip test — using a scratch temp dir — never caught it. A
newbuild_pathsresolves the project once, up front, so every downstream path is
cwd-independent (apps/wovyr-cli/src/scaffold.rs),
with unit tests pinning that both resolved paths are absolute and that the target
dir is never re-resolved against the project dir. Verified against the real binary:
plugin new hello2 && plugin build hello2now stageshello2.wasm+ a
digest-completeplugin.yaml, with no nestedhello2/hello2/directory. -
Three shipped example workflows run again.
greet-and-fetch.yaml,
saga-order.yaml(all five activities) andsupport.yamldeclared
type: functionwith noname, which RM-GA-P4 HLTH-901 turned into a permanent
activity failure when it unifiedfunctionandtoolonto one tool-invoking
dispatch — so each died on its first activity. Two of them are reachable straight
off a README quickstart line. All three now nameechoexplicitly.
The comment incrates/wovyr-runtime/src/lib.rs
that justified the unification claimed "every real example/test that uses
type: functionalready expects a tool invocation" — it did not, and that wrong
premise is why the breakage shipped; it now records what actually broke, and notes
thatwovyr-workflow's own engine tests still use namelessfunctionactivities
(they supply their own executor and never reach this dispatch, so they were never
evidence either way). A new test walks everyexamples/workflows/*.yamland fails
if afunction/toolactivity lacks aname—Definition::from_yamlcan't
catch this, sincenameis legitimately absent onwait/human/for_each, so
the breakage only ever surfaced when a human ran the example. -
A quota set on a nonexistent project is refused instead of silently accepted.
PATCH /api/v1/projects/{id}/quotawrote to the quota map by raw id with no
existence check, so a typo'd project — or an empty id from a doubled slash —
returned200and persisted a limit under a key no run would ever consult, while
GETreturned200 {}for the same ghost project, even though
GET /api/v1/projects/{id}itself correctly 404s. An operator got a success
response for a budget enforcing nothing: the same silent-no-op class as an unpriced
model reporting$0. Both handlers now check the project exists first
(crates/wovyr-server/src/tenancy.rs), with
a regression test asserting both routes 404 and that the rejected write leaves no
stored limits behind. -
A rolled-back saga is reported as
Failed, notCompleted. After a clean
rollback the engine transitioned the execution toCompleted, so a saga whose
activity failed was missing from?status=failedand listed among the
successes — while its own event log saidworkflow_failedand the CLI printed
"rolled back after failure". Every durable surface (Engine::status/query/
list,GET /api/v1/workflows?status=, any dashboard over them) reads that
checkpoint status, so the wrong value was the one operators actually see. The
terminal state is nowFailed— the compensation succeeded, the workflow did
not — andCompensationCompletedin the event log is what distinguishes "failed,
and rolled back cleanly" from a bare failure.(Compensating, Completed)is
removed from the transition table and(Compensating, Failed)added, so the old
state is now unreachable by construction rather than merely unused
(state.rs,
engine.rs). A failed rollback still stays
non-terminal inCompensating, by design. Chosen over adding a distinct
Compensatedstate, which would have been a breaking wire/on-disk change for a
distinction the event log already carries;
compensation-engine.md's state
diagram — whose ambiguity is arguably what licensed the original behavior — now
says so explicitly. -
Real cost is recorded behind an OpenAI-compatible gateway that renames the
model.PriceBook::pricematched a model id only from the left (exact, then
longest known key that is a prefix), so a vendor-prefixed id matched nothing:
OpenRouter answers a request forgpt-4o-miniwithopenai/gpt-4o-mini, and cost
is deliberately keyed on the model the upstream reports it billed, so every
call loggedno price for model and no default configuredand reported
cost_usd: 0.000000. That silently reinstated the failure class RM-AIM-P1 PRV-101
existed to remove —wovyr-server's per-projectllm_cost_per_day_usdbudget is
fed from this number, so on such a deployment it never accrued and never denied.
price()now retries the ex...