A note on the version
Earlier drafts of this changelog labelled the recent feature wave v1.0.0
and v1.1.0. That was premature. v1.0 is an API stability promise we
are not yet ready to honour — generics are still type-erased, async codegen has
synchronous fallback paths, the package manager is still Python, and the foundation is
in formation rather than incorporated. Those entries have been consolidated into v0.6.0.
No code was reverted. Only the version label was corrected.
See the ROADMAP for the explicit v1.0 exit criteria. Pre-1.0 versions are bumped on real progress, not to look further along than we are.
Decimal — exact base-10 arithmetic
IEEE-754 floats are not acceptable for currency, taxation, or regulatory reporting. So
std.decimal now exposes a real Decimal struct on top of the
runtime's arbitrary-precision integers. Parse from a string, format with a locale,
round-trip through JSON, never lose a cent.
import std.decimal as dec
fn main():
subtotal = dec.parse("1234.56")?
vat_rate = dec.parse("0.21")?
vat = subtotal.mul(vat_rate).round(2, dec.HALF_EVEN)
total = subtotal.add(vat)
# Locale-aware formatting
print(total.format_currency("€", dec.DE_DE))
# => "€ 1.493,82"
# Lossless JSON round-trip via the safe-string form
j = total.to_json_string()
back = dec.from_json_string(j)?
assert(total.cmp(back) == 0)
Six rounding modes — HALF_EVEN (banker's rounding), HALF_UP,
HALF_DOWN, CEILING, FLOOR, DOWN,
UP — plus locale formatting for en_US, de_DE,
nl_NL, fr_FR, and currency formatting for €, $, £. The
legacy fixed-point cents helpers are kept for backwards compatibility.
This unblocks finance and government deployment.
std.compliance now requires Decimal rather than
float for monetary fields — auditors stop asking why your tax
calculation rounds differently on different machines.
LLM proxy and Qdrant — circuit breaker, retry, backoff
Calling someone else's network service has three failure modes that a one-shot HTTP
call ignores: transient blips you should retry, persistent outages you should not
hammer, and malformed responses that should be rejected before parsing. v0.6.0 wraps
both kern_llm_api_call and kern_qdrant_request in the same
resilience layer:
- Per-provider circuit breaker — opens after 5 consecutive failures, stays open for 30 seconds, then admits a single half-open probe before closing again
- Bounded retry — three attempts maximum, exponential backoff with jitter, no retry storms
- JSON sanity-check — response body is validated before parsing, malformed bodies are rejected with a clear error
- Thread-safe — circuit state is held in C11 atomics, no global lock contention
import std.ai.llm as llm
import std.ai.vector as vec
# Multi-provider — fall back across providers, but each call
# is independently circuit-broken, retried, and validated.
async fn answer(question: str) -> Result<str>:
store = vec.qdrant("docs")
hits = await store.search(question, top_k: 5)?
context = hits.map(|h| h.text).join("\n\n")
model = llm.mistral("mistral-large")
return await model.complete(
prompt: "Context:\n${context}\n\nQuestion: ${question}"
)?
Both calls above route through the same retry/backoff/circuit-breaker path. When Qdrant blips, your service waits 250 ms and tries again. When Mistral has a sustained outage, your service stops hammering it and reports the error in 3 seconds, not 30. EU AI Act audit logging is mandatory per call and runs after the resilient call succeeds — every successful inference lands in the audit log.
Event log — flock, fsync, chain re-verification
The BLAKE2b chained event log was the right primitive but the wrong durability story:
no flock meant two processes could interleave appends, no fsync
meant a crash could lose entries, and the chain was only verified by tooling, not by
the writer itself. v0.6.0 closes all three:
kern_eventlog_opencallskern_eventlog_verifyfirst — it refuses to open a tampered chainkern_eventlog_appendtakes an exclusiveflock(2)for the duration of the write- The log fsyncs every 16 appends by default; tune with
eventlog_set_fsync_intervalor force witheventlog_sync eventlog_closealways fsyncs
import std.audit as audit
fn on_user_export(user_id: int, requester: str):
log = audit.open("/var/log/kern/audit.jsonl")?
# open() refuses if the BLAKE2b chain is broken
log.append({
"event": "gdpr_export",
"user_id": user_id,
"requester": requester,
"article": "GDPR Art. 15",
})
# flock held for the duration of the write,
# fsync triggered every 16 appends
log.close() # always fsyncs
SSE production polish
Server-Sent Events that work on a developer laptop tend to fail on the way through a reverse proxy: idle connections get reaped, reconnecting clients can't tell the server what they last saw. v0.6.0 ships the two missing pieces:
sse_send_heartbeatemits a comment frame to keep idle connections alive across proxiessse_send_event_idwrites theid:line so reconnecting clients can resume viaLast-Event-ID
net.tls — passive audit against BSI TR-02102-2
The new net.tls module classifies every TLS connection against the German
Federal Office for Information Security's TR-02102-2 cipher-suite
recommendations. TLS version validation, cipher suite classification (forward-secrecy,
AEAD, BSI-approved), connection audit reports, and a strict policy mode that requires
TLS 1.3 + AEAD + FS.
For a Dutch or German public-sector deployment, this is the difference between "we use TLS" and "we use TLS in a configuration the regulator has explicitly endorsed."
std.compliance — explicit article-level mapping
Compliance is not "encryption + audit log + good intentions." It is a specific set of
articles in a specific set of regulations. std.compliance now maps every
runtime operation — collect, store, process, transfer, delete, access, encrypt — to
its specific article in GDPR Art. 5–44, DORA Art. 5/9/10/11,
and NIS2 Art. 21/23. With consent validation, data classification
(standard / sensitive / special-category), cross-border transfer checks (adequacy /
SCCs), retention recommendations, and audit entries that name the article they satisfy.
Auditor-readable, not lawyer-readable.
Other stdlib additions
- std.crypto attack-aware primitives — nonce reuse detection (
encrypt_tracked), nonce registry, key strength validation, constant-time secure compare, domain-separated hashing - std.graph — directed/undirected graphs with integer weights; Dijkstra, BFS, DFS, edge/node queries
- std.observe — OODA-based latency monitoring; response-time measurement, latency classification, route-level metrics, degradation detection
- std.ai.feedback — generate-test-improve loops with integer scoring (0–100), prompt refinement with iteration feedback, few-shot injection, convergence detection
Compiler hardening
- Parser recursion-depth limit —
parse_blocknow caps at 256 (matchingparse_expr); pathologically deepif/while/loopnesting returns a parser diagnostic instead of stack-overflowing the compiler - PersonalData → AI test coverage — added 10 conformance + 5 negative tests covering
pd_anonymize/pd_access/ aliasing / string concat taint /llm_chat/llm_api_call(prompt)/llm_api_call(system). The flagship privacy guard is now a load-bearing test set, not a single check. - Generics strategy committed — hybrid: monomorphization for stdlib containers,
Decimal,Result,Option; boxed RTTI for user generics by default;@monomorphizeattribute to opt in. Seedocs/design/generics-strategy.mdfor the rationale. - Async codegen — explicit TODO + design doc — the gap (
Await/Spawncurrently fall through to synchronous evaluation in some branches) is now documented indocs/design/async-codegen.mdwith the v1.0 plan
Why this is v0.6.0 and not v1.0.0
v1.0 is a contract: no breaking changes without a v2. That contract requires the known ABI-breaking decisions made before the promise, the flagship safety claims covered by enough tests that a regression is impossible to land silently, and production primitives hardened with retry, backoff, fsync, and reconnect — not one-shot best-effort. We aren't there yet.
The remaining gates:
- Generics are type-erased to
i8*. The hybrid strategy is committed; the implementation is not - Async codegen is not complete in every path; some branches still emit synchronous code
- WebSocket auto-reconnect with backoff and ping/pong keepalive is not yet shipped
- Qdrant connection pooling is not yet shipped (retry is)
kern-pkgis still Python, not Kern. HTTP GET and tar.gz both work in Kern's stdlib so the port is unblocked, but it is not done- Stichting Kern Foundation is in formation, not yet legally incorporated
- v0.9.x stability soak — six months of zero breaking changes before the v1.0 tag is cut
Every item above is on the ROADMAP with a checkbox. None are aspirational; all are blockers.
By the numbers
- 871 tests at 100% pass — 690 conformance, 181 negative
- 83 stdlib modules across
std,data,net,db,sys - 23,300 lines of Kern in the self-hosted compiler
- 27,400 lines in the C runtime, ~300 FFI bridge functions
- 15 LSP features via pygls
- 5 fuzz targets — JSON, HTTP, string, BLAKE2b, base64 — libFuzzer + ASAN/UBSAN
- Reproducible builds verified byte-for-byte identical with
SOURCE_DATE_EPOCH - EUPL-1.2, hosted on Codeberg, governed from the Netherlands
Try v0.6.0
Install on Linux or macOS. Single binary, no runtime dependencies.
curl -fsSL https://kern-lang.eu/install.sh | bash