# On main: generics, cooperative async, Secure<T>, native OCI

> The preview that traced the path from v0.6 to v0.9.0-rc1.

Source: https://kern-lang.eu/blog/v07-v08-preview

May 6, 2026

# On main: generics, cooperative async, Secure\<T>, native OCI

What's landed on `main` between v0.6.0 and v0.7 / v0.8: selective monomorphization for stdlib generics, real `spawn`/`await` codegen on cooperative kern routines, the `Secure<T, State>` type-state with EU-sovereign cloud KMS, a native OCI container runtime, and the `kern-pkg` port to Kern. The released line is still v0.6.0 — and that is intentional.

## At a glance

Tests, 100% pass

892

Negative tests reject

190 / 190

Released line

v0.6.0

In flight on main

v0.7 · v0.8

## 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](https://codeberg.org/PUAGroup/kern-lang/src/branch/main/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 as `linkonce_odr` typed accessors so multiple compilation units share them without link conflicts
- **`@monomorphize`** annotation 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 `spawn` codegen** for direct `CallExpr`s. 0-arg and 1-arg fast paths; N-arg via a heap-bundled struct. `await` calls `kern_async_coro_await` and unboxes the i8\* result back to the typed return. Both `await 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 fn` awaits.** `await foo(...)` where `foo` is declared `async fn` automatically spawns onto the cooperative scheduler so multiple concurrent `await async_fn(...)` calls actually overlap.
- **Phase 4 — cooperative kern routines.** `kern_async_coro_spawn` runs each task on a stackful coroutine with assembly context switching (x86\_64 + ARM64) and a 16 KB guard-paged stack. `await` from 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_read` all 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 to `kern_async_spawn` (libuv worker pool) instead of the cooperative scheduler. Two parallel `spawn_thread`ed 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 a `Secure` value is just `T`; 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 into `db_*` or `http_*` 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_config`** reads the image config blob — env, cmd, workdir — for honest run semantics
- **`kern_container_run_native` on Linux** — namespaces, pivot\_root, the standard cgroup setup. Not Docker; not a wrapper around Docker
- **`container_stats_native`** reads cgroup v2 metrics for live container telemetry
- **`sys.container.oci_run_image`** wraps 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 Kern
- `add`, `remove`, `verify` — dependency management with a Lockfile, in Kern
- `install` — fetch, verify, extract dependencies, in Kern
- `publish` — pack, hash, sign with Ed25519, upload to the registry — the producer side
- `search` — query the registry index — the discovery side
- `update` — re-resolve manifest deps into `kern.lock` — the maintenance loop
- Manifest `[dependencies]` and `[dev-dependencies]` parsing, plus `toml_section_keys` and the rest of the TOML helpers needed to read `kern.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 `PersonalData` through 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. `verify` now recomputes the per-record hash, so silent data tampering inside an entry (not just at chain boundaries) is caught
- **HTTPS resilience** — `post_headers` now 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_backoff` and `send_resilient`: the WebSocket reconnect path that v0.6.0 didn't ship
- **`std.compliance`** — `gdpr_art30` record-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](https://codeberg.org/PUAGroup/kern-lang/src/branch/main/ROADMAP.md) 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 `spawn`ed 80 ms tasks (and the same for two `spawn_thread`ed 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.

[Read the changelog ](https://codeberg.org/PUAGroup/kern-lang/src/branch/main/CHANGELOG.md) [See the roadmap ](https://codeberg.org/PUAGroup/kern-lang/src/branch/main/ROADMAP.md)
