Skip to the content.

Roadmap

Native milestones, numbered N0.. so they never collide with the port’s M numbers (LZ’s docs/ROADMAP.md, M0..M17; M0..M14 are also this repository’s history). Every milestone has a mechanical exit criterion: just ci green (which includes cargo xtask lz check, lz drift --strict and unsafe-report --check), just ci-full green, just diff-openbsd equal with the same expected list, cargo xtask lz drift --security empty, and the subsystem’s unsafe totals against the baseline of N0. The numbers in the criteria are filled at N0 from the tools (real data only, docs.md); until then they read <n>.

Order (recommended, the user’s call at the go): leaves first, where the tests are strongest and the risk lowest; the security subsystems last, with their own review rule. LZ sync is a rolling gate for every milestone, never a milestone of its own.

Since decision 44 (the user, 2026-10-18; docs/ZERO_UNSAFE.md section 10) only arm64 is built, booted and measured until N10 homologates amd64: the smokes, ci, ci-full, diff-openbsd and bench run on the architectures of EMIBSD_ARCHS (default arm64, the switch N4.0 lands); no amd64 code is written or fixed before N10, and a hal boundary type is still designed with the amd64 C in view (the planner’s “amd64 constraints”). The order after N8 is N9 (the close, on arm64, the amd64 tree frozen), N10 (amd64), N11 (verification) and N12 (performance).

