Why this is a status post, not a release
Earlier drafts of the changelog labelled work on main as v1.0.0
and v1.1.0. That label was retracted in April. v1.0 is an API
stability promise we are not yet ready to honour: until generics and async ABI
land, every async fn frame layout could move; until the foundation is
legally incorporated, governance is informal; until the soak window passes, "no
breaking changes" is wishful.
So: the released line is still v0.6.0. Everything below is real, on
main, with tests, but it is not tagged. The order matters — generics first,
async on a stable generic ABI second, KMS providers and container runtime on top.
See ROADMAP.md
for the explicit v1.0 exit criteria.
Generics — selective monomorphization for stdlib hot paths
Generics in v0.6.0 were type-erased to i8*. Fine for compiler simplicity,
bad for throughput on List<Decimal> and Map<str,
Decimal> — exactly the workloads finance and government deployments lean
on. The v0.7 strategy committed to a hybrid: monomorphize what matters,
box what doesn't, let users opt in.
- Selective monomorphization for stdlib hot paths —
List<T>,Map<K,V>,Result<T>,Option<T>,Decimal— emitted aslinkonce_odrtyped accessors so multiple compilation units share them without link conflicts @monomorphizeannotation as the opt-in marker for user-level generics, parsed and recorded in the typechecker's parallel-marker tracking- Call-site retargeting — generic callsites compile to the monomorphized typed wrappers when available; type-erased path is the fallback for everything else
Why selective: full monomorphization inflates binaries unacceptably for kern's
container deployment model (scratch-image, single static binary). Full erasure leaves
throughput on the table for the workloads that actually pay people. The hybrid means
List<Decimal> in a tax engine doesn't pay a vtable round-trip per
add, and a one-off List<User> in a CLI tool doesn't bloat the
resulting binary.
This is the ABI-breaking decision that had to land before async.
Async codegen heap-allocates an __async_ctx struct that contains the
function's locals. If generics changed the struct layout afterwards, every
async fn's frame would re-layout — doubling the migration cost. Land
generics first; build async on top.
Async — cooperative routines with libuv-backed IO
v0.6.0 had async plumbing — libuv at startup, kern_async_spawn,
kern_async_await — but the codegen was incomplete: spawn and
await in some branches fell through to synchronous evaluation. v0.8 fixes
that across five phases.
- Phase 1–2 — real
spawncodegen for directCallExprs. 0-arg and 1-arg fast paths; N-arg via a heap-bundled struct.awaitcallskern_async_coro_awaitand unboxes the i8* result back to the typed return. Bothawait spawn f(...)and the variable form (let t = spawn f(...); await t) work — the variable form looks up the task return type via the__task_ret__parallel map. - Phase 3 — auto-spawn of
async fnawaits.await foo(...)wherefoois declaredasync fnautomatically spawns onto the cooperative scheduler so multiple concurrentawait async_fn(...)calls actually overlap. - Phase 4 — cooperative kern routines.
kern_async_coro_spawnruns each task on a stackful coroutine with assembly context switching (x86_64 + ARM64) and a 16 KB guard-paged stack.awaitfrom inside a routine parks it on the awaitee — no OS thread is held during suspension. - Phase 4b — routine-aware libuv IO.
kern_async_file_read/_write,_dns_resolve,_tcp_connect/_tcp_readall branch on routine context: in a routine they submit with a callback and park; the callback re-enqueues the parked routine when the IO completes. - Phase 4d — routine-aware libpq + mbedTLS handshake. Postgres queries and the TLS handshake park during DB roundtrips instead of holding the routine.
- Phase 5 —
spawn_thread. A keyword for CPU-bound work: routes tokern_async_spawn(libuv worker pool) instead of the cooperative scheduler. Two parallelspawn_threaded 80 ms tasks complete in ≈82 ms — wall-clock, not summed.
import std.async
import net.dns
# Two DNS lookups concurrently — each parks the routine
# during the resolver call, libuv wakes them up on completion.
async fn resolve_pair():
a = spawn dns.resolve("codeberg.org")
b = spawn dns.resolve("qdrant.tech")
ip_a = await a
ip_b = await b
print("codeberg=${ip_a} qdrant=${ip_b}")
# Wall-clock: ~max(rtt_a, rtt_b), not rtt_a + rtt_b.
What's not yet there: stackless coroutine lowering for the 1M-routines-at-1.5 GB
target (today: 16 GB at 1M with the 16 KB stack — needs LLVM coroutine
intrinsics or segmented stacks). HTTPS and a few stdlib paths still hold the routine.
Channels and select integration. Those are the v0.8.0 finish line, not
regressions.
Secure<T, State> — type-state IO governance
PersonalData<T> said "this string is personal data; don't pass it
to LLMs without consent." Secure<T, State> says "this value is
cryptographic material; don't pass it to a network sink in plaintext." It's the same
idea applied to a different concern, and it composes with PersonalData.
- Type-state in the typechecker —
State ∈ { Plain, Encrypted, Hashed }. At runtime aSecurevalue is justT; the state lives in the typechecker's parallel-marker tracking (__sec_state__<name>) and propagates on assignment - Compile-time IO-sink check —
Secure<_, Plain>flowing intodb_*orhttp_*calls is rejected with a clear diagnostic. You have to encrypt or hash first - State transitions via
secure_wrap,kms_encrypt,kms_decrypt,kms_hash,secure_unwrap_plain— each one audited - Local KMS — AES-256-GCM via mbedTLS, per-encryption nonces, master key from
KERN_KMS_MASTER_KEY(or per-process random with warning, for dev) - EU-sovereign cloud KMS providers — Scaleway KMS (FR) and Azure Key Vault. Same abstraction, no provider lock-in
import std.secure as sec
import db.postgres as pg
# Token enters as Plain — fresh from a form post.
token: Secure<str, Plain> = sec.wrap(form.get("api_key"))
# Compile error: Plain must be encrypted before db_insert.
# pg.insert("tokens", { "raw": token })
# ^ E0162: Secure<str, Plain> cannot flow into db_*
ct: Secure<str, Encrypted> = sec.kms_encrypt(token, key_id: "prod-tokens")?
pg.insert("tokens", { "ct": ct }) # OK
Native OCI container runtime
kern's deployment story has always been "static binary into a scratch image." That
still works. What changed is that main can now pull, verify, and
run OCI images directly from Kern, without shelling out to docker
or podman:
- OCI Distribution Spec v2 client — pull container images natively over HTTPS
- SHA-256 digest verification on every layer; cache hits validate against the digest before reuse
oci_pull_configreads the image config blob — env, cmd, workdir — for honest run semanticskern_container_run_nativeon Linux — namespaces, pivot_root, the standard cgroup setup. Not Docker; not a wrapper around Dockercontainer_stats_nativereads cgroup v2 metrics for live container telemetrysys.container.oci_run_imagewraps pull + run with config — one call from image reference to running container
For an EU institution that does not want a US-headquartered container runtime in the
production critical path, this is the missing piece. The OCI client speaks the
standard registry protocol — pulls from any conformant registry — but the runtime
that actually executes the container is in main, with kern's audit and
supply-chain story.
kern-pkg — substantially ported to Kern
kern-pkg in v0.6.0 was a Python shim. The asterisk on "self-hosted"
bothered everyone. The port on main now covers the full cargo-equivalent
surface, in Kern itself:
init,build,run,test— the everyday loop, in Kernadd,remove,verify— dependency management with a Lockfile, in Kerninstall— fetch, verify, extract dependencies, in Kernpublish— pack, hash, sign with Ed25519, upload to the registry — the producer sidesearch— query the registry index — the discovery sideupdate— re-resolve manifest deps intokern.lock— the maintenance loop- Manifest
[dependencies]and[dev-dependencies]parsing, plustoml_section_keysand the rest of the TOML helpers needed to readkern.toml - Parser tolerates
@annotation(arg: …, …)syntax — the prerequisite for richer annotation grammar in user code
Ed25519 in-band signatures land first; HSM/PKCS#11 signing is its own subproject and is decoupled — that decoupling is what unblocked the port. Removing the Python shim also removes a CPython dependency from a build that otherwise has zero runtime dependencies on the host. For an EU institution evaluating whether the kern toolchain runs end-to-end on European infrastructure, "the package manager is written in Python" was a fair objection. It is on its way out.
Other things in flight
- PersonalData taint propagation through containers — the typechecker now tracks
PersonalDatathrough list and map containers, through trait calls, and through string interpolation. The flagship privacy guard catches more cases than the v0.6 minimum - Tamper-evident AI audit log — SHA-256 hash chain on top of the BLAKE2b event log.
verifynow recomputes the per-record hash, so silent data tampering inside an entry (not just at chain boundaries) is caught - HTTPS resilience —
post_headersnow enforces a 60 s socket timeout, so the LLM proxy's circuit breaker can actually fire when an upstream stalls instead of just when it errors - Qdrant — socket timeouts plus
qdrant_health()for explicit readiness probes net.ws—connect_with_backoffandsend_resilient: the WebSocket reconnect path that v0.6.0 didn't shipstd.compliance—gdpr_art30record-of-processing-activities export, plus the CycloneDX 1.5 SBOM export now lives in stdlib (was tooling-only)- Routine memory tuning — default routine stack cut from 64 KB to 16 KB (16 GB at 1M routines vs 64 GB before). Operator override via
KERN_ROUTINE_STACK_SIZE(4 KB – 1 MB) - Negative-test gate is CI-blocking. 190 / 190 reject. Promoting it from advisory to blocking was the last piece before generics + async could land safely
What this is not
This is not v0.7. It is not v0.8. It is not v1.0. It is what's on main
between releases, written down so that institutions evaluating kern can see the
trajectory and time their pilots accordingly.
The remaining gates before v0.7.0 and v0.8.0 ship as tagged releases:
- Stackless coroutine lowering for the 1M-routines-at-1.5 GB target. Today: 16 GB at 1M with the 16 KB stack. Needs LLVM coroutine intrinsics or segmented stacks
- HTTPS / channel / select integration with the cooperative scheduler — IO that already parks vs IO that doesn't
- v0.9.x stability soak — six months of zero breaking changes once the ABI-breaking decisions are in. v1.0 is the tag at the end of that soak, not before
- Stichting Kern Foundation incorporation — in formation, not yet legally incorporated
Every item above is on the ROADMAP with a checkbox. Pre-1.0 versions are bumped on real progress, not to look further along than we are.
By the numbers
- 892 tests at 100% pass — 702 conformance, 190 negative
- 190 / 190 negative tests reject — gate is now CI-blocking
- Released line: v0.6.0. v0.7 / v0.8 are in flight on
main - ~82 ms wall-clock for two cooperative
spawned 80 ms tasks (and the same for twospawn_threaded ones) — concurrency proof, not a synthetic benchmark - 16 KB default routine stack — 16 GB at 1M routines, configurable via
KERN_ROUTINE_STACK_SIZE - Two EU-sovereign cloud KMS providers shipped behind the
Secure<T>abstraction: Scaleway (France) and Azure Key Vault - EUPL-1.2, hosted on Codeberg, governed from the Netherlands
Track the work
v0.6.0 is the release line you can deploy today. main is where the
next two milestones are taking shape.