Back to blog

kern v0.9.0-rc1 — the stability candidate

Code-side, every v1.0 ROADMAP gate is closed or has its harness shipped. Generics, async, Secure<T, State> with EU-sovereign cloud KMS, native OCI containers, kern-pkg ported to Kern, and a chain-hashed AI audit log all land here. What v0.9.x exists to wait out is calendar and governance, not code.

901
Tests, 100% pass
16/20
v1.0 gates closed
25K
Compiler lines (Kern)
31K
C runtime lines

What “stability candidate” means

v1.0 is a contract: no breaking changes without a v2. The ROADMAP lists 20 explicit v1.0 exit gates. Through this release wave — v0.7 (generics), v0.8 (async, Secure<T, State>), v0.9.0-rc1 (kern-pkg port, cloud KMS, native containers, chain-hashed audit) — 16 of those gates are now closed or have their harness shipped.

The four that remain are not code:

Three of the four are calendar / governance. So v0.9.0-rc1 is the version where, code-side, we’re done.

Why generics before async, and async before kern-pkg. Async codegen heap-allocates an __async_ctx struct that contains the function’s locals; if the generic ABI changes after async ships, every async function’s frame layout changes too. Generics first, then async on a stable ABI. Then the package manager port — because the only way to kill the Python-shim asterisk on “self-hosted” is to port it on top of finished primitives, not parallel to in-flight ones.

Generics — selective monomorphization on a frozen ABI

The strategy committed in v0.6 is now the implementation. Stdlib hot paths get monomorphized typed accessors emitted as linkonce_odr, so multiple compilation units share them without link conflicts. Generic call sites compile to the monomorphized typed wrappers when they exist; the type-erased path is the fallback.

Async — spawn, await, spawn_thread on cooperative routines

The async fallback paths v0.6.0 documented as TODO are now the real codegen. Every direct spawn / await runs through libuv-driven code. Bodies execute on cooperative kern routines — stackful coroutines with 16 KB guard-paged stacks (configurable via KERN_ROUTINE_STACK_SIZE). await from inside a routine parks it on the awaitee; no OS thread is held during suspension.

concurrent.kern
async fn work(label: str) -> str:
    await sleep_ms(80)
    return label

async fn main():
    # Two cooperative routines — both park on the timer.
    a = spawn work("alpha")
    b = spawn work("beta")

    ra = await a
    rb = await b
    # Both finish in ~82 ms total, not 160.

    # CPU-bound work routes to the libuv worker pool:
    h = spawn_thread crunch_numbers(big_input)
    result = await h

Routine-aware IO is not just sleep_ms: kern_async_file_read / kern_async_file_write / kern_async_dns_resolve / kern_async_tcp_connect / kern_async_tcp_read all branch on routine context — inside a routine they submit with a callback and park; the callback re-enqueues the routine when the IO event fires. Outside a routine, the existing synchronous-pump path is unchanged. Routine-aware libpq and mbedTLS land in the same wave.

Concurrency proof. Two spawned sleep_ms(80) tasks complete in ≈82 ms (not 160). Two parallel DNS resolves in ≈11 ms. Two spawn_thread 80 ms tasks complete in ≈82 ms via the libuv worker pool.

Secure<T, State> — compile-time IO governance with EU cloud KMS

Secure<T, State> is opt-in compile-time governance for sensitive data, with State ∈ { Plain, Encrypted, Hashed }. At runtime a Secure value is just T; the type-state lives in the typechecker’s parallel-marker tracking (__sec_state__<name>) and is propagated on identifier assignment. The compile-time IO-sink check rejects Secure<_, Plain> flowing into db_* or http_* calls with a clear diagnostic.

secure_kms.kern
import std.crypto.kms as kms
import std.crypto.secure as secure

# KERN_KMS_PROVIDER routes to: scaleway | azure | ovh | local
# Default is local AES-256-GCM via mbedTLS.

async fn store_session_token(raw: str) -> Result<str>:
    plain   = secure.wrap(raw)              # Secure<str, Plain>
    encrypted = await kms.encrypt(plain)?  # Secure<str, Encrypted>

    # db_insert(plain, ...) — COMPILE ERROR:
    #   Secure<str, Plain> cannot reach a db_* sink.
    return await db.insert("sessions", encrypted)?

KERN_KMS_PROVIDER selects the backend at runtime. Two EU-sovereign providers ship in this release:

kern-pkg — the Python shim is gone

The package manager is no longer a transition asterisk on “self-hosted.” Every command runs on Kern:

Manifest [dependencies] and [dev-dependencies] parse into a typed List<Dependency>. The lockfile is read and written with chain-integrity verification. The Python kern-pkg/ directory remains in-tree as a transition shim only and is on its way out. This closes the ROADMAP toolchain self-hosting gate.

Native OCI containers — no daemon, no shellout

kern_container_run_native() runs a sandboxed process directly: Linux namespaces (PID, mount, net, UTS, IPC), cgroup v2 for CPU / memory / IO limits, overlayfs for the rootfs, and a veth pair for the network. No daemon, no docker / podman / runc shellout.

Pulling images is just as direct. kern_oci_pull() talks to any OCI Distribution Spec v2 registry — Docker Hub, GHCR, Quay, GitLab CR — with SHA-256 verification on every blob and cache-keyed-by-digest. Tampered blobs are detected and unlinked. oci_run_image() composes pull + config + run into a single call.

infra.kern
import sys.container as ctr

async fn main():
    db = await ctr.run(
        image:   "postgres:16-alpine",
        name:    "kern-db",
        ports:   ["5432:5432"],
        env:     {"POSTGRES_DB": "kern"},
        restart: "unless-stopped"
    )?
    await db.ready(timeout: 30)

    # cgroup v2 metrics by name:
    stats = ctr.stats_native("kern-db")
    print(stats.cpu_usage_us, stats.mem_rss_bytes)

Chain-hashed AI audit log — closes the EU AI Act gate

BLAKE2b on the event log was correct for crash safety; it was not enough for tamper-evidence on its own — a determined writer could rewrite history and rehash. v0.9.0-rc1 lifts the AI audit log onto a SHA-256 hash chain:

Tamper-evident by construction. Closes the EU AI Act audit-trail gate.

Production primitive resilience — finishing the v0.6 work

v0.6 wrapped the LLM proxy and Qdrant in a circuit breaker + retry layer. v0.9 adds the slow-path armour around it:

Compliance exports — SBOM and Art. 30 records

Compiler and runtime fixes worth naming

What v0.9.x is for

v0.9.0-rc1 is not v1.0. The point of the v0.9.x line is soak time:

Every item above is on the ROADMAP with a checkbox. None are aspirational; all are blockers.

By the numbers

Try v0.9.0-rc1

Install on Linux or macOS. Single binary, no runtime dependencies.

terminal
curl -fsSL https://kern-lang.eu/install.sh | bash
Get Started View on Codeberg