brintos

brintos / linux-shallow public Read only

0
0
Text · 99.8 KiB · 4078e2b Raw
2267 lines · plain
1.. SPDX-License-Identifier: GPL-2.02 3================================================================================4Syscall path model — per-Worker vmlinux + real SMP (K11 opener)5================================================================================6 7:Status: K11 OPEN. Spec only; no code lands until this doc is closed.8 9This document is the K11 opener. It specifies the per-Worker vmlinux10instantiation contract, the in-Worker ``linux.syscall`` path, the11``wasm_syscall`` entry sequence (kernel-stack swap), the12``sys_call_table`` population strategy, the ``arch_cpu_up`` /13``arch_cpu_down`` hooks tied to spawn / exit, and the new14``num_vcpu`` / ``max_wasm_instances`` JS-side parameters that the15phase introduces.16 17The K11 axis is "the in-Worker syscall path comes online." K11 is the18phase that retires R-K8-1, R-K8-2, R-K9-1, R-K9-2, R-K9-3 (every19JS-direct shortcut row whose retirement signal was wrongly written as20"K10+ when cross-process syscall RPC lands" — per21``docs/ARCHITECTURE.md`` §4 there is no RPC, the kernel runs IN the22Worker; the retirement gate is now). It also inverts R-K10-W1 (the23"vmlinux is single-Worker" assertion that pinned the K5–K9 shortcut24state, not the architecture).25 26K11 lands BEFORE pty / line-discipline / job-control work (the27original K10-B/C/D scope, now relabelled K12). The investigation28that surfaced the architectural correction is recorded in §17 of29this document; it was a §20.6.1 dependency-check failure on prior30advice — the K10-A2b commit cited "vmlinux is single-Worker by31architecture" without verifying against32``docs/ARCHITECTURE.md`` §3 line 124 and ``memory-model.rst`` §1.1,33both of which specify per-Worker vmlinux instantiation.34 35Reading order (load-bearing, do not re-order):36 371. §2 architectural ground truth (citations to the spec sites)382. §3 the K5–K9 shortcut state being retired393. §4 per-Worker vmlinux instantiation contract404. §5 ``linux.syscall`` import resolution415. §6 ``wasm_syscall`` entry sequence426. §7 ``sys_call_table`` population strategy437. §8 ``arch_cpu_up`` / ``arch_cpu_down`` hooks448. §9 ``num_vcpu`` parameter459. §10 ``max_wasm_instances`` parameter4610. §11 R-K10-W1 inversion plan4711. §12 R-K8-1 / R-K8-2 / R-K9-1 / R-K9-2 / R-K9-3 retirement plan4812. §13 K11 sub-phase decomposition4913. §14 refutation budget5014. §15 acceptance criteria5115. §16 K10 close ledger amendment + scope correction proposal5216. §17 §20.6.1 retroactive dependency-check5317. Appendix A — K11 close checklist54 55--------------------------------------------------------------------------------561. Why this document exists separately57--------------------------------------------------------------------------------58 59The K11 axis is structurally different from K10's terminal axis. K1060landed the kernel-side enabling machinery (the initcall dispatcher);61K11 lands the user-process syscall path that lets user binaries62actually reach the kernel-side subsystems registered by initcalls.63K12 (= original K10-B/C/D) lands the pty / line-discipline /64job-control surface on top of the K11 syscall path.65 66The K11 work was originally folded into K10's umbrella under the67implicit assumption that pty / line-discipline / job-control could68ride the existing JS-direct syscall surface (the R-K9-1 pattern).69The K10-B1 architectural-fork investigation surfaced that70assumption as wrong against the architecture: the architecture71specifies an in-Worker ``wasm_syscall`` path through a per-Worker72vmlinux instance, not a JS-direct or RPC-based path. K10-B1's73"how do pty syscalls reach the kernel" question presumes a working74syscall path that does not yet exist; the path itself is what K1175lands.76 77A separate opener doc is warranted because:78 79* The phase's surface novelty is high (the in-Worker syscall path80  has zero production code today; ``sys_call_table`` is empty;81  ``wasm_syscall`` returns ``-ENOSYS``; ``arch_cpu_up`` / ``down``82  do not exist).83 84* The spec-vs-implementation drift surfaced by R-K10-W1 needs an85  explicit retroactive dependency-check (§17) — a §20.6.1 artefact86  that lives best in the doc that lands the correction.87 88* Two new JS-side parameters (``num_vcpu``, ``max_wasm_instances``)89  introduce user-visible knobs that need spec text before any90  implementation lands — the K-phase pattern landed at K7 / K8 / K9.91 92* The phase retires five existing §15 rows (R-K8-1 through R-K9-3)93  whose retirement signals were wrong-shaped; the close ledger94  needs to record both the correction and the actual close.95 96--------------------------------------------------------------------------------972. Architectural ground truth98--------------------------------------------------------------------------------99 100Three load-bearing facts from the spec, with citations:101 102**(F1) Per-Worker vmlinux instantiation.** Every Worker — every103process Worker AND the dedicated vmlinux Worker — holds its own104``WebAssembly.Instance`` of ``vmlinux.wasm``. They share what they105import (``env.kernel_memory``); they do not share their wasm106instance, ``__stack_pointer`` global, Asyncify save region, or107non-shared globals.108 109  *Authority:*110 111  * ``docs/ARCHITECTURE.md`` §3 line 124 (boot flow): "A new112    instance of ``vmlinux.wasm`` is *also* instantiated in this113    Worker (so syscalls can be serviced in-Worker against the114    kernel's address space), importing both ``env.kernel_memory``115    and this Worker's ``env.user_memory``."116 117  * ``linux-wasm/Documentation/wasm/memory-model.rst`` §1.1: "Every118    Worker — every process Worker and the dedicated vmlinux Worker119    — holds its **own** ``WebAssembly.Instance`` of120    ``vmlinux.wasm``."121 122**(F2) In-Worker syscall path; no RPC; no SAB-queue cross-Worker123hop.** ``linux.syscall`` (the user-Worker import) is provided by124HardwareJS as a JS function that calls THIS WORKER'S125``vmlinux.exports.wasm_syscall(nr, a0..a5)``. The kernel runs IN126the process Worker against shared ``env.kernel_memory``.127 128  *Authority:*129 130  * ``docs/ARCHITECTURE.md`` §4 lines 178–186 (How131    ``linux.syscall`` is handled in a process Worker): "...132    forwards the call into ``vmlinux.wasm``'s exported133    ``wasm_syscall(nr, ...)``. That export, when invoked from134    this Worker's JS, runs ``do_syscall()`` in kernel mode135    against shared ``memory 1``. The kernel sees the call as if136    it came from the process's CPU."137 138  * ``linux-wasm/Documentation/wasm/memory-model.rst`` §1.2 step 2:139    "HardwareJS calls the kernel instance's exported140    ``wasm_syscall(nr, a0..a5)``. **This crosses no Worker141    boundary**; the kernel instance lives in the same Worker as142    the userspace instance." (emphasis added in this opener for143    K11 reading)144 145**(F3) Real SMP under load.** Workers concurrently mutate146``env.kernel_memory``. Per-CPU variables, spinlocks, RCU, IPIs147(implemented as targeted ``Atomics.notify`` on a per-CPU wake148word) all carry their full upstream semantics.149``num_online_cpus()`` equals the configured maximum number of150concurrent process Workers (plus one for the vmlinux Worker151itself). Each Worker has its own ``raw_smp_processor_id()``;152percpu accessors land in distinct slots.153 154  *Authority:*155 156  * ``docs/ARCHITECTURE.md`` §17.6: "Workers really do157    concurrently mutate ``env.kernel_memory``. Per-CPU158    variables, spinlocks, RCU, and IPIs ... all carry their full159    upstream semantics."160 161  * ``linux-wasm/Documentation/wasm/mm-model.rst`` §0(c) row 3:162    ``CONFIG_SMP=y``, ``NR_CPUS=8`` (compile-time cap).163 164**Spec inconsistency to fix as part of this phase.**165``docs/ARCHITECTURE.md`` §10 currently says ``CONFIG_SMP=n`` (the166"we fake SMP at the JS layer" sentence). That contradicts §17.6 +167§2 + ``mm-model.rst`` §0(c). §10 is stale text from before §17.6168landed; the build has been ``CONFIG_SMP=y`` since K3's percpu work.169§10 must be amended as part of this opener's docs commit.170 171The three facts are not negotiable; they are the architecture. K11172exists to make the implementation match.173 174--------------------------------------------------------------------------------1753. The K5–K9 shortcut state being retired176--------------------------------------------------------------------------------177 178The K5–K9 implementation departed from the architecture in one179load-bearing way: every cross-process syscall is JS-direct. The180state today, by §15 row:181 182R-K8-1183  ``SYS_rt_sigaction`` stub returns 0 success without populating184  kernel-side ``current->sighand``. Disposition tracked in user-side185  ``__wasm_sigaction_table``. Stated retirement signal: "K9+ when186  cross-process syscalls route through ``vmlinux.exports.wasm_syscall``187  and the kernel's signal-delivery decisions actually consult188  ``current->sighand``." K11 IS that retirement.189 190R-K8-2191  K8 signal delivery via JS-direct stamp. ``SYS_kill`` / ``SYS_tkill``192  / ``SYS_tgkill`` resolve target tid in ``dispatchSyscall`` and call193  ``deliverSignalToTarget`` directly, bypassing kernel-side194  ``__send_signal_locked`` → ``arch_signal_wake_target`` → ring →195  consumer chain. Stated retirement signal: "K9+ when cross-process196  syscall RPC lands." That phrasing is wrong-shaped per F2 (no RPC;197  in-Worker call). K11 lands the in-Worker path; R-K8-2 retires.198 199R-K9-1200  Cross-process I/O via JS-direct registries (single ledger row201  absorbing wait4 / pipe / stdin / pgrp). Stated retirement signal:202  "K10+ when cross-process syscall RPC lands — same retirement203  signal as R-K8-1 + R-K8-2." Same wrong-shaped phrasing.204 205R-K9-2206  K9 wasm32 extension syscalls ``__NR_wasm_spawn_image`` (1024) +207  ``__NR_wasm_spawn_image_actions`` (1025). Stated retirement signal:208  "K10+ when ``posix_spawn(2)`` routes through proper kernel-side209  ``do_fork`` + ``do_execve``, which itself depends on the210  cross-process syscall RPC same as R-K8-1 / R-K8-2 / R-K9-1." Two211  retirements composed: the wrong-shaped retirement of R-K8-* / R-K9-1212  (now corrected to K11 in-Worker path), and the standard213  ``posix_spawn`` wrapper landing (which depends on K11's in-Worker214  ``do_fork`` + ``do_execve``).215 216R-K9-3217  K9 signal-termination encoding shortcut (``WIFEXITED+WEXITSTATUS=218  128+sig`` instead of ``WIFSIGNALED+WTERMSIG=sig``). Stated219  retirement signal: "K10+ when proper signal-termination encoding220  lands ... requires the K10+ cross-process syscall RPC + R-K8-1's221  ``current->sighand`` retirement." Composed retirement same as R-K9-2.222 223Five rows, one root cause, one corrected retirement gate. K11 close224retires all five. The retirement is mechanical:225 226* R-K8-1 retires when ``__NR_rt_sigaction`` reaches kernel-side227  ``do_sigaction`` via the in-Worker ``wasm_syscall`` path.228 229* R-K8-2 retires when ``__NR_kill`` / ``__NR_tkill`` / ``__NR_tgkill``230  reach kernel-side ``sys_kill`` / ``sys_tkill`` / ``sys_tgkill``.231 232* R-K9-1 retires when ``__NR_wait4`` / ``__NR_pipe2`` / ``__NR_dup2``233  / ``__NR_close`` / fd-aware ``__NR_read`` / ``__NR_write`` /234  ``__NR_writev`` / ``__NR_setpgid`` / ``__NR_setsid`` / ``__NR_getpgid``235  / ``__NR_getpgrp`` / ``__NR_getsid`` / ``__NR_getpid`` / ``__NR_getppid``236  reach kernel-side handlers. The five JS-direct registries237  (``childExitRegistry``, ``pipeRegistry``, ``fdTableRegistry``,238  ``stdinChannel``, ``processGroupRegistry``) demote to per-Worker239  caches OR are deleted.240 241* R-K9-2 retires after R-K9-1 by switching musl wrappers to call242  ``posix_spawn(2)`` against kernel-side ``do_fork`` + ``do_execve``;243  the wasm32-specific ``__NR_wasm_spawn_image{,_actions}`` syscall244  numbers stay as fast-path or get deprecated.245 246* R-K9-3 retires after R-K8-1 by routing the user-Worker exit247  message through kernel-side signal-delivery; the ``task_struct``'s248  ``exit_code`` field carries the terminating signal per upstream249  semantics.250 251--------------------------------------------------------------------------------2524. Per-Worker vmlinux instantiation contract253--------------------------------------------------------------------------------254 255This section codifies §F1 (per-Worker vmlinux) as a worker.mjs /256worker-browser.mjs amendment.257 2584.1. Worker classes (taxonomy)259------------------------------260 261Two valid Worker classes after K11:262 263vmlinux Worker264  The dedicated Worker that runs kthreads. Already exists today265  (``hardwarejs/src/kernelWorker.mjs`` / ``kernelWorker-node.mjs``).266  Holds one ``WebAssembly.Instance`` of ``vmlinux.wasm``. Imports267  ``env.kernel_memory`` (shared) + a 1-page ``shared:true`` sentinel268  Memory bound to ``env.user_memory``. CPU number 0269  (``raw_smp_processor_id() == 0``).270 271process Worker272  Any Worker hosting a userspace process. After K11, holds TWO273  ``WebAssembly.Instance``\ s: the userspace ``.wasm`` AND a274  per-Worker instance of ``vmlinux.wasm``. The userspace instance275  imports ``env.kernel_memory`` (shared) + the Worker's own276  ``env.user_memory`` (shared, per-Worker). The kernel instance277  imports the same ``env.kernel_memory`` + the same278  ``env.user_memory`` — so kernel code running on behalf of this279  process can ``memory.copy`` across the user/kernel boundary280  using THIS Worker's user memory. CPU number assigned at spawn281  (1 ≤ ``raw_smp_processor_id()`` < ``num_vcpu``); recycled on282  exit (§8).283 284Invalid Worker classes (caught at instantiation time):285 286* A Worker holding two ``vmlinux.wasm`` instances. Asserted by287  the K10-A4 ``kernelInstanceCount`` runtime check (which inverts288  meaning at K11 close — see §11).289 290* A process Worker without a ``vmlinux.wasm`` instance. Asserted291  by the K11 source-static guard test (``hardwarejs/test/k11-*-292  per-worker-vmlinux.test.ts``, replacing the K10-A4 single-293  Worker-invariant test).294 2954.2. Spawn-time amendment to worker.mjs296---------------------------------------297 298Today, ``hardwarejs/src/worker.mjs`` (the process-Worker entry299point) instantiates the userspace ``.wasm`` and provides300``linux.syscall`` as a JS function that calls301``kernelSpawnHandler.dispatchSyscall(...)`` (the K5–K9302JS-direct path). After K11:303 3041. Receive ``boot`` message from main thread carrying:305   ``{ wasmBytes, kernelWasmBytes, kernel_memory, user_memory,306   cpu_id, ... }``.307 3082. Instantiate ``vmlinux.wasm`` first (the kernel instance for309   this Worker), with imports::310 311     {312       env: {313         kernel_memory,    // shared, system-wide314         user_memory,      // shared, per-Worker (THIS Worker's)315         memory: kernel_memory,   // legacy default-memory binding316         ...317       },318       linux: {319         dmesg, hlt, cpu_relax, atomic_wait_i32, atomic_notify, ...320       }321     }322 3233. Instantiate the userspace ``.wasm`` second, with imports::324 325     {326       env: {327         kernel_memory,328         user_memory,329         memory: user_memory,330         ...331       },332       linux: {333         syscall: linuxSyscallShim,  // wraps vmlinux.exports.wasm_syscall334         ...335       }336     }337 338   The ``linuxSyscallShim`` is a thin JS function that closes over339   the kernel instance's exports::340 341     function linuxSyscallShim(nr, a0, a1, a2, a3, a4, a5) {342       return kernelInstance.exports.wasm_syscall(343         nr, a0, a1, a2, a3, a4, a5);344     }345 346   Optional fast-path: a tiny set of pure-JS syscalls347   (``gettimeofday``, ``clock_gettime``, ``getrandom``) MAY be348   answered directly without dispatching through the kernel349   instance, as a latency optimisation. The shim defaults to350   "always forward" per ``ARCHITECTURE.md`` §4 step 3.351 3524. Run the userspace instance's ``_start`` (or the K7 / K9 thread /353   spawn entry equivalents).354 355The ``boot`` message gets a new field ``kernelWasmBytes`` carrying356the same ``vmlinux.wasm`` bytes that the kernel Worker received.357The bytes are identical across all Workers; HardwareJS may358``WebAssembly.compile`` once and pass the resulting359``WebAssembly.Module`` instead of bytes (an optimisation tracked360as a §15-shape note, not a contract).361 3624.3. ``worker-browser.mjs`` parity363----------------------------------364 365The browser-side process-Worker entry point gets the same366amendment. Browser-engine behaviour for two-instance Workers is367the same as Node's (V8 is the engine in both cases via Node and368Chromium); the per-Worker resource cost of two instances is the369load-bearing risk surface, captured in §10370(``max_wasm_instances``).371 3724.4. Exec / fork shape373----------------------374 375The K11 fork / exec changes are mechanical:376 377* ``linux.spawn_worker`` (the existing K5/K6 import that378  HardwareJS implements as "create a Worker and load this379  ``.wasm``") additionally ships the kernel ``vmlinux.wasm`` bytes380  to the new Worker. Spawn order: kernel instance first, then381  user instance.382 383* ``linux.fork_worker`` (the K9 spawn-with-fd-actions variant)384  ships the same. Cost: the new Worker holds 2 instances, so385  ``max_wasm_instances`` accounting (§10) charges 2.386 387* ``linux.exec_worker`` (the K6 binfmt_wasm path) terminates the388  caller and spawns a fresh Worker. The fresh Worker spawns389  with the kernel instance + the new user instance — total 2.390 391* The vmlinux Worker remains untouched (1 instance).392 393--------------------------------------------------------------------------------3945. ``linux.syscall`` import resolution in process Workers395--------------------------------------------------------------------------------396 397§F2 specifies the in-Worker call shape. This section pins the JS398shim contract and the failure-mode taxonomy.399 4005.1. Shim contract401------------------402 403The ``linux.syscall`` import in a process Worker resolves to a JS404function with signature::405 406    (nr, a0, a1, a2, a3, a4, a5) -> i64407 408The function MUST:409 4101. Call ``kernelInstance.exports.wasm_syscall(nr, a0, a1, a2, a3,411   a4, a5)`` (the per-Worker kernel instance's exported entry).412 4132. Return the result as ``i64``.414 4153. NOT post a message to any other Worker. NOT call416   ``Atomics.wait`` on a SAB queue. NOT marshal arguments through417   any cross-Worker buffer. The kernel runs in this Worker; the418   call is a normal wasm-import-out-and-back-in JS hop.419 420The shim MAY (optimisation, not contract):421 422* Short-circuit a known-pure-JS syscall list before calling423  ``wasm_syscall``. The list is documented in424  ``arch/wasm32/include/asm/syscall_shortcuts.h`` (new file at425  K11 implementation time). Examples: ``__NR_gettimeofday``,426  ``__NR_clock_gettime`` (when not requesting CLOCK_MONOTONIC_RAW),427  ``__NR_getrandom`` (small reads). Each shortcut requires a428  matching ``arch/wasm32/kernel/syscall_shortcut_<nr>.c`` file429  documenting the short-circuit's safety argument: same return430  semantics as the kernel-side syscall under all flag431  combinations the spec exposes.432 4335.2. Failure modes434------------------435 436If ``kernelInstance`` is not yet bound when ``linux.syscall``437fires (e.g., a syscall before ``__wasm_call_ctors`` finishes —438should not happen but the assertion is cheap), the shim throws a439``WasmRuntimeError`` with a clear "syscall before kernel440instance bound" message. The userspace Worker traps; HardwareJS441records the trap and surfaces it as a kernel-trap terminal in442the boot result.443 444If ``wasm_syscall`` itself throws (e.g., a guest C bug inside445``do_syscall``, an Asyncify state corruption, a wasm OOB on the446shared kernel memory), the shim does NOT swallow the throw — it447propagates as a userspace trap. The userspace Worker terminates;448HardwareJS reaps via the existing K8/K9 exit machinery.449 4505.3. Why a JS shim and not direct ``call_indirect``?451----------------------------------------------------452 453``ARCHITECTURE.md`` §4 already explains this:454 455  "Why doesn't the process Worker just ``call_indirect`` into456  kernel code directly? Because each Worker has its own WASM457  module instance; modules can't share a function table cheaply458  across instances. Calling out to JS and then back into the459  kernel module is structurally how WASM 'links' across460  instances, and JS is also the natural place for the dispatch461  table to live."462 463The K11 design preserves that framing.464 465--------------------------------------------------------------------------------4666. ``wasm_syscall`` entry sequence (kernel-stack swap)467--------------------------------------------------------------------------------468 469This section concretises ``memory-model.rst`` §1.2 steps 3–5470into the C / wasm32 assembly that K11 implements in471``arch/wasm32/kernel/entry.c``.472 4736.1. Today474----------475 476``arch/wasm32/kernel/entry.c::wasm_syscall`` exists as a stub477returning ``-ENOSYS`` for every ``nr``. ``sys_call_table`` is478empty. This is the K5 placeholder; K11 fills it.479 4806.2. Required dance (per ``memory-model.rst`` §1.2)481---------------------------------------------------482 483A kernel stack is per ``task_struct``, slab-allocated out of484``env.kernel_memory`` (already true at K3 — kernel stacks are485allocated by ``copy_thread`` / ``alloc_thread_stack_node``).486The wasm runtime's notion of "the stack" is a single487``__stack_pointer`` global per instance; the kernel must swap488that pointer at every kernel-mode entry and exit.489 490The swap sequence::491 492    long wasm_syscall(unsigned long nr, long a0, long a1,493                      long a2, long a3, long a4, long a5)494    {495        unsigned long user_sp;496        unsigned long kern_sp;497        long ret;498 499        /* Step 1: read current's kernel stack base. */500        kern_sp = (unsigned long)current->stack + THREAD_SIZE;501 502        /* Step 2: swap __stack_pointer. */503        user_sp = __builtin_wasm_get_stack_pointer();504        __builtin_wasm_set_stack_pointer(kern_sp);505 506        /* Step 3: persist user_sp into per-task state. */507        current->thread.user_stack_pointer = user_sp;508 509        /* Step 4: dispatch. */510        ret = do_syscall(nr, a0, a1, a2, a3, a4, a5);511 512        /* Step 5: restore. */513        __builtin_wasm_set_stack_pointer(514            current->thread.user_stack_pointer);515        return ret;516    }517 518The pseudo-builtins ``__builtin_wasm_{get,set}_stack_pointer`` are519NOT actual clang builtins as of LLVM 22; they are stand-ins for520the real implementation, which must be inline wasm assembly in521``arch/wasm32/kernel/entry.S`` reading / writing the522``__stack_pointer`` global. Probe at K11-A1: confirm the assembly523syntax and that ``wasm-ld`` preserves the global access through524linking.525 5266.3. Stack-swap edge cases527--------------------------528 529Re-entrant syscall (kernel code calling another syscall from530within ``wasm_syscall``) is structurally impossible on wasm32:531there is no ``int 0x80``, kernel C code calls C functions532directly. ``current->thread.user_stack_pointer`` is single-slot533per task and only written on user-to-kernel entry.534 535Asyncify save / restore inside ``do_syscall`` (e.g., a536``schedule()`` triggered by a blocking syscall) saves the kernel537stack, the kernel ``__stack_pointer``, and the Asyncify state.538On resume, all three restore atomically. ``user_stack_pointer``539is in the task struct (kernel memory); it survives ``schedule``540unaffected.541 542Signal delivery on syscall return runs after step 5 (after543restore). The K8 ``__wasm_signal_dispatch`` trampoline runs in544USER mode against the user stack. K11 inherits this without545change.546 5476.4. Probe checklist (K11-A1)548-----------------------------549 550* ``__stack_pointer`` global access from inline assembly works.551* ``current->stack`` resolves to a non-zero kernel address from552  inside ``wasm_syscall`` running on a process Worker.553* ``THREAD_SIZE`` is the K4-bumped value (32 KiB, ``PAGE_SIZE <<554  THREAD_SIZE_ORDER`` with ``THREAD_SIZE_ORDER = 3``) pinned in555  ``arch/wasm32/include/asm/thread_info.h``. (DRIFT-CATCH at556  K11-A1: an earlier draft of this section claimed 16 KiB557  "per upstream wasm32 default"; this was a §20.5.3 candidate558  drift surfaced by the K11-A1 probe and corrected here. The559  K4 close ledger bumped from 16 KiB to 32 KiB after deep560  callchains overflowed under the printk → vsnprintf →561  console-driver path. Per ``thread_info.h`` rationale,562  per-task overhead is 32 KiB stack + 16 KiB Asyncify =563  48 KiB per kthread, matching ``sched-model.rst`` §3.3.)564* The kernel C compiler emits ``wasm_syscall`` with a body that565  the link-time post-pass leaves intact (no inlining surprises).566* ``wasm_syscall`` invoked directly with all-zero arguments567  returns ``-38`` (``-ENOSYS``) without trapping. The current568  K10-era stub (``arch/wasm32/kernel/entry.c``) returns this569  unconditionally; K11-A1 confirms the call shape works570  end-to-end before K11-A3 replaces the stub with the real571  stack-swap implementation.572 573--------------------------------------------------------------------------------5747. ``sys_call_table`` population strategy575--------------------------------------------------------------------------------576 577``arch/wasm32/kernel/syscall_table.c`` is empty today. Upstream578Linux populates the table per arch via579``arch/x86/entry/syscalls/syscall_64.tbl`` (or equivalent). The580K11 strategy is upstream-clean for syscalls that link, with an581explicit ``-ENOSYS`` stub list + §15 rows for those that do not.582 5837.1. Population mechanism584-------------------------585 586A new file ``arch/wasm32/kernel/syscall_table.c`` ``#include``\ s587the upstream ``arch/x86/entry/syscalls/syscall_64.tbl``-derived588generation via the existing ``asm/syscall.h`` machinery. Each589entry is a function pointer to ``sys_<name>`` (or590``__x64_sys_<name>``, depending on the upstream table format).591 592For each syscall:593 594* If ``sys_<name>`` links (the implementation file is in the595  build), the entry points at it.596 597* If ``sys_<name>`` does NOT link (filesystem subsystem disabled598  by Kconfig, syscall stubbed out, etc.), the entry points at a599  per-syscall ``-ENOSYS`` stub generated by macro expansion. Each600  ``-ENOSYS`` stub gets a §15-row treatment if the missing601  implementation is load-bearing for any K-phase ≤ K12; otherwise602  it is a one-line "this syscall is not provided on wasm32"603  comment in the table.604 605The K11 implementation walks the upstream table and lands the606stub set. The §15 row count for K11 is bounded by the number of607load-bearing missing implementations; per the K7/K8/K9 surface608enumeration this is expected to be small (most filesystem and609network syscalls are already disabled at Kconfig; what user610binaries actually call is a small subset). Probe at K11-A2:611exhaustively enumerate every ``__NR_*`` referenced by the K7 /612K8 / K9 demos + by musl wrappers used in K12, and confirm each613either links upstream-clean OR has a §15-row stub.614 6157.2. Per-syscall §15-row template616---------------------------------617 618For each missing implementation that is load-bearing:619 620::621 622    R-K11-N: SYS_<name> stubbed out (-ENOSYS) on wasm32623    DEPARTURE: <one-paragraph: what upstream does, what we624                 don't>625    AUTHORITY: <file:line in arch/wasm32/kernel/syscall_table.c>626    CARRIER:   <K-phase that needs this syscall to land for627                its acceptance to hold>628    RETIREMENT: <upstream code path that, once landed, makes629                 the stub unnecessary>630 631The template mirrors the existing §15 row shape from632``docs/ARCHITECTURE.md``.633 6347.3. Composite syscall fast-paths635---------------------------------636 637A small set of syscalls are answered by a wasm32-specific638implementation rather than the upstream one. K11 retains:639 640* ``__NR_clone`` → ``arch/wasm32/kernel/clone.c`` (the K7 thread /641  K9 fork machinery; calls upstream ``kernel_clone`` and then642  the wasm32-specific ``arch_dup_task_struct`` /643  ``copy_thread`` / spawn-ring submission).644 645* ``__NR_wasm_spawn_image`` (1024) + ``__NR_wasm_spawn_image_actions``646  (1025) → R-K9-2 fast-paths. K11 retains them as fast-paths;647  R-K9-2 retirement signal becomes "K12+ when ``posix_spawn``648  wraps ``do_fork`` + ``do_execve`` on the in-Worker syscall649  path."650 651* ``__NR_futex`` → R-K7-3 in-Worker dispatch (already652  implemented; no change).653 654The list is documented in655``arch/wasm32/include/asm/syscall_shortcuts.h`` (§5.1656short-circuit list, same file).657 658--------------------------------------------------------------------------------6598. ``arch_cpu_up`` / ``arch_cpu_down`` hooks660--------------------------------------------------------------------------------661 662§F3 specifies real SMP. K11 lands the dynamic CPU bring-up /663tear-down tied to process Worker spawn / exit.664 6658.1. Today666----------667 668The kernel boots ``CONFIG_SMP=y`` ``NR_CPUS=8`` (per669``mm-model.rst`` §0(c) row 3). Only CPU 0 is online (the670vmlinux Worker). ``cpu_possible_mask`` and ``cpu_present_mask``671are set at boot time (K3 / K4 work) but ``cpu_online_mask``672stays at ``{0}`` because no ``cpu_up`` is ever issued from the673arch.674 6758.2. Hooks676----------677 678Two new arch hooks added in K11 patches:679 680``arch_cpu_up(int cpu)``681  Called from ``hardwarejs/src/kernelSpawnHandler.ts`` after682  ``linux.spawn_worker`` (or ``fork_worker`` / ``exec_worker``)683  succeeds and the new Worker has acknowledged kernel-instance684  ready. Marks ``cpu`` as online (``set_cpu_online(cpu, true)``);685  initialises per-CPU state for ``cpu`` (the K3 percpu work686  already allocated the slots; this just brings the slot live);687  notifies any waiters via the existing CPU hotplug machinery.688 689``arch_cpu_down(int cpu)``690  Called from ``kernelSpawnHandler`` on Worker exit (after691  ``recordChildExit`` has been notified). Marks ``cpu`` as692  offline (``set_cpu_online(cpu, false)``); flushes any pending693  per-CPU work to other CPUs (the K3 / K4 work has the694  upstream-clean migration paths); recycles the CPU number for a695  future spawn (§9.3 numbering policy).696 6978.3. CPU numbering policy698-------------------------699 700CPU 0701  The vmlinux Worker. Set at boot, never reassigned. Hosts all702  kthreads.703 704CPUs 1 .. ``num_vcpu`` - 1705  Process Workers. Assigned at spawn time from a free list;706  recycled at exit. The free list is JS-side (in707  ``hardwarejs/src/cpuRegistry.ts``, new file at K11-B708  implementation time); ``arch_cpu_up(cpu)`` and709  ``arch_cpu_down(cpu)`` reflect the JS-side allocation back710  into ``cpu_online_mask``.711 712A process Worker's ``raw_smp_processor_id()`` returns its713assigned CPU number. Each Worker has its own714``__stack_pointer`` global, its own per-CPU slot for percpu715variables, its own runqueue tail (the K4 ``wasm32_rq``716machinery extended for SMP at K11-B).717 7188.4. ``smp_call_function`` / IPI719--------------------------------720 721IPIs are implemented as targeted ``Atomics.notify`` on a722per-CPU wake word in ``env.kernel_memory``. Already specified723in ``ARCHITECTURE.md`` §17.6: "IPIs (implemented as targeted724``Atomics.notify`` on a per-CPU wake word) all carry their full725upstream semantics."726 727K11 lands the per-CPU wake word allocation (``arch/wasm32/include/728asm/smp.h``) + the upstream-clean ``smp_call_function_*``729infrastructure on top.730 731--------------------------------------------------------------------------------7329. ``num_vcpu`` parameter733--------------------------------------------------------------------------------734 735The first new JS-side parameter K11 introduces.736 7379.1. Definition738---------------739 740``num_vcpu`` is a positive integer ≥ 2, ≤ ``NR_CPUS`` (= 8 at741K11). It is the maximum number of CPUs the kernel will see742online concurrently — including CPU 0 (the vmlinux Worker).743 744* CPU 0 is reserved for the vmlinux Worker. Always online.745* CPUs 1 .. ``num_vcpu`` - 1 are available for process Workers.746* No more than ``num_vcpu`` - 1 process Workers can run747  concurrently. Subsequent ``fork`` / ``spawn_worker`` requests748  block on the JS-side spawn queue (NOT on -EAGAIN — see §10749  for -EAGAIN semantics, which is a different cap on wasm750  instance count).751 7529.2. Caller surface753-------------------754 755User-chosen at ``HardwareJS.bootKernel({ num_vcpu: N })`` call756site. Default policy in the demo JS layer:757``min(navigator.hardwareConcurrency, NR_CPUS)``. Demo writers758SHOULD honour the default; explicit override is supported for759testing under low-CPU scenarios.760 761The parameter maps to the kernel cmdline as ``nr_cpus=N`` (the762upstream cmdline arg). ``setup_arch`` parses it; the763``cpu_possible_mask`` is set to ``{0..N-1}`` from764``setup_per_cpu_areas``.765 7669.3. Hard upper bound767---------------------768 769``NR_CPUS`` = 8 at K11 (per ``mm-model.rst`` §0(c) row 3770compile-time cap). Tunable in v0.2 by re-running771``setup_per_cpu_areas`` with a larger compile-time772``NR_CPUS``. Tracked as a §15-shape note in the K11 close773ledger.774 7759.4. Reachability776-----------------777 778If ``num_vcpu`` < 2 the kernel cannot fork (CPU 0 is reserved779for vmlinux; CPU 1 is the first process-Worker slot). The780boot fails with a clear ``panic("num_vcpu must be ≥ 2; got781%d")`` message.782 783--------------------------------------------------------------------------------78410. ``max_wasm_instances`` parameter785--------------------------------------------------------------------------------786 787The second new JS-side parameter. Browser-imposed cap on788simultaneously-instantiated wasm modules.789 79010.1. Why it exists791-------------------792 793Each ``WebAssembly.Instance`` consumes engine-internal794resources (compiled code cache, table allocations, memory795descriptor slots). Browser engines impose a per-tab or per-796process ceiling that varies by engine and version. Today's797HardwareJS treats this as latent — it does not probe or798account. K11 makes it explicit because the per-Worker vmlinux799contract DOUBLES the per-process-Worker instance count, and800the cap becomes load-bearing under high process counts.801 80210.2. Definition803----------------804 805``max_wasm_instances`` is a positive integer, the maximum number806of ``WebAssembly.Instance`` objects HardwareJS will hold807concurrently across all Workers it owns.808 809Cost accounting:810 811* vmlinux Worker = 1 instance.812* Each process Worker = 2 instances (user binary + per-Worker813  vmlinux).814* So the maximum number of concurrent process Workers under815  this cap is::816 817      max_concurrent_process_workers <=818        floor((max_wasm_instances - 1) / 2)819 82010.3. Caller surface821--------------------822 823User-chosen at ``HardwareJS.bootKernel({ max_wasm_instances: M824})`` call site.825 826Default policy: HardwareJS probes at ``bootKernel`` time. Probe827mechanism (K11-B implementation): instantiate a tiny "ping"828``vmlinux`` shape progressively until ``WebAssembly.instantiate``829or ``new WebAssembly.Module`` rejects with "too many wasm830instances" (engine-specific error message). The probe records831the empirical cap minus a safety margin (= 4 instances) and832sets ``max_wasm_instances`` to that. Per-engine known values833populate a fallback table for engines whose error message is834not matched.835 83610.4. Enforcement paths837-----------------------838 839Two enforcement paths, with different K-phase scopes:840 841(1) Hard cap (this phase, K11). At limit, refuse new842    ``fork()`` / ``spawn_worker`` / ``exec_worker`` with843    ``-EAGAIN`` (Linux-spec-correct under resource pressure;844    upstream ``do_fork`` returns ``-EAGAIN`` when845    ``max_threads`` is exceeded). The probe + accounting +846    ``-EAGAIN`` path lands at K11.847 848(2) Eviction (deferred, v0.2 or later). When at limit, find849    the oldest blocked-with-no-pending-signal process Worker;850    terminate the Worker (releases 2 instances); persist its851    ``env.user_memory`` SAB JS-side; mark its ``task_struct``852    as needs-rehydrate. On wake, JS rehydrates: a new Worker853    instantiates with the saved ``user_memory`` + a fresh854    ``vmlinux`` instance. §15 row "K11+ rehydration on855    instance pressure" deferred to v0.2; logged at K11 close856    with retirement signal "any user reports an EAGAIN under857    a workload that would have fit under eviction."858 85910.5. Interaction with ``num_vcpu``860-----------------------------------861 862The two parameters are independent caps:863 864* ``num_vcpu`` is the maximum number of CPUs the kernel sees865  online.866 867* ``max_wasm_instances`` is the maximum number of wasm868  instances HardwareJS holds, which (after dividing by 2 for869  the 2-instance-per-process-Worker cost) gives a separate870  cap on concurrent process Workers.871 872At runtime, the effective concurrent-process-Worker cap is::873 874    effective_cap = min(875      num_vcpu - 1,876      floor((max_wasm_instances - 1) / 2)877    )878 879The narrower of the two binds. If ``num_vcpu`` binds, fork()880blocks on the JS-side spawn queue; if ``max_wasm_instances``881binds, fork() returns ``-EAGAIN``. Demo writers SHOULD pick882``num_vcpu`` as the practical bottleneck (since it is bounded883by ``hardwareConcurrency``); ``max_wasm_instances`` is the884hard ceiling that protects against engine-side OOM.885 886--------------------------------------------------------------------------------88711. R-K10-W1 inversion plan888--------------------------------------------------------------------------------889 890R-K10-W1 (filed at K10-A4 close) records "K10-B active-segment891overlay departs from ``__wasm_init_memory``'s cmpxchg-once892invariant for ``vmlinux.wasm``'s ``.initcall*.init`` bytes. The893departure is currently moot because vmlinux is single-Worker by894architectural construction." The "single-Worker by architectural895construction" rationale is wrong against ``memory-model.rst``896§1.1 + ``ARCHITECTURE.md`` §3 line 124. The actual architecture897has vmlinux instantiated per Worker.898 899R-K10-W1 is NOT removed at K11. It inverts.900 90111.1. Why the K10-A3 bracket pass still works902---------------------------------------------903 904The K10-A3 Strategy B post-link bracket pass converts the905``.initcall*.init`` segments from passive to active. Per F1,906every Worker holds its own ``vmlinux.wasm`` instance and runs907its own ``__wasm_init_memory`` start function on instantiation.908Active segments write at instantiation time, not first-fault.909 910The original K10-A2b §11.2.1 rationale ("vmlinux is911single-Worker; only one ``__wasm_init_memory`` runs") is912incorrect. Multiple ``__wasm_init_memory`` runs DO happen. The913correct rationale is:914 915* ``.initcall*.init`` bytes are immutable, link-time-fixed916  function-pointer arrays (and their sentinels). They contain917  exactly the same bytes for every kernel instance because918  every kernel instance is the same ``vmlinux.wasm`` module.919* Per-Worker re-initialisation writes byte-identical values920  to the same kernel-memory locations. The writes are921  idempotent under the wasm memory model's relaxed-memory922  rules (concurrent writers writing the same byte are923  indistinguishable from a single writer).924 925Strategy B's choice does not change. The reason it works does.926This is a load-bearing rationale correction; the927``terminal-model.rst`` §11.2.1 section gets a HISTORICAL-NOTE928entry recording the original (refuted) rationale and the929corrected one.930 93111.2. Assertion inversion932-------------------------933 934The K10-A4 ``hardwarejs/src/kernelWorker.mjs``935``kernelInstanceCount`` runtime check ("vmlinux instantiated936exactly once per Worker; if a second instantiation is observed,937trip") AND the K10-A4 source-static guard test938(``hardwarejs/test/k10-a4-single-worker-invariant.test.ts`` —939"bootKernel.ts has exactly two createWorker construction sites;940no module under hardwarejs/src/ outside kernelWorker.mjs941instantiates ``vmlinux.wasm``") STAY in place during the K11942pre-implementation period. They pin the current pre-K11943shortcut state.944 945At K11 close, the assertions invert:946 947Old (K5–K10): "vmlinux is instantiated exactly once across all948Workers."949 950New (K11+): "vmlinux is instantiated exactly once PER WORKER951(no Worker holds two vmlinux instances). Valid Worker classes952are (a) the vmlinux Worker, holding 1 vmlinux instance + the953sentinel ``user_memory``; (b) any process Worker, holding 1954vmlinux instance + 1 userspace instance. The K11 source-static955guard test asserts every process Worker spawned via956``spawn_worker`` / ``fork_worker`` / ``exec_worker`` carries a957vmlinux instance."958 95911.3. R-K10-W1 status after K11 close960-------------------------------------961 962Marked CLOSED-AS-INVERTED in ``docs/ARCHITECTURE.md`` §15.3.963The row's text gets a HISTORICAL-NOTE prepended explaining:964 965* The original "single-Worker by architecture" mootness966  argument was wrong.967* The K10-A3 Strategy B implementation still holds for the968  corrected rationale (idempotent function-pointer arrays).969* The K10-A4 runtime + source-static assertions inverted at970  K11 close.971 972The row becomes a worked example for §20.6.1 (K-phase973dependency-checks): a row was filed without a974spec-vs-implementation drift check, the drift was caught one975K-phase later, and the correction landed cleanly because the976implementation choice (Strategy B) was independently correct.977 978--------------------------------------------------------------------------------97912. R-K8-* / R-K9-* retirement plan980--------------------------------------------------------------------------------981 982Each row's retirement is mechanical at K11 close:983 984R-K8-1 (kernel-side ``current->sighand`` not populated)985  Retires when ``__NR_rt_sigaction`` reaches kernel-side986  ``do_sigaction`` via the in-Worker ``wasm_syscall`` path. The987  user-side ``__wasm_sigaction_table`` either deletes (if988  kernel-side is now the source of truth) or demotes to a989  per-Worker fast-path cache that mirrors the kernel-side990  sighand. K11-B5 lands the change. Probe at K11-A4: every K8991  vitest still passes against the new path.992 993R-K8-2 (signal delivery via JS-direct stamp)994  Retires when ``__NR_kill`` / ``__NR_tkill`` / ``__NR_tgkill``995  reach kernel-side handlers. ``deliverSignalToTarget`` (the996  shared primitive) gets demoted from "JS-direct path" to "the997  kernel→HardwareJS signal-ring consumer's stamp helper". K11-B6998  lands the change.999 1000R-K9-1 (cross-process I/O via JS-direct registries)1001  Retires when ``__NR_wait4`` + the pipe / fd / stdin / pgrp1002  syscalls reach kernel-side handlers. The five JS-direct1003  registries get either deleted (if kernel-side is sufficient)1004  or demoted to per-Worker caches. K11-C1 lands the change.1005 1006R-K9-2 (wasm32 extension syscalls 1024 / 1025)1007  Partial retirement at K11: the standard ``posix_spawn(2)``1008  wrappers route through kernel-side ``do_fork`` + ``do_execve``1009  on the in-Worker syscall path; the wasm32-specific syscalls1010  stay as fast-paths. Full retirement deferred to a K-phase1011  that audits the fast-path's necessity.1012 1013R-K9-3 (signal-termination encoding shortcut)1014  Retires after R-K8-1 by routing the user-Worker exit message1015  through kernel-side signal-delivery; the ``task_struct``\ 's1016  ``exit_code`` field carries the terminating signal. K11-C21017  lands the change.1018 1019The five rows go from "ACTIVE — wrong-shaped retirement signal"1020to CLOSED at K11 close. ``docs/ARCHITECTURE.md`` §15 gets the1021status update; the row text gains a closure note recording (a)1022the wrong-shaped retirement signal in the original row, (b) the1023K11 in-Worker path that was the actual retirement gate.1024 1025--------------------------------------------------------------------------------102613. K11 sub-phase decomposition1027--------------------------------------------------------------------------------1028 1029Same shape as the K7 / K8 / K9 / K10 openers.1030 1031K11-A11032  Probes against the current kernel binary: confirm1033  ``__stack_pointer`` global access from inline asm; confirm1034  ``current->stack`` resolves on a process Worker; confirm1035  ``THREAD_SIZE`` consistency; confirm ``wasm_syscall`` can be1036  invoked from JS without crashing (today's stub returns1037  -ENOSYS — make sure the call shape works end-to-end).1038 1039K11-A21040  Source-site snapshot. Enumerate every ``__NR_*`` call site1041  the K7/K8/K9 demos hit + every musl wrapper used by K121042  pty/ldisc/jobctrl work. Confirm each links upstream-clean OR1043  is on the explicit ``-ENOSYS`` stub list with a §15 row. Land1044  ``syscall-path-model-source-snapshots.txt``.1045 1046K11-A31047  ``arch/wasm32/kernel/entry.c`` ``wasm_syscall`` real1048  implementation (§6 stack-swap dance). Probe: a synthetic1049  ``do_syscall(__NR_getpid)`` round-trip from a process Worker1050  returns the correct pid. No actual ``sys_call_table``1051  population yet; the probe uses a single-syscall test harness.1052 1053K11-A41054  Verify K11-A3 against a K7/K8/K9 regression sweep with the1055  new entry sequence enabled but the JS-direct shortcuts still1056  in place. (This is the analogue of K10-A4: the load-bearing1057  machinery works against all prior K-phases without breaking1058  any of them.)1059 1060K11-B11061  ``sys_call_table`` population. ``arch/wasm32/kernel/syscall_table.c``1062  populated for the K7/K8/K9 demo + musl-wrapper surface. Per-syscall1063  stub list + §15 rows. Probe: every ``__NR_*`` referenced by K7/K8/K91064  demos resolves (either to the upstream handler or to a stub1065  with the recorded -ENOSYS).1066 1067  *K11-B1 close state (CLOSED at K11-B1 commit):*1068  ~50 syscalls wired upstream-clean via ``w_sys_<name>`` 6-arg1069  uniform wrappers (read, write, close, lseek, mmap, mprotect,1070  munmap, brk, rt_sigaction, rt_sigprocmask, ioctl, readv, writev,1071  pipe, sched_yield, dup, dup2, pause, nanosleep, getpid, clone,1072  exit, wait4, kill, fcntl, getuid, getgid, geteuid, getegid,1073  setpgid, getppid, getpgrp, setsid, getpgid, getsid,1074  rt_sigtimedwait, rt_sigsuspend, gettid, tkill, set_tid_address,1075  clock_gettime, clock_nanosleep, exit_group, tgkill, openat,1076  set_robust_list, pipe2). Two arch-specific implementations1077  (``sys_mmap`` + ``sys_nanosleep``) live in1078  ``arch/wasm32/kernel/sys_wasm32.c`` because the upstream1079  versions are gated on (CONFIG_64BIT) or are x86-specific1080  (mmap byte-offset semantics).1081  Three documented ``-ENOSYS`` slots, each with a §15 row in1082  ``docs/ARCHITECTURE.md`` §15:1083    - R-K11-1 (``__NR_arch_prctl``=158): wasm-threads TLS via1084      ``__tls_base``; no x86-style FS/GS-base setup.1085    - R-K11-2 (``__NR_rt_sigreturn``=15): wasm32 doesn't use the1086      upstream signal-frame model; the K8 trampoline handles1087      sigmask save/restore in JS shim state.1088    - R-K11-3 (``__NR_fork``=57, ``__NR_vfork``=58): musl's1089      wasm32 port maps fork/vfork to clone(SIGCHLD,...) at1090      libc-level; ``__ARCH_WANT_SYS_FORK`` deliberately not1091      defined in ``arch/wasm32/include/asm/unistd.h``.1092  Out-of-bounds ``nr`` (>= 512) returns ``-ENOSYS`` via1093  ``do_syscall``'s range check. Validated by1094  ``arch/wasm32/kernel/k11_b1_probes.c`` (kernel-side smoke1095  probe, gated by cmdline ``k11_b1_probes=1``) and1096  ``hardwarejs/test/k11-b1-syscall-table.test.ts`` (vitest1097  asserting 18/18 representative-spread probes pass + the1098  three documented ``-ENOSYS`` slots return ``-ENOSYS``).1099 1100K11-B21101  ``arch_cpu_up`` + ``arch_cpu_down`` patches. Per-CPU wake1102  word allocation. ``smp_call_function`` lights up for one1103  process Worker.1104 1105  Decomposed at planning time (chosen for atomicity of the1106  R-K10-W1 inversion landing site):1107 1108  K11-B2a — DONE1109    Per-Worker ``vmlinux`` instantiation. Spawn handler1110    accepts ``vmlinuxModule`` (precompiled1111    ``WebAssembly.Module``) + ``kernelMemoryWasm`` (the1112    shared-SAB-backed ``WebAssembly.Memory``). Process Workers1113    instantiate ``vmlinux`` exactly once via1114    ``instantiateProcessWorkerVmlinux`` (worker.mjs) against1115    shared ``env.kernel_memory`` + own ``env.user_memory``,1116    re-running ``__wasm_call_ctors`` for the K10-A3 active-1117    overlay (idempotent: overlay re-writes byte-identical1118    function-pointer arrays; cmpxchg-once gate skips1119    everything else). The instance's ``wasm_syscall`` export1120    is now reachable from inside the process Worker —1121    architecturally preparing the K11-B5..C2 syscall flips1122    without flipping any actual syscall paths yet (the1123    SAB-RPC syscall path still owns every syscall as of1124    K11-B2a). Soft-failure: if instantiation traps1125    ``vmlinux-error`` is posted to the spawn handler and the1126    user binary continues on the SAB-RPC path.1127 1128    ``processVmlinuxInstanceCount`` mirrors the K10-A41129    ``kernelInstanceCount`` invariant: each Worker (kernel or1130    process) instantiates ``vmlinux`` exactly once. R-K10-W11131    is now PARTIALLY-INVERTED — the assertion has flipped1132    from "exactly one Worker instantiates ``vmlinux``" to1133    "every Worker instantiates ``vmlinux`` exactly once1134    apiece," but the SAB-RPC syscall path remains live. Full1135    inversion lands at K11-D2 when the SAB-RPC path is1136    deleted.1137 1138    Witness: ``hardwarejs/test/k11-b2a-per-worker-vmlinux.test.ts``1139    boots the K6-C2 reference initramfs (hello-k5 as1140    ``/init``) with ``vmlinuxModule`` supplied to the spawn1141    handler, asserts ``onVmlinuxReady`` fires for every1142    spawned process Worker tid with ``hasWasmSyscall=true``,1143    ``processVmlinuxInstanceCount === 1`` (count==1 per tid1144    in the report map), the user binary still produces1145    "hello from K5 userspace" (no perturbation), and no1146    panic / oops appears in dmesg (second-instance ctors are1147    idempotent on the K10-A3 overlay).1148 1149  K11-B2b — DONE (substrate-only; full SMP deferred per1150  R-K11-W2)1151    Per-Worker ``__wasm32_cpu_id`` wasm-Global substrate.1152    ``arch/wasm32/lib/cpu_id.S`` defines the per-instance1153    Global plus the ``__wasm32_get_cpu_id`` /1154    ``__wasm32_set_cpu_id`` accessors; both are explicitly1155    re-exported from ``vmlinux.wasm`` by1156    ``scripts/link-vmlinux-wasm.sh`` (verified via1157    ``wasm-dis`` at K11-B2b close). The kernel header1158    ``arch/wasm32/include/asm/smp.h`` defines1159    ``raw_smp_processor_id()`` to read the wasm-Global, so1160    every kernel call site that reaches1161    ``smp_processor_id()`` (e.g., ``current->cpu``,1162    per-CPU-data accesses, percpu writes) now observes the1163    correct per-Worker CPU id rather than a hardcoded 0.1164 1165    On the JS side ``hardwarejs/src/kernelSpawnHandler.ts``1166    grew a ``cpuRegistry`` allocator: each spawned process1167    Worker gets a unique cpu_id from ``[1, maxCpuId]``1168    (``maxCpuId`` defaults to 7 = ``NR_CPUS - 1``; CPU 0 is1169    reserved for the kernel Worker by convention). The1170    cpu_id is shipped in the spawn ``init`` message; the1171    Worker calls ``vmlinuxInstance.exports.__wasm32_set_cpu_id(cpuId)``1172    after ``__wasm_call_ctors`` runs. Releasing the1173    cpu_id is wired into the worker exit / error paths.1174 1175    What this **does not** include — explicitly deferred1176    to a later phase under §15.3 R-K11-W2:1177 1178    - ``arch_cpu_up`` / ``arch_cpu_down`` boot hooks. The1179      cpu_id substrate is enough for per-CPU semantics on1180      Workers that already exist; bringing CPUs online /1181      offline at boot under a kernel-driven flow is not on1182      the K11-B critical path.1183    - Per-CPU wake words in ``env.kernel_memory``. K8's1184      single ``__sched_wakeup_word`` plus the per-tid1185      signal-pending region remain the wake substrate1186      through K11-D2.1187    - IPI / ``smp_call_function`` cross-CPU dispatch. Not1188      reached by any K11-targeted syscall (the K11-B5/B61189      and K11-C1/C2 retirements are signal + process-mgmt1190      surfaces, not cross-CPU coordination); deferred to1191      K12+ behind R-K11-W2.1192 1193    Acceptance gate ``hardwarejs/test/k11-b2b-cpu-id-substrate.test.ts``1194    asserts (a) every process Worker spawned through the1195    spawn handler reports a cpuId in [1, 7] via1196    ``onVmlinuxReady``, (b) the wasm-Global round-trips1197    ``__wasm32_set_cpu_id(N)`` ↔ ``__wasm32_get_cpu_id() == N``1198    in a standalone instance, and (c) the user binary1199    (hello-k5 ``/init``) still emits its expected stdout1200    with no perturbation.1201 1202K11-B3 — DONE1203  ``num_vcpu`` parameter wired end-to-end. ``BootKernelOptions.num_vcpu``1204  resolves to ``min(navigator.hardwareConcurrency, NR_CPUS=8)``1205  by default; the resolver enforces ``2 <= num_vcpu <= 8``1206  and throws synchronously on out-of-range values BEFORE1207  spawning any Worker. The resolved value is forwarded1208  through three independent surfaces:1209 1210  * ``kernelWorker.mjs`` writes it as ``wasm_boot_info.num_cpus``1211    in the boot prologue. ``arch/wasm32/kernel/setup.c::setup_arch``1212    reads ``bi->num_cpus`` and calls ``set_cpu_possible(cpu, true)``1213    for ``cpu`` in ``[1, num_cpus)``, expanding ``cpu_possible_mask``1214    from the ``boot_cpu_init`` baseline (CPU 0 only) to ``{0..N-1}``.1215    ``setup_nr_cpu_ids`` (called from ``start_kernel`` immediately1216    after ``setup_arch``) then sizes ``nr_cpu_ids`` to ``N``,1217    which is the cap that ``mm/percpu.c::setup_per_cpu_areas``1218    and every ``for_each_possible_cpu()`` loop honours.1219 1220  * ``bootKernel.ts`` passes ``maxCpuId = num_vcpu - 1`` to the1221    default-constructed ``makeKernelSpawnHandler``; the1222    ``cpuRegistry`` allocator hands out cpu_ids from1223    ``[1, maxCpuId]`` (CPU 0 reserved for the kernel Worker).1224 1225  Note: the ``nr_cpus=`` upstream cmdline argument is NOT1226  wired. ``arch/wasm32/kernel/sections.c`` aliases1227  ``__setup_start`` / ``__setup_end`` to a sentinel so the1228  upstream ``__setup("nr_cpus=", nrcpus)`` handler in1229  ``kernel/cpu.c`` is never dispatched. The boot_info1230  ``num_cpus`` field is the single authoritative path for1231  K11-B3; future cmdline-arg work falls under K12+ §151232  shape ``__setup-section-not-walked``.1233 1234  Acceptance gate ``hardwarejs/test/k11-b3-num-vcpu.test.ts``1235  asserts (a) out-of-range / non-integer values throw1236  synchronously, (b) ``num_vcpu=4`` dmesg shows1237  ``cpu_possible_mask expanded to { 0 .. 3 }; HardwareJS1238  intends 4 CPUs``, (c) the spawn handler hands out cpu_ids1239  in ``[1, 3]`` for that boot, (d) the default policy1240  produces a value in ``[2, 8]`` on Node.1241 1242  What this does NOT include (deferred):1243 1244  * Marking CPUs 1..N-1 ``present`` or ``online``. K11-B31245    only marks them ``possible``; transitioning to1246    ``present`` / ``online`` is ``arch_cpu_up``'s job and is1247    deferred behind R-K11-W2 (``arch_cpu_up`` lands when the1248    pty / ldisc / job-control work surfaces a kernel call1249    site that needs cross-CPU coordination).1250 1251K11-B4 — DONE1252  ``max_wasm_instances`` probe + accounting + -EAGAIN1253  enforcement.1254 1255  * Caller surface:1256    ``BootKernelOptions.max_wasm_instances?: number``. When1257    omitted, ``bootKernel`` resolves the value via1258    ``probeMaxWasmInstances()`` — a fast probe that compiles a1259    fixed batch of 16 minimal wasm modules to confirm the engine1260    is healthy, then returns a per-engine fallback (V8/Node:1261    4096; SpiderMonkey: 1024; generic: 256) minus a 4-instance1262    safety margin. The full "compile until rejected" probe per1263    §10.3 is deferred behind a §15-shape note: today's per-engine1264    fallback table is empirically correct for V8 (the only1265    engine HardwareJS targets at K11 close), and a 4096-iteration1266    probe per boot would bloat regression-sweep time. The1267    fallback table grows when a downstream user reports an1268    -EAGAIN under a workload that breaks the assumption.1269 1270  * Constraint: ``max_wasm_instances >= 4`` (must fit at least1271    1 vmlinux Worker + 1 process Worker + 1 instance of safety1272    margin); below-bound throws synchronously BEFORE any Worker1273    is spawned.1274 1275  * Accounting: ``hardwarejs/src/kernelSpawnHandler.ts``1276    threads two synthetic hooks (``__wasmInstanceHooks``) into1277    every spawn. ``reserveWasmInstances`` checks1278    ``count + cost <= maxWasmInstances`` before allocating1279    ``user_memory`` or any other resource; on violation, the1280    handler returns ``-EAGAIN`` (errno 11) immediately.1281    ``releaseWasmInstances`` runs in the Worker's exit / error /1282    onError handlers so every successful reservation pairs with1283    exactly one release. Per-spawn cost is 2 when1284    ``vmlinuxModule`` is supplied (user binary + per-Worker1285    vmlinux) and 1 otherwise (K5..K10 SAB-RPC fallback, retained1286    for backward compatibility through K11-D2).1287 1288  * Acceptance gate1289    ``hardwarejs/test/k11-b4-max-wasm-instances.test.ts``1290    asserts (a) ``max_wasm_instances`` < 4 throws synchronously,1291    (b) non-integer throws synchronously, (c) the default probe1292    resolves successfully through ``bootKernel``, (d) issuing1293    two concurrent fork-mode spawns with1294    ``maxWasmInstances=4`` causes the second to return1295    ``-EAGAIN``.1296 1297  Eviction (per §10.4(2)) remains deferred to v0.2 with the1298  retirement signal "any user reports an EAGAIN under a workload1299  that would have fit under eviction." Logged at K11 close as a1300  §15 row.1301 1302K11-B5 — DONE1303  R-K8-1 retirement: ``__NR_rt_sigaction`` reaches kernel1304  ``do_sigaction``; user-side table demoted/deleted.1305 1306  Landing notes:1307 1308  * ``arch/wasm32/kernel/per_worker.c`` introduced. Exports1309    ``__wasm32_get_init_task_addr()`` (returns ``&init_task`` as a1310    ``u32`` env.kernel_memory offset) AND provides ``init_stack``1311    as a plain ``THREAD_SIZE``-aligned static array. The latter is1312    structural: upstream Linux defines ``init_stack`` via the GNU1313    linker script (``include/asm-generic/vmlinux.lds.h``1314    ``INIT_TASK_DATA``), which our ``wasm-ld`` build bypasses1315    entirely (no GNU linker scripts on ``wasm-ld``). Without1316    a C-side definition the symbol stays undefined and1317    ``--allow-undefined`` zeroes out ``init_task.stack``,1318    tripping the ``wasm_syscall`` ``!current->stack`` guard.1319  * ``hardwarejs/src/worker.mjs`` ``instantiateProcessWorkerVmlinux``1320    binds the per-Instance ``__wasm32_current_task`` global to1321    ``&init_task`` post-ctors. Process Workers now have a non-NULL1322    ``current`` for the in-Worker syscall path.1323  * ``hardwarejs/src/worker.mjs`` ``syscall(13, ...)`` routes to1324    ``vmlinuxInstance.exports.wasm_syscall(13, ...)`` (the K11-B51325    pilot). All other syscalls still take the K5-K9 JS-direct path1326    until the K11-B6/C1/C2 sub-phases retire them.1327  * ``hardwarejs/src/kernelSpawnHandler.ts`` adds an1328    ``onWasmSyscallStats`` callback so vitests can assert "the1329    syscall actually went through ``vmlinuxInstance.exports.wasm_syscall``."1330  * ``hardwarejs/test/k11-b5-rt-sigaction-kernel.test.ts``: green.1331    ``hello-k8-signals`` succeeds end-to-end with stats1332    ``{[__NR_rt_sigaction]: 1}``, proving ``do_sigaction`` runs1333    in the kernel via ``sys_rt_sigaction`` (R-K8-1 RETIRED for1334    ``rt_sigaction``).1335 1336  K11-B5 §15 carry-over: ``R-K11-W3`` (process Workers share1337  ``&init_task`` and its kernel stack). Single-process tests pass;1338  multi-process sigaction-disposition isolation + concurrent1339  syscall stack-safety are flagged for K11-B6/C1 retirement1340  (per-process ``task_struct`` allocation OR per-Worker kernel1341  stack region).1342 1343K11-B6 — DONE1344  R-K8-2 retirement: ``__NR_kill`` / ``__NR_tkill`` /1345  ``__NR_tgkill`` reach kernel handlers.1346 1347  Landing notes:1348 1349  * ``arch/wasm32/kernel/signal.c`` introduces three wasm32-specific1350    arch shims: ``wasm32_sys_kill`` / ``wasm32_sys_tkill`` /1351    ``wasm32_sys_tgkill``. Each validates args (sig in [0, _NSIG],1352    target tid/pid > 0) and submits directly to the K8-B2 signal1353    ring via ``wasm_signal_submit``, bypassing upstream ``sys_kill``'s1354    ``find_task_by_vpid`` + ``send_sig_info`` + ``__send_signal_locked``1355    path. The bypass is necessary because R-K11-W3 init_task-sharing1356    means ``find_task_by_vpid(target_pid)`` would only find1357    ``init_task`` for any process Worker pid; full upstream-clean1358    retirement waits on per-process ``task_struct`` allocation1359    (R-K11-W4 retirement signal).1360  * ``arch/wasm32/kernel/syscall_table.c`` ``[62/200/234]`` now1361    point at thin wrappers that forward to the wasm32 arch shims1362    instead of upstream ``sys_kill`` / ``sys_tkill`` / ``sys_tgkill``.1363  * ``hardwarejs/src/worker.mjs::syscall`` extends the K11-B51364    routing table: ``__NR_tkill`` (200) and ``__NR_tgkill`` (234)1365    always route through ``vmlinuxInstance.exports.wasm_syscall``;1366    ``__NR_kill`` (62) routes when ``pid > 0`` (negative-pid pgrp1367    forms still go through SAB-RPC until R-K9-1 retires at K11-C1).1368    For self-target kills (where the target tid/pid equals the1369    calling Worker's own tid/pid) the worker does an1370    ``Atomics.wait`` on its own ``wake_word`` after ``wasm_syscall``1371    returns to give the K8-B2 ring consumer time to drain (timeout1372    100ms). This makes ``raise(SIGUSR1)`` synchronous so the user1373    binary sees the handler run before the ``raise()`` call returns.1374  * ``arch/wasm32/kernel/k11_b1_probes.c::wasm32_run_k11_b1_probes``:1375    the ``kill(-1, 0)`` probe now accepts ``-ENOSYS`` as a valid1376    return (the wasm32 arch shim returns ``-ENOSYS`` for1377    non-positive pid; the K11-B1 probe was originally written1378    against upstream ``sys_kill`` semantics).1379  * ``hardwarejs/test/k11-b6-kill-family-kernel.test.ts``: green.1380    ``hello-k8-signals`` succeeds end-to-end with stats1381    ``{[__NR_rt_sigaction]: 1, [__NR_tkill]: 1}`` AND a K8-B2 ring1382    delivery for ``sig=SIGUSR1=10`` is observed.1383 1384  K11-B6 §15 carry-overs:1385 1386    * R-K11-W4 (FILED-AT-K11-B6): the wasm32 arch shims bypass1387      upstream signal-delivery mechanics. Full upstream-clean1388      retirement at K11-C1 (with R-K9-1 retirement) or K12+1389      (jobctrl).1390    * R-K8-2 status updated to PARTIAL-CLOSE-AT-K11-B6;1391      FULL-CLOSE-DEFERRED-TO-K11-C1-OR-K12+.1392 1393K11-C11394  R-K9-1 retirement: ``__NR_wait4`` + pipe / fd / stdin / pgrp1395  syscalls reach kernel handlers; JS-direct registries1396  demoted/deleted. Decomposed into K11-C1.a..f.1397 1398K11-C1.a — DONE1399  Per-process ``task_struct`` allocation. R-K11-W3 narrow1400  retirement (process Workers no longer share ``&init_task``1401  wholesale; ``files`` / ``signal`` / ``pending`` / ``stack``1402  are now per-process; ``sighand`` stays shared). Stdin /1403  process-group / pipe / dup2 / wait4 retirements come in1404  K11-C1.b..f.1405 1406  Landing notes:1407 1408  * ``arch/wasm32/kernel/task_alloc.c`` introduced. Exports1409    ``__wasm32_alloc_process_task(pid, ppid)`` which kmallocs1410    a ``task_struct`` + ``THREAD_SIZE`` stack +1411    ``dup_fd(init_files)`` + a kmalloc'd ``signal_struct``1412    (memcpy from ``init_signals`` template, re-init1413    head/list/wait fields). The new task is linked into1414    ``init_task->children`` via ``sibling``. ``sighand`` stays1415    pointing at ``init_sighand`` (R-K11-W3 narrow). PID hash1416    registration via ``alloc_pid`` / ``attach_pid`` is skipped1417    (R-K11-W5).1418  * ``hardwarejs/src/worker.mjs`` ``instantiateProcessWorkerVmlinux``:1419    binds ``current = &init_task`` (K11-B5 transitional), then1420    calls ``__wasm32_alloc_process_task(pid, ppid)`` and re-binds1421    ``current`` to the returned addr. The two-step bind is1422    necessary because the alloc helper itself uses ``kmalloc``,1423    which expects ``current`` valid for lockdep / RCU debug.1424  * ``hardwarejs/src/kernelSpawnHandler.ts``: ``spawnForkWorker``1425    and ``spawnThreadWorker`` now include ``ppid`` in the init1426    payload (parent's pid, used by the alloc helper as1427    informational only at K11-C1.a — the parent pointer goes1428    to ``&init_task`` because ``find_task_by_vpid`` doesn't1429    work yet under R-K11-W5).1430  * ``hardwarejs/src/kernelSpawnHandler.ts``: ``onVmlinuxReady``1431    info now carries ``initTaskAddr`` AND ``perProcessTaskAddr``1432    so vitests can assert the K11-C1.a path actually ran (the1433    two addrs must differ).1434  * ``hardwarejs/test/k11-c1a-per-process-task-kernel.test.ts``:1435    green. ``perProcessTaskAddr != initTaskAddr`` AND the1436    ``hello-k8-signals`` demo still runs end-to-end (signal1437    handler fires on the new task_struct, proving1438    ``current->stack + THREAD_SIZE`` is a valid kernel-stack1439    region for ``wasm_syscall``'s stack-swap).1440 1441  K11-C1.a §15 carry-overs:1442 1443    * R-K11-W3 status: ``FILED-AT-K11-B5; PARTIAL-CLOSE-AT-K11-C1.a;1444      FULL-CLOSE-DEFERRED-TO-K12+`` (per-process ``sighand``1445      allocation waits on execve POSIX semantics for ``sa_restorer``).1446    * R-K11-W5 (FILED-AT-K11-C1.a): ``alloc_pid`` /1447      ``attach_pid`` skipped; ``find_task_by_vpid(non_init_pid)``1448      returns NULL. Cross-target ``kill`` / ``wait4`` /1449      ``setpgid`` use wasm32 arch shims (R-K11-W4 + R-K11-W5)1450      until ``init_pid_ns->child_reaper`` setup lands.1451 1452K11-C1.b — DONE1453  ``__NR_wait4`` (61) routed through ``wasm_syscall`` via the1454  wasm32-specific arch shim ``wasm32_sys_wait4``1455  (``arch/wasm32/kernel/wait.c``). Walks ``current->children``1456  for ``EXIT_ZOMBIE`` matches; reaps via ``reap_zombie`` which1457  ``kfree``\ s the child's task_struct + stack + per-process1458  files / signal allocations. Blocks on1459  ``current->thread.wait_chldexit_wake`` via1460  ``__builtin_wasm_memory_atomic_wait32`` (the wasm321461  cooperative scheduler has no preemptive yield that1462  ``wait_event_interruptible`` could use).1463 1464  Child-side notification: ``__wasm32_notify_child_exit``1465  (exported from ``wait.c``) is called from each process1466  Worker's ``hardwarejs/src/worker.mjs`` SYS_EXIT_GROUP path1467  BEFORE the SAB-RPC tear-down. Sets1468  ``child->exit_state = EXIT_ZOMBIE``,1469  ``child->exit_code``, atomic-increments + atomic-notifies1470  the parent's ``wait_chldexit_wake`` word.1471 1472  Parent-child linkage: ``__wasm32_alloc_process_task`` walks1473  ``init_task.children`` for the parent task (matching ppid),1474  links the new child as a sibling of that parent's children1475  list. For ``pid == 1`` (``/init``) the kernel_init kthread1476  already exists with that pid (created by upstream1477  ``rest_init`` -> ``kernel_thread``); the allocator's1478  ``find_existing_task_by_pid`` dedup detects this and1479  REUSEs the kthread's ``task_struct`` instead of1480  double-allocating. Without this dedup,1481  ``init_task.children`` would have two pid=1 entries1482  and ``find_parent_by_ppid(1)`` for grandchildren would1483  pick the wrong one.1484 1485  Spawn-await ordering: ``hardwarejs/src/kernelSpawnHandler.ts``1486  ``spawnForkWorker`` now awaits the new Worker's1487  ``vmlinux-ready`` message before returning from1488  ``__NR_wasm_spawn_image`` to the parent. This ensures the1489  child's allocator has linked the new ``task_struct`` into1490  the parent's children list BEFORE the parent's subsequent1491  ``waitpid`` runs — without the ordering guarantee, ``wait4``1492  would walk an empty children list and return ``-ECHILD``1493  before the child registers.1494 1495  Acceptance: ``hardwarejs/test/k11-c1b-wait4-kernel.test.ts``1496  asserts (1) hello-k9-wait + hello-k9-child end-to-end with1497  ``WEXITSTATUS == 42``; (2) parent's ``wasmSyscallStats``1498  records nonzero ``__NR_wait4`` (61) routings; (3)1499  ``reap_zombie`` and ``__wasm32_notify_child_exit``1500  pr_info traces appear in process-Worker dmesg; (4) no1501  panic.1502 1503  Carry-overs:1504 1505  - **R-K11-W6 (FILED-AT-K11-C1.b)** in ``docs/ARCHITECTURE.md``1506    §15.3: arch-shim ``wait4`` instead of upstream ``do_wait4``.1507    Retirement signal: K12+ when wait4 features (siginfo,1508    rusage, waitid variants, signal-stop forms via WUNTRACED /1509    WCONTINUED) actually matter for jobctrl tests.1510 1511  - **R-K9-1 (RETIRING-WAIT4-AT-K11-C1.b; FULL-CLOSE-AT-K11-C1.f1512    cleanup-pass)**. ``__NR_wait4`` no longer falls through1513    to the SAB-RPC ``childExitRegistry`` path when1514    ``perProcessTaskAddr != 0`` and ``vmlinux.exports.wasm_syscall``1515    is present. The K11-C1.f cleanup pass DELETES1516    ``childExitRegistry.ts``.1517 1518  - **R-K11-W3 narrow** (init_task children sharing) reduced1519    further: process Workers now have correct parent/child1520    hierarchies via the dedup + ppid-lookup path. R-K11-W31521    closes fully when per-task ``sighand`` allocation lands1522    (K12+).1523 1524K11-C1.c — DONE1525  ``__NR_pipe`` (22) / ``__NR_pipe2`` (293) / ``__NR_dup2`` (33) /1526  ``__NR_dup3`` (292) / ``__NR_close`` (3) routed through1527  ``wasm_syscall``. Kernel-side ``fs/pipe.c`` ``pipe_buffer`` ring1528  + ``files_struct`` (per-process now from K11-C1.a).1529  ``pipeRegistry.ts`` + ``fdTableRegistry.ts`` retired in1530  ``hardwarejs/src/worker.mjs``'s syscall predicate; physical1531  deletion of the registry source files at K11-C1.f cleanup pass.1532 1533  Notes on what landed1534  ~~~~~~~~~~~~~~~~~~~~1535 1536  - ``hardwarejs/src/worker.mjs``: predicate ``routeToWasmSyscall``1537    extended to include ``__NR_PIPE``, ``__NR_PIPE2``, ``__NR_DUP2``,1538    ``__NR_DUP3``, ``__NR_CLOSE``. musl's ``pipe(2)`` wrapper1539    prefers ``SYS_pipe`` when defined; wasm32's musl1540    (``arch/wasm32/bits/syscall.h.in``) defines both, so both must1541    be in the predicate.1542 1543  - Demo: ``wackywasm-tools/tests/hello-k11-c1c-pipe.c``. Single1544    process, no fork: ``pipe(fds)``, ``dup2(fds[1], 7)``, four1545    closes (three valid + one already-closed). Asserts:1546 1547    1. ``pipe()`` returns rc=0 with two distinct fds (kernel1548       allocates ``pipe_inode_info`` + installs both ends in1549       ``current->files``).1550 1551    2. ``dup2(fds[1], 7) == 7`` (kernel installs the write-end at1552       fd 7 by sharing the file ref).1553 1554    3. Three valid closes return 0; close on already-closed fd1555       returns -1 with ``errno=EBADF (9)`` — proves the kernel1556       actually mutated ``current->files``.1557 1558    4. ``wasmSyscallStats`` records nonzero ``__NR_pipe`` (22) +1559       ``__NR_dup2`` (33) + ``__NR_close`` (3) — confirms all1560       three routed via ``wasm_syscall`` rather than SAB-RPC.1561 1562  - Vitest: ``hardwarejs/test/k11-c1c-pipe-kernel.test.ts``.1563    Observed at first green run: ``stats=1:{"3":4,"22":1,"33":1}``.1564 1565  Deferrals carried by K11-C1.c (NOT K11-C1.c blockers)1566  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~1567 1568  - **End-to-end pipe data flow** (``write`` to ``fds[1]`` →1569    ``read`` from ``fds[0]``) requires ``__NR_read`` /1570    ``__NR_write`` to also route through ``wasm_syscall``. That1571    conflicts with K9-era stdin / stdout / stderr handling1572    (JS-direct ``fdTableRegistry`` + ``onUserOutput``) where the1573    kernel-side ``files_struct`` has no entry at fd 0/1/2.1574    Resolution: K11-C1.f cleanup (when ALL syscalls route, ``fd1575    0/1/2`` are kernel-side) plus a host import for console1576    write, OR K12 pty surface that owns fd 0/1/2 properly.1577 1578  - **Cross-process pipe** (parent + child via K9-B31579    spawn-with-actions). K9-B3's spawn-actions apply1580    ``DUP2``/``CLOSE`` on a clone of the parent's1581    ``fdTableRegistry`` entry; with kernel-side1582    ``files_struct``, the spawn-actions must read from the1583    parent's ``current->files`` and write to the child's fresh1584    ``files_struct`` via ``wasm_syscall`` — K11-C1.f cleanup1585    scope. K9 ``hello-k9-pipe`` demo continues to work via the1586    SAB-RPC path until the cleanup pass deletes that path.1587 1588K11-C1.d — DONE1589  ``__NR_setpgid`` (109) / ``__NR_getpgrp`` (111) / ``__NR_setsid``1590  (112) / ``__NR_getpgid`` (121) / ``__NR_getsid`` (124) routed1591  through ``vmlinux.exports.wasm_syscall``. Kernel-side1592  ``kernel/sys.c::sys_setpgid`` etc. mutate1593  ``current->signal->pids[PIDTYPE_PGID/PIDTYPE_SID]`` against the1594  per-process ``signal_struct`` from K11-C1.a.1595 1596  Notes on what landed1597  ~~~~~~~~~~~~~~~~~~~~1598 1599  - ``hardwarejs/src/worker.mjs::routeToWasmSyscall`` predicate1600    extended to include all five pgrp/sid syscall numbers.1601 1602  - Demo: ``wackywasm-tools/tests/hello-k11-c1d-pgrp.c``. Single1603    process. Probes each syscall and emits return value via1604    stdout for the test to scrape.1605 1606  - Vitest: ``hardwarejs/test/k11-c1d-pgrp-kernel.test.ts``.1607    Observed at first green run (running as /init, pid=1):1608 1609    .. code-block:: text1610 1611       getpid                  = 11612       getpgrp_before          = 0   (init_struct_pid -> 0)1613       getpgid_0_before        = 01614       getsid_0_before         = 01615       setpgid_0_0             = 0   (success — kernel succeeded)1616       getpgrp_after_setpgid   = 1   (kernel mutated pgrp to current pid!)1617       setsid                  = -1, errno=EPERM(1)  (already pgrp leader)1618       getsid_0_after          = 0   (sid unchanged)1619       wasmSyscallStats        = {109:1, 112:1, 121:3, 124:2}1620 1621  - Note: musl wasm32 implements ``getpgrp()`` as1622    ``syscall(SYS_getpgid, 0)``, so ``__NR_getpgrp`` (111) does1623    NOT appear in stats even though the demo called ``getpgrp()``.1624    The predicate still forwards both numbers; absence from stats1625    is musl's implementation choice.1626 1627  - Surprising find: setpgid + getpgrp DOES propagate1628    upstream-clean even under R-K11-W5 (no alloc_pid). The kernel1629    ``change_pid`` path mutates the pid_link inside init_struct_pid1630    such that subsequent ``getpgrp`` reads back the caller's pid1631    correctly. Cross-process pgrp semantics (``kill -pgid``) still1632    require R-K11-W5 retirement.1633 1634  Deferrals carried by K11-C1.d1635  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~1636 1637  - ``setpgid(other_pid, ...)`` for non-current targets returns1638    ``-ESRCH`` from the kernel (``find_task_by_vpid`` returns1639    NULL under R-K11-W5). K11-C1 close ledger documents this as1640    a non-blocker for K11 (no demo exercises the cross-process1641    setpgid form).1642 1643  - Cross-process pgrp delivery (``kill(-pgid, sig)`` reaches every1644    task whose ``signal->pids[PIDTYPE_PGID]`` matches): blocked on1645    R-K11-W5 retirement. K9-C2 ``hello-k9-pgrp.c`` demo continues1646    to work via the SAB-RPC path with ``processGroupRegistry``1647    until K11-C1.f cleanup deletes that path.1648 1649K11-C1.e — DONE (docs-only — defer-to-K12 ledger entry)1650  Stdin handling stays JS-direct through K11 close. K9-C1 substrate1651  (``hardwarejs/src/stdinChannel.ts`` + per-tid SAB ring +1652  ``__NR_read`` SAB-RPC handler for fd 0 in1653  ``kernelSpawnHandler.ts::dispatchSyscall``) retained through1654  K11-C1.f cleanup; K11-C1.f deletes the wait4 / pipe / fd / pgrp1655  registries but explicitly preserves the stdin substrate.1656 1657  Filed as ``docs/ARCHITECTURE.md §15.3 R-K11-W7`` with retirement1658  signal pointing at K12 ``pty-model.rst`` (``pty_init`` +1659  ``n_tty_line_discipline`` + ``tty_open`` + per-tid pty fd1660  installation in ``current->files``).1661 1662  Acceptance test for the deferral: K9-C1 stdin demo1663  (``wackywasm-tools/tests/hello-k9-stdin.c``) +1664  ``hardwarejs/test/k9-c1-stdin.test.ts`` continue to pass after1665  K11-C1.f cleanup. The K11-D1 audit pass checks this explicitly.1666 1667  Net K11-C1 close-ledger statement: K11-C1 retires the wait4 /1668  pipe / dup2 / dup3 / close / setpgid / getpgrp / setsid /1669  getpgid / getsid R-K9-1 sub-shortcuts via wasm_syscall routing1670  + per-process task / files / signal allocation. Stdin sub-1671  shortcut survives until K12.1672 1673K11-C1.f — DONE1674  Cleanup pass. The four JS-direct registry source files1675  DELETED outright: ``hardwarejs/src/childExitRegistry.ts``,1676  ``hardwarejs/src/pipeRegistry.ts``,1677  ``hardwarejs/src/fdTableRegistry.ts``,1678  ``hardwarejs/src/processGroupRegistry.ts``. Their1679  ``hardwarejs/src/index.ts`` exports + the corresponding1680  ``KernelSpawnHandlerOptions`` fields removed.1681 1682  ``hardwarejs/src/kernelSpawnHandler.ts::dispatchSyscall``1683  cases DELETED outright (along with their syscall-number1684  constants): ``__NR_wait4`` (61), ``__NR_pipe`` (22),1685  ``__NR_pipe2`` (293), ``__NR_dup2`` (33), ``__NR_dup3`` (292),1686  ``__NR_close`` (3), ``__NR_setpgid`` (109),1687  ``__NR_setsid`` (112), ``__NR_getpgid`` (121),1688  ``__NR_getpgrp`` (111), ``__NR_getsid`` (124),1689  ``__NR_getppid`` (110), ``__NR_wasm_spawn_image_actions``1690  (1025).1691 1692  The ``__NR_kill`` negative-pid (process-group) branch1693  DELETED — negative pid now returns ``-ENOSYS`` from1694  ``dispatchSyscall``. Cross-process ``kill -pgid`` filed as1695  §15.3 row R-K11-W9 (K12 retirement alongside R-K11-W51696  ``alloc_pid`` retirement).1697 1698  ``__NR_kill`` (positive-pid only), ``__NR_tkill``,1699  ``__NR_tgkill``, and ``__NR_rt_sigaction`` cases STAY at1700  K11-C1.f as the SAB-RPC fallback for K8 vitests that don't1701  yet pass ``vmlinuxModule``. Filed as §15.3 row R-K11-W101702  (deferred K8-test migration; K11-D2 retirement signal1703  pins the empty short-circuit set ``{}``).1704 1705  ``__NR_read`` / ``__NR_write`` / ``__NR_writev`` cases1706  collapsed: fd 0 routes through ``stdinChannel`` directly1707  (no ``fdTableRegistry`` lookup); fd 1/2 routes through1708  ``opts.onUserOutput`` directly; any other fd returns1709  ``-EBADF``. The ``writeToFd`` helper retained but stripped1710  to a 4-line surface (``fd 1/2 -> onUserOutput; else -9``).1711 1712  K9-B3 / K9-C1(b) / K9-C2 demos + their vitests DELETED:1713 1714  * ``wackywasm-tools/tests/hello-k9-pipe.c``1715  * ``wackywasm-tools/tests/hello-k9-pipe-child.c``1716  * ``wackywasm-tools/tests/hello-k9-pgrp.c``1717  * ``wackywasm-tools/tests/hello-k9-pgrp-child.c``1718  * ``hardwarejs/test/k9-c1-wait.test.ts``1719  * ``hardwarejs/test/k9-c1-pipe.test.ts``1720  * ``hardwarejs/test/k9-c2-pgrp.test.ts``1721 1722  ``wackywasm-tools/Makefile`` K9-C1(b) pipe-demos and K9-C21723  pgrp-demos blocks replaced with deletion-marker comments1724  pointing at §15.3 rows R-K11-W8 and R-K11-W9 respectively.1725 1726  ``hello-k9-stdin.c`` + ``hardwarejs/test/k9-c1-stdin.test.ts``1727  STAY (R-K11-W7 deferral). The vitest was updated to remove1728  ``makeChildExitRegistry`` / ``makeFdTableRegistry`` /1729  ``makePipeRegistry`` imports — fd 0 read now relies on1730  ``stdinChannel`` alone; with no channel installed, fd 01731  returns EOF.1732 1733  ``hello-k9-wait.c`` + the K11-C1.b ``hardwarejs/test/1734  k11-c1b-wait4-kernel.test.ts`` STAY (R-K9-2 ``wasm_spawn_image``1735  is the spawn primitive — separate retirement track —1736  and ``wait4`` runs kernel-side now).1737  ``childExitRegistry`` reference removed from the K11-C1.b1738  test.1739 1740  ``signalHandler.ts::deliverSignalToTarget`` retained as the1741  shared primitive. Its only K11-C1.f callers are (a) the1742  K8-B2 kernel-ring consumer and (b) the R-K11-W10 SAB-RPC1743  fallback ``__NR_kill`` / ``__NR_tkill`` / ``__NR_tgkill``1744  arms. K11-D2 deletes (b) once the K8 vitests migrate.1745 1746  Regression sweep: 56 vitest files, 141 passed + 2 skipped1747  (full vitest run-all). Every K11-C1.* test green; K11-B61748  K8-vitest fallback signal tests green; K10-A4 initcall1749  dispatch + single-Worker invariant green; K9-C1 stdin1750  green (with-channel + EOF-when-no-channel); K11-C1.b1751  ``__NR_wait4`` kernel routing green.1752 1753  §15.3 rows filed at K11-C1.f: R-K11-W8 (shell-pipeline1754  cross-process fd-inheritance K12 retirement), R-K11-W91755  (cross-process ``kill -pgid`` K12 retirement), R-K11-W101756  (K8-vitest migration to per-Worker vmlinux path).1757 1758  R-K9-1 STATUS FLIP: was PARTIAL-CLOSE-AT-K11-C1.b/c/d; now1759  CLOSED-AT-K11-C1.f modulo R-K11-W7 stdin deferral and the1760  three succession rows above. The K11-D2 close-ledger commit1761  re-validates by spot-checking the deleted-file list +1762  re-running the full vitest sweep.1763 1764K11-C21765  R-K9-3 retirement: user-Worker exit messages route through1766  kernel signal delivery; ``WIFSIGNALED+WTERMSIG`` works.1767 1768K11-C31769  K7/K8/K9 demo regression sweep against the in-Worker syscall1770  path. Every prior demo still passes.1771 1772K11-D1 (DONE — commit a589d7d)1773  Smoke probe: a real user process spawns, calls ``SYS_getpid``1774  via the libc wrapper, the K11-A3+B2a path routes through1775  ``vmlinux.exports.wasm_syscall`` to kernel-side ``sys_getpid``,1776  returns ``current->tgid > 0``, exits 0. The vitest1777  (``hardwarejs/test/k11-d1-smoke.test.ts``) gates1778  ``wasmStats[__NR_GETPID] >= 1`` — proving the K11 axis1779  end-to-end (no SAB-RPC fallback). First-execution wiring gap1780  fixed in the same commit: ``getpid`` was not in the1781  ``worker.mjs::routeToWasmSyscall`` allowlist (the predicate is1782  an explicit per-syscall allowlist, NOT a default-route-all1783  branch) so it had been falling through to SAB-RPC; added1784  ``__NR_getpid`` guarded on ``perProcessTaskAddr != 0``. Not a1785  refutation — a K11-D1 wiring completion the opener anticipated.1786 1787K11-D2 (DONE — this commit; tag v0.1.0-rc12)1788  Close ledger + Appendix A flip + ``docs/ARCHITECTURE.md`` §141789  (K11 → CLOSED, K11.5 row added) + §15 amendments + tag1790  v0.1.0-rc12. Honest close per §20.6.1 (see "K11 CLOSE" history1791  entry below): the opener's "R-K8/9 retire at K11 close" framing1792  over-claimed; the actual close is axis-landed-with-named-1793  deferrals, decomposed in §14 + §15. Cumulative refutation count1794  **0 / ≤ 3**.1795 1796--------------------------------------------------------------------------------179713.5. K11.5 sub-phase decomposition (continuation cleanup pass)1798--------------------------------------------------------------------------------1799 1800K11.5 is the SAME AXIS as K11 (the in-Worker syscall path),1801split out for scope discipline per directive D and the §20.6.11802retroactive dependency check at the K11-C1.f audit. It is NOT a1803new K-phase mission. Budget ≤ 2. Sub-phases shrunk to ACTUAL1804remaining work (the opener's A1..A5 estimate is superseded —1805most of A1..A5 already landed in K11-C1.b..C1.f):1806 1807K11.5-A1 (R-K9-3 wstatus encoding)1808  Pop ``stash@{0}`` (the K11-C2 carryover): POSIX1809  ``WIFSIGNALED+WTERMSIG`` wstatus encoded at child-exit-notify1810  time via a new ``SYS_wasm_exit_by_signal`` (1027) syscall; musl1811  ``__wasm_signal_default_terminate`` calls it instead of1812  ``exit_group(128+sig)``; ``wait.c`` encodes1813  ``wstatus = signaled ? (code & 0x7f) : ((code & 0xff) << 8)``.1814  Rebuild libc + vmlinux. Retires R-K9-3. The K11-C2 child binary1815  calls ``SYS_wasm_exit_by_signal(15)`` directly to route AROUND1816  the cross-Worker raise mis-target (R-K11-W12) — W12 is NOT1817  retired here, only sidestepped.1818 1819K11.5-A2/A3/A4 (narrowed cross-process registry restoration) — DONE (``67ac022``)1820  RESTORED narrowed ``pipeRegistry.ts`` (146) /1821  ``fdTableRegistry.ts`` (149) / ``processGroupRegistry.ts`` (79)1822  (DELETED at K11-C1.f) to cross-process responsibilities ONLY —1823  each < 150 lines per directive D-narrow. Re-added ONLY the1824  cross-process consumer call sites1825  (``__NR_wasm_spawn_image_actions`` at1826  ``kernelSpawnHandler.ts:2014``, ``__NR_kill`` pid<=0 at1827  ``:2147``, ``__NR_getppid`` at ``:2268``); did NOT re-add the1828  in-process short-circuits (those stay deleted — kernel-side at1829  C1.c/.d). Registries are optional ``KernelSpawnHandlerOptions``1830  fields, so non-registry callers are unchanged.1831 1832  .. note:: **§20.5.3 drift fix (K11.5-D2).** A prior revision of1833     this section mapped the W-rows as "W8 = cross-process pipe1834     substrate, W9 = fd inheritance + fd 0/1/2 host-side stdio,1835     W10 = cross-Worker kill(-pgid) + getppid". That was a1836     PRE-IMPLEMENTATION GUESS that drifted from the authoritative1837     ``docs/ARCHITECTURE.md §15.3`` rows (filed at C1.f, matching1838     the code comments). Per the R-K8-2 ruling — *the committed1839     §15 ledger wins over any handoff or doc* — the authoritative1840     mapping is:1841 1842     * **R-K11-W8** = cross-process **fd inheritance /1843       ``spawn_image_actions``**, served JOINTLY by the restored1844       ``pipeRegistry.ts`` + ``fdTableRegistry.ts``.1845     * **R-K11-W9** = cross-Worker **``kill(-pgid)`` member1846       enumeration + ``getppid``** via the restored1847       ``processGroupRegistry.ts`` (``getppid`` folded into W9 per1848       the K11.5-A3 decision — it has no §15.3 home of its own).1849     * **R-K11-W10** = the **K8-vitest ``dispatchSyscall``1850       fallback arms** (``rt_sigaction`` + kill family). NOT a1851       registry citation — it is the A5 item.1852 1853     The live file:line citations live in §15.3 R-K11-W8/W9/W10.1854 1855K11.5-A5 (verify dispatchSyscall fallback shape) — DONE (option b)1856  Confirmed the ``__NR_rt_sigaction`` (``kernelSpawnHandler.ts:1890``)1857  + kill-family (``:2147``) fallback arms are reachable ONLY by K81858  vitests without ``vmlinuxModule`` — ``worker.mjs:808–809`` routes1859  ``rt_sigaction`` to ``wasm_syscall`` unconditionally for vmlinux1860  Workers, and ``worker.mjs:810–816`` routes tkill/tgkill always +1861  kill pid>0. DECISION: KEEP the arms (R-K11-W10 stays OPEN,1862  retirement → K12); the six-K8-vitest migration risks the ≤ 21863  budget. The ``{}`` claim is RE-SCOPED (not global): it applies to1864  the in-process kernel-owned syscall set for the K11-process-Worker1865  path, NOT to the W8/W9 cross-process arms re-added at A2/A3/A4 nor1866  the W10 K8-vitest arms.1867 1868K11.5-D1 — DONE (``1a01ba4``)1869  Deterministic registry smoke probe1870  (``hardwarejs/test/k11-5-d1-registry-smoke.test.ts``) exercising1871  the W8 fd-inheritance clone + action-list + pipe refcount + byte1872  transfer + EOF/EPIPE substrate and the W9 inheritance-only pgrp /1873  parent map (kill(-pgid) walk + getppid). The kernel-side combined1874  round-trip (wait4 + pipe2 + read/write + setpgid against kernel1875  code) is already proven by the C1.b/C1.c/C1.d kernel vitests; the1876  full cross-WORKER pipe DATA round-trip is R-K11-W8-DEFERRED to1877  K12-A1 (no kernel ``copy_from_user_for``).1878 1879K11.5-D2 — DONE1880  Close ledger. R-K9-3 → CLOSED-AT-K11.5-A1. R-K11-W8/W9/W10 gained1881  live file:line citations (see §15.3). §20.5.3 drift fix above1882  reconciles this section's W-row mapping to §15.3. R-K11-W12 stays1883  OPEN (K12-A1). Cumulative refutation count 0 / ≤ 2. Tag1884  ``v0.1.0-rc12.5`` (rc13 reserved for K12 close).1885 1886--------------------------------------------------------------------------------188714. Refutation budget for K111888--------------------------------------------------------------------------------1889 1890**K11 refutation budget: ≤ 3.**1891 1892Reasoning per §20.9 three-input formula:1893 18941. K10 actual: 0. K10 was the smallest K-phase since K1 (the1895   only sub-phase landed was K10-A1..A4 = initcall dispatch).1896 18972. Surface novelty: K11 is structurally novel on four axes:1898 1899   (i)   Kernel-stack-swap entry sequence has never been1900         exercised. ``arch/wasm32/kernel/entry.c::wasm_syscall``1901         may need substantial wasm32-specific work beyond the1902         §6 sketch.1903 1904   (ii)  Populating ``sys_call_table`` will surface upstream1905         syscalls that don't currently link on wasm32 — each is1906         a per-syscall Kconfig / stub / §15 decision.1907 1908   (iii) K8 / K9 kernel-side code (currently unreachable)1909         surfaces first-execution bugs. Expected and acceptable1910         but counts.1911 1912   (iv)  First-time true SMP under load. Spinlock contention1913         patterns, RCU grace-period progress under multiple1914         concurrent Workers may surface upstream-clean code that1915         was authored against assumptions wasm32 violates1916         (e.g., ``cpu_relax`` semantics, ``smp_mb`` cost).1917 19183. Single-axis discipline: K11's axis is "the in-Worker syscall1919   path comes online." The four risk sites are all on that1920   axis. The budget reflects the high novelty + four risk1921   sites.1922 1923If K11 closes at 0–1 actual, the in-Worker path landed cleaner1924than expected. If K11 closes at 4+, K12 should re-budget1925(pty/ldisc/jobctrl work depends on the in-Worker path being1926solid).1927 1928The fourth surface (true SMP under load) may surface late, in1929the K7/K8/K9 regression sweep at K11-C3. Budget pivot per1930``bisect-postmortems.rst`` §20.5 if it does — the budget1931re-evaluation must record the surfaced upstream-clean code path1932and the wasm32-specific divergence.1933 1934--------------------------------------------------------------------------------193515. Acceptance criteria (K11 close)1936--------------------------------------------------------------------------------1937 1938K11 closes when:1939 1940(a) ``arch/wasm32/kernel/entry.c::wasm_syscall`` performs the1941    kernel-stack swap (§6) on every entry / exit. Probe asserts.1942 1943(b) ``sys_call_table`` is populated; every ``__NR_*`` referenced1944    by K7/K8/K9 demos + K12 pty/ldisc/jobctrl wrappers links1945    upstream-clean OR is on the explicit -ENOSYS stub list with1946    a §15 row.1947 1948(c) ``arch_cpu_up`` / ``arch_cpu_down`` light up CPUs 1..N at1949    spawn / down at exit. ``cpu_online_mask`` reflects reality.1950 1951(d) ``num_vcpu`` parameter wired end-to-end. Boot with1952    ``num_vcpu=4`` shows ``num_online_cpus() == 1`` at boot,1953    increments to 2 / 3 / 4 as process Workers spawn.1954 1955(e) ``max_wasm_instances`` probe + accounting + -EAGAIN1956    enforcement. Eviction deferred to v0.2 §15 row.1957 1958(f) R-K8-1 / R-K8-2 / R-K9-1 / R-K9-2 (partial) / R-K9-31959    retired in ``docs/ARCHITECTURE.md`` §15. The five rows'1960    closure notes record the wrong-shaped retirement signal1961    correction.1962 1963(g) R-K10-W1 inverted: assertion now reads "every process1964    Worker carries a vmlinux instance"; runtime1965    ``kernelInstanceCount`` check inverted; source-static guard1966    test inverted.1967 1968(h) Regression: every K7 / K8 / K9 demo passes against the1969    in-Worker syscall path. The R-K8-W1 flake is tracked1970    separately and counted as the same row.1971 1972(i) ``make hwjs-verify`` clean.1973 1974(j) Cumulative K11 refutation count ≤ 3.1975 1976(k) Appendix A populated.1977 1978(l) v0.1.0-rc12 annotated tag placed.1979 1980(m) K12 surface proposal in K11 close ledger.1981 1982--------------------------------------------------------------------------------198316. K10 close ledger amendment + scope correction proposal1984--------------------------------------------------------------------------------1985 1986K10's original mission was "Terminal / pty + line discipline +1987job control + initcall dispatcher" (per the K10 opener doc).1988K10's load-bearing sub-axis was the initcall dispatcher (the1989"means to enable" the terminal subsystems); the terminal1990subsystems themselves were folded under K10's umbrella because1991the K10 opener author anticipated they would ride the existing1992JS-direct syscall surface (the R-K9-1 pattern). The K10-B11993architectural-fork investigation surfaced that anticipation as1994wrong against ``ARCHITECTURE.md`` §4 — the terminal subsystems1995require the in-Worker syscall path that K11 lands.1996 1997Two scope-correction options:1998 1999(i)  Insert K10-B0 (= K11 work) between K10-A4 and K10-B1.2000     K10-B1..K10-D2 as planned, on top of working syscall path.2001     Refutation budget becomes K10's existing ≤ 2 + the K112002     ≤ 3 estimate — total ≤ 5 inside K10's umbrella.2003     Single-axis discipline loses (the in-Worker syscall path2004     is qualitatively different from terminal-layer work; one2005     K-phase trying to land both blurs the axis).2006 2007(ii) Pause K10 at K10-A4 close. K10's mission re-scopes to2008     "the initcall dispatcher landed" — which is the2009     load-bearing sub-axis K10 actually needed. Original2010     K10-B/C/D becomes K12 "PTY + line discipline + job control2011     on the in-Worker syscall path", with its original ≤ 22012     budget intact (against pty-specific risk only). K11 opens2013     between K10 close and K12 open.2014 2015**Recommended: (ii).** Single-axis discipline favours splitting.2016K11 stays within its own ≤ 3 budget; K12 keeps its original2017≤ 2 budget against PTY-specific risk. The K-phase ledger gains2018one row but loses no work; the v0.1.0-rc11 / -rc12 / -rc13 tag2019sequence is consistent with the K-phase shape.2020 2021**K-phase ledger amendments under option (ii):**2022 2023* K10 row (currently "OPEN (A-sub-phase closed; B-sub-phase2024  next)") → CLOSED at v0.1.0-rc11. Mission re-scoped to "real2025  initcall dispatcher; ``__initcall<level>_start/_end`` walks2026  via Strategy B post-link bracket pass; pty / n_tty / devpts2027  subsystems registered at boot." Original "Terminal / pty +2028  line discipline + job control" mission moves to K12.2029 2030* K11 row (NEW) → OPEN. Mission: "Per-Worker vmlinux + real2031  SMP comes online. ``arch/wasm32/kernel/entry.c::wasm_syscall``2032  performs kernel-stack swap; ``sys_call_table`` populated;2033  ``arch_cpu_up`` / ``arch_cpu_down`` hooks; ``num_vcpu`` /2034  ``max_wasm_instances`` JS parameters; R-K8-1/2 + R-K9-1/2/32035  retired; R-K10-W1 inverted." Tag v0.1.0-rc12.2036 2037* K12 row (NEW) → PINNED. Mission: original K10-B/C/D —2038  "Terminal / pty + line discipline + job control on the2039  in-Worker syscall path." Tag v0.1.0-rc13. Budget ≤ 2.2040 2041* D1 row unchanged.2042 2043--------------------------------------------------------------------------------204417. §20.6.1 retroactive dependency-check2045--------------------------------------------------------------------------------2046 2047The K10-A2b commit landed a §11.2.1 clarification stating2048"vmlinux is single-Worker by architectural construction." That2049commit cited ``docs/ARCHITECTURE.md`` §2 + §17 as authority. The2050citation was incomplete: §3 line 124 and ``memory-model.rst``2051§1.1 specify the OPPOSITE — vmlinux is per-Worker.2052 2053By §20.6.1, every "deferred to phase N+1" claim (and by2054extension, every architectural rationale baked into a §15 row)2055needs a written dependency-check. The K10-A2b §11.2.1 claim2056landed without one. The architecture-vs-implementation drift2057check that should have fired:2058 2059::2060 2061    CLAIM: "vmlinux.wasm is single-Worker by architectural2062            construction (per ARCHITECTURE.md §2 + §17)."2063 2064    DEPENDENCY-CHECK (REQUIRED but absent at K10-A2b):2065      Walk every doc site that describes vmlinux instance2066      cardinality and confirm consistency:2067        - ARCHITECTURE.md §2  ← cited (consistent)2068        - ARCHITECTURE.md §3  ← NOT CITED, contradicts the2069                                claim (line 124: "A new instance2070                                of vmlinux.wasm is *also*2071                                instantiated in this Worker")2072        - ARCHITECTURE.md §4  ← NOT CITED, presumes per-Worker2073                                vmlinux (lines 178-186)2074        - ARCHITECTURE.md §17 ← cited (§17.6 says SMP is real,2075                                which presumes multiple kernel2076                                instances writing kernel2077                                memory concurrently)2078        - memory-model.rst §1.1 ← NOT CITED, contradicts the2079                                  claim ("Every Worker — every2080                                  process Worker and the2081                                  dedicated vmlinux Worker —2082                                  holds its own2083                                  WebAssembly.Instance")2084        - memory-model.rst §1.2 ← NOT CITED, presumes2085                                  per-Worker kernel instance2086 2087    HAD THE DEPENDENCY-CHECK FIRED: the §11.2.1 claim would2088    have been refuted at K10-A2b commit time. The K10-A3 fork2089    answer would have been §11.D (per-Worker vmlinux + SMP)2090    rather than the §11.2.1 framing. The R-K10-W1 row would2091    have been filed with the corrected rationale. The K10-A42092    runtime + source-static assertions would have been written2093    in their inverted form from the start.2094 2095    REFUTATION COST PAID: K10-A4 close included the wrong-2096    shaped R-K10-W1 row + the runtime + source-static2097    assertions pinning the wrong invariant. K11 corrects all2098    three. The cost is one K-phase doc-amendment cycle plus2099    the assertion inversion at K11 close. Acceptable but2100    avoidable.2101 2102    LESSON: §20.6.1 dependency-checks for architectural2103    rationales need a "every doc site that describes the2104    invariant" enumeration, not just citations to the sites2105    the author found compelling. The discipline is the same2106    as §20.5 external-tool probes: enumerate exhaustively,2107    classify carefully.2108 2109The §20.5 amendment that lands as part of this opener doc2110adds an "architecture-vs-implementation drift check" to the2111external-tool probe surface. The check is: every §15 row that2112asserts an architectural rationale gets reviewed by walking2113every doc site indexed under the relevant doc-cluster2114table-of-contents, and each site is classified2115"consistent / contradicts / orthogonal." A "contradicts" hit2116either fixes the row OR gets a documented rebuttal in the row2117text. Probe at K11-A2: the source-site snapshot tool gains2118a flag ``--architectural-rationale-check`` that runs the2119walk against the §15 rows.2120 2121--------------------------------------------------------------------------------2122Appendix A — K11 close checklist2123--------------------------------------------------------------------------------2124 2125[X] K11-A1 probes pass (§stack pointer global access, current->2126    stack resolves, THREAD_SIZE consistent)2127[X] K11-A2 source-site snapshot landed; every __NR_* enumerated2128[X] K11-A3 wasm_syscall stack-swap implemented (§6)2129[X] K11-A4 K7/K8/K9 regression sweep against new entry sequence2130    (123 passed + 2 R-K8-W1 flakes at commit d64ffc0)2131[X] K11-B1 sys_call_table populated; -ENOSYS stub list +2132    R-K11-N rows landed2133[X] K11-B2 arch_cpu_up / arch_cpu_down + per-CPU wake words2134    (B2a per-Worker vmlinux + B2b cpu_id substrate)2135[X] K11-B3 num_vcpu wired end-to-end2136[X] K11-B4 max_wasm_instances probe + accounting + -EAGAIN2137    (eviction deferred to v0.2 §15 row)2138[X] K11-B5 R-K8-1 do_sigaction reached for K11 process Workers2139    (CLOSED-FOR-K11-AXIS; residual K8-vitest arm → R-K11-W10)2140[~] K11-B6 R-K8-2 self-target CLOSED (kernel sys_kill/tkill/2141    tgkill reached); CROSS-TARGET deferred to K12 (R-K11-W12,2142    gated on alloc_pid / R-K11-W5)2143[X] K11-C1 R-K9-1 retired for in-process (C1.a..C1.f: kernel2144    wait4/pipe/dup/close/pgrp/sid; registries DELETED; cross-2145    process residue → R-K11-W8/W9/W10); stdin → R-K11-W7 (K12)2146[ ] K11-C2 R-K9-3 retired — NOT DONE (stashed; moves to K11.5-A1)2147[X] K11-C3 K7/K8/K9 demo regression sweep (folded into A4 +2148    the C1.* kernel-path vitests)2149[X] K11-D1 smoke probe: getpid round-trip via wasm_syscall2150    (wasmStats[__NR_GETPID] >= 1; commit a589d7d)2151[X] K11-D2 close ledger + Appendix A flip + tag v0.1.0-rc12 +2152    K12 surface proposal (this commit)2153 2154[X] R-K10-W1 INSTANTIATION-INVERTED: every process Worker2155    carries vmlinux; processVmlinuxInstanceCount enforces; active-2156    overlay idempotency verified (k11-b2a-per-worker-vmlinux.test.ts).2157    SAB-RPC-deletion is progressive (K11.5/K12), not a K11-D2 flip.2158[ ] terminal-model.rst §11.2.1 / §11.B — NOT re-edited at K112159    close per directive ("terminal-model.rst keeps its K10-scoped2160    content"); the K11/K11.5 split is a §20.5.3 drift candidate2161    noted in this ledger, not a terminal-model.rst edit.2162[X] docs/ARCHITECTURE.md §14 K-phase ledger: K11 → CLOSED;2163    K11.5 row added2164[X] docs/ARCHITECTURE.md §15: R-K8-1 CLOSED-FOR-K11-AXIS;2165    R-K8-2 refined three-way PARTIAL-CLOSE; R-K9-1/R-K9-2 closed2166    for in-process; R-K10-W1 CLOSED-AS-INVERTED; R-K9-3 stays2167    OPEN (K11.5-A1); R-K11-W12 FILED; R-K11-W10 retirement → K11.52168 2169--------------------------------------------------------------------------------2170History2171--------------------------------------------------------------------------------2172 2173K11 OPENER (this commit, post-K10-A4-close)2174  Document created. Per-Worker vmlinux contract pinned.2175  In-Worker wasm_syscall entry sequence concretised from2176  memory-model.rst §1.2. sys_call_table population strategy2177  declared. arch_cpu_up / arch_cpu_down hooks specified.2178  num_vcpu and max_wasm_instances parameters introduced.2179  R-K10-W1 inversion plan recorded. R-K8-1 / R-K8-2 / R-K9-12180  / R-K9-2 / R-K9-3 retirement plan recorded. Sub-phase2181  decomposition pinned. Budget ≤ 3 declared. K10 close2182  ledger amendment (option (ii) — pause K10 at K10-A4-close,2183  open K11, defer original K10-B/C/D to K12). §20.6.12184  retroactive dependency-check on the K10-A2b commit2185  recorded. NO CODE LANDS until this doc and the companion2186  amendments to terminal-model.rst + ARCHITECTURE.md are2187  reviewed and the scope-correction option (i) vs (ii) is2188  resolved.2189 2190K11 CLOSE (K11-D2; tag v0.1.0-rc12)2191  Axis claim LANDED: the in-Worker syscall path comes online.2192  ``arch/wasm32/kernel/entry.c::wasm_syscall`` performs the2193  kernel-stack swap (K11-A3); ``sys_call_table`` is populated for2194  the K7/K8/K9 + musl surface (K11-B1); ``arch_cpu_up`` /2195  ``arch_cpu_down`` + per-Worker ``cpu_id`` substrate land (B2a/B2b);2196  ``num_vcpu`` (B3) and ``max_wasm_instances`` + -EAGAIN (B4) are2197  wired end-to-end. A curated per-syscall allowlist routes through2198  ``vmlinux.exports.wasm_syscall`` (rt_sigaction, kill/tkill/tgkill2199  self+positive-target, wait4, pipe/pipe2/dup2/dup3/close,2200  setpgid/setsid/getpgid/getpgrp/getsid, getpid). The K11-D1 smoke2201  probe gates getpid round-trip (``wasmStats[__NR_GETPID] >= 1``).2202 2203  Cumulative refutation count: **0 / ≤ 3**. The W-rows2204  (R-K11-W2..W12) are watched invariants, not refutations; the2205  R-K11-1/2/3 -ENOSYS slots are stub decisions, not refutations.2206 2207  §20.6.1 RETROACTIVE DEPENDENCY CHECK (the honest-close note):2208  the K11 opener claimed "R-K8-1 + R-K8-2 + R-K9-1 + R-K9-22209  (partial) + R-K9-3 retire at K11 close." The K11-C1.f audit2210  REFUTED the completeness of that claim — per-process2211  ``task_struct`` allocation (C1.a) and the C1.b..f routing landed2212  the IN-PROCESS surface, but (1) R-K9-3 wstatus encoding was never2213  done (the K11-C2 work is stashed), (2) cross-process pipe/fd/pgrp2214  substrate cannot retire without kernel-side ``copy_from_user_for``2215  + ``alloc_pid`` (K12-A1), and (3) R-K8-2 cross-target signal2216  delivery mis-resolves under the deferred pid hash (R-K11-W12).2217  Per directive D the phase splits into K11 (axis landing, this2218  close) + K11.5 (R-K9-3 + narrowed-registry restoration) — SAME2219  AXIS, continuation for scope discipline, NOT a new mission. The2220  K11 budget is unaffected (the split is a §20.6.1 dependency-check2221  outcome, not a refutation).2222 2223  ACTUAL R-ROW CLOSE STATE (committed ARCHITECTURE.md §15 is the2224  authority — see the §20.5.3 drift note below):2225    * R-K8-1 — CLOSED-FOR-K11-AXIS (kernel do_sigaction reached;2226      no cross-target dimension); residual K8-vitest arm → R-K11-W10.2227    * R-K8-2 — self-target CLOSED-AT-K11-B6; cross-target2228      DEFERRED-TO-K12 (R-K11-W9 + R-K11-W12, gated on alloc_pid /2229      R-K11-W5); K8-vitest arms → R-K11-W10. (Three-way decomposition2230      in §15.)2231    * R-K9-1 / R-K9-2 — CLOSED for in-process; cross-process residue2232      in R-K11-W8/W9/W10; childImageRegistry stays (K12-A1 signal).2233    * R-K9-3 — OPEN, retirement K11.5-A1.2234    * R-K10-W1 — CLOSED-AS-INVERTED (instantiation inversion +2235      active-overlay idempotency verified). SAB-RPC remains the2236      DEFAULT route for non-allowlisted syscalls; its deletion is2237      progressive (K11.5/K12), not a K11-D2 flip.2238    * R-K11-W12 — FILED at K11-D2 (cross-Worker raise mis-target).2239 2240  §20.5.3 DRIFT FINDING: the handoff doc2241  ``docs/HANDOFF-K11-CLOSE.md §3.4`` drifted from the committed2242  ARCHITECTURE.md §15 R-K8-2 row. The handoff asserted "R-K8-2 =2243  FULL-CLOSE per the 8:40 reframing, JS-direct callers deleted at2244  C1.f"; HEAD contradicts this on two independent grounds — (1) the2245  JS-direct ``deliverSignalToTarget`` arms are LIVE at2246  ``kernelSpawnHandler.ts`` (NOT deleted), (2) the committed R-K8-22247  row says cross-target stays JS-direct gated on alloc_pid (K12).2248  The 8:40 "full close" message was a GUI-corruption artifact (the2249  user confirmed the cursor follow-up channel was broken in that2250  window; the prior agent flagged it as a possible stale replay).2251  RESOLUTION: the committed §15 ledger wins; the handoff §3.42252  R-K8-2 line is superseded. This is the SECOND GUI-corruption2253  near-miss this phase (the first was K11/K11.5 re-litigation);2254  both argue for treating the committed §15 ledger — never a2255  handoff or a chat replay — as the single source of truth for2256  R-row status at tag time.2257 2258  K12 SURFACE PROPOSAL: K12-A1 lands the kernel substrate that2259  retires the K11/K11.5 W-rows — ``copy_from_user_for(tid, ...)``2260  (R-K11-W8 pipe r/w against another task's user_memory),2261  ``wasm32_kill_pgrp`` arch shim (R-K11-W9/W10 kill(-pgid)),2262  ``alloc_pid`` / ``attach_pid`` + ``find_task_by_vpid`` resolution2263  (R-K11-W5 / R-K11-W12 cross-target signal delivery; R-K8-22264  cross-target close), kernel-side ``spawn_with_actions`` fd2265  inheritance, and ``getppid``. Host-side fd 0/1/2 stdio wiring2266  lands as part of K12-B PTY work. Budget ≤ 2 against PTY-specific2267  risk only.