Milestone Scope Exit criterion
N0 Bootstrap In EmiBSD/, in place, from lz-origin: the governance rewrite applied (CLAUDE.md, .claude/rules/, docs/PHASE2.md, this file, docs/IDIOMS.md, docs/SYNC.md, the README); lz/PINNED.md; reference/emibsd-lz/ cloned; lineage.toml generated 1:1 from ports.toml (every LZ .rs maps to itself, inherited, licences and wip notes carried over; the adapted status and the [[module.fn]] rows in the schema from the first commit); cargo xtask lz {check,status,drift,trace}, unsafe-report --check, unsafe-budget.toml at the current counts, just lz-sync; lz-sync.toml with [meta] only; ports.toml, the ports subcommands and docs/PORTING.md retired; the RCS ident lines removed (decision 20); the baseline in docs/STATUS.md, unsafe-budget.toml and the N0 section of docs/JOURNAL.md (unsafe totals, smoke count, diff-openbsd result, the inherited blockers; the benchmarks when just bench exists); the memory directory curated cargo xtask lz check passes; cargo xtask lz drift prints nothing; cargo xtask unsafe-report --check passes; just ci and just ci-full green; just diff-openbsd equal to LZ’s last run; the baseline numbers recorded in docs/JOURNAL.md
N1 Leaves sys/lib/libkern, sys/lib/libz, the primitives of sys/crypto/ (ciphers, hashes, MACs; not the framework): slice and &mut [u8] APIs, no raw pointers, unsafe to zero or a justified list, the C’s test vectors as table-driven tests, property tests for the parsers. It keeps 15 modules inherited: the crypto framework (crypto, cryptodev, criov; N8’s), the constant tables (DES podd, sk, spr; libz inffixed, zconf), libkern’s crate root and six helpers forbid as they stand unsafe total of lib/libkern, lib/libz and crypto at 15 (from 10, 2 and 5 at lz-origin: 10, 0 and 5; the 10 of libkern are StaticCell, API frozen by the timing rule, and the explicit_bzero volatile store; the 5 of crypto are in the framework, N8), each remaining unsafe with a soundness argument; host test count (just ci) 2605 -> 2695; every crypto smoke passes (smoke-wg, smoke-esp, smoke-ipcomp, smoke-softraid, smoke-https)
N2 Foundations (met 2026-10-16; replanned the same day, docs/ZERO_UNSAFE.md sections 0 and 0.9) The crates sys/hal/ (the 24 bsd-free machine headers and 18 constant headers of sys/sys/, moved in inherited (types.rs follows uvm_param’s page arithmetic at N3); the rest of the hardware enters hal with the kernel owner of each boundary, decisions 26-28; the new runtime: Mutexed<T>, Rwlocked<T>, TypedPool<T>, PoolArc<T>, MBox<T>, KmBuf, OnceStatic<T>, List/Tree written new with their first kernel user (the lists and trees of sys/sys/queue.rs, sys/sys/tree.rs, sys/kern/subr_tree.rs stay in bsd), UserPtr<T> with abi_pod!, the bridge registry), sys/kernel/ (#![forbid(unsafe_code)]; crypto from sys/crypto/; pipe(2) from sys/kern/sys_pipe.rs, sys/sys/pipe.rs, the first subsystem by the new path) and sys/abi/, built as the loadable module abi_openbsd.ko (#![forbid(unsafe_code)]; the syscall table, all entries Legacy but pipe; the constants through hal; loaded by hal’s ELF loader at boot, registered as the syscall handler, given KernelApi, decision 22); bsd links kernel and hal; cargo xtask hal-check, abi-check, unsafe-report v2, /progress v2, bridges.toml kernel and abi build with forbid at the root on both archs; cargo xtask hal-check and abi-check pass in just check; hal ≤ 2,150 unsafe, every site with // SAFETY:; abi_openbsd.ko loaded at boot on both archs (smoke-abi); sys/kern/sys_pipe.rs and sys/sys/pipe.rs absent from bsd; the 24 + 18 headers under sys/hal/; just ci and just diff-openbsd equal (ci-full at N3’s close, decision 24). Met 2026-10-16: both forbid crates build on both archs (just build, just check rc=0); hal-check (74 files, 35 sites <= hal_ceiling 35, every block proved) and abi-check in just check; hal 35 <= 2,150; abi_openbsd.ko: 331 syscalls, 2 native on both archs (smoke-abi, smoke-efiboot; cd80.iso without it, decision 29); sys/kern/sys_pipe.rs and sys/sys/pipe.rs absent from bsd; 24 + 18 headers under sys/hal/; just jobs=3 ci rc=0 (85 of 85 smokes, 1734 s) and just diff-openbsd rc=0 (107 steps per arch, 104 equal, 3 expected, 0 unexpected); ci-full at N3 (decision 24)
N3 Memory (met 2026-10-18; re-planned at N3.4, docs/ZERO_UNSAFE.md 0.10) sys/uvm/ -> sys/kernel/vm/ in owner slices (km, object, anon, amap, aobj, the vnode pager, page, pmemrange, addr, map, mmap, glue; uvm_fault with the owners, before the map, since the owners could not flip before the fault held their witnesses: N3.4, security-review.md); the pmap behind a safe hal::Pmap API: no file of the pmap cluster of sys/arch was movable as it stood (all 14 name bsd), so N3.1 redesigned the two pmaps into hal (the LZ pmap files and pmap_module.rs left sys/arch; the two cpufunc.rs and vm_machdep.rs wait for N4; decision 26) Headers and machine contract it owns: sys/sys/types.rs, sys/sys/swap.rs, sys/machine/pmap.rs. sys/uvm/ absent from bsd; the pmap files absent from sys/arch/; sys/machine/pmap.rs an adapter over hal::pmap with no unsafe block (its legacy unsafe fn signatures leave with their last caller); cargo xtask unsafe-report shows the uvm row of bsd gone; hal has no pub unsafe fn (hal-check). Met 2026-10-18: sys/uvm/ absent (its 26 modules redesigned into the 22 of sys/kernel/vm/ and hal’s pieces); no pmap file in sys/arch/*/; sys/machine/pmap.rs without an unsafe block (three legacy unsafe fn signatures left); unsafe-report has no uvm row (215 at the N2 close); hal-check 111 files, 100 sites <= hal_ceiling 110, no pub unsafe fn; the swap device, the page daemon and the coredump stay visible stubs (unported: lines, expected.toml’s coredump entry); just jobs=3 ci at a043de71: 86 of 86 smokes in 18 min 32 s, rc=1 only at check-drift (the record of LZ 602141098d1d named the deleted sys/uvm/uvm_init.rs), fixed in 614d65af, the steps after it rc=0; just jobs=3 ci-full at 614d65af rc=0 in 2,248 s (its ci on four CPUs, 86 of 86 smokes in 21 min 18 s, no retry, then the eight installer recipes); just diff-openbsd rc=0 (107 steps per arch, 104 equal, 3 expected, 0 unexpected, as at N2); cargo xtask lz drift --security empty
N4 Processes and scheduling sys/kern/ by owner -> sys/kernel/: timeout, task, kqueue (kern_event.rs, sys/sys/event.rs), descriptors (sys/sys/file.rs, kern_descrip.rs), signals, proc, the scheduler, sysctl’s core, tty; StaticCell uses in kern -> OnceStatic; the cpu, clock, interrupt, trap, syscall-entry and copy files of sys/arch (97) into hal (decision 26; arm64’s: the amd64 half is N10’s, decision 44), after N4.0 lands first: the EMIBSD_ARCHS switch in xtask (smoke, smoke2, smoke-all, diff-openbsd, bench) and the justfile (build, clippy, ci-full), an amd64 boot logged SKIPPED (decision 44) and counted apart, never as a pass. Headers and machine contract it owns: sys/sys/proc.rs, sys/sys/sched.rs, sys/sys/sig* (signal, signalvar, siginfo, sigio), sys/sys/selinfo.rs, sys/sys/poll.rs, sys/sys/event*, sys/sys/fil* (file, filedesc, filio), sys/sys/fcntl.rs, sys/sys/resource*, sys/sys/ucred.rs, sys/sys/user.rs, sys/sys/task.rs, sys/sys/time* (time, timeout, timetc), sys/sys/_time.rs, sys/sys/clockintr.rs, sys/sys/evcount.rs, sys/sys/syscall* (syscall_mi, syscallargs), sys/sys/sysctl.rs, sys/sys/systm.rs, sys/sys/param.rs, sys/sys/syslimits.rs, sys/sys/acct.rs, sys/sys/msgbuf.rs, sys/sys/tprintf.rs, sys/sys/syslog.rs, sys/sys/tty*, sys/sys/termios.rs, sys/sys/ioccom.rs, sys/sys/ioctl.rs, sys/sys/uio.rs; sys/machine/cpu.rs, sys/machine/copy.rs, sys/machine/intr.rs, sys/machine/proc.rs, sys/machine/signal.rs, sys/machine/tcb.rs, sys/machine/atomic.rs, sys/machine/cons.rs (decision 26: cpu, clock, interrupts, trap, syscall entry, copy). sys/kern/ in bsd holds only vfs_* (N5), uipc_*/sys_socket.rs (N6) and the N8 files; the Proc, Fd and Kq bridges removed from bridges.toml
N5 VFS and file systems sys/kern/vfs_*, sys/ufs/, sys/isofs/, sys/msdosfs/, sys/nfs/, sys/ntfs/, sys/tmpfs/, sys/miscfs/ -> sys/kernel/fs/ Headers it owns: sys/sys/vnode.rs, sys/sys/mount.rs, sys/sys/namei.rs, sys/sys/buf.rs, sys/sys/dirent.rs, sys/sys/stat.rs, sys/sys/specdev.rs, sys/sys/lockf.rs, sys/sys/fusebuf.rs. those directories absent from bsd; the Vfs bridge removed
N6 Network stack sys/net/, sys/netinet/, sys/netinet6/, sys/kern/uipc_*, sys/kern/sys_socket.rs -> sys/kernel/net/, except the files of N8 Headers it owns: sys/sys/mbuf.rs, sys/sys/sock* (socket, socketvar, sockio), sys/sys/protosw.rs, sys/sys/domain.rs, sys/sys/un.rs, sys/sys/unpcb.rs. those directories absent from bsd but N8’s files; the Sockets bridge removed
N7 Devices sys/dev/ and sys/scsi/ -> sys/kernel/dev/, one bus directory at a time, over hal::Mmio<Regs> and hal::DmaBuf; sys/dev/rnd.rs last (security-review.md); the bus, autoconf and ACPI/FDT glue of sys/arch (74 files) into hal, its 19 drivers into sys/kernel/dev/ (decisions 26, 28; arm64’s: the amd64 half is N10’s, decision 44) Headers and machine contract it owns: sys/sys/device.rs, sys/sys/conf.rs, sys/sys/disk.rs, sys/sys/disklabel.rs, sys/sys/*io.rs (ataio, audioio, cdio, dkio, mtio, scsiio), sys/sys/gpio.rs, sys/sys/extent.rs, sys/sys/sensors.rs; the rest of sys/machine/ (bus, autoconf, acpi_machdep, fdt, mpconfig, pci_machdep, pci_chipset, pciide_machdep, isa_machdep, bootinfo, disklabel, x86: decision 26, bus, autoconf and the ACPI/FDT glue). sys/dev/ and sys/scsi/ absent from bsd but sys/dev/softraid_crypto.rs (N8)
N8 Security subsystems Under security-review.md, each with Security-Review: and the area’s smokes before every commit: sys/net/pf*, sys/net/if_pf*, sys/net/if_wg.rs, sys/net/wg_*, sys/netinet/ip_ipsp.rs, sys/netinet/ip_esp.rs, sys/netinet/ip_ah.rs, sys/netinet/ip_ipcomp.rs, sys/netinet/ipsec_*, sys/dev/softraid_crypto.rs, sys/kern/kern_pledge.rs, sys/kern/kern_unveil.rs, sys/kern/exec_*, the crypto framework -> sys/kernel/ Headers it owns: sys/sys/exec* (exec, exec_script). those files absent from bsd; cargo xtask lz drift --security empty
N9 Close What is left: sys/ddb/ with the ddb glue and the configuration tables of sys/arch/, sys/conf/, sys/reftest.rs, init/, the old runtime files (sys/kern/subr_pool.rs, sys/kern/kern_lock.rs, sys/kern/kern_rwlock.rs, sys/kern/kern_malloc.rs, sys/kern/kern_smr.rs, sys/kern/subr_percpu.rs, their sys/sys/ headers) deleted with their last user; sys/sys/ emptied into abi and kernel; the bsd crate deleted; the kernel binary is kernel + abi + hal; Miri on hal’s host tests; the amd64 tree frozen (decision 44): sys/arch/amd64/, sys/hal/arch/amd64/ and the amd64 implementations of sys/machine left as they are, N10’s row owning them, inherited, outside every crate, counted as frozen by /progress and unsafe-report (that tooling is this row’s). Headers and machine contract it owns: sys/sys/malloc.rs, sys/sys/pool.rs, sys/sys/lock.rs, sys/sys/mutex.rs, sys/sys/rwlock.rs, sys/sys/mplock.rs, sys/sys/percpu.rs, sys/sys/smr.rs, sys/sys/refcnt.rs, sys/sys/queue.rs, sys/sys/tree.rs, sys/kern/subr_tree.rs (the old runtime’s headers, and the lists and trees that leave with their last user); sys/machine/db_machdep.rs, sys/machine/conf.rs (the ddb glue and the configuration tables). sys/ holds hal/, kernel/, abi/, stand/, lib/ and the frozen amd64 tree, nothing else; cargo xtask unsafe-report: no bsd, kernel 0, abi 0, hal at its recorded floor, frozen with its count; bridges.toml empty; just ci-full and just diff-openbsd equal on arm64
N10 amd64 homologation (decision 44; after N9) The compile-and-fix pass of the frozen tree (sys/arch/amd64/, sys/hal/arch/amd64/, the amd64 implementations of sys/machine) against hal’s and kernel’s APIs at N9; the amd64 halves N4 and N7 deferred: cpu, clock, trap, syscall entry and copy into hal; bus, autoconf and the ACPI glue into hal, the amd64 drivers of sys/arch into sys/kernel/dev/; the module window of decision 37 revisited; the open amd64 performance of decision 42 (pagefault +49 %, the pmap side of the fault, fileread’s spread); the deferred-to-N10 LZ records applied EMIBSD_ARCHS=amd64,arm64 just ci rc=0 with the recipes’ amd64 expectations unchanged; just ci-full with the amd64 installer recipes (smoke-install-amd64, smoke-install-boot-amd64, smoke-install80-amd64) and cd80.iso; just diff-openbsd on amd64 with the same expected.toml, 0 unexpected; just bench amd64 against docs/bench/n3-perf-after-amd64.toml and n3-before-amd64.toml, every ratio at or under its value before N3 or a reason written per measurement; frozen 0 files; cargo xtask hal-check under hal_ceiling; no deferred-to-N10 record open
N11 Verification The measurement gaps deferred on 2026-10-17 after the comparison with DARPA TRACTOR: the semantic count of unsafe operations (raw-pointer dereferences, calls to unsafe fn, mutable statics, union fields) beside the syntactic one in cargo xtask unsafe-report; the six-dimension idiomaticity rubric (project structure, ownership and data flow, errors and unsafe, types, control flow, API and docs) as the reviewer’s scorecard, the score in the commit body; an organic and hidden battery, OpenBSD’s regress through diff-openbsd (widening the reference clone is the user’s decision); decision 8’s renames (RawRwlock, the plain names for the owning types); -accel hvf for the arm64 VMs tried once through xtask and recorded unsafe-report prints operations and keywords; every module redesigned from N11 on carries its six scores; the regress battery runs under diff-openbsd with its own expected.toml and 0 unexpected; the hvf result (works or not, and why) in docs/ARCHITECTURE.md
N12 Performance What decision 42 left open, on both archs: the QEMU profile of the fault and of fork (the pmaps in hal, the page system’s locks under TCG, the trap and the TLB shootdowns), the lookups beyond the C that the review listed for sys/kernel/vm/map.rs, zero_page/copy_page over typed pages; whether the smokes’ kernel moves to the release profile is the user’s question just bench on both archs: every ratio at or under its value before N3 (docs/bench/n3-before-<arch>.toml), or a written reason per measurement; Bench: on every commit (decision 43); no new unsafe, no check relaxed

Every exit also requires:

Since 2026-10-16 the goal is zero unsafe in the kernel: the crates sys/kernel/ and sys/abi/ forbid it at their roots, sys/hal/ holds the only unsafe, and bsd is emptied (docs/ZERO_UNSAFE.md section 0); a row’s scope resolves as /progress resolves it, the most specific path winning. A header of sys/sys/ belongs to the row whose C fills its fields (proc.h to N4, vnode.h to N5), so it is redesigned with its owner into kernel; the constant headers nobody fills move into hal, inherited, and kernel and abi name them through hal (decision 27: 18 at N2, types.rs at N3.1’s first commit, into sys/hal/sys/types.rs and sys/hal/vm/param.rs).

Rolling: LZ sync

After every LZ milestone close and every LZ pin bump, and before any N is marked met: cargo xtask lz drift --security, one lz-sync.toml record per LZ commit, lz-sync: commits, then lz: bump pin to <12-hex> with the user’s OK (.claude/rules/lz-sync.md). The ABI grows only this way (M16’s and M17’s devices, syscalls and sysctls arrive as applied records).

Adding or changing a milestone

State the subsystem it claims, the LZ modules it derives from (they turn redesigned in lineage.toml), and one mechanical exit criterion with numbers from the tools. Update docs/STATUS.md when a milestone starts and when it closes; docs/JOURNAL.md gains its section at the close.