All notable changes to Akua will be documented in this file.
The format follows Keep a Changelog,
and this project adheres to Semantic Versioning
once v1 ships. Until then, @akua-dev/sdk versions bump independently of the
Rust workspace; breaking changes to v1alpha1 data shapes trigger a
minor bump in the SDK.
Note: the SDK was published as
@akua/sdkon JSR through 0.5.0. Starting with 0.6.0 it ships as@akua-dev/sdkon npm — JSR's 20 MB single-file/total-package cap is incompatible with the bundled napi addon (~129 MB compressed across the per-platform packages).
- Hosted API bridge in the single
akuaCLI (api.rs, docs/cli.md).akua apinow calls the hosted Akua API with token auth, configurable base URL, workspace context, request field/body helpers, and Akua structured errors for auth, permission, rate-limit, transport, and timeout failures.akua api specfetches the public OpenAPI document; elevated audiences returnE_UNSUPPORTEDuntil the server exposes authorized audience-specific specs.
- SDK launch docs and generated site now describe the shipped Node/Bun NAPI surface (docs/sdk.md, site/concepts/sdk.html). The public SDK page no longer advertises stale browser support, shell-out dispatch, or future Akua Cloud namespaces as part of
@akua-dev/sdk; it documents the current package as a native-addon-backed Package SDK with render/export/check/lint/verify and vendor drift guards. - Large render/export regressions stay covered by launch-readiness tests (packages/sdk, crates/akua-cli). The SDK and CLI regression coverage now guards the large render-output path and export surface that previously drifted during launch hardening.
- Helm
values.schema.jsondefaults that contradict generated KCL types are omitted (values_schema.rs, helm_union_schema.rs). Chart schemas that declare unsafe defaults such asnullfor non-null fields, numeric defaults for strings, or mismatched array items no longer emit invalid KCL defaults that abort the render worker.
- Render budget handling is stable across persistent nested engine sessions (engine-host-wasm, render.rs). Helm/Kustomize sessions now refresh their Wasmtime epoch deadline before each call, so a reused session no longer inherits a stale expired deadline after idle time. Worker interrupt traps are also classified as
E_RENDER_BUDGET_DEADLINEand return the timeout exit code instead of surfacing as generic KCL evaluation failures.
- helm-repo dep resolution tolerates
v-prefixed chart versions (helm_repo_fetcher.rs). The resolver parsed every Helm repo index entry with strict SemVer and aborted the entire resolution on the first non-conforming entry, so a chart whose index publishesv-prefixed tags (e.g. loft-sh'svcluster:v0.28.0-next.12) failed withE_DEP_RESOLVEeven when a valid target version was published. A single leadingvis now stripped before parsing (matching Helm's Masterminds/semver behavior) and an entry is skipped only if it is still unparseable — validv-prefixed versions resolve, truly-malformed entries are ignored, andNoMatchingVersioncontinues to list the index's raw version strings.
akua cachenow inventories and clears the helm chart cache ($XDG_CACHE_HOME/akua/helm) added with the helm-repo dep source. Previously the helm cache grew unmanaged:akua cache listdid not show it andakua cache cleardid not reclaim it.--helmscope flag added toakua cache clear;akua cache pathandakua cache listnow report the helm root alongside oci and git. The default clear (no scope flag) wipes all three caches.
- Helm subchart
condition/tagsdisabling now honored (helm-engine-wasm). The embedded engine now callschartutil.ProcessDependenciesbefore rendering, socassandra.enabled = false(etc.) actually skips the subchart. Previously bundled subcharts always rendered regardless of theircondition— for every dependency source (repo, path, oci), not just helm-repo. Disabling a chart's subcharts now requires you to supply your own datastore/config values the subchart used to provide (expected: the chart points at your backend).
Security hardening release. Remediates all 17 findings from the 2026-05-29 internal security audit (no sandbox-escape was found — these are supply-chain integrity + DoS-resilience hardening). See docs/security-audit-2026-05-29.md.
- Hardening from the 2026-05-29 security audit (docs/security-audit-2026-05-29.md).
akua publishnow stripsreplacedirectives from the signed manifest (consumers never inherit a publisher's replace); untrusted chartvalues.schema.jsonproperty names + descriptions are validated/escaped before KCL codegen (no injection); helm/kustomize engine Stores get a memory cap + finite epoch (chart-DoS ceiling); HTTP fetch bodies + gzip→tar extraction are size-capped (OOM / decompression-bomb);BasicAuthredacts its password inDebugand no longer derivesSerialize; the git transport forces TLS verification (ignores ambientGIT_SSL_NO_VERIFY); plus several lower-severity fixes (UTF-8 panic on registry error bodies, helmhttp://scheme-downgrade rejection, helmchart-name + OCI userinfo validation, git tree-entry name guard,vendor addURL canonicalization).--timeoutnow bounds the render worker's epoch deadline (previously the worker always used the 6s default). No sandbox-escape was found; these are integrity + DoS-resilience hardening.
- Signing posture documented precisely (CLAUDE.md). The "verify by default on pull" wording now states the actual model: digest-pinning is the universal verified-before-write integrity gate (always on); cosign signature/attestation verification engages, fail-closed, when a
[signing].cosign_public_keyis configured. Signing stays opt-in.
Typed composition and Bun. Cross-Package composition is now typed-only
(the untyped pkg.render({ path }) form is gone, and nested renders
finally resolve typed aliases), and the published SDK renders Helm
charts under Bun — the native addon takes its engines directory
programmatically instead of relying on env-var propagation Bun doesn't do.
- Untyped
pkg.render({ path = "..." })composition form (breaking) (pkg_render.rs, stdlib/akua/pkg.k). Cross-Package composition now goes through typed dep aliases only:pkg.render(pkg.Render { package = "<alias>" }), where<alias>is declared under[dependencies]in the calling Package'sakua.toml. Supplyingpath(or no target) is a hard error directing the user to the alias form — honoring CLAUDE.md's "No filesystem paths in user-authored KCL." Path dependencies inakua.toml({ path = "./..." }) stay valid; only the inlinepkg.renderpath call is gone. Migrate by declaring the sub-package as a dep and composing it by alias (seeexamples/08-pkg-compose).
- Nested renders resolve typed aliases (package_k.rs). A composed sub-package can now use
pkg.render(package = "<alias>")against deps declared in its ownakua.toml, not just at the root.render_optsenters each render frame with the dep-alias map derived from that frame's resolved charts (RenderScope::enter_for_render), with budget inheritance preserved. This is the enabling change that let the untypedpathform be removed.
- Helm/kustomize engines now load under Bun via the SDK (helm-engine-wasm, kustomize-engine-wasm, akua-napi). The per-platform native addon ships with engines off and resolves the engine wasm from disk; it previously found that directory only through the
AKUA_NATIVE_ENGINES_DIRenv var, which the JS loader set viaprocess.env. Bun doesn'tsetenvonprocess.envassignment, so the addon saw nothing and rendering failed withhelm-engine.wasm not built. The addon now accepts the engines directory programmatically via a newsetEnginesDirbinding (checked before the env var), which the loader calls at module-load time. The env var still works for Node back-compat. macOS note: napi-rs's generated loader unconditionally probes the unpublished@akua-dev/native-darwin-universalpackage before the per-arch one; the resultingbun install404 /loadErrorsentry is benign (the per-arch package then loads) and is documented inloader.js— we don't publish an empty universal package.
Real-world Helm rendering. This release makes akua render the charts people actually deploy — pulling from classic HTTPS Helm repositories, composing charts inside modular sub-packages, and surviving the schema shapes and output sizes of large upstream charts (temporal, argo-cd, traefik, prometheus, grafana, cassandra). It also fixes the release pipeline's version-stamping.
- HTTPS helm-repo dependency source (helm_repo_fetcher.rs).
akua.tomldeps can now name a classic Helm repository —repo+chart+version(exact or semver range) — alongsideoci/git/path. Resolved against the repo'sindex.yamlat add/lock time, content-pinned by.tgzsha256 inakua.lock, rendered deterministically from the cache offline. Private repos use the existing host-keyed--auth. The CLI gainsakua add --repo <url> --chart <name> --version <req>; the SDK'sadd()mirrors it. examples/13-subpackage-helm— a modular sub-package that itself composes a Helm chart, exercising the cross-package context fix below.examples/14-helm-repo-dep— a chart pulled from an HTTPS Helm repository (network-gated e2e test, run pre-release likeexamples_kcl_ecosystem).
- Composed sub-packages now resolve their own external deps (pkg_render.rs). A
pkg.render-composed (or typedpkgs.<alias>) sub-package can nowimport charts.<x>(Helm) andimport k8s.api…(kcl-ecosystem) declared in its ownakua.toml— previously these failed withCannotFindModulebecause external-package context was only set up for the root. The recursion loads the child manifest, resolves its deps, and registers them for the child eval, with the parent'sreject_replace/offlineposture propagated so a sub-package can't open an escape the root forbade. - Helm
NOTES.txtno longer breaks renders (helm.rs). The engine returnsNOTES.txt(top-level and per-subchart) among its files, but those are free-form prose, not manifests. akua parsed every file as YAML, so notes containingkubectl …:lines aborted the whole render withcould not find expected ':'.NOTES.txtis now skipped before parsing. - Union-typed chart values no longer crash the evaluator (values_schema.rs). A
values.schema.jsonfield typed["string","integer","null"]was collapsed to its first member while its default was emitted verbatim (port: str = 8080), aborting the wasm KCL evaluator. akua now emits a real KCL union (int | str) and marksnull-bearing unions optional. Unblocks charts like traefik. - Hyphenated dependency names now import correctly (mod_file.rs). A dep keyed
cnpg-operatorproduced acnpg-operator.kmodule thatimport charts.cnpg_operatorcouldn't see (-is not a KCL identifier). Dep names are sanitized to KCL identifiers at every materialization site, with collision detection and digit-leading handling. - Large renders no longer fail with an opaque I/O error (render_worker.rs). The worker's stdout pipe was capped at 1 MiB, so a chart rendering >1 MiB (e.g. argo-cd, ~1.36 MB) died with
os error 29. The cap is raised to the worker's memory ceiling (256 MiB) and overflow now surfaces as a typedE_RENDER_OUTPUT_TOO_LARGE. - Sub-package stubs no longer leak
charts.*imports (pkg_stub.rs). A sub-package'simport charts.<x>was carried into the synthesized stub compiled in the root context, wherechartsisn't registered. Chart imports are stripped from stubs (the schemas are what the stub needs). - Release binaries report their tag version (release.yml). The pipeline derives the workspace version from the pushed git tag (
scripts/set-cargo-version.sh) and assertsakua -Vmatches the tag in the build smoke-test. PreviouslyCARGO_PKG_VERSIONwas pinned to the committedCargo.toml, so 0.8.9–0.8.13 binaries (and their SLSA provenance / OCI annotations) all reported0.8.8.
Host-keyed HTTPS basic auth for vendor add. The CLI and SDK now
accept caller-supplied credentials keyed by URL prefix; akua does not
auto-load ~/.netrc, ~/.docker/config.json, or any env var.
Lockfile URLs are canonicalized at write time so credentials cannot
leak through akua.lock. Manifest URLs with embedded user:pass@ are
rejected at parse time.
- Host-keyed HTTPS basic auth for
vendor add(host_auth.rs). The CLI gains repeatable--auth <prefix>=<user>:<password>and explicit--auth-file <path>flags; the SDK'svendorAdd()accepts anauth?: Record<string, BasicAuth>parameter. Resolution: longest-URL-prefix match (same rule as git's credential helper /.npmrc). Akua never reads ambient credential files (~/.netrc,~/.docker/config.json) or env vars — the SDK/CLI surface is the only auth source. Aimed at multi-tenant SDK consumers (e.g. cnap install bootstrap) where ambient lookups can leak credentials cross-tenant. - Lockfile URL canonicalization.
akua.lock'ssourcefield now stores the canonicalized URL — userinfo stripped, default ports stripped (:443https,:80http), trailing.gitand/stripped. Idempotent. Even a malformed credentialed URL reaching the lockfile-write path cannot leak credentials.
- Manifest hardening:
akua.tomlrejectsgit = "https://user:pass@..."URLs at parse time with new codeE_MANIFEST_GIT_USERINFO. Hostile-to-secrets-in-VCS by default; the lockfile-canonicalization rule above is the second-line defense.
Workspace-vendor surfacing: the CLI now exposes akua vendor add,
akua vendor check, and akua vendor list; the SDK mirrors those
entry points. vendor add writes the lockfile pin alongside
materializing the tree, vendor-first resolver lookup is universal
across all dep kinds, and lockfile metadata clears on digest change
so cosign signatures can't outlive the bytes they were produced over.
The 0.8.7-rc1 through rc4 tags exist but their release workflows were cancelled before publish; this is the first 0.8.7 release on npm / GitHub Releases / Homebrew.
akua vendorwithadd,check, andlistsubcommands.@akua-dev/sdkvendor methods:vendorAdd,vendorCheck, andvendorList.vendor addwrites aLockedPackagepin intoakua.lockalongside materializing the tree, sovendor checkandakua verifyhave a stable digest to compare against — required for the offline-render contract once the canonical source is GC'd.examples/12-vendor-offline/— end-to-end demonstration of the offline-render contract:.akua/vendor/upstream/+akua.lockare committed, the canonicalupstream-chart/source is intentionally absent, and render still succeeds. Wired into the workspace withexamples_vendor_offline.rsintegration test.
- Shipped verb counts in the CLI reference, architecture overview, and roadmap were bumped from 26 to 27.
E_CHART_RESOLVErenamed toE_DEP_RESOLVE. The code is emitted byadd/update/render/lock/vendorfor any dep-resolution failure (path / oci / git / vendor) — not only Helm charts. Pre-alpha rename; consumers grepping for the old code should update.- Vendor-first resolver lookup is now universal across dep kinds. The
resolver previously only checked
.akua/vendor/<name>/for OCI and git deps; path deps fell straight through to the canonical source. After this release,.akua/vendor/<name>/wins forpathdeps too — makingvendor addsemantics symmetric across all three kinds and unblocking install-pipeline patterns that GC the canonical source after vendoring. - Bytes-tied lockfile metadata (cosign signature, SLSA attestation,
transitive dependency list,
yanked, Kyverno-converter fields) now drops when the digest changes during a lockfile upsert. Preserving these across a digest change would write(digest=B, sig=sig(A))entries that no consumer can verify. Thesource/version/digesttriple is always rewritten; everything else is conditional onprior.digest == new.digest.
- New
Dependency::spec()returns a typedDependencySpec<'_>enum carrying the source-form data (path / oci+version / git+tag/rev), replacing theOption-triple pattern matching that scattered.expect("path dep has path")calls across the resolver and vendor modules.DependencySpec::Oci::versionis&str(notOption) — manifest validation already enforces presence, so the type now encodes the invariant. - New
AkuaLock::find_slot/upsert_atlockfile primitives — single-scan lockfile upserts.merge_into_lockis now O(n) over the workspace's deps, was O(n²). - Shared
lock_file::VENDORED_LOCK_FALLBACKconstant for the sentinelversion/tag_or_revvalue (was a bare"vendored"string literal at four call sites). - Shared
chart_resolver::upsert_locked_from_sourcehelper used by bothmerge_into_lock(resolver-driven) andvendor::add_impl(vendor-add-driven), so the lockfile shape stays identical regardless of which codepath produced the entry.
Internal-only release: coverage uplifts, testability refactors, and a content-hash-based worker freshness guard for release builds. No public CLI or SDK behavior changes — the headline is supply-chain transport coverage rising from 9–20 % to 73–95 % on the OCI push / pull / fetch paths.
- New
crates/source-hash/workspace member: a tiny pure-Rust lib + binary used bytask build:render-workerto record the content hash ofakua-render-worker.wasm's source inputs alongside the.wasmartifact. verbs::dev::run_loop,verbs::sign::run_with, andverbs::publish::run_with(allpub(crate)) — testability seams that let unit tests drive the watch loop withoutctrlcand the signing flow with an explicit passphrase, withoutstd::env::set_varracing in nextest's process-per-test pool.oci_transport::registry_scheme(registry):httpfor loopback hosts (localhost,127.0.0.1,[::1]),httpsotherwise. Matches the conventiondocker,oras,crane,skopeouse for self-hosted local registries; not a test-only seam.
- Default render-worker wall-clock budget bumped from 3 s to 6 s
(
ResourceLimits::epoch_deadline30 → 60 ticks). The 3 s default worked for hand-written Packages but tripped during cold-load of thekcl-lang/k8secosystem bundle (~24 K lines of schemas) — the kcl loader's allocation pattern inside the wasm sandbox runs longer than the original budget on every cold render. SDK / future- API callers needing a tighter security boundary can still override via the public field.
- Render-worker freshness guard for release profiles.
cargo build -p akua-cli --profile {release,ci-release}now hard-fails when the embeddedakua-render-worker.wasmwas built from different source content than the akua-core code that's about to embed it. Previously the build emitted acargo:warning=and shipped the binary anyway, which let host/worker drift slip past CI. The check is content-hash-based via the newsource-hashcrate; mtime-based fallback applies if the hash file is missing (e.g. the worker was built outside the Taskfile flow). Taskfile.ymlbuild:render-workerpreviously listed onlycrates/akua-render-worker/**assources:. Addscrates/akua-core/**to the dependency list so an akua-core edit re-triggers the worker build instead of leaving Taskfile reporting "up to date" while the akua-cli build.rs flags drift.examples_kcl_ecosystemintegration test gated behind#[ignore]. It pulls live fromoci://ghcr.io/kcl-lang/k8sand occasionally trips the wasmtime epoch budget on cold caches. Now excluded fromcargo test --workspace;task release:validatepasses-- --include-ignoredso the pre-tag smoke still exercises it.
| File | Before | After |
|---|---|---|
crates/akua-core/src/oci_pusher.rs |
19.9 % | 95 % |
crates/akua-core/src/oci_puller.rs |
9.2 % | 87 % |
crates/akua-core/src/oci_fetcher.rs |
14.9 % | 80 % |
crates/engine-host-wasm/src/lib.rs |
56 % | 76 % |
crates/akua-cli/src/verbs/publish.rs |
0 % | 87 % |
crates/akua-cli/src/verbs/pull.rs |
0 % | 85 % |
crates/akua-cli/src/verbs/dev.rs |
0 % | 85 % |
crates/akua-cli/src/verbs/sign.rs |
84 % | 87 % |
crates/akua-core/src/helm.rs |
49 % | 68 % |
crates/akua-cli/src/observability.rs |
31 % | 50 % |
~80 new tests, all running through cargo nextest in seconds.
Branch coverage on the load-bearing security invariants:
digest-mismatch detection, layer rejection, lockfile pin
enforcement, bearer-auth retry, registry tampering, signing /
attestation round-trip, encrypted-key signing.
Republish of 0.8.4 — the npm publish step on native-release 401'd
mid-loop on v0.8.4, leaving @akua-dev/native-engines@0.8.4 and
@akua-dev/native-darwin-arm64@0.8.4 published but the other 6
per-platform packages + the @akua-dev/native meta + @akua-dev/sdk
missing from npm. v0.8.4 stays partial-published as a graveyard
version; install @akua-dev/sdk@0.8.5 to get the SDK __dirname
fix described under 0.8.4.
native-release.ymlpublish step is now idempotent. Probesnpm view <pkg>@<v>before each publish and skips packages already on the registry. A future partial-publish failure (network blip, OIDC hiccup) can be recovered by re-running the workflow rather than bumping the version. The 0.8.4 → 0.8.5 surgery yesterday is the kind of dance this avoids.
No code changes vs 0.8.4. SDK contract identical (all-napi, no
WASM transport, no __dirname bug class, the same requires-block
discussion etc. that landed in 0.8.4 stays valid).
Two-headline release. Critical SDK bug fix + main-CI green again.
-
@akua-dev/sdkAkua.check()/Akua.export()no longer ENOENT. Published 0.8.3 SDK had a CI build-machine path baked into the bundleddist/mod.js:ENOENT: no such file or directory, open '/home/runner/work/akua/akua/packages/sdk/wasm/nodejs/akua_wasm_bg.wasm'Cause:
bun build --target node --format esmsubstituted the wasm-pack wrapper's__dirnamereference with the build-time absolute path, since ESM has no__dirname.Akua.render()was unaffected (different transport).Fix: drop the WASM transport from the SDK entirely. Every method (
check,fmt,lint,tree,diff,export,inspectpackage mode) now routes through the napi addon — same pathrender/verify/version/whoamialready used. Napi already had all the bindings since the addon was first introduced; the SDK was simply still using the wasm side-channel for "browser-portability" that the published Node-only package never collected on. Net dist bundle: 62 KB → 38 KB; one transport, no__dirnamebugs by construction.See PR #50 review thread for the original bug report.
-
CI on main — two pre-existing failures cleared.
lock_rejects_helm_dep_referenced_via_importwas written before the path-escape guard landed; itspath = "../nginx-chart"now correctly tripsE_DEP_RESOLVEbefore the kind-mismatch check. Test relocated so the chart sits inside the install workspace.crates/helm-engine-wasm/fork/apply.shexited 128 in CI whenactions/cacherestored a partial.gitdirectory, then exited 1 when go-task fanned out tobuild:helm-engine-wasmfrom two dep paths concurrently. Three fixes: health-probe.gitand re-clone ifgit statusfatals; commit the patched state so repeat invocations hit the fast path; mkdir-mutex around the apply for concurrency safety. Taskfile cleanup removed the redundantbuild:enginesfromsdk:testso the race is gone even before the script-level guards fire.
Akua.renderSource(packageFilename, source, inputs?)legacy positional overload. The object form (renderSource({source, packageFilename?, inputs?})) is the only signature now. Existing callers update mechanically.crates/akua-wasm/andpackages/sdk/wasm/directories. Workspace member dropped; build:akua-wasm / build:akua-wasm:nodejs Taskfile tasks deleted; CLAUDE.md verb-shipping checklist updated to point at the napi crate instead of the WASM wrapper.
New tasks for coverage-driven bug discovery and broader Rust quality:
coverage:rust—cargo llvm-cov nextestover the workspace + feature-gated example pass + doctests. Auto-installs cargo-llvm-cov- cargo-nextest at pinned versions. Test failures don't abort report
generation (
ignore_error: true).
- cargo-nextest at pinned versions. Test failures don't abort report
generation (
coverage:sdk—bun test --coveragewith text + lcov reporters.coverage:gaps— lists Rust + SDK files belowTHRESHOLD% line coverage. Bug-discovery candidate list across both languages.coverage:rust:open,coverage:sdk:open,coverage:clean.audit—cargo auditagainst the RustSec advisory DB.hack/hack:test—cargo hack --feature-powersetover 18 feature flags across 4 crates.deps:unused—cargo machete.miri—cargo +nightly miri test -p engine-host-wasm(the unsafe-heavy crate hostingModule::deserialize).build:timings—cargo build --workspace --timingsHTML.
Workspace baseline at the time of this release: 80.39% line
coverage Rust / 76.82% line coverage SDK. Bug-discovery candidates
identified: crates/akua-napi/src/lib.rs at 0%, multiple CLI verbs
(dev, publish, pull, vendor) at 0%, oci_puller.rs at 9.2%,
oci_pusher.rs at 19.9%. Tests for these lined up in the next release.
-
Test execution ~2× faster in the dev loop. Same workspace, binaries pre-warmed:
cargo test --workspace 58.6s cargo nextest run --workspace 29.3sCompile time (the dominant cost on cold CI) is unchanged; speedup shows up on the inner loop. nextest's process-per-test isolation also makes the integration suite immune to libtest's shared-state fragility.
Follow-up to 0.8.2. Two fixes that surfaced when the 0.8.2 release attempt actually executed the matrix end-to-end.
- Cross-compile cwasm architecture mismatch.
macos-latestrunners are now aarch64 (M-series). When the matrix cross-compiledx86_64-apple-darwin,akua-cli/build.rsprecompiled the worker cwasm on the aarch64 host and embedded it in the x86_64 binary; runtimeModule::deserializethen trapped withModule was compiled for architecture 'aarch64'. Fixed by passing cargo'sTARGETtriple through towasmtime::Config::target()at build time — same path taken by every engine plugin's build.rs via the sharedengine_host_wasm::build_script_config()helper. Native builds (host == target) skip the call so the cwasm hash stays byte-identical to prior releases.
- cli-release: deleted the duplicate
validatejob.ci.ymlalready runstask ci(which exercises the example golden tests) on every push to main; by the time a release tag exists, main has been validated. The matrix smoke test (init && render) is the remaining guard against ship-without-worker / wrong-arch-cwasm regressions. Saves ~14 min off the release critical path. - cli-release: shared wasm-bundle job. Engines + render-worker
.wasmare wasm32-wasip1, byte-deterministic across runners. Build them once on Linux, share viaactions/upload-artifact, download per matrix runner. Saves ~5-10 min × 5 matrix runners. Side benefit: the Windows runner now ships with helm + kustomize engines (it previously skipped them because the helm fork'sapply.shis bash and won't exec on Windows).
Critical fix: every released CLI binary since 0.6.0 shipped without
the wasmtime render-worker embedded — akua render against any
package failed with E_RENDER_KCL "render sandbox unavailable —
worker module wasn't compiled into this akua binary." The cli-release
matrix runners never invoked task build:render-worker, so
build.rs wrote an empty .cwasm placeholder and the binary went
out the door with a 0-byte sandbox.
task release:validate (run on a separate validate runner) didn't
catch it because cargo test reuses the validate runner's
locally-built worker artefact in target/. The matrix runners had
no such artefact and no test ever exercised the produced binary.
- cli-release matrix builds the worker + engines pre-
cargo build. Two new steps beforeBuild akua CLI:task build:enginesandtask build:render-worker. Without these,akua-cli/build.rshas nothing to embed. - Smoke test on the produced binary. New step runs
<binary> init smoke && <binary> renderagainst the scaffolded Package and asserts./deploy/is non-empty. Catches future ship-without-sandbox / ship-without-engine regressions before users hit them. Skipped on cross-compile targets (linux/arm64 on x86_64, windows on ubuntu) where the runner can't execute the binary. build.rshard-fails in release profiles when the worker.wasmis missing. Dev / test profiles still get the empty- sandbox fallback so unrelated unit tests can compile without a full worker build first;releaseandci-releasenow turn the silent empty-bytes failure into a build error, not a runtime trap.
Upgrade to 0.8.2 — brew upgrade akua (homebrew tap auto-bumped),
npm install -g @akua-dev/sdk@0.8.2, or download the GitHub release
tarball. The earlier binaries cannot render any package.
Doc + example consistency for the alias-method form. No behavioural
change; the change is bundled stdlib (akua/helm.k docstring) so the
worker .wasm bytes differ.
- Helm at the call site is now alias-method in every documented
example:
<chart>.template(<chart>.TemplateOpts{values = <chart>.Values{...}}). Same shape aspkgs.<name>.renderfor Akua packages — synthesized per dep by the resolver, noimport akua.helmin user code. CLAUDE.md, docs/cli.md, docs/package-format.md, theakua/helm.kstdlib docstring, and examples 02 / 03 / 05 / 06 + their READMEs updated. - Engine-direct callables (
akua.kustomize,akua.kro,akua.oci) stay first-class for engines whose input isn't a typed dep. Documented as the explicit non-dep surface. akua.helm.templateretained as escape hatch for advanced Helm cases (multi-chart dynamic dispatch, post-renderer variations the stub doesn't expose). Doc redirects new code toimport charts.<name>first.
- Examples 02 / 03 / 05 / 06 used
<chart>.Chart{...}as the argument tohelm.template— a shape the chart-stub generator never actually emits. They render now (alias-method form).
The observability + security round. Three big additions on top of the
0.7 pkg.render substrate: a full tracing stack (with OpenTelemetry
export), the pkgs.<alias> typed-input shape that mirrors
charts.<name> for Helm, and a path-escape / replace rejection
guard that closes the host-side dep-resolution attack surface.
- Observability stack. Wasmtime trap symbolication
(
generate_address_map+ preservednamesection in the worker.wasm) — opaquewasm function NNNNtraps now resolve to KCL source frames. Newtracingsubscriber on the host wired to--log/--log-level/-v; worker-side spans replay through the host's stderr pipe so a single trace coversworker.invoke → bridge.call → kcl eval. OpenTelemetry export activates when any standardOTEL_*env var is set (no CLI flag needed — the OTel spec is the contract).AKUA_BRIDGE_TRACE=1back-compat shortcut for the legacy bridge eprintln pattern. docs/debugging.md— playbook for diagnosing render-pipeline failures: the three knobs (--log=json,--log-level=debug,RUST_LOG), target taxonomy (akua/akua::worker/akua::bridge), reading symbolicated traps, host-vs-worker triage, anti-patterns (running KCL outside the worker,eprintln!in plugin handlers).pkg.render(package = "<alias>")— typed dep-alias resolution replaces path strings in user-authored KCL. The alias referencesakua.toml [dependencies]. Unknown alias errors list known aliases for typo discovery.import pkgs.<alias>synthesized stubs. Each Akua-package dep gets a per-render<alias>.kmounting only the upstream's schemas + arenderlambda. Consumers writeupstream.render(upstream.Input { ... }), mirroringwebapp.template(webapp.Values { ... })for Helm. KCL type-checks the input at the call site — typos surface as compile errors, not as runtime worker traps.- Path-escape guard on
chart_resolver::resolve_path— rejects absolute paths in user-authoredpath = "..."/replace = { path = "..." }and any post-canonicalize result that escapes the workspace root. Internal vendor / OCI cache paths bypass the guard by construction. AKUA_REJECT_REPLACE=1— production gate that fails any render whose dep graph touches areplacedirective. Auto-on in agent context; CI / agent / container invocations no longer honor publisher-supplied replaces. Strict"1"-only (matches theAKUA_BRIDGE_TRACEconvention).akua render --timeout=<duration>/--max-depth=<N>— wires the existingBudgetSnapshotto the CLI surface. Go-duration parser (30s,5m,250ms); typo'd values surface asE_INVALID_FLAG. Newakua_core::duration_parsecrate-public.@akua-dev/sdkRenderOptions.timeout/maxDepth— same knobs reach the SDK; threaded through napi viaContext.timeout.- CLAUDE.md invariants — a dedicated "
replaceandpathdeps are workspace-local" section captures the threat model so reviewers reject violations. docs/cli-contract.md§9 / §9.1 — logging contract + OpenTelemetry env-var surface. §5 grows the duration unit list + the new--max-depth.
PackageK::loadcanonicalizes the path — bare--package package.kpreviously stored a relative path with empty parent; plugin handlers fed that empty dir tocanonicalizeand reportedi/o resolving`` (the opaque trap that motivated the observability work). Now resolves to the package's absolute directory unconditionally.- Wasmtime config deprecation — drop
wasm_backtrace(true); capture is on by default in wasmtime 43.
engine-host-wasm::shared_configenables symbolication.- Workspace
[profile.release]strip overridden for the worker build (task build:render-workerkeeps the wasmnamesection). RenderFrame.resolved_pkgs(alias → abs dir) propagated throughRenderScope::enter_for_render.pkg_stub::extract_schemas+build_stub_modulesynthesize the per-dep stubs.- Three new error variants:
ChartResolveError::AbsolutePathRejected,PathEscape,ReplaceRejected. New codeE_INVALID_FLAG.
The pkg.render round: a synchronous engine plugin that mirrors
helm.template / kustomize.build, plus the supporting work to
make composition first-class (path-deps + OCI Akua-package deps,
budget guards, structured error codes, a worked install-as-Package
example).
pkg.renderis a synchronous engine plugin. Returns a real[{str:}]list, not a deferred sentinel. List-comprehension patches ([r | overlay for r in pkg.render(...)]), filter expressions, and slicing all work natively. Requires the akua KCL fork (cnap-tech/kcl@akua-wasm32, commitd584c0bc) which copiesPLUGIN_HANDLER_FN_PTRout of its mutex before invoking the plugin callback so reentrant KCL eval no longer deadlocks.- OCI Akua-package deps.
[dependencies] upstream = { oci = "..." }resolves to aKclModuleeven when the artifact carriespackage.k(nokcl.mod) anddev.akua.*annotations. - Budget header for
pkg.render.BudgetSnapshot { deadline, max_depth }propagated through the render stack and checked before nested invocations. Default depth cap is 16; outermost callers can install an explicit deadline viaRenderScope::enter_with_budget. Catches recursive-composition runaway. - Structured error codes for plugin failures:
E_RENDER_CYCLE,E_RENDER_BUDGET_DEPTH,E_RENDER_BUDGET_DEADLINE. Routed through a marker→code lookup table. examples/11-install-as-package/— worked install-as-Package shape: outer Package overlays a tenant label, filters out a kind, and appends an extras ConfigMap on top ofpkg.render'd upstream.renovate.json— pre-1.0 cargo bumps no longer batch into a single PR.
- Render-worker rebuild trigger now watches
akua-render-worker/srcakua-core/srcand emits acargo:warning=when the staged.cwasmis stale.
akua init .derives the package name frombasename($PWD)instead of writingname = ".".E_PATH_ESCAPEerrors now carry ahintfield with both remediations (vendor under the Package or declare inakua.toml).akua render --debug(under--json) emitsevalResultalongside the summary — the post-eval resources list before YAML normalization.
- The
pkg.renderdeferred-sentinel mechanism + theE_PKG_RENDER_PATCH_UNSUPPORTEDfail-loud arm. Patching the return is now native, so the workaround retired.
The SDK moves from JSR to npm and renames to @akua-dev/sdk. This is
also the version that pivots from the chart-tooling shape (pullChart,
packChart, pullChartStream, inspectChartBytes — last shipped as
@akua/sdk 0.5.0) to a CLI-mirror shape: an Akua class whose methods
map 1:1 to the binary's verbs. Same --json envelopes, same typed
errors, all in-process via a bundled napi addon.
Akuaclass — every shipping CLI verb is a method:- Read-only:
version,whoami,lint,fmt,check,tree,diff,export,verify. - Render:
render({ package, inputs, out, dryRun, strict, offline })returns the sameRenderSummaryenvelope the CLI emits, byte-for-byte.renderSource({ source | package, inputs })returns rendered YAML directly for source-string consumers.
- Read-only:
- Full feature parity with the binary in-process — Helm + Kustomize
engines, OCI fetch, cosign verify, JSON Schema / OpenAPI export.
No
akuabinary on$PATH, no shell-out. Native addon (@akua-dev/native, per-platform via Node-API / napi-rs) for the feature-rich path; the existingakua-wasmbundle stays for the pure-KCL fast path and browser targets. renderSource({ source, package, packageFilename, packageDir, inputs })— accepts raw KCL source or an on-disk Package; engine callouts (helm.template,kustomize.build) work transparently when apackageDiris provided.- Typed-error routing across the napi boundary: thrown errors
preserve the
code(E_PACKAGE_MISSING,E_RENDER_KCL, …) soinstanceof AkuaUserError/AkuaSystemErroretc. continues to work.
- The chart-tooling surface (
pullChart,pullChartStream,pullChartCached,packChart,packChartStream,inspectChartBytes,streamTgzEntries,unpackTgz,@akua/sdk/cache,SsrfError). Subsumed byAkua.render's in-process resolver, which ships the same OCI fetch + digest verify + cosign check as the CLI uses. new Akua({ binary: '...' })— the binary path option is gone (no shell-out anymore).new Akua()is the only valid construction.
The two surfaces don't overlap; consumers of the chart-tooling
APIs need to rewrite. The 0.5.0 line stays installable on JSR for
legacy callers; new code should use 0.6.0 and the Akua-class
shape.
pullChart/pullHelmHttpChartstream-consume the response body with a running per-chunkmaxBytesguard; reader is cancelled on overrun so a hostile server can't keep pushing bytes after we've given up.- OCI path: preflight
Content-LengthagainstmaxBytesbefore opening the blob stream (parity with the HTTP Helm path).
- P2
tar.tslint errors resolved (Uint8Array<ArrayBuffer>TS variance). No runtime change — but removes noise that was hiding real errors inbun run lint. - P2 Native
Error.causethroughout (wascause_underscore field in a couple of subclasses).
pullChartStream(ref, options)— streaming variant returningReadableStream<Uint8Array>; pipe straight intoinspectChartBytes/ Convex / fetch bodies without buffering.@akua/sdk/cache(Node-only subpath export) —pullChartCached(ref)shares$XDG_CACHE_HOME/akua/v1/with the CLI. RespectsAKUA_NO_CACHEandAKUA_CACHE_DIR.SsrfError+ SSRF guard: pull hosts resolving to loopback / RFC1918 / link-local IPs (incl. AWS metadata at169.254.169.254) are rejected. Bypass withAKUA_ALLOW_PRIVATE_HOSTS=1.streamTgzEntries/unpackTgzoptions:maxEntries(20 000),maxTotalBytes(500 MB),maxEntryBytes(100 MB) — gzip-bomb caps mirroring the Rust side.
- Replace hand-rolled YAML parser in
tar.tswith theyamlnpm package. Fixes Helm-repoindex.yamlcompact-list edge cases that tripped the narrow parser (Bitnami, Jetstack). - Consolidated auth helpers into
src/auth.ts—credentialsToAuthHeader,toHost,base64Encode. Removes duplicate logic acrossoci.ts/helm-http.ts/docker-config.node.ts. AkuaErrornow uses native ES2022cause(was acause_underscore field).- Per-repo
index.yamlfetch cache — a build pulling N deps from the same Helm HTTP repo issues one index fetch instead of N. packChartscrubsdependencies[].repositorystarting withfile://before emittingChart.yaml(matches CLIakua package).docker-credential-*helpers: validate helper name regex, drain stderr to prevent pipe stalls, enforce 5 s timeout.pullChart/pullHelmHttpChartnow stream-consume response bodies with a runningmaxBytesguard — a server advertising an oversized Content-Length can't force a full buffer allocation.
- P0 Tar extraction — reject symlink + hard-link entries in Rust
unpack_chart_tgz. Prevents arbitrary file read viaakua inspecton a malicious chart whose entry points at/etc/passwd. - P0
engine-helmfileremoved from default cargo features. Helmfile's Go-templateexec/readFile/requiredEnvfunctions let an attacker package run arbitrary commands at build time. Opt in only for trusted packages. - P1 SSRF guard across Rust + SDK — private-range IP literals
rejected;
reqwestredirect policy re-validates each hop. - P1
helm-clirender engine now pre-populatescharts/via akua's audited fetcher and skipshelm dependency update. Helm never makes network calls for an untrusted package. - P1
kcl.entrypoint+helmfile.pathconfined to the package directory (no absolute paths, no..). - P2 Redacted
Debugimpls forOciAuth/RegistryCredentials/BasicAuth— prevents accidental?authtracing leaks. - P2 Strict OCI hostname validation (no userinfo, fragments, queries).
- P2 Strict helm-layer media-type enforcement — reject manifests
without the canonical
application/vnd.cncf.helm.chart.content.v1.tar+gziplayer instead of falling back tolayers[0]. - P2 URL userinfo redaction in error messages. Prevents
oci://user:pass@host/...userinfo from leaking into log lines. - P2 Content-Length preflight on OCI + HTTP Helm pulls; capped
Vec::with_capacitypreallocation (4 MB max) — a server spoofingContent-Length: u64::MAXcan't force a 100 MB up-front allocation. - P2 CEL evaluation: source length capped at 8 KB, 5 s wall-clock
timeout. Malicious
x-input.celcan't pin the worker thread. - P2 Cache LRU eviction (
AKUA_MAX_CACHE_BYTES, default 5 GB). - P2
--helm-binmust be an absolute path when--engine=helm-cli— prevents$PATHshadowing by a writable directory. - P2
manifest.schemapath validated at load time (relative, no..). - P2 Per-call
FetchOptionsoverride forAKUA_MAX_*limits — multi-tenant workers no longer share process-global caps. - P2 Migrated from deprecated
serde_yamltoserde_yml(the maintained fork).
- New top-level
SECURITY.md— threat model, fixed attack surfaces, remaining caveats, reporting process. - Rewrote
packages/sdk/README.md— worked examples forpullChart,packChart,dockerConfigAuth, streaming, cache, safety limits. - Top-level
README.mdupdated: SDK shipped (was "planned"), project structure reflects actual layout.
pullChartdispatches on scheme:oci://(existing) andhttps:///http://(new, Helm HTTP repos).packChartoptions:valuesSchema,metadata,signal— emitvalues.schema.json/.akua/metadata.yamlalongsideChart.yaml/values.yaml.dockerConfigAuth()Node helper — reads~/.docker/config.json, supportsauth,identitytoken,credHelpers,credsStore.AkuaErrorbase class;OciPullError,TarError,HelmHttpError,DockerConfigError,WasmInitErrorall extend it.buildMetadata(sources, fields?, options?)+packChartmetadataoption. HonoursSOURCE_DATE_EPOCHfor reproduciblebuildTime.dependencyToOciRef(dep)helper.ChartYamltype gainedappVersion,keywords,home,sources,icon,annotations,maintainers(optional).
AbortSignalwired throughpackChartandpackChartStream.
Initial published SDK.
pullChart(ref, options)— pure-TS OCI pull, nohelmbinary.unpackTgz,streamTgzEntries,packTgz,packTgzStream,inspectChartBytes.buildUmbrellaChart,mergeSourceValues,mergeValuesSchemas,extractUserInputFields,applyInputTransforms,validateValuesSchema,hashToSuffix.packChart+packChartStream.- Node (
@akua/sdk) + browser (@akua/sdk/browser) entries. - Published to JSR via GitHub Actions OIDC.
First tagged release of the akua binary. Substrate-shape only — no
curated catalog, no cluster control plane. Ten green examples render
deterministically; 26 verbs implement the universal CLI contract.
Authoring + render
- KCL-typed Packages:
package.kwithimport+schema+resourcesregions, published as signed OCI artifacts. akua render— wasmtime-sandboxed evaluation. Engines (Helm v4, Kustomize) compiled towasm32-wasip1and hosted inside akua's own wasmtime — no$PATH, no shell-out, no ambient filesystem.akua export— emit the Package'sInputschema as JSON Schema 2020-12 or OpenAPI 3.1. Field docstrings becomedescription;@ui(...)decorators becomex-uiextensions for form renderers (rjsf, JSONForms) and admission-webhook validators.@ui(...)schema decorators onInputattributes (order,group,widget,min,max,placeholder, …). Decorator arguments project losslessly into the exported schema; render strips them before handing source to KCL's resolver.- Determinism invariant: same inputs + same
akua.lock+ same akua version → byte-identical output. Nonow(), norandom(), no env reads in the render pipeline.
Dependency + lockfile shape
akua.toml+akua.lock— human intent + digest-pinned ledger. Three source kinds:oci,git,path.[replace]sections for vendor + path overrides.- KCL ecosystem support — pull
oci://ghcr.io/kcl-lang/*packages alongside Helm charts.import k8s.api.apps.v1resolves against the upstream KCL bundle inside the sandbox.
Verbs (26 shipped) —
init · whoami · version · verify · render · add · dev ·
test · tree · pull · publish · sign · update · lock ·
push · repl · pack · remove · diff · check · inspect ·
lint · fmt · cache · auth · export. Universal contract:
every verb supports --json, --plan, --timeout,
--idempotency-key; typed exit codes 0–6; structured-error stderr.
Agent-first surface
- Auto-detection of Claude Code, Cursor, Codex, Gemini CLI, Goose,
Amp, OpenCode, Cline, and 25+ other agents. Detected sessions
auto-enable
--json,--no-color,--no-progress,--no-interactive. - Skill manifests under
skills/conforming to the Agent Skills Specification.
Signing + attestation
akua publishemits cosign signatures (ECDSA P-256 keyed) and SLSA v1 predicates by default; consumers verify on pull. Air-gap flow:akua pack→akua sign→akua verify --tarball.
SDK
@akua/sdk(packages/sdk) — in-process WASM viaakua-wasmcrate,wasm32-unknown-unknowntarget. Verbs callable without spawning the binary:version,whoami,render,renderSource,check,lint,fmt,inspect,tree,verify,diff,export. Same shapes the CLI emits — typed viats-rs-generated TypeScript types andschemars-emitted JSON Schemas.
- Wasmtime sandbox is structural — no
--unsafe-hostescape hatch, no shell-out fallback. Memory / epoch / wall-clock caps enforced per render; capability-model preopens scope filesystem access. - Adversarial test suite: zip-bomb resistance, path traversal,
symlink escape,
PATH=/nonexistentinvariance, fork-bomb caps — all green. - Every Rust-side hardening from the SDK 0.3.0 entry applies to the
CLI: tar-extraction symlink rejection, SSRF guard, OCI media-type
strictness, Content-Length preflight,
engine-helmfileopt-in,helm-cliopted out ofhelm dependency update.
Alpha. v0.1.0 is the first tagged release. Stable contracts: the
26-verb CLI surface, the universal flag/exit-code contract, the
WASM-backed SDK methods, the wasmtime sandbox invariant. Anything in
docs/roadmap.md under Phase 5+ may change before
v0.2.0. Safe for CI and agent workflows today; pin akua versions for
production rollouts.