# kern v0.9.0-rc1: the stability candidate

> Code-side, every v1.0 ROADMAP gate is closed or has its harness shipped.

Source: https://kern-lang.eu/blog/v090-rc1-release

May 6, 2026

# 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.

## At a glance

Tests, 100% pass

901

v1.0 gates closed

16/20

Compiler lines (Kern)

25K

C runtime lines

31K

## What “stability candidate” means

v1.0 is a contract: no breaking changes without a v2\. The [ROADMAP](https://codeberg.org/PUAGroup/kern-lang/src/branch/main/ROADMAP.md) 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:

- **Parser-fuzzer 24 h soak.** Harness shipped this release (`tools/fuzz_parser.sh`). The 24-hour clean run is calendar.
- **Real-account exercise of cloud KMS providers.** Code lands here; the regulatory check is using a real Scaleway / Azure tenant against the interface.
- **Stichting Kern Foundation.** In formation. Incorporation is governance, not engineering.
- **6 months of zero breaking changes.** The point of `v0.9.x`: soak.

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.

- **Monomorphized:** `List<T>`, `Map<K,V>`, `Result<T>`, `Option<T>`, `Decimal`
- **Opt-in:** `@monomorphize` annotation, parsed and recorded as `__annotation_*` TraitInfo entries
- **Frozen for v0.7.0:** the ABI decision is locked in before async lands on top of it. See `docs/design/generics-strategy.md`.

## 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.

```
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 `spawn`ed `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.

```
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:

- **Scaleway KMS** — EU-native, regions `fr-par` and `nl-ams`
- **Azure Key Vault** — with AAD bearer-token caching, EU-only deployments
- **OVH** — stub interface awaiting real-account exercise
- **Local** — AES-256-GCM via mbedTLS, the default. Per-encryption nonces, master key from `KERN_KMS_MASTER_KEY`.

## `kern-pkg` — the Python shim is gone

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

- `init` / `build` / `run` / `test` — local lifecycle
- `add` / `remove` / `verify` / `install` — dependency management
- `publish` — pack, hash, sign with Ed25519, upload
- `search` — query the registry index
- `update` — re-resolve manifest deps into `kern.lock`

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.

```
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:

- Every record carries `prev_hash` \+ `record_hash`, where `record_hash = SHA-256(prev_hash || canonical_body)`
- On open, `kern_ai_audit_verify()` replays the file and refuses appends if any link mismatches
- The event-log verifier also recomputes each record’s hash from `seq | ts | type | data | prev_hash` and rejects tampered payloads even when the chain link is kept consistent

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:

- **LLM proxy** — 60 s socket timeout (`SO_RCVTIMEO` / `SO_SNDTIMEO`) on the shared HTTPS-with-headers path. A hung TLS handshake to a third-party LLM no longer holds a routine indefinitely.
- **Qdrant** — 30 s socket timeouts plus `kern_qdrant_health()` for readiness / liveness probes.
- **WebSocket** — stdlib `connect_with_backoff()` (200 ms base, doubling, capped at 30 s, ±25% jitter) and `send_resilient()`.
- **Event log** — `fcntl` exclusive lock per append plus tunable fsync interval. Multi-process safe, crash-safe.

## Compliance exports — SBOM and Art. 30 records

- **`std.compliance.cyclonedx_sbom()`** — CycloneDX 1.5 JSON SBOM listing all runtime dependencies. Compliance-scanner ready for the EU Cyber Resilience Act.
- **`std.compliance.render_art30_register()`** — emits a GDPR Art. 30 record-of-processing-activities document directly from runtime metadata.

## Compiler and runtime fixes worth naming

- **`println(int|float|bool)` segfault** — typed args were being passed to the `i8*` helper instead of the type-specific one. Now routed through `compile_print_call_direct`, like `print` always was.
- **Cross-unit struct projection from Ok-arm bindings** — `bind_variant_args` now sets `__kt__<bind>` on the Ok-payload bitcast, so `for x in lf.field` chains correctly across compilation units.
- **PersonalData taint leaks** — list elements, map values, string interpolation, and trait method dispatch all propagate `PersonalData` taint to the AI guard.
- **OCI digest verification** — every layer + config blob is verified before extraction; a tampered blob is detected and unlinked.
- **`test_perf_regex_match_1k`** — was failing the int→bool narrowing rule; comparison made explicit. **Conformance is now 706/706.**

## 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:

- **Parser fuzzer 24 h clean.** Harness shipped (commit `4b700d2`) — five mutators (bit flip, insert, delete, truncate, token splice), smoke run 521 iters / 0 crashes / 0 timeouts in 120 s. CI gate command for v1.0: `tools/fuzz_parser.sh 86400`.
- **HSM / PKCS#11 signing.** Adds a PKCS#11 binding for hardware-backed Ed25519 release signing.
- **Full DORA Art. 9/10/11 risk mapping.** Currently mapped at the operation level; the runtime risk register is the v0.9.x deliverable.
- **Real-account exercise of cloud KMS providers.** Code interfaces ship here; running them against real Scaleway / Azure tenants is the regulatory check.
- **External security audit.** Scheduled inside the `v0.9.x` window.
- **6 months of zero breaking changes.** Then v1.0.

Every item above is on the [ROADMAP](https://codeberg.org/PUAGroup/kern-lang/src/branch/main/ROADMAP.md) with a checkbox. None are aspirational; all are blockers.

## By the numbers

- **901 tests** at 100% pass — 706 conformance, 195 negative; both gates CI-blocking
- **16 of 20 v1.0 ROADMAP gates** closed or harness shipped
- **84 stdlib modules** across `std`, `data`, `net`, `db`, `sys`
- **25,499 lines** of Kern in the self-hosted compiler (10 source files)
- **31,000 lines** in the C runtime, \~300 FFI bridge functions
- **5 libFuzzer targets** — JSON, HTTP, string, BLAKE2b, base64 — plus the new structure-aware parser fuzzer
- **Reproducible builds** verified byte-for-byte identical with `SOURCE_DATE_EPOCH`
- **EUPL-1.2**, hosted on Codeberg, governed from the Netherlands

## Try v0.9.0-rc1

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

`curl -fsSL https://kern-lang.eu/install.sh | bash`

[View on Codeberg ](https://codeberg.org/PUAGroup/kern-lang)
