648 lines · plain
1================================================================================2PTY / line-discipline / job-control model for the wasm32 port (K12)3================================================================================4 5:Status: K12 opener (implementation doc). Written docs-first per the6 K12 mandate. **RE-BASELINED post-K11.6** (see §8.0): the7 cross-process pid foundation this opener originally folded into8 K12-A1 was SPLIT to K11.6 and is now DONE + tagged9 ``v0.1.0-rc12.75``; K12 reopens PTY-pure on that base.10:Axis: PTY + line discipline + job control, on a kernel-owned process11 identity (``alloc_pid`` / ``attach_pid`` / pgrp hash, all landed12 at K11.6).13:Tag at close: ``v0.1.0-rc13``. (``v0.1.0-rc12.75`` was NOT unused — the14 K11.6 split it was reserved for happened; see §8.0.)15:Budget: K12 refutation budget reset to ``0 / ≤ 2`` at the K11.6 split16 (R-K12-1 resolved-by-split). The whole budget is available for the17 tty/ldisc surface — job control's hardest dependency (cross-Worker18 group signal delivery) is ALREADY PROVEN at K11.6 (§8.0).19 20.. contents::21 :local:22 :depth: 123 24--------------------------------------------------------------------------------250. Relationship to the other docs (read this first)26--------------------------------------------------------------------------------27 28This is a NEW doc, deliberately not folded into ``terminal-model.rst``.29 30* ``terminal-model.rst`` is the **K10-era design doc**. It was written31 when "terminal / pty / line discipline / job control" was the K1032 mission, before that mission was re-scoped to K12 at the K11 opener33 (``ARCHITECTURE.md`` §14 K10 row: "K10's original ... mission moved34 to K12 because it requires the in-Worker syscall path that K1135 lands"; ``syscall-path-model.rst`` §16 records the scope correction).36 It stays K10-scoped and frozen as the original design intent. Per the37 standing directive it is NOT edited for K12.38 39* **This doc** (``pty-model.rst``) is the K12 *implementation* model: it40 re-states the F-facts with citations against the actually-present41 upstream v6.12 sources, records the K12-A1 cross-process foundation42 decision (the scoping fork), maps the W-row retirement cascade to43 sub-phases, and pins the sub-phase decomposition + acceptance + budget.44 45* ``process-model.rst`` §4 (pipe / pipefs contract) + §6 (stdin wiring)46 own the cross-process I/O substrate. The PTY master/slave buffers47 reuse the same shared-``env.kernel_memory`` insight (§2 below).48 49* ``ARCHITECTURE.md`` §15.3 R-rows are the authoritative debt ledger.50 Where this doc and §15.3 disagree, **§15.3 wins** (the R-K8-2 ruling,51 re-affirmed by the K11.5-D2 §20.5.3 drift fix). Citations here are a52 convenience, not a second source of truth.53 54Cross-references to upstream are to the in-tree clone at55``linux-wasm/linux/`` (v6.12); line numbers are snapshot-time and56re-pinned at each sub-phase's A2 source-site read per the §17.7 lesson57("decide forks at A2 after reading bodies, not function names").58 59--------------------------------------------------------------------------------601. F-facts: the PTY pair contract (ptmx / pts)61--------------------------------------------------------------------------------62 63**F1.1 — A pty pair is two coupled tty_structs.** Upstream64``drivers/tty/pty.c`` (947 lines) registers two drivers at boot:65``ptm_driver`` (master, ``/dev/ptmx``) and ``pts_driver`` (slave,66``/dev/pts/N``). ``ptmx_open()`` (``pty.c``) allocates a slave index67via the devpts IDR, creates the slave device node under68``/dev/pts/``, and couples ``master->link == slave`` /69``slave->link == master``. Bytes written to one end appear on the70other end's input path. Registration happens in ``pty_init()``, an71``initcall`` that **already runs at boot on wasm32** as of K10-A3 (the72post-link initcall bracket pass; ``ARCHITECTURE.md`` §14 K10 row +73``terminal-model.rst`` §3, §10). So the driver objects exist; what K1274adds is the *syscall path* that opens and drives them.75 76**F1.2 — devpts is the slave-side filesystem.** ``fs/devpts/inode.c``77(618 lines) implements the ``devpts`` filesystem mounted at78``/dev/pts``. ``devpts_new_index()`` allocates the slave number;79``devpts_pty_new()`` creates the inode. ``init_devpts_fs()`` (initcall)80registers the fs type; it runs at boot via the same K10-A3 path. The81slave path string (``/dev/pts/3``) is returned to userspace by82``ptsname()`` via ``ioctl(ptmx_fd, TIOCGPTN, &n)``.83 84**F1.3 — the open sequence userspace expects.**85 86.. code-block:: c87 88 int m = posix_openpt(O_RDWR | O_NOCTTY); /* open("/dev/ptmx") */89 grantpt(m); unlockpt(m); /* ioctl TIOCSPTLCK=0 */90 char *s = ptsname(m); /* ioctl TIOCGPTN */91 int slave = open(s, O_RDWR); /* open("/dev/pts/N") */92 93The syscalls on this path: ``openat`` (×2), ``ioctl`` (TIOCSPTLCK,94TIOCGPTN), and on the slave fd, ``read`` / ``write`` / ``ioctl``95(termios). All of these are now in-Worker ``wasm_syscall`` candidates96(K11 landed the per-Worker vmlinux path). The blocker is NOT the driver97(it boots) but uaccess for the ``ioctl`` arg structs and the98fd→tty_struct resolution living in ``current->files`` (per-process as99of K11-C1.a). See §2 for the wasm32-specific buffer-flow shape.100 101--------------------------------------------------------------------------------1022. The wasm32 PTY buffer-flow shape (the shared-kernel-memory insight)103--------------------------------------------------------------------------------104 105The K11.5 cross-Worker pipe-data deferral (R-K11-W8) and the PTY106master/slave coupling share ONE enabling fact, worth stating plainly107because it governs the whole K12 I/O design:108 109**F2.1 — ``env.kernel_memory`` is a single shared SharedArrayBuffer110imported by every wasm module in the system.** (``hardwarejs/src/111physRam.ts`` lines 4–11, 43–61: "one shared ``WebAssembly.Memory`` ...112imported by every wasm module ... Backed by a SharedArrayBuffer so the113main thread + every Worker can memcpy into it directly.") Each114process Worker runs its OWN vmlinux ``WebAssembly.Instance`` (separate115code + kernel stack = an SMP "CPU"), but they all share ONE kernel116address space. ``init_task``, ``init_pid_ns.idr``, the slab heap, and117any ``kmalloc``'d tty/pipe buffer are at the same physical bytes for118every Worker.119 120**F2.2 — consequence for PTY (and pipe) data.** A tty flip buffer or121pipe ring allocated by ``kmalloc`` lives in shared kernel memory.122Worker A's ``write(slave_fd, ...)`` does ``copy_from_user`` from A's123OWN ``env.user_memory`` (``current == A``) into the kernel buffer;124Worker B's ``read(master_fd, ...)`` does ``copy_to_user`` into B's OWN125``env.user_memory`` (``current == B``). **Neither side needs126``copy_*_user_for(other_tid)``** — the cross-Worker hop happens through127the shared kernel buffer, and each endpoint only ever touches128``current``'s user memory. This is why the PTY data path is tractable129at K12 even though ``copy_from_user_for`` (process-model.rst §4) is130still deferred.131 132**F2.3 — what the cross-Worker hop DOES still need.** Both endpoints133must hold fds that resolve to the SAME kernel ``tty_struct`` (or pipe134``struct file``). That requires the child's per-process135``files_struct`` to inherit the parent's ``struct file *`` at spawn —136i.e. kernel-side ``spawn_with_actions`` (clone parent ``files`` +137apply dup2/close). That is the residual R-K11-W8 work; it is138**K12-B-scoped** (it is the same machinery a PTY-driven shell needs to139wire a child's stdin/stdout to a pts fd) and is NOT part of the K12-A1140foundation. See the cascade in §9.141 142--------------------------------------------------------------------------------1433. F-facts: the N_TTY line-discipline state machine144--------------------------------------------------------------------------------145 146**F3.1 — N_TTY is the default ldisc.** ``drivers/tty/n_tty.c`` (2560147lines) implements the canonical line discipline. ``n_tty_init()``148(initcall, boots via K10-A3) registers it; every tty defaults to it.149The ldisc is the state machine between raw bytes and the slave-fd150reader.151 152**F3.2 — canonical vs raw (ICANON).** In canonical mode153(``ICANON=1``, default), ``n_tty_receive_buf`` accumulates a line154buffer (``ldata->read_buf``) and only releases a line to ``read()`` on155``\n`` / EOL / EOF (Ctrl-D). Erase (Ctrl-H / DEL) and kill (Ctrl-U)156edit the buffer; ECHOE/ECHOK control whether the edits are echoed. In157raw mode (``ICANON=0``), bytes pass straight through subject to158``VMIN`` / ``VTIME``. (terminal-model.rst §4 has the long-form159narrative; this is the citation pin.)160 161**F3.3 — output post-processing (OPOST).** Slave-side ``write()``162output is post-processed by ``do_output_char`` (LF→CRLF when163``OPOST|ONLCR``) on the way to the master read side.164 165**F3.4 — echo (ECHO).** Master-side input is echoed to the master166output queue so the user sees their typing; ``n_tty`` owns the echo167buffer. Programs disable ECHO via ``tcsetattr`` for password entry.168 169**F3.5 — wasm32 wiring is only the two endpoints.** Per170terminal-model.rst §4(a)/(b): bytes flow xterm → HardwareJS →171``tty_insert_flip_string`` (master input) and master output → a172HardwareJS polling consumer → ``term.write``. The ldisc state machine173itself is upstream-unmodified. The arch work is (a) a kernel→JS hook to174drain master output and (b) a JS→kernel entry to inject master input;175both are ``tty_struct`` driver-ops shaped, pinned at the K12-B A2 read.176 177--------------------------------------------------------------------------------1784. F-facts: controlling terminal179--------------------------------------------------------------------------------180 181**F4.1 — session leader claims a ctty.** ``drivers/tty/tty_jobctrl.c``182(593 lines): a session leader (made by ``setsid()``, which routes183kernel-side as of K11-C1.d) claims a controlling tty via184``ioctl(fd, TIOCSCTTY)`` → ``tiocsctty()`` → ``__proc_set_tty()``,185which sets ``tty->session`` / ``current->signal->tty``.186 187**F4.2 — /dev/tty.** ``open("/dev/tty")`` resolves to188``current->signal->tty`` via the ``TTY_MAJOR=5, TTY_MINOR=0`` special189device (``drivers/tty/tty_io.c``). The devtmpfs node must exist in the190initramfs (terminal-model.rst §6); K12-B verifies/creates it.191 192**F4.3 — SIGHUP on leader death.** When the session leader exits,193``__do_SAK`` / ``disassociate_ctty`` sends SIGHUP to the foreground194pgrp. This rides on the same ``kill_pgrp`` machinery as §6.195 196--------------------------------------------------------------------------------1975. F-facts: foreground / background process groups198--------------------------------------------------------------------------------199 200**F5.1 — tcsetpgrp / tcgetpgrp.** ``tty_jobctrl.c``:201``ioctl(fd, TIOCSPGRP, &pgid)`` sets ``tty->pgrp``;202``TIOCGPGRP`` reads it. Bash uses these for ``fg`` / ``bg``.203 204**F5.2 — background-read → SIGTTIN, background-write+TOSTOP →205SIGTTOU.** ``__tty_check_change()`` (``tty_jobctrl.c``) compares206``current``'s pgrp against ``tty->pgrp`` and calls207``kill_pgrp(pgrp, SIGTTIN/SIGTTOU, 1)`` for the background case (or208returns ``-EIO`` if the signal is ignored/blocked). This is the209job-control core.210 211**F5.3 — all of F5 needs cross-process ``kill_pgrp``.** ``kill_pgrp``212walks ``pgrp->tasks`` (the ``PIDTYPE_PGID`` hlist on a ``struct pid``).213That hlist is empty until tasks are registered via214``alloc_pid`` + ``attach_pid(p, PIDTYPE_PGID)``. **This is the hard215dependency that makes the K12-A1 foundation (§8) a prerequisite for216all job control** — without it, every ``kill_pgrp`` in §4/§5/§6 hits an217empty list.218 219--------------------------------------------------------------------------------2206. F-facts: control-char → signal generation221--------------------------------------------------------------------------------222 223**F6.1 — ISIG intercepts.** In canonical + ``ISIG`` mode,224``n_tty_receive_signal_char()`` intercepts the special chars before the225line buffer:226 227* Ctrl-C (``VINTR``, 0x03) → ``SIGINT`` to ``tty->pgrp``.228* Ctrl-Z (``VSUSP``, 0x1A) → ``SIGTSTP`` to ``tty->pgrp``.229* Ctrl-\\ (``VQUIT``, 0x1C) → ``SIGQUIT`` to ``tty->pgrp``.230 231via ``__isig(sig, tty)`` → ``kill_pgrp(tty->pgrp, sig, 1)``. The char232is consumed, not delivered to the app (unless IEXTEN literal-next).233 234**F6.2 — delivery rides the K8 path.** ``kill_pgrp`` →235``group_send_sig_info`` → ``__send_signal_locked`` →236``arch_signal_wake_target`` (K8-B2 ring) → ``deliverSignalToTarget``237→ the per-tid pending region + ``Atomics.notify``. So control-char238signals work end-to-end ONLY once ``kill_pgrp`` can enumerate the pgrp239(F5.3) AND cross-target delivery resolves the right Worker (R-K11-W12).240Both are K12-A1 (§8).241 242--------------------------------------------------------------------------------2437. F-facts: termios (tcgetattr / tcsetattr)244--------------------------------------------------------------------------------245 246**F7.1 — termios is per-tty kernel state.** ``tcgetattr(fd, &t)`` /247``tcsetattr(fd, opt, &t)`` are ``ioctl(TCGETS/TCSETS[W|F])`` reading /248writing ``tty->termios`` (``struct ktermios``) in ``tty_io.c``. The249``c_iflag`` / ``c_oflag`` / ``c_lflag`` / ``c_cc[]`` fields drive every250ICANON/ECHO/ISIG/OPOST decision above.251 252**F7.2 — the only arch work is uaccess for the arg struct.** The253ioctl arg is a userspace ``struct termios`` pointer; the handler254``copy_from_user`` / ``copy_to_user`` against ``current``'s255``env.user_memory`` (in-Worker, ``current``-local — no256``copy_*_user_for`` needed, F2.2). ``-ENOTTY`` is returned for a257non-tty fd, which is why pre-K12 ``tcgetattr(0)`` fails258(terminal-model.rst §1): there's no controlling tty yet, only stdio259fixtures.260 261--------------------------------------------------------------------------------2628. Process-identity foundation — DONE at K11.6 (was the K12-A1 scoping fork)263--------------------------------------------------------------------------------264 2658.0 RE-BASELINE (post-K11.6, ``rc12.75``) — read this instead of §8.1+266~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~267 268Everything §8.1 below frames as "K12-A1 cross-process foundation" was269SPLIT off to **K11.6** (refutation R-K12-1; the FOLD decision missed the270pid/tid *authority* axis) and is now **landed + tagged ``v0.1.0-rc12.75``**.271The original §8.1+ FOLD analysis is retained as provenance only; for the272current state read this subsection.273 274**DONE at K11.6 (no longer K12 work):**275 276* **Kernel owns pid allocation.** ``__wasm32_alloc_process_task`` calls277 ``alloc_pid(&init_pid_ns, NULL, 0)`` NATURALLY (no ``set_tid``);278 kthreads keep 0/1/2…, ``/init`` keeps pid 1 (kthread-reuse path),279 children get next-free. The child reads its kernel pid280 (``__wasm32_task_pid`` export) and posts it up the ``vmlinux-ready``281 handshake; HardwareJS keys every per-Worker SAB structure by the282 kernel pid (shape ii; watched row R-K11.6-W1). ``nextThreadTid`` retired283 to ``nextLegacyRoutingId`` (legacy/non-vmlinux + thread routing only).284* **R-K11-W5 (alloc_pid / attach_pid / find_task_by_vpid) — RETIRED.** The285 global pid hash is populated cross-Worker in the shared286 ``env.kernel_memory``; ``getpid()`` / ``getppid()`` return real kernel287 pids end-to-end.288* **R-K11-W9 (kill(-pgid) + getppid) — RETIRED.** ``wasm32_sys_kill``289 handles ``pid<=0`` kernel-side, walking the real process group via290 ``do_each_pid_task(PIDTYPE_PGID)``; ``task_alloc.c`` promotes the reused291 ``/init`` kthread to session + process-group leader (``change_pid``) so292 children inherit a coherent ``{/init, …}`` group. ``getppid`` routes via293 ``__NR_GETPPID`` kernel-side.294* **R-K11-W12 (cross-Worker raise mis-target) — RETIRED.** The kernel pid295 is the single routing key; the signal ring delivers each slot to the296 Worker registered under that pid. **Proven** by297 ``k12-a1-characterization.test.ts``: a child's ``kill(0,SIGTERM)``298 reaches the PARENT in a *different* Worker.299 300**Why this de-risks K12 (the budget is genuinely available):** job301control's single hardest dependency is **cross-Worker group signal302delivery** — ``kill_pgrp`` reaching every member across Worker303boundaries. That is exactly what K11.6 proved (the characterization demo304is a cross-Worker ``kill(0)`` round-trip). So Ctrl-C → ``SIGINT`` to the305foreground pgrp (§6), SIGTTIN/SIGTTOU (§5), and SIGHUP-on-leader-death306(§4) all ride machinery that is **already green**. The ``≤ 2`` K12 budget307is reserved for the tty/ldisc *surface* (the pty pair, N_TTY state308machine, ctty acquisition, termios), not for the signal substrate.309 310**What is NOT in the K12 PTY axis** (placement decided here per the311single-axis + decision-rule discipline; see §9 for the per-row mapping):312 313* **R-K11-W7 (stdin → tty / N_TTY / ldisc) — IN K12** (squarely the PTY314 axis: it IS the master-input injection endpoint, F3.5). Lands in K12-A.315* **R-K11-W8 (spawn-with-actions fd inheritance + cross-process pipe-DATA316 round-trip) — DEFERRED to a follow-on (K13), NOT K12.** It is a317 shell-pipeline need, not a tty need: the PTY axis is demonstrable end-318 to-end without it (a process ``open("/dev/pts/N")``\ s its own slave fd;319 ctty/fg-pgrp/termios/ctrl-char all work on that fd). And the320 ``spawn_with_actions`` primitive carries the SAME JS-direct-spawn321 chicken-egg as the K11.6 pid problem (no parent kernel context at322 ``wasm_spawn_image`` request time; ``kernelSpawnHandler.ts``), so323 folding it risks a 2nd refutation against the PTY budget — exactly the324 shape we just avoided. Keeping it out protects the tty/ldisc budget.325* **R-K11-W10 (K8-vitest fallback-arm migration + the registry deletions326 it gates) — DEFERRED to the same follow-on (K13), NOT K12.** It is pure327 test-migration + dead-code retirement, orthogonal to the PTY axis. The328 ``processGroupRegistry`` / ``pipeRegistry`` / ``fdTableRegistry`` are329 already NARROWED to the legacy non-vmlinux fallback (K11.6 / K11.5), so330 they are inert for every vmlinux Worker; their full deletion belongs331 with the W8 pipe/fdTable deletion in one cleanup phase, after the six332 K8 vitests are migrated to ``vmlinuxModule``.333 334 *Decision-rule escape:* if a PTY-driven-shell demo turns out to NEED335 W8 fd-inheritance to wire a child to a pts (i.e. ``open("/dev/pts/N")``336 is insufficient for the acceptance), AND the ``spawn_with_actions``337 primitive forces a 2nd-refutation-risk mechanism, that is a split-338 forcing surface to be surfaced (the same shape as the K12-A1 spawn339 round-trip), not absorbed silently.340 341.. note::342 343 **UPDATE (K12-A1 implementation — the escape hatch below was exercised;344 FOLD → SPLIT). Refutation R-K12-1.** The FOLD decision recorded in this345 section evaluated the two *named* structural-novelty triggers346 (shared-sighand; per-Worker-vmlinux vs global pid ns) and correctly347 found both false — but it missed a THIRD axis: the **pid/tid AUTHORITY348 model**. The implemented spawn flow makes JS the pid authority349 (``nextThreadTid`` → ``__wasm32_alloc_process_task(pid=JS-tid)`` →350 ``alloc_pid(set_tid=JS-tid)``), which (a) collides with the kernel idr's351 boot pids (``alloc_pid(set_tid=2)`` → ``-EEXIST``; kthreadd owns 2) and352 (b) contradicts the documented kernel-owns-allocation design (§3 L123 +353 §4 ``spawn_worker(pid,…)`` ABI). A characterization sweep showed the JS354 re-key surface is BROAD (7 tid-keyed structures, 3 ``allocThreadTid``355 sites, process+thread, touches the signal-pending/wake-word registry),356 so the foundation **SPLITS to K11.6** "pid-namespace reconciliation:357 kernel owns pid allocation" (``rc12.75``, fresh ``≤ 2`` budget), keeping358 K12 PTY-pure (budget reset 0/2). The reconciliation uses **shape (ii)**:359 the child boot ``alloc_pid``s NATURALLY (no ``set_tid``; kthreads keep360 0/1/2…, ``/init`` keeps pid 1 via the kthread-reuse path, children get361 next-free), the Worker reads its pid and posts it via the existing362 ``vmlinux-ready`` handshake, and JS defers all per-Worker SAB-structure363 registrations to AFTER that await, keyed by the kernel pid;364 ``nextThreadTid`` is retired. **Shape (i)** — route ``wasm_spawn_image``365 through the parent's kernel so the pid is allocated *before*366 ``spawn_worker`` (matching §3 L123 / §4 literally) — was REJECTED here:367 ``wasm_spawn_image`` is JS-direct on the main thread with no kernel368 context at spawn time, so shape (i) would rebuild the kernel-routed369 spawn primitive K9 deferred (R-K9-2) — 2nd-refutation exposure. The370 shape-(ii) docs↔ABI timing divergence is tracked by the watched row371 **R-K11.6-W1** (NOT a refutation; retires when ``wasm_spawn_image``372 routes through the parent's kernel). Full record: ``ARCHITECTURE.md``373 §15.3 R-K12-1 + R-K11.6-W1, §20.6.3 §20.6.1 retro-note, and the K11.6 +374 K12 ledger rows. The FOLD reasoning below is retained for provenance.375 3768.1 Original FOLD analysis (SUPERSEDED by §8.0 — provenance only)377~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~378 379The PTY/job-control axis (§3–§7) has a hard floor: **kernel-side pgrp380enumeration and correct cross-target signal delivery**, which need the381PID hash populated (``alloc_pid`` + ``attach_pid``). That is the382cross-process debt currently carried JS-side by the narrowed registries383(``processGroupRegistry`` / ``pipeRegistry`` / ``fdTableRegistry``,384restored at K11.5) and gated on **R-K11-W5** (``alloc_pid`` /385``attach_pid`` / ``find_task_by_vpid``).386 387The K12 mandate poses a scoping fork, to be resolved at A2 source-site388snapshot time AFTER reading the ``alloc_pid`` call sites and the389R-K11-W3 per-process ``task_struct`` shape (§17.7 discipline). The390reads have been done; the decision follows.391 392**DECISION: FOLD the ``alloc_pid`` / ``attach_pid`` /393``find_task_by_vpid`` foundation into K12-A1. Keep PTY as K12-B+. One394K12 phase, ``≤ 2`` refutation budget covering both. NO K11.6 split;395``rc12.75`` is NOT used.**396 397*Why no structural novelty (the split trigger fails on both examples398the mandate named):*399 4001. **"per-Worker vmlinux instantiation interacts badly with the global401 pid namespace" — FALSE.** ``env.kernel_memory`` is a single shared402 SAB across all per-Worker vmlinux instances (F2.1,403 ``physRam.ts:4-11,57-61``). ``init_pid_ns.idr`` therefore lives at404 one physical location visible to every Worker; a ``struct pid``405 allocated by Worker A is found by Worker B's406 ``find_task_by_vpid``. A global pid hash is *coherent by407 construction* — this is the intended SMP-shared-RAM model, not a bad408 interaction. (The cross-Worker concurrency on409 ``pidmap_lock`` / ``idr`` is real SMP contention, the same class the410 K11 axis already brought online and that ``task_alloc.c`` already411 serializes with ``wasm32_task_alloc_lock``; it is upstream-with-412 precedent, not novelty.)413 4142. **"shared-sighand conflicts with pid-hash expectations" — FALSE.**415 R-K11-W3 keeps ``t->sighand == &init_sighand`` shared.416 ``attach_pid`` touches ``task->pid_links[type]`` /417 ``task->thread_pid`` / ``signal->pids[type]`` (``kernel/pid.c:330-418 344``); it never reads or writes ``sighand``. The two are419 orthogonal. Per-process ``sighand`` (the W3 full close) is a420 *separate* K12+ item that the PTY axis only forces if a test421 installs different dispositions in two sibling Workers (W3 §15 close422 criterion) — not required by the A1 foundation.423 424*Why the fold is viable (verified at source-site, correcting the425R-K11-W5 row's stated blockers per §17.7):*426 427* ``init_pid_ns.child_reaper = &init_task`` is **statically set**428 (``kernel/pid.c:84``) — already true at boot. The R-K11-W5 row's429 "child_reaper not set" half is stale (it predates reading the static430 initializer); only the caps half remains, and it resolves favorably:431* ``pid_idr_init()`` is a direct call in ``start_kernel``432 (``init/main.c:1075``), NOT in ``initcall-skip-list.txt``, so433 ``init_pid_ns.pid_cachep`` + ``idr_init`` are live before any process434 Worker spawns — ``alloc_pid``'s ``kmem_cache_alloc`` won't NULL-deref.435* The ``set_tid`` path (``alloc_pid(&init_pid_ns, &tid, 1)``, needed so436 the kernel pid == the JS-assigned Worker tid and the cross-layer pid437 contract holds) requires ``checkpoint_restore_ns_capable(user_ns)``438 (``kernel/pid.c:210``). Under the shared ``init_cred`` (CAP_FULL_SET)439 this **passes** for ``init_user_ns``. (The R-K11-W5 row cited440 init_cred sharing as a *reason it fails*; reading the body shows full441 caps mean it *passes* — exactly the §17.7 "read bodies not names"442 correction. Re-verified by a boot probe at A1 implementation time.)443 444*Residual A1 risks (absorbed by the ``≤ 2`` budget, NOT novelty):*445``idr_preload`` per-cpu / ``local_irq`` semantics on wasm32; the446``attach_pid`` ordering for PGID/SID (must set ``signal->pids[PGID]`` =447the inherited pgid's ``struct pid`` before attach); and whether448``pidmap_lock`` (a plain ``spin_lock_irq``) is sufficient under genuine449cross-Worker concurrency. Each is a known upstream-integration mechanic.450 451*Escape hatch (kept, not exercised):* if A1 implementation surfaces an452ACTUAL structural novelty (e.g. ``idr_preload``'s per-cpu model proves453fundamentally incompatible with per-Worker instances, or454``pidmap_lock`` cannot be made cross-Worker-correct without a new455primitive), the work splits to **K11.6** at that point with its own456``≤ 2`` budget and an ``rc12.75`` tag, and a ``§20.6.1`` note is filed.457This opener's standing decision is FOLD; the split is a contingency the458implementation may invoke and document, per the mandate.459 460A confirming note is filed at ``ARCHITECTURE.md §20.6`` (K12 opener461fork resolution).462 463--------------------------------------------------------------------------------4649. W-row retirement cascade (explicit, per sub-phase + acceptance)465--------------------------------------------------------------------------------466 467Each row's authoritative text is ``ARCHITECTURE.md §15.3``; this maps468the *cascade* as re-baselined post-K11.6. The original469"all four retire inside K12" plan changed: W5/W9/W12 retired at **K11.6**;470W7 is the only PTY-axis row left for K12; W8/W10 deferred to a follow-on471(see §8.0 for the placement reasoning).472 473**R-K11-W5 (alloc_pid / attach_pid / find_task_by_vpid) — RETIRED at474K11.6.** PID-hash registration landed in ``__wasm32_alloc_process_task``475(natural ``alloc_pid`` + ``attach_pid`` for PID/TGID/PGID/SID).476``find_task_by_vpid`` resolves cross-Worker; ``getpid()``/``getppid()``477return real kernel pids. (Was the K12-A1 keystone; the split moved it.)478 479**R-K11-W9 (kill(-pgid) + getppid) — RETIRED at K11.6.** ``wasm32_sys_kill``480walks the real PGID hlist kernel-side; ``getppid`` routes via481``__NR_GETPPID``. The ``processGroupRegistry`` is NARROWED to a legacy482non-vmlinux fallback (NOT deleted — its full deletion is W10-gated, see483below). The §10 characterization demo reproduces identical observable484results kernel-side.485 486**R-K11-W12 (cross-Worker raise mis-target) — RETIRED at K11.6.** The487kernel pid is the single routing key; the signal ring delivers to the488Worker registered under that pid. Proven by the §10 demo (child→parent489cross-Worker ``SIGTERM``).490 491**R-K11-W7 (stdin → tty / N_TTY / ldisc) — K12 (the one PTY-axis row).**492Today stdin is a JS-direct per-tid queue (process-model.rst §6). The PTY493axis replaces it with the real tty input path: master-side input is494injected via ``tty_insert_flip_string`` into the N_TTY ldisc, and the495slave-fd ``read`` drains the line buffer (F3.5). *Acceptance:* a slave496``read`` returns a line assembled by N_TTY from master-injected bytes;497ICANON line-buffering + ECHO observed. Lands in **K12-A** (open path +498endpoints) and is exercised through **K12-B** (ldisc behaviors).499 500**R-K11-W8 (spawn-with-actions fd inheritance + pipe-DATA round-trip) —501DEFERRED to follow-on (K13), NOT K12.** Shell-pipeline need, not a tty502need; carries the ``spawn_with_actions`` JS-direct chicken-egg503(2nd-refutation risk); the PTY axis is demonstrable without it (§8.0).504*Follow-on acceptance:* ``pipeRegistry.ts`` + ``fdTableRegistry.ts``505deleted; ``cmd1 | cmd2`` cross-Worker pipe-DATA round-trip green506kernel-side.507 508**R-K11-W10 (K8-vitest migration + registry deletions) — DEFERRED to the509same follow-on (K13), NOT K12.** Pure test-migration + dead-code510retirement; the registries are already inert legacy fallbacks for511vmlinux Workers. *Follow-on acceptance:* the six K8 vitests512(``k8-c1-signals`` / ``-mt`` / ``-default-term`` / ``k8-b3-trampoline`` /513``k8-b4-default-action`` / ``k8-b5-mask``) migrated to ``vmlinuxModule``;514the ``dispatchSyscall`` kill/sigaction fallback arms + ``processGroupRegistry``515deleted; short-circuit set literally ``{}``.516 517**K12 end state (PTY-pure):** the pty pair opens and drives bytes through518N_TTY; ctty + fg/bg pgrp + control-char signals work (riding K11.6's519kernel ``kill_pgrp``); termios honored; W7 stdin is the real tty input520path. The three narrowed registries still EXIST (as inert legacy521fallbacks) until the K13 follow-on retires W8 + W10. (R-K11-W3 per-process522``sighand`` stays open as a narrow item unless a test forces it; it does523not block the PTY axis.)524 525--------------------------------------------------------------------------------52610. K12-A1 characterization-test ordering (the D1 demo's home)527--------------------------------------------------------------------------------528 529**DONE at K11.6.** Both halves of the ordering below were executed:530``hello-k12-a1-baseline{,-child}`` + ``k12-a1-characterization.test.ts``531recorded the JS-direct baseline GREEN (commit ``b4a252b``), then the532K11.6 kernel-side replacement reproduced **identical observable533behavior** (``getpid``/``getppid`` real kernel pids; child534``kill(0,SIGTERM)`` → parent caught it cross-Worker; ``WTERMSIG=15``) —535the diff is only the route (``wasmSyscallStats`` now shows the kill536family + getppid through ``wasm_syscall``). The section is retained as537the record of the discipline that was followed; no K12 action remains.538 539**Ordering is load-bearing:**540 5411. **BASELINE FIRST.** Before retiring any cross-process path542 kernel-side, write a Worker-spawning C demo (+ its vitest) that543 exercises the CURRENT JS-direct behavior:544 545 * ``getppid()`` from a spawned child returns the parent tid546 (``processGroupRegistry`` parentPid).547 * ``kill(-pgid, SIGTERM)`` from one Worker reaches the inherited548 pgrp members (``processGroupRegistry`` enumeration).549 * cross-Worker ``raise``/``kill(other_tid, sig)`` delivery (the550 R-K11-W12 exerciser).551 552 Capture the observable results (exit codes, which Worker's handler553 ran, dmesg ``wasmSyscallStats``) as the **golden baseline**. Commit554 it GREEN on the pre-A1 (JS-direct) code so the baseline is a real555 recording, not a prediction.556 5572. **THEN land the kernel-side replacement** (alloc_pid + kill_pgrp +558 find_task_by_vpid) and prove the SAME demo produces **identical559 observable behavior** — now with ``wasmSyscallStats`` showing the560 kill family routed through ``wasm_syscall`` and the JS registries561 removed. The diff in the demo is *only* the route taken, never the562 result.563 564This is a characterization test in the strict sense: it freezes565externally-observable behavior across an internal re-implementation.566 567--------------------------------------------------------------------------------56811. Sub-phase decomposition, acceptance, budget569--------------------------------------------------------------------------------570 571**Refutation budget: ``0 / ≤ 2`` for K12** (reset at the K11.6 split;572R-K12-1 resolved-by-split). The load-bearing pause condition is budget573overflow (a 3rd open risk surface); a clean within-budget pivot does not574pause. The process-identity foundation is DONE (§8.0), so this budget575covers ONLY the tty/ldisc/jobctrl surface below.576 577The decomposition is **PTY-pure**. Sub-phase A2 source-site reads re-pin578line numbers and decide any forks per §17.7 ("read bodies, not names").579 580* **K12-A — pty pair open path + the two ldisc endpoints (incl. W7).**581 582 - A.open: the F1.3 sequence — ``open("/dev/ptmx")`` →583 ``ioctl(TIOCSPTLCK=0)`` (unlockpt) → ``ioctl(TIOCGPTN)`` (ptsname)584 → ``open("/dev/pts/N")``. Needs ``openat`` + ``ioctl`` uaccess for585 the arg ints against ``current``'s ``env.user_memory`` (F2.2 — no586 ``copy_*_user_for``). *Acceptance:* a process obtains coupled587 master + slave fds resolving to one ``tty_struct`` pair.588 - A.endpoints: the two HardwareJS wiring points (F3.5) — a kernel→JS589 drain of master output and a JS→kernel inject of master input590 (``tty_insert_flip_string``). *Acceptance:* a byte injected at the591 master appears on a slave ``read`` and a slave ``write`` appears at592 the master-output drain, through N_TTY.593 - A.w7: re-point stdin from the JS-direct per-tid queue onto the594 master-input endpoint. **Retires R-K11-W7.** *Acceptance:* a slave595 ``read`` returns an N_TTY-assembled line from injected bytes.596 597* **K12-B — N_TTY line discipline + termios.**598 599 - B.canon: ICANON line buffering (EOL/EOF/erase/kill), raw-mode600 passthrough (VMIN/VTIME), ECHO, OPOST|ONLCR (F3.2–F3.4).601 *Acceptance:* canonical-vs-raw + echo-toggle demo through the pair.602 - B.termios: ``ioctl`` TCGETS/TCSETS[W|F] (tcgetattr/tcsetattr)603 reading/writing ``tty->termios`` (F7). *Acceptance:* a program604 toggles ICANON/ECHO and the ldisc behavior changes accordingly;605 ``-ENOTTY`` for a non-tty fd.606 607* **K12-C — controlling terminal + job control + control-char signals.**608 609 - C.ctty: ``setsid`` (kernel-side as of K11-C1.d) + ``TIOCSCTTY`` +610 ``open("/dev/tty")`` → ``current->signal->tty`` (F4). Builds on the611 session-leader ``/init`` K11.6 already established.612 - C.fgpgrp: ``tcsetpgrp`` / ``tcgetpgrp`` (``tty->pgrp``);613 background-read → SIGTTIN, background-write+TOSTOP → SIGTTOU via614 ``__tty_check_change`` → ``kill_pgrp`` (F5).615 - C.sig: ISIG control chars — Ctrl-C→SIGINT / Ctrl-Z→SIGTSTP /616 Ctrl-\\→SIGQUIT to ``tty->pgrp`` via ``__isig`` → ``kill_pgrp``617 (F6). **Rides K11.6's proven cross-Worker ``kill_pgrp``** — no new618 signal substrate.619 - *Acceptance:* Ctrl-C at the master interrupts the foreground child;620 a backgrounded reader gets SIGTTIN.621 622* **K12-D — close.**623 624 - D1: end-to-end smoke (a minimal interactive line: type, echo,625 Enter, Ctrl-C). D2: close ledger (R-row CLOSE entries; W-row audit626 incl. the W7 retirement + the W8/W10→K13 deferral; any deleted-file627 list); reconcile §13.5/§16 drift; tag ``v0.1.0-rc13`` (no push).628 629**§20.6.1 note:** the original scoping fork DID get refuted (R-K12-1),630but the resolution was the K11.6 SPLIT, not a redefinition of "K12 = PTY631axis" — and post-split K12 is now strictly the PTY axis. The §20.6.1632provenance lives in the K11.6 + K12 ledger rows + the R-K12-1 row; no633further note is required at this re-baseline.634 635--------------------------------------------------------------------------------63612. Pause-and-ask conditions (standing, K12)637--------------------------------------------------------------------------------638 639Per the K12 mandate, pause ONLY for: (1) refutation budget overflow640(3rd open risk surface); (2) a refutation against already-closed,641tagged work (rc12 / rc12.5 / **rc12.75**); (3) a user-visible contract642break (``bootKernel`` signature, ``hwjs:sync``, prestrike-web embedding643beyond ``num_vcpu`` / ``max_wasm_instances``); (4) a destructive /644irreversible action. The ``alloc_pid`` scoping fork is no longer a645pause surface (resolved by the K11.6 split). The one PTY-axis surface to646surface if it bites: a W8 fd-inheritance need that forces a6472nd-refutation-risk ``spawn_with_actions`` primitive (§8.0 escape).648