akua's package manager is modeled on Go's two-file split but uses TOML for both files (the format Cargo, Poetry, and pnpm adopted for richer dep graphs than Go's directive syntax expresses).
akua.toml— what you asked for. Declared deps, version constraints, workspace members, package metadata. Human-edited.akua.lock— what you got. One[[package]]entry per resolved artifact with digest + cosign signature + optional transitive dep references. Machine-maintained, never hand-edited.
Both files are TOML. Both are checked into git. Both are required.
Clear separation of concerns:
| intent | evidence | |
|---|---|---|
| file | akua.toml |
akua.lock |
| edited by | human | akua add / akua pull / akua publish / akua update |
| shape | small, stable | may be large; churns on every resolved-version change |
| review focus | "do we want this dep?" | "is this the expected digest + signature?" |
A PR that modifies akua.lock but not akua.toml is automatically suspicious (someone changed what they got without changing what they asked for). CI can lint for this.
Merged-lockfile alternatives (npm's package-lock.json, Cargo's Cargo.lock with deps embedded in Cargo.toml) bundle both concerns differently; we take Go's split (intent vs evidence in separate files) and Cargo's structured-TOML lockfile (so we can express a richer dep graph than go.sum's line-per-hash format).
- Lowercase throughout — matches
go.mod,poetry.lock,pnpm-lock.yaml,pyproject.toml(every lockfile shipped after ~2014 went lowercase). .tomlextension on the manifest — honest about the format; editors pick up TOML highlighting automatically.- No extension on the lockfile — matches
Cargo.lock/poetry.lock/yarn.lock/package-lock.jsonconvention (lockfile name is descriptive; the tool knows the format).
[package]
name = "my-app"
version = "0.1.0"
edition = "akua.dev/v1alpha1" # akua schema compat marker
# (Optional) workspace members — for monorepos with many akua packages
[workspace]
members = ["./", "./apps/*"]
# Dependencies — every import in KCL / Rego must be declared here
[dependencies]
# key = { source_type = "<ref>", version = "..." }| form | example | use when |
|---|---|---|
| OCI | { oci = "oci://ghcr.io/.../foo", version = "1.2.3" } |
published signed artifact (most common) |
| Git | { git = "https://github.com/foo/bar", tag = "v1.2.3" } |
non-OCI-distributed sources |
| Path | { path = "../shared" } |
workspace-local, dev-only |
| Replace | { oci = "...", replace = { path = "../fork" } } |
local-fork override for debugging |
[package]
name = "payments-api"
version = "3.2.0"
edition = "akua.dev/v1alpha1"
[dependencies]
# KCL sources
k8s = { oci = "oci://ghcr.io/kcl-lang/k8s", version = "1.31.2" }
cnpg = { oci = "oci://ghcr.io/cloudnative-pg/charts/cluster", version = "0.20.0" }
webapp = { oci = "oci://ghcr.io/acme/charts/webapp", version = "2.1.0" }
# Rego policies (compile-resolved as data. imports — see policy-format.md)
tier-prod = { oci = "oci://policies.akua.dev/tier/production", version = "1.2.0" }
kyv-sec = { oci = "oci://policies.akua.dev/kyverno/security", version = "2.0.0" }
# Local fork for debugging
our-glue = { oci = "oci://pkg.acme.internal/glue", version = "0.3.0",
replace = { path = "../glue-fork" } }- Exact pin preferred.
version = "1.2.3"means that version, nothing else. No implicit semver-range resolution. - Semver range allowed (
version = "^1.2.0") for dependencies where minor updates are trusted. akua's resolver picks the highest matching version satisfying all constraints across the graph. - Conflicts error out. If two dependencies pin different versions of a shared transitive, the resolver fails with a clear message. Use
replaceto force a single version.
| field | required | notes |
|---|---|---|
[package].name |
yes | a valid KCL package identifier |
[package].version |
yes | semver |
[package].edition |
yes | akua.dev/v1alpha1 for v0 compatibility |
[package].strictSigning |
no | default true; set false to permit unsigned deps (discouraged) |
[workspace].members |
no | glob patterns; enables monorepo |
[dependencies] |
yes (can be empty) | see dependency forms above |
Cargo.lock-flavored TOML: one [[package]] entry per resolved artifact, alphabetically ordered, with optional dependencies for the transitive graph.
# akua.lock — machine-maintained. Never hand-edit.
# Regenerated by `akua add`, `akua pull`, `akua publish`, `akua update`.
version = 1 # lockfile format version; bumped on incompatible changes
[[package]]
name = "cnpg"
version = "0.20.0"
source = "oci://ghcr.io/cloudnative-pg/charts/cluster"
digest = "sha256:3c5d9e7f1a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
signature = "cosign:sigstore:cloudnative-pg"
[[package]]
name = "webapp"
version = "2.1.0"
source = "oci://ghcr.io/acme/charts/webapp"
digest = "sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
signature = "cosign:key:acme"
dependencies = [
"[email protected]",
]
[[package]]
name = "common"
version = "2.20.0"
source = "oci://ghcr.io/bitnamicharts/common"
digest = "sha256:f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0"
signature = "cosign:sigstore:bitnamicharts"| field | required | notes |
|---|---|---|
name |
yes | matches the [dependencies] key in akua.toml |
version |
yes | exact resolved semver (not a range) |
source |
yes | full source ref: oci://…, git+https://…, or path+file://… |
digest |
yes | content-addressable hash: sha256: for OCI; sha256 over tarball for git |
signature |
conditional | cosign signature. Keyless: cosign:sigstore:<issuer>. Keyed: cosign:key:<identity>. Required unless [package].strictSigning = false in akua.toml |
dependencies |
no | ["name@version", …] — transitive edges for graph walks |
attestation |
no | SLSA attestation digest; present when the dep's author publishes one alongside |
replaced |
no | { path = "…" } when a local replace is active |
yanked |
no | true for retracted versions |
- Alphabetical order by
name. Stable diffs even across unrelated PRs. - One
[[package]]per resolved (name, version). Two major versions of the same dep means two entries. - No mutable metadata. No timestamps, no resolver versions, no author info. Everything is deterministic.
- No comments in generated content. The tool-written
[[package]]entries are clean; put explanations inakua.toml. - Trailing newline. POSIX file discipline.
- Source code (not a vendor directory — see
akua vendorfor that) - Version ranges (those live in
akua.toml) - User-facing comments
- Reads current
akua.toml - Adds the new entry to
[dependencies] - Fetches the artifact; computes digest
- Verifies cosign signature
- Updates
akua.lock— inserts/updates the[[package]]entry; adds transitive deps alphabetically - If the new dep transitively pulls others, repeats for each
Result: both akua.toml and akua.lock updated in one atomic operation.
- Reads
akua.tomlandakua.lock - Resolves every dep from
akua.toml - Compares expected (manifest) vs locked (lockfile) digest + signature
- Exits 0 if everything matches; non-zero otherwise
Run in CI on every PR to catch lockfile tampering.
Updates to the highest allowed version per akua.toml constraints; rewrites the relevant [[package]] entries in akua.lock. Leaves other deps untouched unless their constraints also match a new version.
Writes full dependency tree to ./vendor/ for air-gapped builds. Content matches akua.lock digests. Not needed for online builds.
For monorepos with multiple akua packages:
platform/
├── akua.toml # workspace root
├── akua.lock
├── apps/
│ ├── api/
│ │ └── package.k # member package
│ ├── worker/
│ │ └── package.k
│ └── dashboard/
│ └── package.k
└── policies/
└── org-baseline/
└── policy.rego # member policy module
Workspace root akua.toml:
[workspace]
members = ["./apps/*", "./policies/*"]
[dependencies]
# Shared deps used by all members
k8s = { oci = "oci://ghcr.io/kcl-lang/k8s", version = "1.31.2" }Members inherit workspace dependencies; they may override in a member-local akua.toml (minimal). Cross-member imports work as path-type deps.
akua's akua.toml is not the same as KCL's kcl.mod. We don't try to be. akua packages can contain a kcl.mod in their source tree for pure-KCL consumers who want to use upstream kcl run against the package directly; akua's resolver honors either file when it's unambiguous.
Pulling kpm-published packages. OCI deps published by kpm push (e.g. everything under ghcr.io/kcl-lang/*) work transparently through the same [dependencies] shape Helm charts use. The fetcher detects the artifact family from the manifest's media type + org.kcllang.package.* annotations, unpacks the plain tar, and registers each as a typed ExternalPkg entry inside the wasmtime render sandbox — so a Package.k can write import k8s.api.apps.v1 and have it resolve against the upstream schema bundle. See examples/10-kcl-ecosystem/ for a worked example.
See the broader architecture note for the "akua is the outer package manager; kpm is the inner KCL-layer tool" framing.
./
├── akua.toml
├── akua.lock
├── apps/
│ ├── api/
│ │ └── package.k
│ └── worker/
│ └── package.k
├── policies/
│ └── production.rego
└── environments/
├── dev/inputs.yaml
├── staging/inputs.yaml
└── production/inputs.yaml
akua.toml:
[package]
name = "acme-platform"
version = "0.1.0"
edition = "akua.dev/v1alpha1"
[workspace]
members = ["./apps/*"]
[dependencies]
k8s = { oci = "oci://ghcr.io/kcl-lang/k8s", version = "1.31.2" }
cnpg = { oci = "oci://ghcr.io/cloudnative-pg/charts/cluster", version = "0.20.0" }
webapp = { oci = "oci://ghcr.io/acme/charts/webapp", version = "2.1.0" }
tier-prod = { oci = "oci://policies.akua.dev/tier/production", version = "1.2.0" }
kyv-sec = { oci = "oci://policies.akua.dev/kyverno/security", version = "2.0.0" }akua.lock (after resolution):
version = 1
[[package]]
name = "cnpg"
version = "0.20.0"
source = "oci://ghcr.io/cloudnative-pg/charts/cluster"
digest = "sha256:d4e5f6…"
signature = "cosign:sigstore:cloudnative-pg"
[[package]]
name = "k8s"
version = "1.31.2"
source = "oci://ghcr.io/kcl-lang/k8s"
digest = "sha256:a1b2c3…"
signature = "cosign:sigstore:kcl-lang"
[[package]]
name = "kyv-sec"
version = "2.0.0"
source = "oci://policies.akua.dev/kyverno/security"
digest = "sha256:j0k1l2…"
signature = "cosign:sigstore:akua-release"
[[package]]
name = "tier-prod"
version = "1.2.0"
source = "oci://policies.akua.dev/tier/production"
digest = "sha256:g7h8i9…"
signature = "cosign:sigstore:akua-release"
[[package]]
name = "webapp"
version = "2.1.0"
source = "oci://ghcr.io/acme/charts/webapp"
digest = "sha256:m3n4o5…"
signature = "cosign:key:acme"CI runs akua verify on every PR; any digest mismatch or missing signature fails the build.