brintos

brintos / linux-shallow public Read only

0
0
Text · 90.2 KiB · 6a075cb Raw
2017 lines · plain
1================================================================================2Rootfs + first-fs-loaded execve model for the wasm32 port3================================================================================4 5This document is the spec-first opener for K6. K6's deliverable is the6**kernel-side filesystem and exec surface**: a real initramfs unpacked7into a real rootfs, a kernel that opens ``/init`` via real ``do_execve``8machinery, and a ``binfmt_wasm.load_wasm_binary`` that reads the WASM9bytes from the file (not from an embedded ``.rodata`` blob).10 11The payload binary K6 boots is **still K5's libwacky-built12``hello-k5.wasm``**, repositioned from embedded-in-vmlinux to lying on13the initramfs as ``/init``. The "first real userland" milestone — a14binary linked against real musl, rebuilt by the real wackywasm-tools15toolchain — is named K7 (the U-series) and proposed in §11 below.16 17This framing is **Option C from the K5-close scope decision**: K6 is18the **first-fs-loaded-execve** milestone, not the **first-real-userland**19milestone. The deliverable is the kernel-side surface; the payload20stays K5-shape on purpose. Single-axis discipline (see §10 below)21preserves the refutation-budget integrity the K-series has shown since22K1.23 24Status: K6 OPENER. Each of the seven enumerated framework sections25(§§3-9) has a "K6 enforcement" subsection telling K6 implementers26what must ship and what's allowed to land later. The sub-phase27implementation sequence is captured in §13.28 29--------------------------------------------------------------------------------301. Why this document exists separately31--------------------------------------------------------------------------------32 33``threading-model.rst`` answers "how do multiple Workers cooperate for34a multi-threaded user process"; ``memory-model.rst`` answers "where35does memory live"; ``sched-model.rst`` answers "how do tasks yield".36None of them answer **"where does the first user binary come from"**.37 38K5 sidestepped that question with an embedded ``hello-k5.wasm`` in39``.rodata`` and an ``arch_kernel_init_pre_run_init()`` override that40bypassed ``kernel_init``'s standard ``/init`` iteration. That worked41for K5's "first userspace via execve" deliverable but it is **not**42upstream-faithful: a real Linux kernel reads ``/init`` from a real43filesystem.44 45K6 closes that gap. The question "where does the first user binary46come from" has shape only when there's a backing filesystem; every47assumption K5 baked in about an embedded image, about48``arch_kernel_init_pre_run_init``, and about ``binfmt_wasm`` returning49``-ENOEXEC`` is now a load-bearing constraint that K6 must either50preserve or knowingly retire.51 52Reading order:53 54* If you are about to implement K6's initramfs bring-up: §3 (R155  initramfs), §6 (source-site enumeration of init/initramfs.c +56  fs/exec.c), §13 (K6-A1 sub-phase) are load-bearing on day one.57 58* If you are about to implement K6's ``binfmt_wasm.load_wasm_binary``59  real-read path: §4 (R2 + R3 + R7 — read, dispatch, post-execve60  handoff), §6 (source-site enumeration of fs/binfmt_elf.c as the61  shape-template), §13 (K6-B1+B2 sub-phases) are load-bearing.62 63* If you are about to wire the K6-C end-to-end demo: §5 (R564  ``/init`` payload), §13 (K6-C1+C2 sub-phases), §14 (acceptance65  checklist) are load-bearing.66 67* If you are about to design the K7 U-series: §11 (proposed K768  surface) and §10 (refutation-budget projection) are load-bearing.69 70* If you arrived here just to understand the §20.5 / §20.671  K5-shipping amendments folded into K6 (tsc-gate, bootKernel72  default-spawn-handler): §12 has the amendment block.73 74--------------------------------------------------------------------------------752. Scope decision — Option C (K5-close pin)76--------------------------------------------------------------------------------77 78The K5-close scope decision named K6 as "filesystem completion only"79with three explicit framings:80 81* Option A: K6 = filesystem only; wackywasm-tools + real musl as82  parallel U-series starting in parallel; K6 demo uses libwacky83  binary on real fs.84 85* Option B: K6 = filesystem + wackywasm-tools + real musl, bundled;86  K6 demo = real-musl binary on real fs. Largest K6.87 88* **Option C (chosen)**: K6 = filesystem only; **the milestone story89  is "first fs-loaded execve, not first real userland"**; libwacky-90  on-fs is the **deliberate** end state, named that way to make the91  deferral explicit. wackywasm-tools + real musl moves to K7 = the92  U-series.93 94Reasoning (pinned here in the spec so it can't drift later):95 96* **Single-axis discipline.** K1 through K5 each closed with one97  conceptual surface in motion (boot, mem, sched, threading-98  foundation). Option B bundles two surfaces (filesystem + toolchain99  + musl) into one phase, breaking the pattern and re-introducing100  the multi-axis refutation surface the K-numbering was designed101  to avoid.102 103* **Narrative honesty.** Option B's "first real userland" framing104  promises a payload milestone that the deliverable doesn't105  actually depend on. K6's value is the **infrastructure** (fs +106  exec + vfs + mm engagement), not whichever payload happens to107  ride on it. Option C names the deliverable for what it is.108 109* **Refutation-budget integrity.** K5 closed at 0/≤1 against the110  ARCHITECTURE.md §20.9 chained recurrence. Option C inherits that111  trajectory; Option B's bundle would force a budget expansion to112  absorb both fs-surprises and toolchain-surprises, eroding the113  compounding gain.114 115K7 then takes the U-series surface (§11 below), and K8+ opens the116next single-axis phase (whichever — threading completion, signals,117or real init system — is naturally next given K6+K7's end state).118 119--------------------------------------------------------------------------------1203. R1 — initramfs (kernel-side unpacking + rootfs population)121--------------------------------------------------------------------------------122 123K6 boots a kernel that **unpacks a cpio-format initramfs into a real124rootfs** during ``populate_rootfs()`` (upstream ``init/initramfs.c``),125gives the result to ``mount_root()``, and lets ``kernel_init``'s126standard ``/init`` iteration find a real file at ``/init``.127 128**Upstream contract (do NOT modify).**129 130Upstream's initramfs flow:131 132::133 134    start_kernel135        → rest_init136            → kernel_init (kthread)137                → kernel_init_freeable138                    → populate_rootfs (unpacks __initramfs_start..__initramfs_end)139                    → prepare_namespace OR (if rootfs has /init) skip140                → run_init_process iteration (/sbin/init, /etc/init, /bin/init, /bin/sh)141 142The kernel either embeds the initramfs blob in vmlinux (via the143upstream ``CONFIG_INITRAMFS_SOURCE`` linker-script hook between144``__initramfs_start`` and ``__initramfs_end``) or loads it from a145bootloader-provided memory region (``initrd_start`` /146``initrd_end``). For wasm32, the natural fit is **HardwareJS supplies147the initramfs bytes at boot** (analogous to how it supplies cmdline148and boot_info today) and the kernel parses ``initrd_start`` /149``initrd_end`` as a fixed memblock region.150 151**K6 enforcement.**152 153(R1.a) HardwareJS-side: a new ``initramfs`` field on154``BootKernelOptions`` carrying a ``Uint8Array`` of cpio bytes. When155present, HardwareJS writes the bytes into ``env.kernel_memory`` at156an aligned offset and updates the ``wasm_boot_info`` struct's157new ``initrd_offset`` / ``initrd_size`` fields. ABI bump from v1 → v2158(see §15 below for the ``wasm_boot_info`` struct evolution).159 160(R1.b) Kernel-side ``setup_arch`` reads ``boot_info.initrd_offset``,161calls ``initrd_start = phys_to_virt(initrd_offset)``-equivalent (on162wasm32 the C pointer IS the offset, identity-mapped per163``memory-model.rst`` §1), ``initrd_end = initrd_start +164initrd_size``, and lets ``init/initramfs.c::populate_rootfs``165discover the region via its standard166``unpack_to_rootfs(__initramfs_start, ...)`` and167``unpack_to_rootfs((char *)initrd_start, ...)`` calls.168 169(R1.c) The unpacked filesystem lives in a **ramfs/tmpfs** instance170(upstream does this automatically — ``init_rootfs`` and171``mnt_init`` set up ``rootfs`` as a tmpfs). No fs-driver work on172the wasm32 side is required for R1 itself; the upstream code173already drives ramfs/tmpfs through arch-agnostic paths. Verify174by booting with an empty (header-only) cpio and observing175``/init`` lookup fail with ``-ENOENT`` (not with a fs-driver176crash).177 178(R1.d) The vitest acceptance shape: K6's boot test supplies a179fixed ``initramfs`` Uint8Array containing exactly one file180(``/init``) whose content is ``hello-k5.wasm``. The kernel181unpacks, ``kernel_init`` finds ``/init``, dispatches to182``binfmt_wasm.load_wasm_binary``, the load succeeds, and the183user Worker prints ``hello from K5 userspace\n``.184 185--------------------------------------------------------------------------------1864. R2 + R3 + R7 — binfmt_wasm.load_wasm_binary real-read, do_execve flow, arch_post_execve187--------------------------------------------------------------------------------188 189K5-B3 landed ``binfmt_wasm.load_wasm_binary`` that **matched** WASM190binaries but always **returned -ENOEXEC** after the match — the191parser ran on ``bprm->buf`` (the first 256 bytes upstream pre-loads192for every binfmt walk) but the actual file read was deferred. K5-C2193dispatched via an embedded image through ``wasm32_exec_image``194instead. K6 retires both shortcuts.195 196**R2 — real kernel_read.**197 198**Landed at K6-B4.** ``binfmt_wasm.load_wasm_binary`` reads the199**full** file via ``kernel_read(bprm->file, buf, size, &pos)``200into a kmalloc'd buffer in env.kernel_memory; the kmalloc'd201pointer's value (= the env.kernel_memory offset on wasm32) becomes202the ``exec_image_offset`` of the spawn-ring FORK request.203 204(The K6 opener originally specified ``kernel_read_file``; K6-B4205surfaced that ``kernel_read_file``'s ``deny_write_access(file)``206fails with -ETXTBSY when the initramfs unpacker's deferred207``fput()`` hasn't been task-work-run yet. ``kernel_read`` is the208lower-level path that ``binfmt_elf`` itself uses via ``elf_read``,209and ``bprm->file`` already has FMODE_EXEC + deny-writes applied210via ``do_open_execat`` so the redundant guard is unnecessary at211this call site. See §16.10 "§20.5 source-name drift (1)".)212 213The K5 path through ``wasm32_exec_image(...)`` is retained as a214**shared helper** called by both the ``arch_kernel_init_pre_run_init``215fallback path AND the new ``binfmt_wasm`` real-read path. The216helper's signature stays: ``long wasm32_exec_image(const u8217*image, size_t size, const char *name)``. ``load_wasm_binary``218calls it with the kmalloc'd buffer; the existing K5 self-test219continues to call it with the embedded buffer.220 221**R3 — do_execve flow.**222 223``kernel_init``'s ``run_init_process("/init")`` flows through224``do_execve_common`` → ``open_exec`` → ``search_binary_handler``225→ ``binfmt_wasm.load_wasm_binary``. Per the §6 source-site226enumeration: ``do_execve_common`` does signal setup, fd-table227inheritance, env+argv passing, cwd, etc. — all the upstream228infrastructure K5-B3's design discussion called out as the reason229for choosing Shape A (binfmt_wasm sits INSIDE do_execve's binfmt230iteration). K6 finally exercises the full flow.231 232**R7 — arch_post_execve handoff.**233 234K5-C2's ``arch_kernel_init_pre_run_init`` parked ``kernel_init``235forever after the spawn dispatched. K6's path is different: the236spawn dispatches inside ``binfmt_wasm.load_wasm_binary`` (called237by ``do_execve``), then ``do_execve`` continues with its normal238post-binfmt cleanup, and **at the end of do_execve** the arch hook239detects the kthread-to-user-Worker transition and parks the240calling task.241 242The arch hook is named ``arch_post_execve`` (per K5-B3's design243discussion); K6-B2 lands it as a weak no-op default in ``fs/exec.c``244plus a strong override in ``arch/wasm32/kernel/init.c``. The245override's body:246 247::248 249    void arch_post_execve(struct linux_binprm *bprm)250    {251        if (bprm->binfmt == &wasm_format) {252            /* The execve transitioned this task to user-Worker253             * ownership. Park the kthread side; the user Worker254             * now owns this task's execution.255             *256             * K6 case 1: this is the kernel_init task transitioning257             * from kthread to first user process. Park forever258             * (same shape as K5-C2's park_kthread_forever).259             *260             * K6 case 2: a user process called execve. The task is261             * already user-side, NOT a kthread; we don't park, we262             * just let do_execve return cleanly. K6 has no such263             * caller (only kernel_init does execve at K6) but the264             * check keeps K7+ safe.265             */266            if (current->flags & PF_KTHREAD) {267                park_kthread_forever();268            }269        }270    }271 272**K6 enforcement (this section specifically).**273 274(R2) ``binfmt_wasm.load_wasm_binary`` reads the binary via275``kernel_read_file``; returns 0 on success (not ``-ENOEXEC``).276 277(R3) ``kernel_init``'s ``run_init_process("/init")`` succeeds when278the initramfs has a ``/init`` file containing a WASM binary.279 280(R7.a) ``arch_post_execve`` hook added to ``fs/exec.c`` (weak281default) via a new patch ``patches/0005-fs-add-arch_post_execve-282hook.patch``.283 284(R7.b) ``arch/wasm32/kernel/init.c`` adds the strong override.285 286(R7.c) The K5-C2 ``arch_kernel_init_pre_run_init`` override is287**retained** as a fallback — if the initramfs path fails (no288``/init`` found, or kernel_read_file fails), the embedded-image289dispatch still works. K6 close demands the fallback path stays290**operational** (verified by a fresh K6-C2 sub-test with empty291initramfs) so K5 regression coverage isn't lost.292 293--------------------------------------------------------------------------------2945. R5 — /init payload (libwacky-built hello-k5)295--------------------------------------------------------------------------------296 297K6's ``/init`` is the **same** ``wackywasm-tools/build/hello-k5.wasm``298that K5 embedded in vmlinux. The only thing that changes is where it299lives: at K5 it's a static const array in ``.rodata``; at K6 it's a300file inside the initramfs cpio that HardwareJS supplies at boot.301 302This is the **deliberate** K6 end state per §2 Option C. The K7303U-series replaces the libwacky binary with a real-musl binary,304without touching K6's fs/exec path — that's the cleanest possible305K6/K7 handoff and the reason for the scope split.306 307**K6 enforcement.**308 309(R5.a) A new ``linux-wasm/scripts/build-k6-initramfs.sh`` script310produces an ``initramfs.cpio`` containing one file (``/init``,311mode 0755) whose contents are ``wackywasm-tools/build/hello-k5.wasm``.312 313(R5.b) The K6 acceptance test loads the cpio bytes from disk and314passes them to ``bootKernel({ initramfs: ... })``.315 316(R5.c) The K5-C2 embedded image stays for the fallback path; K6317``Makefile`` keeps the ``builtin-init-image`` target intact.318 319--------------------------------------------------------------------------------3206. Source-site enumeration (§20.6 — applied INLINE at K6 opener time)321--------------------------------------------------------------------------------322 323Per ARCHITECTURE.md §20.6, every upstream code site K6 depends on324gets grepped at opener time so the K6 implementation phase doesn't325discover a "we didn't realize upstream did X" surprise.326 327The list below was generated by ``rg`` against the wasm32-built328Linux 6.12 tree as of K5-close. If any line changes in upstream3296.13+, K6 implementation MUST refresh this section (or surface a330refutation if the upstream change invalidates a K6 design choice).331 332**Initramfs unpacking (R1).**333 334* ``init/initramfs.c::populate_rootfs()`` — single entry point;335  iterates ``__initramfs_start..__initramfs_end`` (linker-script336  symbols) then the bootloader-supplied ``initrd_start..initrd_end``337  region.338 339* ``init/initramfs.c::unpack_to_rootfs(buf, len)`` — cpio parser;340  takes a flat buffer pointer + length, walks cpio records,341  creates each file via ``vfs_mkdir`` / ``vfs_create`` / etc.342  Returns NULL on success, an error string on failure.343 344* ``init/initramfs.c::write_buffer()`` / ``flush_buffer()`` —345  decompression path; only invoked if the first 4 bytes look like a346  compression magic. K6 ships uncompressed cpio for simplicity;347  K7+ may opt into gzip/xz.348 349* ``init/main.c::kernel_init_freeable()`` — calls350  ``populate_rootfs()`` then ``prepare_namespace()`` (skipped if351  rootfs is non-empty, which initramfs makes it).352 353* ``init/do_mounts.c::prepare_namespace()`` — skipped at K6 because354  populate_rootfs leaves a non-empty rootfs (initramfs unpack355  succeeded; ``/init`` exists). K7+ may want a real disk-backed356  fs; that work is K8+.357 358**execve flow (R2, R3, R7).**359 360* ``fs/exec.c::do_execve_common(filename, argv, envp)`` — the361  upstream execve entry. Builds ``struct linux_binprm``, opens362  the file, calls ``search_binary_handler(bprm)``, then does363  post-binfmt setup (``check_unsafe_exec``, ``free_bprm``).364 365* ``fs/exec.c::search_binary_handler(bprm)`` — iterates366  ``formats`` linked list (``binfmt_misc``, ``binfmt_script``,367  ``binfmt_elf``, ``binfmt_wasm``). For each, calls368  ``fmt->load_binary(bprm)``. Stops at the first non-ENOEXEC369  return; logs missing-handler / returns ``-ENOEXEC`` if all370  decline.371 372* ``fs/exec.c::open_exec(name)`` — opens the file from filename;373  populates ``bprm->file``.374 375* ``fs/exec.c::prepare_binprm(bprm)`` — pre-reads376  ``BINPRM_BUF_SIZE`` bytes of the file into ``bprm->buf``. This377  is what K5-B3's ``load_wasm_binary`` already uses for magic378  matching. K6 keeps that path AND additionally reads the full379  file via ``kernel_read_file`` for the payload.380 381* ``fs/exec.c::begin_new_exec(bprm)`` — replaces the calling task's382  mm/files/signal/cwd state per the new binary. **K6 question:383  does ``binfmt_wasm.load_binary`` call ``begin_new_exec``?** Per384  K5-B3 doc, the K5 implementation deferred this. K6 must call385  it — otherwise the kthread→user-Worker handoff in386  ``arch_post_execve`` runs against an inconsistent task state.387 388* ``fs/exec.c::kernel_read_file(file, ...)`` — reads an entire389  open file into a kmalloc'd buffer. Returns the size or a390  negative errno. **K6's binfmt_wasm uses this.**391 392* ``fs/exec.c`` ``arch_post_execve`` — **landed at K6-B2** via393  patch ``0005-fs-add-arch_post_execve-hook.patch``. Weak default394  no-op (same shape as patch 0004's395  arch_kernel_init_pre_run_init). Call site is the tail of396  ``bprm_execve()``'s success path, after the standard post-execve397  teardown. See ``rootfs-model-source-snapshots.txt`` for current398  line numbers (snapshot is regenerated whenever patches/0005399  context shifts).400 401**binfmt-format reference (R2).**402 403* ``fs/binfmt_elf.c::load_elf_binary(bprm)`` — upstream ELF404  loader. Shape K6's binfmt_wasm.load_wasm_binary mirrors:405  validate header → kernel_read_file for full payload → set up406  new mm → begin_new_exec → setup_new_exec → install_exec_creds407  → set up stack/auxv → arch-specific entry (jumping to408  ``e_entry`` for ELF; submitting spawn-ring request for WASM)409  → return 0.410 411* ``fs/binfmt_elf.c`` lines ~700-900 covering the read-file412  + setup_new_exec sequence — read at K6 opener time as the413  shape template; deviations from this shape are surface for414  K6 inline-enumeration probes.415 416**Output of `rg` for live confirmation (refresh at K6 implementation417time):**418 419::420 421    $ rg -n "kernel_read_file\b" linux-wasm/linux/fs/exec.c | head -5422    $ rg -n "begin_new_exec\b" linux-wasm/linux/fs/exec.c | head -5423    $ rg -n "unpack_to_rootfs\b" linux-wasm/linux/init/ | head -5424    $ rg -n "populate_rootfs\b" linux-wasm/linux/init/ | head -5425 426K6 sub-phase implementers MUST run those four commands and snapshot427the output into a follow-up doc (``rootfs-model-source-snapshots.txt``)428so the rest of K6 can refer back to specific line numbers without429re-grepping. Same shape as K5's source-snapshots.txt practice.430 431--------------------------------------------------------------------------------4327. Toolchain-semantic probes (§20.5 — applied INLINE at K6 opener time)433--------------------------------------------------------------------------------434 435K5 introduced T1/T2/T3 probes to pin SharedArrayBuffer cross-Worker436semantics, ``memory.atomic.wait32/notify`` substrate, and437exported-mutable-global setter visibility. K6 introduces two new438toolchain-semantic invariants that load-bearing for the fs path,439each pinned by a probe:440 441**T4 — cpio binary-layout invariance.**442 443The kernel's ``init/initramfs.c::unpack_to_rootfs`` parses a binary444cpio format defined by ``Documentation/early-userspace/buffer-445format.rst`` (upstream). Subtle: cpio has multiple variants (old,446new, crc, newc); Linux supports only **newc** (``070701``) and447**crc** (``070702``) in initramfs. Other variants are silently448rejected.449 450K6's ``scripts/build-k6-initramfs.sh`` must emit newc-format cpio.451The probe T4 constructs a synthetic newc cpio with one file in JS,452hands it to a small test that calls a kernel-side453``wasm32_test_unpack_cpio(buf, len)`` exported helper, and asserts454unpack succeeds. Failure shape:455 456* Probe fails → K6's build-k6-initramfs.sh is emitting a non-newc457  variant. Surface immediately; fix the script.458 459* Probe succeeds but real K6 boot fails ``/init`` lookup →460  populate_rootfs is dropping the file via a vfs/mount issue461  upstream of cpio. Distinct refutation surface.462 463T4 deliverable: ``hardwarejs/test/k6-cpio-probe.test.ts`` plus a464kernel-side ``wasm32_test_unpack_cpio`` export from465``arch/wasm32/kernel/initramfs_probe.c``.466 467**T5 — kernel_read_file readback shape.**468 469K5-B3 documented that on wasm32 the C pointer value IS the470env.kernel_memory offset (identity-mapped). K6's binfmt_wasm471relies on this for the kernel_read_file → spawn-ring handoff:472the kmalloc'd buffer's address gets passed to HardwareJS as a473``u32 exec_image_offset``.474 475The probe T5 verifies the round trip:476 4771. Kernel-side: allocate a buffer via ``kmalloc(N, GFP_KERNEL)``,478   write a sentinel pattern, expose its address as a u32 export.479 4802. HardwareJS-side: read the bytes at ``env.kernel_memory[offset]``481   and confirm the sentinel pattern matches.482 483This catches a class of toolchain regressions where kmalloc-484returned pointers stop matching env.kernel_memory offsets (e.g.,485if the kernel ever moves to a non-identity address-space layout486for kernel allocations). The K5 design didn't include this probe487because K5's exec_image was a static const array whose offset was488known at link time. K6 introduces dynamic kmalloc into the489exec_image path; the probe pins the invariant.490 491T5 deliverable: ``hardwarejs/test/k6-kmalloc-probe.test.ts`` plus492a kernel-side ``wasm32_test_kmalloc_export`` export from493``arch/wasm32/kernel/kmalloc_probe.c``.494 495**T-series numbering convention.**496 497T1-T3 are threading-model.rst probes; T4-T5 are rootfs-model.rst498probes. T-rows are per-document, not project-global. Future K-phase499openers add their own T-rows.500 501--------------------------------------------------------------------------------5028. Defer conditions (§20.7 — phase-dependency annotations)503--------------------------------------------------------------------------------504 505Items NOT in K6 scope, with explicit "OK to defer iff …"506verifications:507 508**(D1) wackywasm-tools + real musl arch port (K7 = U-series).**509 510K6 ships with the K5-built libwacky hello-k5 as ``/init``. Real511musl is K7. Verification: K6 ``/init`` is byte-identical to512K5's embedded image; the K6 acceptance test's stdout is513character-identical to the K5-C2 test's stdout ("hello from K5514userspace\n").515 516**(D2) pthread_create / futex / multi-threaded musl (K8+).**517 518K6 ``/init`` is single-threaded by construction (the libwacky519binary's ``pthread_create`` stub returns ``-ENOSYS``). Verification:520the FORK request submitted by K6's binfmt_wasm path always has521``mode = WASM_SPAWN_FORK``; no THREAD-mode requests appear in522the boot.523 524**(D3) signals (kill / SIGCHLD / SIGTERM etc.) (K8+).**525 526K6's user Worker exits cleanly via exit_group(0); no527signal-driven termination. Verification: the528``hardwarejs/src/kernelSpawnHandler.ts`` syscall router still529returns ``-ENOSYS`` for any signal-related syscall (kill,530sigaction, rt_sigprocmask, etc.); no K6 test path exercises any531of them.532 533**(D4) Real init system (systemd / OpenRC / busybox init) (K8+).**534 535K6's ``/init`` is hello-world-shaped; no fork(), no exec() of536secondary binaries. Verification: the FORK request count537observed by the spawn-ring consumer is exactly 1 per boot.538 539**(D5) Real disk-backed filesystem (ext4, fat, etc.) (K8+).**540 541K6 uses initramfs (ramfs) only; no block device driver, no542fs/ext4, no disk I/O. Verification: kernel boot does not543attempt any ``mount`` of a real block device beyond what544upstream's ``prepare_namespace`` skips.545 546**(D6) /proc + /sys (K7+).**547 548K6's ``/init`` does not read /proc or /sys; the initramfs has549no /proc or /sys mount points. Verification: K6 acceptance550test sees no syscalls to open(/proc/...) or open(/sys/...).551 552If any of D1-D6 turns out to be K6-required (test discovers553``/init`` needs it), that's a refutation: K6 underestimated its554surface novelty and the budget reasoning needs to extend.555 556--------------------------------------------------------------------------------5579. K6-required (R1-R7 summary)558--------------------------------------------------------------------------------559 560(R1) Initramfs unpacking — HardwareJS supplies cpio bytes;561``wasm_boot_info`` v2 carries initrd_offset/size; arch/wasm32562populates ``initrd_start``/``initrd_end`` for upstream563``init/initramfs.c::populate_rootfs`` to discover.564 565(R2) ``binfmt_wasm.load_wasm_binary`` reads the full file via566``kernel_read_file``; calls ``begin_new_exec`` and the rest of the567upstream binfmt setup; submits FORK spawn request from the568kmalloc'd buffer; returns 0.569 570(R3) ``kernel_init`` reaches ``run_init_process("/init")``571successfully; ``/init`` is opened and the binfmt walk dispatches572to ``binfmt_wasm``.573 574(R4) ``arch_kernel_init_pre_run_init`` (K5-C2) is retained as a575fallback path. K6 boot with empty initramfs falls through to576the embedded image and still succeeds (the demo's "robust577boot" property).578 579(R5) ``/init`` payload is libwacky-built hello-k5 (per §2 Option580C scope choice). Bit-identical to K5's embedded image.581 582(R6) Spawn handler is single-callsite; no THREAD-mode requests583(per D2).584 585(R7) ``arch_post_execve`` hook in ``fs/exec.c`` (weak default)586with strong override in ``arch/wasm32/kernel/init.c``. The587override parks kernel_init's task when a ``binfmt_wasm`` execve588completes from kthread context.589 590Each (R1)-(R7) gets at least one sub-test in591``hardwarejs/test/k6-*`` per §20.6.592 593--------------------------------------------------------------------------------59410. Refutation budget (per ARCHITECTURE.md §20.9)595--------------------------------------------------------------------------------596 597**K6 declared budget: ≤ 2 refutations during implementation.**598 599Reasoning per ARCHITECTURE.md §20.9.1 three-input formula:600 601* **Prior-phase actual count.** K5 closed at 0 refutations602  against ≤1 budget. Trajectory is favorable; the K5 §9/§10/§11603  discipline-compounding adjustment is empirically vindicated.604 605* **Discipline-compounding adjustment.** K6 inherits K5's606  full §20 discipline kit (§9 source-site enumeration; §10607  toolchain-semantic probes; §11 defer conditions; §20.5/§20.6608  amendment shapes). All applied inline above. K6 contributes609  no new discipline shapes; only new applications.610 611* **Surface-novelty adjustment.** +1 to +2. K6 brings up:612 613  * Three new upstream subsystems engaged614    (``fs/exec.c``, ``init/initramfs.c``, ``fs/binfmt_elf.c``615    as shape-template).616 617  * Two new arch hooks (``arch_post_execve`` patch 0005; cpio618    initramfs population in ``setup_arch``).619 620  * One ABI evolution (``wasm_boot_info`` v1 → v2 with621    initrd fields).622 623  * One new build-pipeline piece624    (``build-k6-initramfs.sh`` cpio emitter).625 626  These are honest new surface. The +1-to-+2 range is the627  estimated probability of a tactical bug or tool-semantic628  surprise that the §6 + §7 inline-enumeration didn't catch629  in advance.630 631Result: 0 − 0 + 1..2 = 1..2, rounded to **≤ 2**.632 633The budget can be spent in any §20.5/§20.6/§20.7/§20.8/§20.9 shape;634the count is post-classification (per K5's pattern, §20.5/§20.8635amendments don't count, only §20.9-shaped genuine contract636refutations do). The K6-close commit body records actual + budget637+ classification of each find, same as K5's close commit.638 639--------------------------------------------------------------------------------64011. K7 — proposed U-series surface (real musl arch port)641--------------------------------------------------------------------------------642 643.. note::644 645   **K7 opener (post-K6-close) AMENDED this section's surface.**646   K7 absorbs thread-mode bring-up (Regime 1b) into its scope as647   an explicit dual-axis K-phase. The amended K7 scope is the648   spec at ``Documentation/wasm/toolchain-model.rst``; the649   proposal below is preserved for historical comparison but is650   superseded.651 652   Specifically, K7-U4 (thread-mode bring-up) was NOT in this653   section's proposal — it was a "K8+" deferral. The K7-opener654   message at K6 close folded it in for the reasons documented in655   ``toolchain-model.rst §2``.656 657Per K5-close scope decision (§2), K7 is the U-series: bring up658real musl + wackywasm-tools so that future K-phase userspace659binaries link against a standards-compliant libc.660 661**Original (pre-K7-opener) proposed K7 surface — superseded by662toolchain-model.rst.**663 664* **K7-U1**: ``wackywasm-tools`` toolchain hardening. The665  ``wackywasm-tools/bin/wasmcc``, ``wasmld``, ``wasmar``,666  ``wasmrun`` wrappers were already present at K5 and used to667  build hello-k5 (with the libwacky stub). K7-U1 verifies the668  toolchain handles a real-musl build: full ``configure``,669  full ``musl/arch/wasm32`` port (replacing K5's libwacky stubs670  with real musl source), full musl static link.671 672* **K7-U2**: real musl 1.2.x ``arch/wasm32`` port.673  ``wackywasm-tools/musl-src/arch/wasm32/*.h`` evolves from the674  K5 hello-world stubs (``crt1.c``, ``runtime.c``,675  ``pthread_stub.c``) to real musl source-tree integration:676  ``syscall_arch.h``, ``pthread_arch.h``, plus the arch-specific677  ASM files (``wasm_thread.S`` already exists from K5-C1; K7678  adds the rest).679 680* **K7-U3**: rebuild ``hello-k5`` against real musl. The binary681  remains hello-world shaped; the only thing that changes is the682  libc behind ``puts()``. K7-U3 close: the rebuilt683  ``hello-k5.wasm`` boots through K6's fs path and prints684  "hello from K5 userspace\n" character-identically.685 686* **K7-U4**: stretch — first real-musl binary that does something687  K5/K6 couldn't (e.g., uses ``printf`` with format strings, or688  reads ``/etc/hostname`` if K8+ adds it). Optional; not gating689  K7 close.690 691**K7 deliverables (close criteria).**692 693(U1) wackywasm-tools/bin/* wrappers handle a real musl build694without modification.695 696(U2) ``wackywasm-tools/musl-src/`` contains a complete wasm32697arch port that ``musl-1.2.5/configure --target=wasm32`` accepts698and ``make`` builds.699 700(U3) ``hello-k5.wasm`` rebuilt against real musl is byte-701different from K5/K6's libwacky version (it goes through real702musl's printf/puts/write rather than libwacky's hand-rolled703ones) but **functionally identical** at the syscall level —704the FORK request, the write(1, ...) syscall, the exit_group(0)705all happen in the same order with the same args.706 707(U4) K6's acceptance test, re-run with the K7-rebuilt /init,708passes character-identically (stdout, exit code, dmesg709markers).710 711**K7 refutation budget projection.**712 713≤ 2 if K6 closes at 0-1. The U-series surface is bounded714(toolchain + libc port; no upstream-kernel work), so the budget715shouldn't need to grow beyond K6's. The "real" projection716happens in the K7 opener once K6 actuals are known.717 718**K8+ deferral pinned.**719 720After K7 closes with real-musl /init AND thread-mode bring-up721(per the K7 opener's Option 2 absorption), K8 opens the next722axis. ``toolchain-model.rst §12`` enumerates four K8 candidates723(signals, initcall-debt repayment, userspace expansion, real724init system); the agent's lean is (a) signals, the natural725threading-companion work. K8 scope is pinned in the K7-close726commit body.727 728The pre-K7-opener proposal here ("K8 = threading completion")729is partially superseded by K7-U4: K7 now ships pthread_create +730futex + multi-threaded musl + the BUG_ON removal. K8 picks up731where K7 leaves off (signal delivery into pthreads, the732remaining threading-companion work).733 734--------------------------------------------------------------------------------73512. K6 opener — folded-in §20 amendments736--------------------------------------------------------------------------------737 738Two K5-shipping finds surfaced during the prestrike-web demo739integration after K5-close. Both classified as §20.5 (tool-740semantic or footgun-shape; NOT §20.9 refutations of contract).741Folded into the K6 opener as design-time amendments so K6742implementation inherits them.743 744**§12.α — tsc --noEmit acceptance gate (§20.5).**745 746K5 vitest passed but ``tsup``'s DTS phase failed on two747strict-mode errors invisible to the transpile-only vitest:748 749* ``bootKernel.ts:219`` — ``as SharedArrayBuffer`` rejected750  because TS ≥5.4 distinguishes ``ArrayBuffer`` vs751  ``SharedArrayBuffer`` via ``Symbol.toStringTag``. Workaround:752  ``as unknown as SharedArrayBuffer``.753 754* ``spawnRingConsumer.ts:311`` — ``Atomics.waitAsync`` not in755  ``tsconfig.json``'s ``lib``. Workaround: add ``ES2024`` or756  ``ES2024.Atomics`` to ``lib``.757 758Both invisible to vitest because vitest transpiles, doesn't759type-check. The K5 acceptance gate ("``pnpm test`` clean") didn't760catch them.761 762**Fix at K6 opener time:**763 764* ``hardwarejs/package.json`` gains a ``verify`` script that runs765  ``tsc --noEmit && vitest run``.766 767* Top-level ``Makefile`` gains a ``hwjs-verify`` target invoking768  ``pnpm verify``.769 770* K6+ acceptance criterion adds: ``make hwjs-verify`` clean. The771  ``make hwjs-test`` shorthand (vitest only) stays for fast inner-772  loop iteration; ``hwjs-verify`` is the gate.773 774* Pre-existing K5 type errors fixed at opener time (test files775  needing ``Uint8Array<ArrayBuffer>`` annotations rather than776  unrestricted ``Uint8Array<ArrayBufferLike>``; the K3 mutator777  callback's TypeScript control-flow narrowing worked around via778  a holder-object pattern). All commits to source pre-K6779  implementation work; the K6 opener commit body records the780  audit summary.781 782**§12.β — bootKernel default-spawn-handler footgun (§20.5).**783 784K5 ``bootKernel()``'s default ``spawnHandler`` (when caller785supplied none) returned ``-ENOSYS``. For K5+ kernels this means786**demo clients that don't supply a handler silently regress to787K4 behavior**: the kernel boots, posts the spawn-ring-offset,788submits the FORK request for the embedded hello-k5, gets789``-ENOSYS``, and parks. Outwardly looks identical to the K4 "No790working init" panic.791 792prestrike-web's ``LaunchMachine.svelte`` hit this. Same793test-shape-catches-contract-drift pattern as the K5-C2794spawn-ring tail-owner bug (§20.6 amendment): K5 vitest795exercised with a test-constructed handler; production default796diverged.797 798**Architectural decision (K6 opener):**799 800The K6 ``bootKernel()`` shape implements a **three-mode801resolution matrix**:802 8031. ``spawnHandler`` supplied: that handler is used verbatim804   (K5 test shape).805 8062. ``spawnHandler`` undefined + new ``userWorkerEntry`` option807   supplied: bootKernel **default-constructs** the handler via808   ``makeKernelSpawnHandler({...})`` (K6+ demo-client shape).809 8103. ``spawnHandler`` undefined + ``userWorkerEntry`` undefined:811   warn-once stub returning ``-ENOSYS``. The ``console.warn`` on812   first request makes the regression visible without breaking813   pre-K5 callers (K4-compat shape).814 815The decision pins to "default-construct when userWorkerEntry is816supplied, warn-once stub otherwise". The intermediate "always817default-construct" was rejected because it requires818``userWorkerEntry`` to default to something, and no sensible819default exists across node/browser/Web Worker/Node Worker.820 821Implementation lands at K6 opener time alongside the doc (file:822``hardwarejs/src/bootKernel.ts``; test:823``hardwarejs/test/k6-bootkernel-spawn-defaults.test.ts``).824 825**§12.γ — DBG cleanup audit (clean — nothing to remove).**826 827Audit performed at K6 opener time for K4-era ``DBG`` pr_info828lines (``DBG fork``, ``DBG enq``, ``DBG cmpl``, ``DBG sto``,829``DBG rewind``, ``DBG yield``). Result: zero matches in source,830zero matches in current boot transcripts. Either an earlier831cleanup-agent succeeded or the lines never made it into a832committed snapshot. The K6 opener inherits a clean dmesg.833 834All remaining ``pr_info`` / ``pr_warn_once`` / ``WARN_ONCE`` in835arch/wasm32 are either load-bearing boot markers (vitest836asserts against several of them), one-shot informational837prints, or intentional debug infrastructure firing only on838unexpected control flow. None are removable noise. Audit839matrix at K5-close commit body.840 841--------------------------------------------------------------------------------84213. Sub-phase reading order (K6 implementation sequence)843--------------------------------------------------------------------------------844 845Implementers MUST read sub-phases in order. Each sub-phase's846work is constrained to its own surface; cross-sub-phase work847gets surfaced as a refutation, NOT silently bundled.848 849**K6-A — probes + scope-snapshot (opener day).**850 851(A1) ``tools/k6-rootfs-model-probe/`` containing T4 (cpio852unpack) and T5 (kmalloc → env.kernel_memory readback). Vitest:853``hardwarejs/test/k6-cpio-probe.test.ts``,854``hardwarejs/test/k6-kmalloc-probe.test.ts``.855 856(A2) Snapshot the §6 source-site enumeration into857``rootfs-model-source-snapshots.txt``. Driven by858``linux-wasm/tools/k6-rootfs-model-probe/snapshot-sources.sh``:859the ``rg`` queries are codified there (byte-deterministic860output, sorted, date-only timestamp). Output is committed.861Drift between this snapshot and the upstream tree at K6-B time862is either spec drift (refresh §6 + re-run) or genuine upstream863movement (note the diff, refresh both).864 865**K6-B — kernel-side fs path.**866 867(B1) ``wasm_boot_info`` v1→v2 with ``initrd_offset``,868``initrd_size`` fields. C ``static_assert`` pins layout; JS869mirror in ``hardwarejs/src/spawn-ring-abi.ts``-equivalent for870boot-info pins layout. ``hardwarejs/src/bootKernel.ts`` writes871the initramfs bytes into env.kernel_memory and fills the new872fields.873 874(B2) ``patches/0005-fs-add-arch_post_execve-hook.patch`` —875upstream ``fs/exec.c`` gains weak ``arch_post_execve()``;876patches/0004 retained for the fallback path.877 878(B3) ``arch/wasm32/kernel/setup.c`` reads879``boot_info.initrd_offset`` / ``initrd_size``, sets up880``initrd_start`` / ``initrd_end`` global symbols (their address881is what ``init/initramfs.c::populate_rootfs`` reads).882 883(B4) ``arch/wasm32/kernel/binfmt_wasm.c::load_wasm_binary``884becomes a **real** loader: validates magic + version,885``kernel_read_file`` for the full payload, ``begin_new_exec``,886``setup_new_exec``, ``install_exec_creds``, submits the spawn887request via ``wasm32_exec_image``, returns 0. K5's ``-ENOEXEC``888return after match becomes a code-path that fires only on889malformed-but-magic-matching binaries.890 891(B5) ``arch/wasm32/kernel/init.c::arch_post_execve`` strong892override (per §4 R7).893 894**K6-C — end-to-end demo.**895 896(C1) ``linux-wasm/scripts/build-k6-initramfs.sh`` — produces897``initramfs.cpio`` containing libwacky hello-k5 at ``/init``.898Single output file; idempotent.899 900(C2) ``hardwarejs/test/k6-fs-init.test.ts`` — boots vmlinux901with a supplied initramfs containing hello-k5 as /init,902verifies the boot dmesg reaches the new R1-R7 markers, asserts903the user Worker spawned via the fs path (NOT via the904arch_kernel_init_pre_run_init fallback), and confirms905"hello from K5 userspace\n" reaches onUserOutput.906 907(C3) ``hardwarejs/test/k6-fs-fallback.test.ts`` — boots908vmlinux with an empty initramfs (cpio header but no /init909file); asserts the kernel falls through to the K5-C2910arch_kernel_init_pre_run_init embedded-image path; asserts911"hello from K5 userspace\n" still surfaces (regression coverage912for the K5 path).913 914**K6-D — sub-test audit.**915 916(D1) Audit ``hardwarejs/test/k6-*`` against R1-R7. Map each917R-row to a sub-test by file:line citation. Anything unrecited918is a §20.6 discipline violation. (Same shape as K5-D.)919 920(D2) Refresh ``Appendix A`` checklist (§14 below) with file921paths + test names. Commit the populated checklist as part of922the K6-close commit body.923 924--------------------------------------------------------------------------------92514. Appendix A — Quick-reference checklist for K6 implementers926--------------------------------------------------------------------------------927 928Before declaring K6 close, verify each item below has a sub-test929in ``hardwarejs/test/`` and a citation in the K6-close commit body.930 931[x] §3 R1   HardwareJS bootKernel accepts ``initramfs: Uint8Array``;932            wasm_boot_info v2 carries initrd_offset/size; arch933            populates ``initrd_start``/``initrd_end`` (and934            ``arch_kernel_init_pre_run_init`` bridges the missing935            initcall dispatch by calling ``unpack_to_rootfs``936            directly — §15 row, see §16.6(c)).937            **Closed at K6-B1** via938            ``hardwarejs/test/k6-b1-bootinfo-v2.test.ts``939            (sub-tests A, B, C all pass).940 941[x] §6      Source-site snapshots committed as942            ``rootfs-model-source-snapshots.txt``.943            **Closed at K6-A2** via944            ``tools/k6-rootfs-model-probe/snapshot-sources.sh``.945 946[x] §7      ``tools/k6-rootfs-model-probe/`` T4/T5 all pass.947            **Closed at K6-A1** (T4, T5 in kernel-side probes)948            and **K6-B1** (T4' end-to-end via JS-supplied cpio).949 950[x] §4 R2   ``binfmt_wasm.load_wasm_binary`` reads via951            ``kernel_read``; returns 0 on success.952            **Closed at K6-B4** via953            ``hardwarejs/test/k6-b4-binfmt-wasm-loader.test.ts``954            (probe-boot: cpio ``/init`` = hello-k5.wasm →955            kernel_execve(/init) → binfmt_wasm load_complete +956            user-Worker "hello from K5 userspace"). Spec drift957            from ``kernel_read_file`` → ``kernel_read`` documented958            in §16.10.959 960[x] §4 R3   ``kernel_init`` reaches a do_execve(/init) successfully961            against an initramfs containing a WASM ``/init``.962            **Closed at K6-C2**. Caller is963            ``arch_kernel_init_pre_run_init`` rather than the964            upstream-canonical ``kernel_init →965            run_init_process(ramdisk_execute_command)`` because966            the wasm32 §15 "no initcall dispatch" row clobbers967            ramdisk_execute_command pre-arch-hook; see §16.13.968            Functional acceptance (binfmt_wasm engaged via969            real do_execve, spawn-ring delivered, hello-k5970            output reaches user-Worker stdout, arch_post_execve971            parks kthread) is met.972 973[x] §4 R7.a ``arch_post_execve`` weak hook in ``fs/exec.c``974            (patch 0005). **Closed at K6-B2** (weak default975            lands; ``hardwarejs/test/k6-b2-arch-post-execve.test.ts``976            asserts callable + no-op).977 978[x] §4 R7.b ``arch_post_execve`` strong override in979            ``arch/wasm32/kernel/init.c`` parks kthread context980            when ``current->mm->binfmt == &wasm_format``.981            **Closed at K6-B5** via the K6-B4 vitest's updated982            assertions (the parking dmesg ``wasm32:983            arch_post_execve parking kthread for pid=...``984            appears; the regression-marker ``K6-B4-PROBE:985            kernel_execve returned 0`` does NOT). K6-B2 sub-test986            A (no-op when binfmt is non-wasm) continues to pass.987 988[x] §4 R4   K5-C2 ``arch_kernel_init_pre_run_init`` fallback989            still operational; K6-C3 fallback test passes.990            **Closed at K6-C3** via991            ``hardwarejs/test/k6-c3-fs-fallback.test.ts``992            (empty TRAILER-only cpio → kern_path(/init)=-ENOENT993            → K5-C2 builtin-init dispatch → hello-k5 stdout).994 995[x] §5 R5   ``/init`` payload is libwacky hello-k5 (bit-identical996            to K5 embedded image). ``build-k6-initramfs.sh``997            ships. **Closed at K6-C1** via998            ``linux-wasm/scripts/build-k6-initramfs.sh`` +999            ``make -C linux-wasm k6-initramfs`` target. Output1000            is byte-deterministic (mtime=0, fixed inodes,1001            uid/gid=0): same input bytes → same SHA256, suitable1002            for snapshot-testing in K6-C2/C3.1003 1004[x] §8      D1-D6 deferral conditions verified by sub-tests.1005            **Closed at K6-D1**: §8's deferrals (no THREAD-mode1006            spawn requests, single binfmt walker callsite,1007            single FS_REQUIRES_DEV, no execve from a non-init1008            task, /init found via vfs rootfs only, no1009            wackywasm-tools userspace) are structural — every1010            K6 test exercises only kernel_init's task context,1011            only binfmt_wasm (no other binfmts registered), and1012            only K5's hello-k5 binary. The K6 C-side surface1013            does not contain THREAD-mode submission code or1014            multi-binfmt iteration. §8's R6 spawn-handler-1015            single-callsite is covered by1016            ``hardwarejs/test/k6-bootkernel-spawn-defaults.test.ts``.1017 1018[x] §11     K7 = U-series surface confirmed (or amended) in1019            K7 opener; this section's proposal is the input.1020            **Pinned at K6 opener**: K7 opens against §11's1021            U1..U4 sequence (wackywasm-tools toolchain, then1022            arch/wasm32 musl port, then hello-k5 rebuilt1023            against real musl, then optional stretch). Re-1024            confirmation lands at K7 opener-write time; K61025            close honors the K6-side commitment.1026 1027[x] §12.α   ``make hwjs-verify`` passes at K6 close.1028            **Closed at K6-D2** (this commit). 19 test files1029            pass, 58 tests pass, 2 skipped.1030 1031[x] §12.β   ``bootKernel`` three-mode handler-resolution matrix1032            verified at K6-opener-time via1033            ``hardwarejs/test/k6-bootkernel-spawn-defaults.test.ts``.1034            **Closed at K6 opener** (pre-implementation);1035            ``hwjs-verify`` includes it and it passes at K6 close.1036 1037[x] §12.γ   DBG-output audit clean (no source-side DBG strings;1038            no boot-output DBG lines). **Closed at K6 opener**1039            (DBG audit baked into the K6 opener commit per the1040            standing-note discipline; ``rg -i 'dbg' linux-wasm/1041            wasm32-port/`` shows no source-side DBG markers and1042            no test-side dmesg assertions on DBG strings).1043 1044Any unchecked item is a §20 discipline violation; landing K61045with one is a refutation owed to a future HISTORICAL NOTE in1046this document.1047 1048--------------------------------------------------------------------------------104915. wasm_boot_info v1 → v2 ABI evolution1050--------------------------------------------------------------------------------1051 1052K6-B1 adds initramfs delivery via two new fields. The struct1053gains v2 ``abi_version`` so v1 callers hitting a v2 kernel are1054immediately observable.1055 1056.. note::1057 1058   The K6 opener's original §15 listed a simplified1059   mental-model struct (7 fields, 56 bytes, magic1060   ``0xB007B007``) that was meant as exposition, not as the1061   actual upstream surface. The real struct shipped at K2 is1062   already 96 bytes with 15+ fields and magic ``0x31494257``1063   (ASCII ``WBI1``). The amendment in §16.6 below documents1064   this drift as a §20.5 source-name reconciliation; the rest1065   of §15 has been rewritten to mirror the real layout.1066 1067The real struct (single source of truth:1068``arch/wasm32/include/asm/boot.h``):1069 1070::1071 1072    /* wasm_boot_info v1 (K2..K5), 96 bytes total, __packed. */1073    struct wasm_boot_info {1074        __le32 magic;             /* 0x31494257 'WBI1' */1075        __le32 abi_version;       /* 1 */1076        __le32 struct_size;       /* 96 */1077        __le32 flags;             /* 0 */1078        __le64 total_memory;1079        __le64 usable_start;1080        __le64 usable_end;1081        __le64 kernel_image_start;1082        __le64 kernel_image_end;1083        __le32 cmdline_offset;1084        __le32 cmdline_length;1085        __le32 num_cpus;1086        __le32 boot_cpu_id;1087        __le64 host_timer_freq_hz;1088        __le32 reserved[4];       /* zero in v1; nonzero rejects */1089    };1090 1091    /* wasm_boot_info v2 (K6-B1+), 96 bytes total, __packed.1092     * Diff vs v1: abi_version 1→2, plus two u32s reclaimed1093     * from reserved at offsets 80..87. sizeof unchanged. */1094    struct wasm_boot_info {1095        /* ... bytes 0..79 identical to v1 ... */1096        __le32 initrd_offset;     /* NEW byte 80: env.kernel_memory1097                                   * offset of the cpio bytes, or 0 */1098        __le32 initrd_size;       /* NEW byte 84: byte length, or 0 */1099        __le32 reserved[2];       /* shrinks from u32[4] to u32[2] */1100    };1101 1102K6-B1 implementation choices:1103 1104* **Strict version match.** ``setup.c::wasm_validate_boot_info``1105  panics on any ``abi_version`` other than the1106  ``WASM_BOOT_INFO_ABI_VERSION`` the kernel was built against1107  (2 at K6-B1). The opener originally proposed v2 kernels1108  accepting v1 callers as a forward-compat path; we chose1109  strict match instead, because both sides bump in lockstep1110  and no current demo client needs the back-compat slack. A1111  §15 row to revisit if a real demo cannot be updated.1112 1113* **Disjointness checks.** When ``initrd_size > 0``, the1114  validator additionally panics if the initrd window overlaps1115  the kernel image, extends past ``total_memory``, or1116  overlaps the memblock window. See1117  ``Documentation/wasm/boot-protocol.rst`` §4 for the rejection1118  table.1119 1120* **C ``static_assert`` pins.** ``asm/boot.h`` asserts1121  ``sizeof(struct wasm_boot_info) == 96`` AND1122  ``offsetof(..., initrd_offset) == 80`` AND1123  ``offsetof(..., initrd_size) == 84`` AND1124  ``offsetof(..., reserved) == 88``. Any layout drift fails1125  the build.1126 1127* **TypeScript mirror.** ``hardwarejs/src/boot-info-abi.ts``1128  exports ``WASM_BOOT_INFO_ABI_VERSION``,1129  ``BOOT_INFO_V2_SIZE``, and each field offset as named1130  constants. ``hardwarejs/src/kernelWorker.mjs`` writes the1131  struct using the same offsets (duplicated with cross-1132  reference comments because ``.mjs`` Worker entries cannot1133  import ``.ts`` modules at runtime); the K6-B1 vitest1134  cross-checks both sides.1135 1136* **wasm32 initcall dispatch is broken (§15 row).** The1137  upstream ``rootfs_initcall(populate_rootfs)`` never runs on1138  wasm32 because ``__initcall*_start/end`` symbols are1139  0-imports. K6-B1's workaround is to call1140  ``unpack_to_rootfs`` directly from1141  ``arch_kernel_init_pre_run_init`` (the K5-C2 arch hook).1142  This is sufficient for K6 acceptance; a generic1143  initcall-dispatch shim is a future K-phase concern.1144 1145--------------------------------------------------------------------------------114616. HISTORICAL NOTES (refutations and amendments recorded here)1147--------------------------------------------------------------------------------1148 114916.1 K6-A1 amendment (§20.5 spec clarification)1150^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1151 1152§7's T4 was originally specified as "the probe constructs a1153synthetic newc cpio with one file in JS, hands it to a small test1154that calls a kernel-side ``wasm32_test_unpack_cpio(buf, len)``1155exported helper, and asserts unpack succeeds". The K6-A11156implementation compresses that to "kernel-embedded synthetic cpio1157+ kernel parses": the cpio bytes are generated by1158``tools/k6-rootfs-model-probe/build-k6-a1-cpio.sh`` (GNU cpio11592.13) into a committed C header1160``arch/wasm32/kernel/k6_a1_probe_cpio.h``, and the kernel calls1161``unpack_to_rootfs()`` directly on that buffer.1162 1163Rationale: the JS→kernel byte handoff requires the1164``wasm_boot_info`` v2 plumbing (§3 R1.a + §15) that itself is1165K6-B1 scope. Defining T4 to require v2 would either invert the1166K6-A1/K6-B1 dependency order or duplicate the v2 work at K6-A1.1167The kernel-side substrate question that T4 actually asks — "does1168the kernel's cpio parser correctly handle a valid newc archive" —1169is identical regardless of where the bytes originate. The1170kernel-embedded variant exercises the same parser code path the1171JS-supplied variant will exercise at K6-B1.1172 1173K6-B1 adds an end-to-end T4' (in the vitest test that also1174exercises wasm_boot_info v2) which builds the cpio in JS, writes1175it via the v2 ``initrd_offset/size`` channel, and verifies the1176kernel reaches the same K6-PROBE-T4-style dmesg shape. T4 and T4'1177together cover both the kernel-parser substrate (K6-A1) and the1178JS→kernel byte-transport substrate (K6-B1).1179 1180Classified as a §20.5 spec clarification (not a contract1181refutation): the property being verified is unchanged; only the1182mechanism is compressed.1183 118416.2 K6-A1 patches/0006 + 0007 (§20.5 toolchain-semantic amendments)1185^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1186 1187Two upstream-patch deltas needed to make1188``CONFIG_BLK_DEV_INITRD=y`` build under the wasm32 toolchain:1189 1190* **patches/0006-init-expose-unpack_to_rootfs-for-arch-probes**:1191  removes the ``static`` qualifier from1192  ``init/initramfs.c::unpack_to_rootfs()`` and adds a new header1193  ``include/linux/initramfs.h`` declaring the prototype. Required1194  for arch probe code to invoke the parser on a buffer that is1195  not at ``__initramfs_start`` or ``initrd_start``.1196 1197* **patches/0007-usr-initramfs_data.S-wasm-friendly-section-syntax**:1198  three wasm-clang-assembler adaptations to1199  ``usr/initramfs_data.S``: (a) ``,"a"`` → ``,"",@`` section flag1200  syntax matching what arch/wasm32's own .S files use; (b)1201  ``.incbin`` removed (wasm assembler does not support it; the1202  upstream-embedded ``__initramfs_*`` blob is left empty on1203  wasm32, with the real initramfs flowing through1204  ``initrd_start/end`` at K6-B); (c) explicit ``.size`` directives1205  added (wasm-clang requires every data symbol to declare its1206  size).1207 1208Both classified as §20.5 toolchain-semantic adaptations (not1209contract refutations): the upstream behaviour stays correct, only1210the assembler-syntax surface changed to accommodate a different1211target's accept set.1212 121316.3 K6-A1 close — refutation count vs budget1214^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1215 1216K6 declared budget: **≤ 2** (per §10).1217 1218K6-A1 actual at close: **0** refutations.1219 1220Two §20.5 amendments folded inline (16.1 spec clarification, 16.21221toolchain adaptations); per §20.9 classification rules these do1222not consume the refutation budget. Both were anticipated at the1223opener — the cpio shape choice was named "JS-built variant1224deferred to K6-B1" in §7 of the opener doc; the1225toolchain-assembler-syntax surface is the same class as the K51226``,"",@``-pattern already in use across arch/wasm32's .S files.1227 1228Trajectory holds favorable (≤ 1 refutation projected for K6-B1+).1229 123016.4 K6-A2 amendment (§20.5 source-name drift)1231^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1232 1233K6-A2's source-site snapshot revealed one upstream-name drift1234against §6:1235 1236* §6 names the execve entry as ``do_execve_common``; upstream1237  v6.12 spells it ``do_execveat_common`` (with the extra ``at``).1238  The opener doc was drafted from memory of older Linux versions1239  where the symbol was simpler; the snapshot pins the actual1240  upstream name as of K6's pinned tag.1241 1242Classified §20.5 (not refutation): the spec's *intent* — "the1243execve entry point that bprm_execve flows into" — is preserved;1244only the symbol name needed correction. The snapshot file1245(``rootfs-model-source-snapshots.txt``) carries an inline1246``[NOTE]`` capturing the drift; this section is the spec-side1247acknowledgement. The next time §6 is refreshed (e.g. an upstream1248tag bump) it should pick up the canonical name.1249 1250K6-A2 also formalised the §6 enumeration as an executable1251artifact rather than a four-rg-command checklist:1252 1253::1254 1255  $ bash linux-wasm/tools/k6-rootfs-model-probe/snapshot-sources.sh1256 1257regenerates ``Documentation/wasm/rootfs-model-source-snapshots.txt``1258byte-deterministically (sorted, date-only timestamp). The script1259is K6-B's source of truth for upstream surface — any K6-B patch1260hunk whose line numbers don't match the snapshot is either spec1261drift (re-run + commit) or stale (re-read the surrounding code).1262 126316.5 K6-A2 close — refutation count vs budget1264^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1265 1266K6 declared budget: **≤ 2** (per §10).1267 1268K6-A2 actual at close: **0** refutations (cumulative K6: **0**).1269 1270One §20.5 source-name drift folded inline (16.4); per §20.91271classification rules this does not consume the refutation budget.1272 1273Trajectory holds favorable (≤ 1 refutation projected for K6-B1+).1274 127516.6 K6-B1 amendments (§20.5 spec/struct drift + §15 row surfaced)1276^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1277 1278Three §20.5 amendments landed at K6-B1, all folded inline:1279 1280(a) **Opener struct mental-model vs. real struct.**1281    The K6 opener's §15 listed a 7-field, 56-byte struct with1282    magic ``0xB007B007`` as the v1 baseline. The actual v1 struct1283    (live in ``arch/wasm32/include/asm/boot.h`` since K2) has 15+1284    fields, is 96 bytes, and uses magic ``0x31494257`` ('WBI1').1285    The opener's struct was an exposition sketch, not a citation1286    of the real surface. §15 has been rewritten to reflect the1287    real layout; the rewrite cites the C header as the single1288    source of truth. Classified §20.5 (spec mental-model1289    correction, not contract refutation).1290 1291(b) **v2 forward-compat path deferred.**1292    The opener said v2 kernels would accept v1 callers (zero-init1293    initrd_start/end and fall through to the K5-C2 fallback).1294    K6-B1 instead implements strict ``abi_version == 2``1295    match-or-panic, matching the K5 strict pattern. Both sides1296    are bumped in lockstep so there is no current caller that1297    needs the v1-compat slack. The choice is reversible (a future1298    K-phase could add an explicit ``if (version == 1) goto1299    no_initrd`` branch); we noted the divergence here so the1300    opener's intent is on the record. Classified §20.5 (scope1301    contraction documented at close, not at open).1302 1303(c) **wasm32 initcall dispatch is broken — §15 row, K6-B11304    workaround.**1305    During K6-B1 implementation, T4' surfaced that the upstream1306    ``rootfs_initcall(populate_rootfs)`` never runs on wasm32:1307    ``__initcall*_start/_end`` are 0-imports (see1308    ``arch/wasm32/kernel/sections.c``), so ``initcall_levels[]``1309    is all-NULL and ``do_initcalls`` is an empty loop. K6-B1's1310    workaround is to call ``unpack_to_rootfs`` directly from1311    ``arch_kernel_init_pre_run_init`` — a single arch-side site1312    that bridges populate_rootfs without invoking the missing1313    dispatch infrastructure. This is sufficient for K61314    acceptance; a generic initcall-dispatch shim is filed as a1315    future §15 row to be picked up when subsystem initcalls1316    beyond populate_rootfs become load-bearing. Classified §20.51317    (newly-discovered §15 row, not contract refutation): the1318    wasm32 port's "initcalls don't run" reality has been1319    consistent since K2; K6-B1 is the first sub-phase to need a1320    specific initcall to fire, so it surfaces now.1321 1322K6-B1 patches:1323 1324* **No new upstream patch files added.** The K6-B1 changes are1325  arch-side only (``arch/wasm32/include/asm/boot.h``,1326  ``arch/wasm32/kernel/setup.c``, ``arch/wasm32/kernel/init.c``,1327  ``arch/wasm32/kernel/k6_b1_probes.c`` new). The1328  ``unpack_to_rootfs`` symbol is exposed via the existing1329  ``patches/0006-init-expose-unpack_to_rootfs-for-arch-probes``1330  landed at K6-A1.1331 1332* **TypeScript ABI mirror** (``hardwarejs/src/boot-info-abi.ts``,1333  new) exports the v2 constants and a ``writeBootInfoV2`` helper.1334 1335* **TypeScript test** (``hardwarejs/test/k6-b1-bootinfo-v2.test.ts``,1336  new) covers three sub-tests: no-initramfs regression (K5-C21337  continues to dispatch), with-initramfs T4' end-to-end (JS-built1338  cpio reaches rootfs and is found via ``kern_path``), and a1339  pure-TS sanity check on the mirror constants vs the C side.1340 1341* **K2 boot-info test updated**1342  (``hardwarejs/test/k2-bootinfo.test.ts``) to construct v21343  boot_info in its negative-path expectTrapWith helpers; the1344  ``reserved[2]`` test became ``reserved[1]`` because v2's1345  reserved is u32[2].1346 134716.7 K6-B1 close — refutation count vs budget1348^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1349 1350K6 declared budget: **≤ 2** (per §10).1351 1352K6-B1 actual at close: **0** refutations (cumulative K6: **0**).1353 1354Three §20.5 amendments folded inline at 16.6 (struct mental-1355model correction, v2 forward-compat deferral, wasm32-initcall1356§15 row); per §20.9 classification rules these do not consume1357the refutation budget. The wasm32-initcall §15 row is the first1358"newly-discovered upstream-surface row" in K6, and it lands as1359a localised K6-B1-scoped workaround rather than a generic fix —1360matching the deferral pattern §8 of this document anticipates.1361 1362Trajectory holds favorable (≤ 1 refutation projected for1363K6-B2..K6-D).1364 1365 136616.8 K6-B2 close — refutation count vs budget1367^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1368 1369K6 declared budget: **≤ 2** (per §10).1370 1371K6-B2 actual at close: **0** refutations (cumulative K6: **0**).1372 1373K6-B2 surface: one new patch1374(``patches/0005-fs-add-arch_post_execve-hook.patch``) adds a1375weak ``arch_post_execve(struct linux_binprm *bprm)`` to1376upstream ``fs/exec.c``'s ``bprm_execve`` success path, after1377the standard post-execve teardown (``sched_mm_cid_after_execve``,1378``in_execve`` clearance, ``rseq_execve``, ``user_events_execve``,1379``acct_update_integrals``, ``task_numa_free``). The weak default1380is a no-op; the strong override lands at K6-B51381(``arch/wasm32/kernel/init.c``).1382 1383Why ``bprm_execve`` (not ``do_execveat_common``): the hook must1384observe ``bprm->binfmt`` (so arch overrides can discriminate1385"this was binfmt_wasm vs. ELF / script / etc.") AND must observe1386the task in its fully-committed post-execve state. ``bprm_execve``1387owns both — ``exec_binprm()`` has just returned with1388``bprm->binfmt`` set, and the standard post-execve hooks have1389just finished. ``do_execveat_common`` would be too early1390(teardown not yet run); after ``do_execveat_common`` returns1391is too late (bprm has been freed). See patches/0005 header for1392the full rationale.1393 1394Why pass full ``struct linux_binprm *`` (not just1395``struct linux_binfmt *``): future arch overrides may want to1396inspect ``bprm->file``, ``bprm->cred``, etc. — passing the bprm1397keeps room without locking the ABI. The K6-B5 override only1398needs ``bprm->binfmt``, but K7+ overrides are not constrained.1399 1400The K6-B2 vitest is shaped to ALSO pass against the K6-B51401strong override (it passes ``struct linux_binprm dummy = { 0 }``1402so ``dummy.binfmt == &wasm_format`` evaluates false and the1403override's parking branch skips). The probe therefore stays1404valid as a regression check across K6-B2 → K6-B5.1405 1406No §20.5 amendments. No new §15 rows surfaced. The patch is1407the smallest possible touch of an upstream core file and1408applies via ``git apply`` cleanly.1409 1410Trajectory holds favorable (≤ 1 refutation projected for1411K6-B3..K6-D).1412 1413 141416.9 K6-B3 close — sub-phase scope fold (§20.5 amendment)1415^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1416 1417K6 declared budget: **≤ 2** (per §10).1418 1419K6-B3 actual at close: **0** refutations (cumulative K6: **0**).1420 1421**K6-B3's described surface landed at K6-B1.** Per §13:1422 1423    (B3) ``arch/wasm32/kernel/setup.c`` reads1424    ``boot_info.initrd_offset`` / ``initrd_size``, sets up1425    ``initrd_start`` / ``initrd_end`` global symbols (their1426    address is what ``init/initramfs.c::populate_rootfs``1427    reads).1428 1429Audited at K6-B3 opener time — every named element is present1430in ``arch/wasm32/kernel/setup.c`` since commit ``c690632``:1431 1432* Reads of ``bi->initrd_offset`` / ``bi->initrd_size`` —1433  ``wasm_validate_boot_info`` lines 115-116 and ``setup_arch``1434  lines 280-281 (Step 7d header at line 420).1435* Sets of ``initrd_start`` / ``initrd_end`` — lines 442-443.1436* Plus surface K6-B3's §13 description doesn't enumerate but1437  which is part of the complete substrate: four validation1438  rejections (one-zero-one-not, extends-past-total,1439  before-kernel-image-end, overlaps-memblock) at lines 192-209,1440  and ``initrd_below_start_ok = 1`` at line 458 (necessary1441  because the wasm32 initrd lives in the prologue below1442  ``min_low_pfn``; upstream's ``page_to_pfn(virt_to_page(1443  initrd_start)) >= min_low_pfn`` check would otherwise discard1444  the initrd with "initrd overwritten - disabling it.").1445 1446**Why folded into K6-B1 instead of landed separately.**1447 1448The two surfaces are inseparable in any acceptance shape:1449 14501. K6-B1 bumps the ABI from v1 to v2 by adding1451   ``initrd_offset`` / ``initrd_size`` to ``struct1452   wasm_boot_info``. v2 is asserted via ``static_assert`` on1453   the C side and v1-rejecting validation in the JS-side test1454   suite. Either both fields exist and have a consumer, or the1455   ABI is half-cooked (kernel accepts v2 but does nothing with1456   the new fields → undetectable spec drift, exactly what1457   §20.6 source-site enumeration is meant to prevent).1458 14592. K6-B1's T4' end-to-end vitest1460   (``hardwarejs/test/k6-b1-bootinfo-v2.test.ts`` sub-test B)1461   asserts the full chain HardwareJS → boot_info v2 →1462   setup_arch → ``initrd_start/end`` → unpacked-rootfs → marker1463   file resolvable via ``kern_path()``. That chain only closes1464   if ``setup.c`` actually reads the v2 fields and publishes1465   the globals — i.e. K6-B3's surface. Splitting K6-B3 into a1466   later commit would have left K6-B1 unable to make its T4'1467   claim.1468 1469The §13 sub-phase split therefore reflected the intended1470sequencing as an exposition (boot ABI first, then setup.c1471consumer), not as a commit boundary. K6-B1's commit ``c690632``1472body explicitly cites the ``setup.c`` changes — the trail is1473auditable.1474 1475**Sub-phase trail correctness.**1476 1477Per K6 opener's standing-note discipline (single-axis,1478commit-per-sub-phase even when surface was empty), K6-B31479closes here as a docs-only commit so the K-phase sequence stays1480auditable. The next sub-phase is K6-B4 (real1481``binfmt_wasm.load_wasm_binary`` loader) and proceeds1482unchanged.1483 1484No §15 rows surfaced. No refutations. Trajectory holds1485favorable (≤ 1 refutation projected for K6-B4..K6-D).1486 1487 148816.10 K6-B4 close — real binfmt_wasm loader (§15 row resolved + §20.5 source-name drift)1489^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1490 1491K6 declared budget: **≤ 2** (per §10).1492 1493K6-B4 actual at close: **1** refutation surfaced and resolved1494inline (cumulative K6: **1**, vs budget ≤ 2 — within budget).1495 1496**Surface landed.** ``arch/wasm32/kernel/binfmt_wasm.c::load_wasm_binary``1497becomes a real loader:1498 1499* Magic + version match → ``kmalloc(i_size, GFP_KERNEL)`` a1500  buffer in env.kernel_memory (identity-mapped per memory-1501  model.rst §1; vmalloc's VMALLOC_START range 0xd0000000+ is1502  NOT in env.kernel_memory and cannot be passed to1503  HardwareJS via the spawn-ring exec_image pointer).1504* ``kernel_read(file, buf, size, &pos)`` for the full payload.1505  (See "§20.5 source-name drift (1)" below for why1506  ``kernel_read``, not ``kernel_read_file``.)1507* ``begin_new_exec(bprm)`` — point of no return; transitions1508  the task to the new exec image and commits creds. The K61509  opener §13 mentioned ``install_exec_creds`` as a separate1510  step; that call was deprecated upstream and merged into1511  ``begin_new_exec`` for kernel ≥ 6.x. (See "§20.5 source-name1512  drift (2)" below.)1513* ``setup_new_exec(bprm)``.1514* ``set_binfmt(&wasm_format)`` so K6-B5's arch_post_execve1515  override can discriminate via ``current->mm->binfmt``.1516  (See "§20.5 source-name drift (3)" below.)1517* ``wasm32_exec_image(buf, size, filename)`` — submits the1518  spawn-ring FORK request (existing K5-C2 entry point).1519* ``finalize_exec(bprm)``.1520* ``kfree(buf)`` (spawn-ring is synchronous — HardwareJS has1521  consumed the bytes by the time host_spawn_process_worker1522  returns).1523* Return 0.1524 1525K5's match-but-return-ENOEXEC stub is removed; pre-PNR1526failures (bad i_size, kernel_read errors) now return real1527errnos like ``-EIO`` / ``-EINVAL`` so the binfmt walker does1528NOT fall through to ``binfmt_script`` on a magic-matched but1529malformed WASM file.1530 1531**§15 row surfaced and resolved.** First end-to-end run of1532the probe failed with ``kernel_execve`` returning ``-ENOMEM``1533silently. Root cause: ``arch/wasm32/mm/pgtable.c::pgd_alloc``1534was a K0-era stub returning ``NULL``, which made1535``mm_init → mm_alloc_pgd`` fail, which made1536``bprm_mm_init → mm_alloc`` return ``NULL``, which made1537``alloc_bprm`` propagate ``-ENOMEM``.1538 1539This is a single refutation in the §20.9 sense: the K6 opener1540§13 (B4) didn't enumerate the mm-allocation prerequisite at1541source-site granularity. Captured under §15 row "mm subsystem1542incomplete (pgd_alloc stub)" and resolved in the same commit1543by upgrading ``pgd_alloc`` to allocate a real1544``__get_free_page(GFP_KERNEL | __GFP_ZERO)`` placeholder. The1545wasm engine handles all real address translation against1546memory 0 (identity-mapped); the PGD page exists only for1547upstream's mm bookkeeping (mm->pgd pointer dereferences in1548free_pgtables, ptdump, etc.). Per-mm cost: one 4 KiB page,1549acceptable at K6 mm-churn levels.1550 1551A regression guard mm_alloc check sits at the top of the1552K6-B4 probe so any future regression of ``pgd_alloc`` is1553caught at the probe-entry boundary with a clear "mm_init/1554pgd_alloc regression" dmesg line rather than silent -ENOMEM1555downstream.1556 1557**§20.5 source-name drift (1) — ``kernel_read`` vs1558``kernel_read_file``.** The K6 opener §13 specified1559``kernel_read_file``; investigation found that1560``kernel_read_file`` does ``deny_write_access(file)`` which1561fails ``-ETXTBSY`` if the file has any pending writer or has1562not had its deferred ``fput()`` task-work run since the last1563write. The initramfs unpacker's ``fput()`` on the just-written1564``/init`` is deferred (task-work), so it has not run by the1565time the next probe-iteration's kernel_execve opens /init for1566exec. ``kernel_read`` is the lower-level path that binfmt_elf1567itself uses (see ``elf_read``); it does not deny_write_access.1568``bprm->file`` already has ``FMODE_EXEC + deny-writes`` applied1569via ``do_open_execat`` so the redundant guard adds no value1570at this call site.1571 1572**§20.5 source-name drift (2) — ``install_exec_creds``1573removed.** The K6 opener §13 (B4) named ``install_exec_creds``1574as a separate step after ``setup_new_exec``. That call was1575deprecated and removed upstream in 5.10 (commit1576``b8a61c9e7b4a``); creds are committed inside1577``begin_new_exec`` via ``commit_creds(bprm->cred)``. K6-B41578follows the canonical 6.x ``binfmt_elf`` shape:1579``begin_new_exec → setup_new_exec → set_binfmt → ... →1580finalize_exec → return 0``.1581 1582**§20.5 source-name drift (3) — discriminator field name.**1583The K6 opener referenced ``bprm->binfmt`` for the K6-B51584override's discriminator; ``struct linux_binprm`` has no such1585field. The matched binfmt is stored canonically in1586``current->mm->binfmt`` via ``set_binfmt()`` (see1587``fs/exec.c`` line 1455). ``load_wasm_binary`` now calls1588``set_binfmt(&wasm_format)`` after ``begin_new_exec`` so K6-B51589can discriminate via ``current->mm->binfmt`` (exposed as the1590file-local helper ``wasm32_current_is_wasm_binfmt()`` in1591``binfmt_wasm.c``).1592 1593**Additional surface needed beyond §13's enumeration.**1594 1595* ``arch/wasm32/kernel/process.c::flush_thread`` — added as1596  no-op stub (begin_new_exec calls it; wasm32 has no user-1597  mode arch state to flush across exec because user state1598  lives in the separate Worker, freshly spawned each execve).1599 1600**Probe shape.** ``arch/wasm32/kernel/k6_b4_probes.c``1601dispatched on cmdline ``k6_b4_probes=1``. Runs1602``mm_alloc`` sanity (regression guard for the §15 row) →1603``kern_path(/init)`` → ``kernel_execve("/init", argv, envp)``1604→ logs return value. JS-side vitest1605(``hardwarejs/test/k6-b4-binfmt-wasm-loader.test.ts``) builds1606a newc cpio containing ``/init`` = ``wackywasm-tools/build/1607hello-k5.wasm``, boots vmlinux with cmdline ``k6_b4_probes=1``,1608and asserts the full chain reaches "hello from K5 userspace"1609on the user-Worker stdout while the kernel-side dmesg shows1610the load_complete + kernel_execve_returned_0 markers.1611 1612**Trajectory.** Cumulative K6: 1 refutation surfaced and1613resolved (§15 row), all inline. Remaining sub-phases (K6-B5,1614K6-C, K6-D) project ≤ 1 additional refutation; total budget1615≤ 2 holds with 1-row margin.1616 1617 161816.11 K6-B5 close — arch_post_execve strong override + test-fold1619^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1620 1621K6 declared budget: **≤ 2** (per §10).1622 1623K6-B5 actual at close: **0** refutations (cumulative K6: **1**).1624 1625**Surface landed.** ``arch/wasm32/kernel/init.c`` defines1626``arch_post_execve(struct linux_binprm *bprm)`` as a strong1627override of the K6-B2 weak default. The body:1628 1629.. code-block:: c1630 1631    void arch_post_execve(struct linux_binprm *bprm)1632    {1633        (void)bprm;1634        if (!wasm32_current_is_wasm_binfmt())1635            return;1636        pr_info("wasm32: arch_post_execve parking kthread "1637                "for pid=%d ...\n", current->pid);1638        park_kthread_forever();1639    }1640 1641Discriminator is ``current->mm->binfmt == &wasm_format`` (via1642the file-local ``wasm32_current_is_wasm_binfmt`` helper added1643at K6-B4). When the just-completed execve matched binfmt_wasm,1644the kernel-side task is parked forever — schedule() in1645TASK_UNINTERRUPTIBLE never returns it. Same parking shape as1646K5-C2's post-spawn dispatcher; this hook moves the parking1647from the embedded-image entry point into the canonical1648post-execve site so the K6-C end-to-end path (real /init via1649kernel_init → run_init_process → bprm_execve → load_wasm_binary1650→ arch_post_execve) parks correctly without arch-specific1651bookkeeping outside fs/exec.c's normal flow.1652 1653For non-wasm execves (e.g. the K6-B2 probe's dummy bprm with1654binfmt=NULL), the discriminator returns false and1655arch_post_execve is a no-op — preserving the K6-B2 weak-default1656contract under the strong override.1657 1658**Test fold.** K6-B5 does NOT add a new vitest file. Its surface1659is covered by:1660 1661* ``hardwarejs/test/k6-b4-binfmt-wasm-loader.test.ts`` —1662  updated to assert the strong override's parking dmesg1663  ``wasm32: arch_post_execve parking kthread for pid=...``1664  appears, AND the regression-marker1665  ``K6-B4-PROBE: kernel_execve returned 0`` does NOT appear1666  (would mean arch_post_execve regressed to the weak default).1667* ``hardwarejs/test/k6-b2-arch-post-execve.test.ts`` sub-test A1668  — verifies the no-op branch for non-wasm bprm; passes1669  unchanged under K6-B5 because dummy.binfmt=NULL ⇒1670  current->mm->binfmt also NULL ⇒ discriminator false ⇒1671  arch_post_execve no-op.1672* K6-B2 sub-test B — K5-C2 builtin-init regression; passes1673  unchanged.1674 1675A dedicated ``k6-b5-arch-post-execve-strong.test.ts`` would1676duplicate the K6-B4 assertions verbatim and the K6-B2 sub-test1677A's discriminator coverage. Same shape as the K6-B3 sub-phase1678scope fold (§16.9), except K6-B5's C surface is non-empty:1679the strong override is genuinely new code; it's only the test1680that folds.1681 1682This is documented inline so the K-phase audit trail shows1683"K6-B5 had a separate C deliverable AND no separate vitest"1684without needing an external commit-body cross-reference.1685 1686**Trajectory.** Cumulative K6: 1 refutation total (K6-B4's §151687row), well within the ≤ 2 budget. K6-C / K6-D project 01688refutations each. K6 close projection unchanged.1689 1690 169116.12 K6-C1 close — build-k6-initramfs.sh ships1692^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1693 1694K6 declared budget: **≤ 2** (per §10).1695 1696K6-C1 actual at close: **0** refutations (cumulative K6: **1**).1697 1698**Surface landed.** ``linux-wasm/scripts/build-k6-initramfs.sh``1699emits a newc cpio archive containing one regular file1700``/init`` with content read from ``wackywasm-tools/build/1701hello-k5.wasm`` (or an empty TRAILER-only archive if the1702hello-k5.wasm is missing, for clean-checkout / K6-C3 fallback1703exercising). Implemented as a thin bash wrapper around an1704inline ``python3`` heredoc that does the newc framing (110-byte1705ASCII header per entry, NUL-padded to 4 bytes). Determinism is1706load-bearing:1707 1708* mtime = 0 (epoch) instead of ``time(NULL)``.1709* uid = gid = 0.1710* nlink = 1.1711* devmajor/minor and rdevmajor/minor = 0.1712* check field = 0 (newc specifies this).1713* Inode numbers assigned 1, 2, ... in entry order (deterministic1714  iteration).1715 1716Verified: two runs of the script against the same input bytes1717produce byte-identical output (matching SHA256). K6-C2 vitest1718treats this as a regression marker.1719 1720**Makefile target.** ``make -C linux-wasm k6-initramfs``1721invokes the script with ``BUILTIN_INIT_WASM`` (already used by1722the K5-C2 ``builtin-init-image`` target) and writes to1723``linux-wasm/build/k6-initramfs.cpio``. The K6-C2 vitest's1724``beforeAll`` also invokes the script directly so test runs1725always exercise the canonical script path (no stale .cpio1726from a previous test session).1727 1728**No refutations.** The opener's description ("byte-deterministic1729so vitest can snapshot-test it") landed as specified.1730 1731 173216.13 K6-C2 close — end-to-end fs-init via kernel_execve in arch hook1733^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1734 1735K6 declared budget: **≤ 2** (per §10).1736 1737K6-C2 actual at close: **1** refutation surfaced and resolved1738inline (cumulative K6: **2** — at the budget cap).1739 1740**Surface landed.** ``arch_kernel_init_pre_run_init`` in1741``arch/wasm32/kernel/init.c`` now:1742 17431. Unpacks the boot_info v2 initramfs (already from K6-B1).17442. Probe-boot dispatches (k6_a1, k6_b1, k6_b2, k6_b4) opt-in1745   on cmdline, park, never return.17463. **K6-C2 branch**: ``kern_path("/init", LOOKUP_FOLLOW, &p)``1747   to test whether /init exists in the rootfs:1748 1749   * Present → ``kernel_execve("/init", argv, envp)`` directly.1750     With K6-B4 + K6-B5 landed, this exercises the full1751     binfmt walker chain (alloc_bprm → prepare_binprm →1752     bprm_execve → search_binary_handler → load_wasm_binary →1753     wasm32_exec_image spawn-ring → arch_post_execve parks1754     current; kernel_execve never returns).1755   * Absent → fall through to K5-C2 builtin-image dispatch1756     (the K6-C3 fallback contract).1757 1758**Refutation #2 of ≤ 2 surfaced and resolved.** The K6 opener1759§13 (C2) described the fs path as flowing through "kernel_init1760→ run_init_process("/init") → bprm_execve → load_wasm_binary".1761That path requires upstream's ``kernel_init_freeable`` to1762NOT have already reset ``ramdisk_execute_command`` to NULL1763before ``arch_kernel_init_pre_run_init`` runs. On wasm32 the1764order is:1765 1766  1. ``kernel_init_freeable()`` runs first; it calls1767     ``init_eaccess("/init")``. Because wasm32 doesn't dispatch1768     initcalls (§15 row, see ``sections.c``), populate_rootfs1769     hasn't run yet → ``/init`` doesn't exist → init_eaccess1770     fails → ``ramdisk_execute_command = NULL``.1771  2. ``arch_kernel_init_pre_run_init()`` then runs and unpacks1772     the rootfs. Too late — kernel_init's downstream1773     ``if (ramdisk_execute_command)`` branch is dead.1774  3. kernel_init iterates ``/sbin/init``, ``/etc/init``,1775     ``/bin/init``, ``/bin/sh`` (none exist) and panics with1776     "No working init found".1777 1778The §15 "wasm32 doesn't dispatch initcalls" row therefore1779strikes twice in K6:1780 1781* K6-B1: ``populate_rootfs`` itself doesn't fire as a1782  rootfs_initcall (bridged by ``wasm32_populate_rootfs_initrd``1783  in arch_kernel_init_pre_run_init).1784* K6-C2: the upstream caller already gave up on1785  ``ramdisk_execute_command`` by the time we unpacked.1786 1787Both bridged by arch_kernel_init_pre_run_init taking the1788relevant action directly. K6-C2's bridge is to call1789``kernel_execve("/init", argv, envp)`` from the arch hook1790once /init is confirmed present in the rootfs. This still1791exercises the full do_execve chain (binfmt walker, real1792load_wasm_binary, arch_post_execve) — the only "missing"1793upstream surface is the **caller** of kernel_execve being1794``kernel_init`` itself vs ``arch_kernel_init_pre_run_init``.1795From the binfmt-walker's perspective the two are identical1796(both run in kernel_init's task context with non-PF_KTHREAD1797flags). The acceptance gate (§4 R3: kernel_init reaches1798run_init_process equivalent for /init) is met functionally1799even though the upstream-text-canonical "Run /init as init1800process" pr_info line does not appear.1801 1802A unified fix (real initcall dispatch on wasm32) would resolve1803both §15 rows and let kernel_init run /init via its normal1804``if (ramdisk_execute_command)`` branch. Tracked as a K8+1805item; K6 ships with the arch-hook bridge.1806 1807**Test.** ``hardwarejs/test/k6-c2-fs-init.test.ts`` boots1808vmlinux with the K6-C1-built cpio (rebuilt in ``beforeAll`` so1809the test always exercises the canonical script path) and NO1810probe cmdline. Asserts:1811 1812* ``wasm32: /init present in rootfs; invoking kernel_execve(/init)1813  directly`` — fs-init engaged via arch hook.1814* ``binfmt_wasm: loading /init (K6-B4 real loader)`` — real1815  loader fired.1816* ``binfmt_wasm: load complete for /init`` — loader returned 0.1817* ``wasm32: arch_post_execve parking kthread for pid=`` — K6-B51818  parking fired.1819* User-Worker stdout contains ``hello from K5 userspace``.1820* K5-C2 fallback markers (``dispatching to builtin init image``,1821  ``Kernel panic``, ``No working init``) do NOT appear.1822 1823**Trajectory.** Cumulative K6: 2 refutations (budget cap).1824Remaining sub-phases (K6-C3, K6-D) project 0 additional1825refutations — fallback test is structural and K6-D is docs-only.1826 1827 182816.14 K6-C3 close — empty-initramfs fallback to K5-C21829^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1830 1831K6 declared budget: **≤ 2** (per §10).1832 1833K6-C3 actual at close: **0** refutations (cumulative K6: **2**).1834 1835**Surface landed.** No new C surface. K6-C3 is a structural1836regression test that pins:1837 1838* ``wasm32_populate_rootfs_initrd`` correctly handles a1839  TRAILER-only newc cpio (parses, creates no files, reports1840  unpack ok). The cpio is built inline in the JS-side test1841  (``buildEmptyNewcCpio()``) — same format as1842  ``build-k6-initramfs.sh`` emits when hello-k5.wasm is1843  missing.1844* The K6-C2 ``kern_path("/init")`` branch's "absent" arm1845  correctly falls through to the K5-C2 builtin dispatch1846  instead of e.g. always taking the fs path and panicking.1847* K5-C2 builtin dispatch still works end-to-end (hello-k51848  stdout reaches the user-Worker output callback) after all1849  the K6-A..C surface accumulation.1850 1851**Test shape.** ``hardwarejs/test/k6-c3-fs-fallback.test.ts``1852asserts the positive dmesg path:1853 1854* ``wasm32: Unpacking initramfs from`` — empty cpio unpacked.1855* ``wasm32: initramfs unpack ok`` — no parse errors on TRAILER.1856* ``wasm32: /init not in rootfs (kern_path=-2)`` — fallback1857  message; -2 = -ENOENT confirms the kern_path failure mode.1858* ``wasm32: dispatching to builtin init image`` — K5-C2 path.1859* User-Worker stdout contains ``hello from K5 userspace``.1860 1861And negative assertions:1862 1863* K6-C2 fs-path message ``wasm32: /init present in rootfs``1864  does NOT appear.1865* No oops/BUG/panic.1866 1867§4 R4 closes.1868 1869**Why the TRAILER-only cpio is built inline rather than via1870build-k6-initramfs.sh.** Vitest worker sandboxes vary in PATH1871and python3 availability across hosts; embedding the1872buildEmptyNewcCpio helper in TS keeps the test self-contained.1873The format is documented in the K6-C1 script's heredoc, and1874the K6-C1 commit verifies the script produces the same shape1875when run directly. Future K-phases could extract a shared1876helper if duplication grows further (already 3 newc-cpio1877emitters across the JS side: K6-B1, K6-B4, K6-C3); K6-D1's1878audit will surface or defer that refactor.1879 1880**Trajectory.** Cumulative K6: 2 refutations (budget cap, no1881change from K6-C2). K6-D projects 0 additional refutations —1882audit and Appendix A refresh are docs-only.1883 1884 188516.15 K6-D1 audit — R1-R7 → test-file mapping1886^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1887 1888Per §13 K6-D1, every R-row from §9 has at least one sub-test1889under ``hardwarejs/test/k6-*``. Mapping:1890 1891============ ===================================================== ====================================================1892R-row        Test file                                              Sub-test / assertion1893============ ===================================================== ====================================================1894R1           ``k6-b1-bootinfo-v2.test.ts``                          Sub-test B (T4'): JS-built cpio → boot_info v2 →1895                                                                    setup_arch → unpack → ``kern_path("/k6-b1-test-marker")``1896                                                                    succeeds.1897R2           ``k6-b4-binfmt-wasm-loader.test.ts``                   "binfmt_wasm: read N bytes from /init"; "load1898                                                                    complete for /init".1899R3           ``k6-c2-fs-init.test.ts``                              "wasm32: /init present in rootfs; invoking1900                                                                    kernel_execve(/init) directly"; "binfmt_wasm: load1901                                                                    complete"; user-Worker stdout contains "hello from1902                                                                    K5 userspace". Caller is the arch hook rather than1903                                                                    upstream kernel_init due to the §15 row §16.13; the1904                                                                    do_execve flow itself is upstream-canonical.1905R4           ``k6-c3-fs-fallback.test.ts``                          Empty TRAILER-only cpio → kern_path(/init)=-ENOENT1906                                                                    → K5-C2 dispatch → "hello from K5 userspace" still1907                                                                    surfaces.1908R5           K6-C1 commit + ``k6-c2-fs-init.test.ts`` beforeAll     Script ships at ``linux-wasm/scripts/build-k6-initramfs.sh``;1909                                                                    K6-C2 test's beforeAll regenerates the cpio so the1910                                                                    deterministic-output property is exercised every1911                                                                    run.1912R6           ``k6-bootkernel-spawn-defaults.test.ts``               Three-mode handler-resolution matrix; pre-K61913                                                                    implementation regression-pin landed at K6 opener.1914                                                                    K6 acceptance retains this test in ``make hwjs-verify``.1915R7.a         ``k6-b2-arch-post-execve.test.ts`` sub-test A          Dummy bprm with binfmt=NULL → arch_post_execve1916                                                                    callable + no-op confirmed.1917R7.b         ``k6-b4-binfmt-wasm-loader.test.ts`` +                 K6-B4 asserts the parking dmesg "wasm32:1918             ``k6-c2-fs-init.test.ts``                              arch_post_execve parking kthread for pid="; K6-C21919                                                                    also asserts it through the natural fs-init path.1920============ ===================================================== ====================================================1921 1922Additional K6 substrate tests (not §9 R-rows but landed during1923implementation and exercised by ``hwjs-verify``):1924 1925* ``k6-a1-rootfs-probes.test.ts`` — T4 cpio-unpack + T51926  kmalloc-readback substrate probes (K6-A1 acceptance).1927* ``k6-b1-bootinfo-v2.test.ts`` sub-tests A, C — no-initramfs1928  regression and boot-info-abi-constants pin.1929* ``k6-b2-arch-post-execve.test.ts`` sub-test B — K5-C21930  builtin-init regression under patches/0005.1931 1932No R-row is unrecited. No §20.6 discipline violations.1933 1934 193516.16 K6 close — cumulative ledger1936^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1937 1938**Refutation budget vs actual.**1939 1940================ ============================== ===========================================1941Sub-phase        Refutations                    Notes1942================ ============================== ===========================================1943K6 opener        0                              §10 budget set to ≤ 2.1944K6-A1            0 (after §20.5 amendment)      Spec drift on T4 wording surfaced and1945                                                folded inline; not counted per §20.51946                                                discipline.1947K6-A2            0                              Source-site enumeration landed clean.1948K6-B1            0 (3 §20.5 amendments)         Spec/struct drift documented inline:1949                                                ABI-v2 mental model, forward-compat1950                                                deferral, initcall-bridge §15 row1951                                                surfaced.1952K6-B2            0                              Weak default landed clean.1953K6-B3            0                              Sub-phase scope-fold (§20.5 amendment);1954                                                substrate landed at K6-B1.1955K6-B4            **1**                          §15 row surfaced and resolved inline:1956                                                pgd_alloc was a K0-era stub returning1957                                                NULL. Upgraded to real ``__get_free_page``.1958                                                Plus 3 §20.5 source-name drifts1959                                                (kernel_read vs kernel_read_file;1960                                                install_exec_creds removed;1961                                                current->mm->binfmt vs bprm->binfmt).1962K6-B5            0                              Strong override + test-fold (no new1963                                                vitest file; coverage via K6-B4 + K6-B2).1964K6-C1            0                              build-k6-initramfs.sh ships.1965K6-C2            **1**                          §15 row strikes again: wasm321966                                                no-initcall-dispatch row clobbers1967                                                ``ramdisk_execute_command`` pre-arch-hook;1968                                                bridged by calling kernel_execve from1969                                                arch_kernel_init_pre_run_init directly.1970K6-C3            0                              Empty-initramfs fallback structural test.1971K6-D1            0                              Audit clean.1972**Cumulative**   **2**                          At the ≤ 2 budget cap; within budget.1973================ ============================== ===========================================1974 1975**Calibration for K7.**1976 1977The K6 close at 2 refutations vs ≤ 2 budget is exactly at the1978cap. Two takeaways for K7 opener-budget setting:1979 19801. Both K6 refutations stemmed from the same §15 row (wasm321981   doesn't dispatch initcalls). A K8+ unified fix (real1982   initcall dispatch on wasm32) would resolve the underlying1983   row and pre-empt this class of refutation. K7 opener may1984   want to budget headroom for "underlying §15 rows surface1985   in new surface areas".1986 19872. The §20.5 source-name drift count was higher than usual1988   (K6 total: 5+ amendments across opener mental model,1989   B4 source-names, B1 ABI mental model). This is because K61990   touched a lot of relatively unfamiliar upstream surface1991   (binfmt walker, mm init, exec credentials, namespaces).1992   K7 = U-series rebuilds the same /init binary against real1993   musl on K6's path; the upstream surface K7 touches is1994   already well-explored at K6 close, so §20.5 drift should1995   be lower at K7. K7 budget proposal: **≤ 1** refutation,1996   per the §20.9 three-input formula (K6 actual 2, decreasing1997   surface novelty, single-axis discipline holding).1998 1999**Test suite at K6 close.**2000 200119 test files pass under ``make hwjs-verify``:2002 2003* K0..K5 regression suite (k0-* through k5-c2-builtin-init).2004* K6-A1, K6-B1, K6-B2, K6-B4, K6-C2, K6-C3.2005* K6-bootkernel-spawn-defaults (opener amendment).2006 200758 tests pass; 2 skipped (unrelated to K6 surface). All gates2008(R1-R7, §12.α/β/γ) closed.2009 2010**K6 acceptance achieved.** "First fs-loaded execve" per §22011Option C: an /init.wasm file in a HardwareJS-supplied2012initramfs runs successfully via the do_execve binfmt walker.2013The K7 U-series can now swap the libwacky-built hello-k5 for2014a real-musl rebuild on this exact path.2015 2016 2017