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:
- 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:
@monomorphizeannotation, 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 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.
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-parandnl-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 lifecycleadd/remove/verify/install— dependency managementpublish— pack, hash, sign with Ed25519, uploadsearch— query the registry indexupdate— re-resolve manifest deps intokern.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, whererecord_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_hashand 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) andsend_resilient(). - Event log —
fcntlexclusive 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 thei8*helper instead of the type-specific one. Now routed throughcompile_print_call_direct, likeprintalways was.- Cross-unit struct projection from Ok-arm bindings —
bind_variant_argsnow sets__kt__<bind>on the Ok-payload bitcast, sofor x in lf.fieldchains correctly across compilation units. - PersonalData taint leaks — list elements, map values, string interpolation, and trait method dispatch all propagate
PersonalDatataint 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.xwindow. - 6 months of zero breaking changes. Then v1.0.
Every item above is on the ROADMAP 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