================================================================================
PTY / line-discipline / job-control model for the wasm32 port (K12)
================================================================================

:Status: K12 opener (implementation doc). Written docs-first per the
         K12 mandate. **RE-BASELINED post-K11.6** (see §8.0): the
         cross-process pid foundation this opener originally folded into
         K12-A1 was SPLIT to K11.6 and is now DONE + tagged
         ``v0.1.0-rc12.75``; K12 reopens PTY-pure on that base.
:Axis: PTY + line discipline + job control, on a kernel-owned process
       identity (``alloc_pid`` / ``attach_pid`` / pgrp hash, all landed
       at K11.6).
:Tag at close: ``v0.1.0-rc13``. (``v0.1.0-rc12.75`` was NOT unused — the
       K11.6 split it was reserved for happened; see §8.0.)
:Budget: K12 refutation budget reset to ``0 / ≤ 2`` at the K11.6 split
       (R-K12-1 resolved-by-split). The whole budget is available for the
       tty/ldisc surface — job control's hardest dependency (cross-Worker
       group signal delivery) is ALREADY PROVEN at K11.6 (§8.0).

.. contents::
   :local:
   :depth: 1

--------------------------------------------------------------------------------
0. Relationship to the other docs (read this first)
--------------------------------------------------------------------------------

This is a NEW doc, deliberately not folded into ``terminal-model.rst``.

* ``terminal-model.rst`` is the **K10-era design doc**. It was written
  when "terminal / pty / line discipline / job control" was the K10
  mission, before that mission was re-scoped to K12 at the K11 opener
  (``ARCHITECTURE.md`` §14 K10 row: "K10's original ... mission moved
  to K12 because it requires the in-Worker syscall path that K11
  lands"; ``syscall-path-model.rst`` §16 records the scope correction).
  It stays K10-scoped and frozen as the original design intent. Per the
  standing directive it is NOT edited for K12.

* **This doc** (``pty-model.rst``) is the K12 *implementation* model: it
  re-states the F-facts with citations against the actually-present
  upstream v6.12 sources, records the K12-A1 cross-process foundation
  decision (the scoping fork), maps the W-row retirement cascade to
  sub-phases, and pins the sub-phase decomposition + acceptance + budget.

* ``process-model.rst`` §4 (pipe / pipefs contract) + §6 (stdin wiring)
  own the cross-process I/O substrate. The PTY master/slave buffers
  reuse the same shared-``env.kernel_memory`` insight (§2 below).

* ``ARCHITECTURE.md`` §15.3 R-rows are the authoritative debt ledger.
  Where this doc and §15.3 disagree, **§15.3 wins** (the R-K8-2 ruling,
  re-affirmed by the K11.5-D2 §20.5.3 drift fix). Citations here are a
  convenience, not a second source of truth.

Cross-references to upstream are to the in-tree clone at
``linux-wasm/linux/`` (v6.12); line numbers are snapshot-time and
re-pinned at each sub-phase's A2 source-site read per the §17.7 lesson
("decide forks at A2 after reading bodies, not function names").

--------------------------------------------------------------------------------
1. F-facts: the PTY pair contract (ptmx / pts)
--------------------------------------------------------------------------------

**F1.1 — A pty pair is two coupled tty_structs.** Upstream
``drivers/tty/pty.c`` (947 lines) registers two drivers at boot:
``ptm_driver`` (master, ``/dev/ptmx``) and ``pts_driver`` (slave,
``/dev/pts/N``). ``ptmx_open()`` (``pty.c``) allocates a slave index
via the devpts IDR, creates the slave device node under
``/dev/pts/``, and couples ``master->link == slave`` /
``slave->link == master``. Bytes written to one end appear on the
other end's input path. Registration happens in ``pty_init()``, an
``initcall`` that **already runs at boot on wasm32** as of K10-A3 (the
post-link initcall bracket pass; ``ARCHITECTURE.md`` §14 K10 row +
``terminal-model.rst`` §3, §10). So the driver objects exist; what K12
adds is the *syscall path* that opens and drives them.

**F1.2 — devpts is the slave-side filesystem.** ``fs/devpts/inode.c``
(618 lines) implements the ``devpts`` filesystem mounted at
``/dev/pts``. ``devpts_new_index()`` allocates the slave number;
``devpts_pty_new()`` creates the inode. ``init_devpts_fs()`` (initcall)
registers the fs type; it runs at boot via the same K10-A3 path. The
slave path string (``/dev/pts/3``) is returned to userspace by
``ptsname()`` via ``ioctl(ptmx_fd, TIOCGPTN, &n)``.

**F1.3 — the open sequence userspace expects.**

.. code-block:: c

   int m = posix_openpt(O_RDWR | O_NOCTTY);   /* open("/dev/ptmx") */
   grantpt(m); unlockpt(m);                   /* ioctl TIOCSPTLCK=0 */
   char *s = ptsname(m);                      /* ioctl TIOCGPTN */
   int slave = open(s, O_RDWR);               /* open("/dev/pts/N") */

The syscalls on this path: ``openat`` (×2), ``ioctl`` (TIOCSPTLCK,
TIOCGPTN), and on the slave fd, ``read`` / ``write`` / ``ioctl``
(termios). All of these are now in-Worker ``wasm_syscall`` candidates
(K11 landed the per-Worker vmlinux path). The blocker is NOT the driver
(it boots) but uaccess for the ``ioctl`` arg structs and the
fd→tty_struct resolution living in ``current->files`` (per-process as
of K11-C1.a). See §2 for the wasm32-specific buffer-flow shape.

--------------------------------------------------------------------------------
2. The wasm32 PTY buffer-flow shape (the shared-kernel-memory insight)
--------------------------------------------------------------------------------

The K11.5 cross-Worker pipe-data deferral (R-K11-W8) and the PTY
master/slave coupling share ONE enabling fact, worth stating plainly
because it governs the whole K12 I/O design:

**F2.1 — ``env.kernel_memory`` is a single shared SharedArrayBuffer
imported by every wasm module in the system.** (``hardwarejs/src/
physRam.ts`` lines 4–11, 43–61: "one shared ``WebAssembly.Memory`` ...
imported by every wasm module ... Backed by a SharedArrayBuffer so the
main thread + every Worker can memcpy into it directly.") Each
process Worker runs its OWN vmlinux ``WebAssembly.Instance`` (separate
code + kernel stack = an SMP "CPU"), but they all share ONE kernel
address space. ``init_task``, ``init_pid_ns.idr``, the slab heap, and
any ``kmalloc``'d tty/pipe buffer are at the same physical bytes for
every Worker.

**F2.2 — consequence for PTY (and pipe) data.** A tty flip buffer or
pipe ring allocated by ``kmalloc`` lives in shared kernel memory.
Worker A's ``write(slave_fd, ...)`` does ``copy_from_user`` from A's
OWN ``env.user_memory`` (``current == A``) into the kernel buffer;
Worker B's ``read(master_fd, ...)`` does ``copy_to_user`` into B's OWN
``env.user_memory`` (``current == B``). **Neither side needs
``copy_*_user_for(other_tid)``** — the cross-Worker hop happens through
the shared kernel buffer, and each endpoint only ever touches
``current``'s user memory. This is why the PTY data path is tractable
at K12 even though ``copy_from_user_for`` (process-model.rst §4) is
still deferred.

**F2.3 — what the cross-Worker hop DOES still need.** Both endpoints
must hold fds that resolve to the SAME kernel ``tty_struct`` (or pipe
``struct file``). That requires the child's per-process
``files_struct`` to inherit the parent's ``struct file *`` at spawn —
i.e. kernel-side ``spawn_with_actions`` (clone parent ``files`` +
apply dup2/close). That is the residual R-K11-W8 work; it is
**K12-B-scoped** (it is the same machinery a PTY-driven shell needs to
wire a child's stdin/stdout to a pts fd) and is NOT part of the K12-A1
foundation. See the cascade in §9.

--------------------------------------------------------------------------------
3. F-facts: the N_TTY line-discipline state machine
--------------------------------------------------------------------------------

**F3.1 — N_TTY is the default ldisc.** ``drivers/tty/n_tty.c`` (2560
lines) implements the canonical line discipline. ``n_tty_init()``
(initcall, boots via K10-A3) registers it; every tty defaults to it.
The ldisc is the state machine between raw bytes and the slave-fd
reader.

**F3.2 — canonical vs raw (ICANON).** In canonical mode
(``ICANON=1``, default), ``n_tty_receive_buf`` accumulates a line
buffer (``ldata->read_buf``) and only releases a line to ``read()`` on
``\n`` / EOL / EOF (Ctrl-D). Erase (Ctrl-H / DEL) and kill (Ctrl-U)
edit the buffer; ECHOE/ECHOK control whether the edits are echoed. In
raw mode (``ICANON=0``), bytes pass straight through subject to
``VMIN`` / ``VTIME``. (terminal-model.rst §4 has the long-form
narrative; this is the citation pin.)

**F3.3 — output post-processing (OPOST).** Slave-side ``write()``
output is post-processed by ``do_output_char`` (LF→CRLF when
``OPOST|ONLCR``) on the way to the master read side.

**F3.4 — echo (ECHO).** Master-side input is echoed to the master
output queue so the user sees their typing; ``n_tty`` owns the echo
buffer. Programs disable ECHO via ``tcsetattr`` for password entry.

**F3.5 — wasm32 wiring is only the two endpoints.** Per
terminal-model.rst §4(a)/(b): bytes flow xterm → HardwareJS →
``tty_insert_flip_string`` (master input) and master output → a
HardwareJS polling consumer → ``term.write``. The ldisc state machine
itself is upstream-unmodified. The arch work is (a) a kernel→JS hook to
drain master output and (b) a JS→kernel entry to inject master input;
both are ``tty_struct`` driver-ops shaped, pinned at the K12-B A2 read.

--------------------------------------------------------------------------------
4. F-facts: controlling terminal
--------------------------------------------------------------------------------

**F4.1 — session leader claims a ctty.** ``drivers/tty/tty_jobctrl.c``
(593 lines): a session leader (made by ``setsid()``, which routes
kernel-side as of K11-C1.d) claims a controlling tty via
``ioctl(fd, TIOCSCTTY)`` → ``tiocsctty()`` → ``__proc_set_tty()``,
which sets ``tty->session`` / ``current->signal->tty``.

**F4.2 — /dev/tty.** ``open("/dev/tty")`` resolves to
``current->signal->tty`` via the ``TTY_MAJOR=5, TTY_MINOR=0`` special
device (``drivers/tty/tty_io.c``). The devtmpfs node must exist in the
initramfs (terminal-model.rst §6); K12-B verifies/creates it.

**F4.3 — SIGHUP on leader death.** When the session leader exits,
``__do_SAK`` / ``disassociate_ctty`` sends SIGHUP to the foreground
pgrp. This rides on the same ``kill_pgrp`` machinery as §6.

--------------------------------------------------------------------------------
5. F-facts: foreground / background process groups
--------------------------------------------------------------------------------

**F5.1 — tcsetpgrp / tcgetpgrp.** ``tty_jobctrl.c``:
``ioctl(fd, TIOCSPGRP, &pgid)`` sets ``tty->pgrp``;
``TIOCGPGRP`` reads it. Bash uses these for ``fg`` / ``bg``.

**F5.2 — background-read → SIGTTIN, background-write+TOSTOP →
SIGTTOU.** ``__tty_check_change()`` (``tty_jobctrl.c``) compares
``current``'s pgrp against ``tty->pgrp`` and calls
``kill_pgrp(pgrp, SIGTTIN/SIGTTOU, 1)`` for the background case (or
returns ``-EIO`` if the signal is ignored/blocked). This is the
job-control core.

**F5.3 — all of F5 needs cross-process ``kill_pgrp``.** ``kill_pgrp``
walks ``pgrp->tasks`` (the ``PIDTYPE_PGID`` hlist on a ``struct pid``).
That hlist is empty until tasks are registered via
``alloc_pid`` + ``attach_pid(p, PIDTYPE_PGID)``. **This is the hard
dependency that makes the K12-A1 foundation (§8) a prerequisite for
all job control** — without it, every ``kill_pgrp`` in §4/§5/§6 hits an
empty list.

--------------------------------------------------------------------------------
6. F-facts: control-char → signal generation
--------------------------------------------------------------------------------

**F6.1 — ISIG intercepts.** In canonical + ``ISIG`` mode,
``n_tty_receive_signal_char()`` intercepts the special chars before the
line buffer:

* Ctrl-C (``VINTR``, 0x03) → ``SIGINT`` to ``tty->pgrp``.
* Ctrl-Z (``VSUSP``, 0x1A) → ``SIGTSTP`` to ``tty->pgrp``.
* Ctrl-\\ (``VQUIT``, 0x1C) → ``SIGQUIT`` to ``tty->pgrp``.

via ``__isig(sig, tty)`` → ``kill_pgrp(tty->pgrp, sig, 1)``. The char
is consumed, not delivered to the app (unless IEXTEN literal-next).

**F6.2 — delivery rides the K8 path.** ``kill_pgrp`` →
``group_send_sig_info`` → ``__send_signal_locked`` →
``arch_signal_wake_target`` (K8-B2 ring) → ``deliverSignalToTarget``
→ the per-tid pending region + ``Atomics.notify``. So control-char
signals work end-to-end ONLY once ``kill_pgrp`` can enumerate the pgrp
(F5.3) AND cross-target delivery resolves the right Worker (R-K11-W12).
Both are K12-A1 (§8).

--------------------------------------------------------------------------------
7. F-facts: termios (tcgetattr / tcsetattr)
--------------------------------------------------------------------------------

**F7.1 — termios is per-tty kernel state.** ``tcgetattr(fd, &t)`` /
``tcsetattr(fd, opt, &t)`` are ``ioctl(TCGETS/TCSETS[W|F])`` reading /
writing ``tty->termios`` (``struct ktermios``) in ``tty_io.c``. The
``c_iflag`` / ``c_oflag`` / ``c_lflag`` / ``c_cc[]`` fields drive every
ICANON/ECHO/ISIG/OPOST decision above.

**F7.2 — the only arch work is uaccess for the arg struct.** The
ioctl arg is a userspace ``struct termios`` pointer; the handler
``copy_from_user`` / ``copy_to_user`` against ``current``'s
``env.user_memory`` (in-Worker, ``current``-local — no
``copy_*_user_for`` needed, F2.2). ``-ENOTTY`` is returned for a
non-tty fd, which is why pre-K12 ``tcgetattr(0)`` fails
(terminal-model.rst §1): there's no controlling tty yet, only stdio
fixtures.

--------------------------------------------------------------------------------
8. Process-identity foundation — DONE at K11.6 (was the K12-A1 scoping fork)
--------------------------------------------------------------------------------

8.0 RE-BASELINE (post-K11.6, ``rc12.75``) — read this instead of §8.1+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Everything §8.1 below frames as "K12-A1 cross-process foundation" was
SPLIT off to **K11.6** (refutation R-K12-1; the FOLD decision missed the
pid/tid *authority* axis) and is now **landed + tagged ``v0.1.0-rc12.75``**.
The original §8.1+ FOLD analysis is retained as provenance only; for the
current state read this subsection.

**DONE at K11.6 (no longer K12 work):**

* **Kernel owns pid allocation.** ``__wasm32_alloc_process_task`` calls
  ``alloc_pid(&init_pid_ns, NULL, 0)`` NATURALLY (no ``set_tid``);
  kthreads keep 0/1/2…, ``/init`` keeps pid 1 (kthread-reuse path),
  children get next-free. The child reads its kernel pid
  (``__wasm32_task_pid`` export) and posts it up the ``vmlinux-ready``
  handshake; HardwareJS keys every per-Worker SAB structure by the
  kernel pid (shape ii; watched row R-K11.6-W1). ``nextThreadTid`` retired
  to ``nextLegacyRoutingId`` (legacy/non-vmlinux + thread routing only).
* **R-K11-W5 (alloc_pid / attach_pid / find_task_by_vpid) — RETIRED.** The
  global pid hash is populated cross-Worker in the shared
  ``env.kernel_memory``; ``getpid()`` / ``getppid()`` return real kernel
  pids end-to-end.
* **R-K11-W9 (kill(-pgid) + getppid) — RETIRED.** ``wasm32_sys_kill``
  handles ``pid<=0`` kernel-side, walking the real process group via
  ``do_each_pid_task(PIDTYPE_PGID)``; ``task_alloc.c`` promotes the reused
  ``/init`` kthread to session + process-group leader (``change_pid``) so
  children inherit a coherent ``{/init, …}`` group. ``getppid`` routes via
  ``__NR_GETPPID`` kernel-side.
* **R-K11-W12 (cross-Worker raise mis-target) — RETIRED.** The kernel pid
  is the single routing key; the signal ring delivers each slot to the
  Worker registered under that pid. **Proven** by
  ``k12-a1-characterization.test.ts``: a child's ``kill(0,SIGTERM)``
  reaches the PARENT in a *different* Worker.

**Why this de-risks K12 (the budget is genuinely available):** job
control's single hardest dependency is **cross-Worker group signal
delivery** — ``kill_pgrp`` reaching every member across Worker
boundaries. That is exactly what K11.6 proved (the characterization demo
is a cross-Worker ``kill(0)`` round-trip). So Ctrl-C → ``SIGINT`` to the
foreground pgrp (§6), SIGTTIN/SIGTTOU (§5), and SIGHUP-on-leader-death
(§4) all ride machinery that is **already green**. The ``≤ 2`` K12 budget
is reserved for the tty/ldisc *surface* (the pty pair, N_TTY state
machine, ctty acquisition, termios), not for the signal substrate.

**What is NOT in the K12 PTY axis** (placement decided here per the
single-axis + decision-rule discipline; see §9 for the per-row mapping):

* **R-K11-W7 (stdin → tty / N_TTY / ldisc) — IN K12** (squarely the PTY
  axis: it IS the master-input injection endpoint, F3.5). Lands in K12-A.
* **R-K11-W8 (spawn-with-actions fd inheritance + cross-process pipe-DATA
  round-trip) — DEFERRED to a follow-on (K13), NOT K12.** It is a
  shell-pipeline need, not a tty need: the PTY axis is demonstrable end-
  to-end without it (a process ``open("/dev/pts/N")``\ s its own slave fd;
  ctty/fg-pgrp/termios/ctrl-char all work on that fd). And the
  ``spawn_with_actions`` primitive carries the SAME JS-direct-spawn
  chicken-egg as the K11.6 pid problem (no parent kernel context at
  ``wasm_spawn_image`` request time; ``kernelSpawnHandler.ts``), so
  folding it risks a 2nd refutation against the PTY budget — exactly the
  shape we just avoided. Keeping it out protects the tty/ldisc budget.
* **R-K11-W10 (K8-vitest fallback-arm migration + the registry deletions
  it gates) — DEFERRED to the same follow-on (K13), NOT K12.** It is pure
  test-migration + dead-code retirement, orthogonal to the PTY axis. The
  ``processGroupRegistry`` / ``pipeRegistry`` / ``fdTableRegistry`` are
  already NARROWED to the legacy non-vmlinux fallback (K11.6 / K11.5), so
  they are inert for every vmlinux Worker; their full deletion belongs
  with the W8 pipe/fdTable deletion in one cleanup phase, after the six
  K8 vitests are migrated to ``vmlinuxModule``.

  *Decision-rule escape:* if a PTY-driven-shell demo turns out to NEED
  W8 fd-inheritance to wire a child to a pts (i.e. ``open("/dev/pts/N")``
  is insufficient for the acceptance), AND the ``spawn_with_actions``
  primitive forces a 2nd-refutation-risk mechanism, that is a split-
  forcing surface to be surfaced (the same shape as the K12-A1 spawn
  round-trip), not absorbed silently.

.. note::

   **UPDATE (K12-A1 implementation — the escape hatch below was exercised;
   FOLD → SPLIT). Refutation R-K12-1.** The FOLD decision recorded in this
   section evaluated the two *named* structural-novelty triggers
   (shared-sighand; per-Worker-vmlinux vs global pid ns) and correctly
   found both false — but it missed a THIRD axis: the **pid/tid AUTHORITY
   model**. The implemented spawn flow makes JS the pid authority
   (``nextThreadTid`` → ``__wasm32_alloc_process_task(pid=JS-tid)`` →
   ``alloc_pid(set_tid=JS-tid)``), which (a) collides with the kernel idr's
   boot pids (``alloc_pid(set_tid=2)`` → ``-EEXIST``; kthreadd owns 2) and
   (b) contradicts the documented kernel-owns-allocation design (§3 L123 +
   §4 ``spawn_worker(pid,…)`` ABI). A characterization sweep showed the JS
   re-key surface is BROAD (7 tid-keyed structures, 3 ``allocThreadTid``
   sites, process+thread, touches the signal-pending/wake-word registry),
   so the foundation **SPLITS to K11.6** "pid-namespace reconciliation:
   kernel owns pid allocation" (``rc12.75``, fresh ``≤ 2`` budget), keeping
   K12 PTY-pure (budget reset 0/2). The reconciliation uses **shape (ii)**:
   the child boot ``alloc_pid``s NATURALLY (no ``set_tid``; kthreads keep
   0/1/2…, ``/init`` keeps pid 1 via the kthread-reuse path, children get
   next-free), the Worker reads its pid and posts it via the existing
   ``vmlinux-ready`` handshake, and JS defers all per-Worker SAB-structure
   registrations to AFTER that await, keyed by the kernel pid;
   ``nextThreadTid`` is retired. **Shape (i)** — route ``wasm_spawn_image``
   through the parent's kernel so the pid is allocated *before*
   ``spawn_worker`` (matching §3 L123 / §4 literally) — was REJECTED here:
   ``wasm_spawn_image`` is JS-direct on the main thread with no kernel
   context at spawn time, so shape (i) would rebuild the kernel-routed
   spawn primitive K9 deferred (R-K9-2) — 2nd-refutation exposure. The
   shape-(ii) docs↔ABI timing divergence is tracked by the watched row
   **R-K11.6-W1** (NOT a refutation; retires when ``wasm_spawn_image``
   routes through the parent's kernel). Full record: ``ARCHITECTURE.md``
   §15.3 R-K12-1 + R-K11.6-W1, §20.6.3 §20.6.1 retro-note, and the K11.6 +
   K12 ledger rows. The FOLD reasoning below is retained for provenance.

8.1 Original FOLD analysis (SUPERSEDED by §8.0 — provenance only)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The PTY/job-control axis (§3–§7) has a hard floor: **kernel-side pgrp
enumeration and correct cross-target signal delivery**, which need the
PID hash populated (``alloc_pid`` + ``attach_pid``). That is the
cross-process debt currently carried JS-side by the narrowed registries
(``processGroupRegistry`` / ``pipeRegistry`` / ``fdTableRegistry``,
restored at K11.5) and gated on **R-K11-W5** (``alloc_pid`` /
``attach_pid`` / ``find_task_by_vpid``).

The K12 mandate poses a scoping fork, to be resolved at A2 source-site
snapshot time AFTER reading the ``alloc_pid`` call sites and the
R-K11-W3 per-process ``task_struct`` shape (§17.7 discipline). The
reads have been done; the decision follows.

**DECISION: FOLD the ``alloc_pid`` / ``attach_pid`` /
``find_task_by_vpid`` foundation into K12-A1. Keep PTY as K12-B+. One
K12 phase, ``≤ 2`` refutation budget covering both. NO K11.6 split;
``rc12.75`` is NOT used.**

*Why no structural novelty (the split trigger fails on both examples
the mandate named):*

1. **"per-Worker vmlinux instantiation interacts badly with the global
   pid namespace" — FALSE.** ``env.kernel_memory`` is a single shared
   SAB across all per-Worker vmlinux instances (F2.1,
   ``physRam.ts:4-11,57-61``). ``init_pid_ns.idr`` therefore lives at
   one physical location visible to every Worker; a ``struct pid``
   allocated by Worker A is found by Worker B's
   ``find_task_by_vpid``. A global pid hash is *coherent by
   construction* — this is the intended SMP-shared-RAM model, not a bad
   interaction. (The cross-Worker concurrency on
   ``pidmap_lock`` / ``idr`` is real SMP contention, the same class the
   K11 axis already brought online and that ``task_alloc.c`` already
   serializes with ``wasm32_task_alloc_lock``; it is upstream-with-
   precedent, not novelty.)

2. **"shared-sighand conflicts with pid-hash expectations" — FALSE.**
   R-K11-W3 keeps ``t->sighand == &init_sighand`` shared.
   ``attach_pid`` touches ``task->pid_links[type]`` /
   ``task->thread_pid`` / ``signal->pids[type]`` (``kernel/pid.c:330-
   344``); it never reads or writes ``sighand``. The two are
   orthogonal. Per-process ``sighand`` (the W3 full close) is a
   *separate* K12+ item that the PTY axis only forces if a test
   installs different dispositions in two sibling Workers (W3 §15 close
   criterion) — not required by the A1 foundation.

*Why the fold is viable (verified at source-site, correcting the
R-K11-W5 row's stated blockers per §17.7):*

* ``init_pid_ns.child_reaper = &init_task`` is **statically set**
  (``kernel/pid.c:84``) — already true at boot. The R-K11-W5 row's
  "child_reaper not set" half is stale (it predates reading the static
  initializer); only the caps half remains, and it resolves favorably:
* ``pid_idr_init()`` is a direct call in ``start_kernel``
  (``init/main.c:1075``), NOT in ``initcall-skip-list.txt``, so
  ``init_pid_ns.pid_cachep`` + ``idr_init`` are live before any process
  Worker spawns — ``alloc_pid``'s ``kmem_cache_alloc`` won't NULL-deref.
* The ``set_tid`` path (``alloc_pid(&init_pid_ns, &tid, 1)``, needed so
  the kernel pid == the JS-assigned Worker tid and the cross-layer pid
  contract holds) requires ``checkpoint_restore_ns_capable(user_ns)``
  (``kernel/pid.c:210``). Under the shared ``init_cred`` (CAP_FULL_SET)
  this **passes** for ``init_user_ns``. (The R-K11-W5 row cited
  init_cred sharing as a *reason it fails*; reading the body shows full
  caps mean it *passes* — exactly the §17.7 "read bodies not names"
  correction. Re-verified by a boot probe at A1 implementation time.)

*Residual A1 risks (absorbed by the ``≤ 2`` budget, NOT novelty):*
``idr_preload`` per-cpu / ``local_irq`` semantics on wasm32; the
``attach_pid`` ordering for PGID/SID (must set ``signal->pids[PGID]`` =
the inherited pgid's ``struct pid`` before attach); and whether
``pidmap_lock`` (a plain ``spin_lock_irq``) is sufficient under genuine
cross-Worker concurrency. Each is a known upstream-integration mechanic.

*Escape hatch (kept, not exercised):* if A1 implementation surfaces an
ACTUAL structural novelty (e.g. ``idr_preload``'s per-cpu model proves
fundamentally incompatible with per-Worker instances, or
``pidmap_lock`` cannot be made cross-Worker-correct without a new
primitive), the work splits to **K11.6** at that point with its own
``≤ 2`` budget and an ``rc12.75`` tag, and a ``§20.6.1`` note is filed.
This opener's standing decision is FOLD; the split is a contingency the
implementation may invoke and document, per the mandate.

A confirming note is filed at ``ARCHITECTURE.md §20.6`` (K12 opener
fork resolution).

--------------------------------------------------------------------------------
9. W-row retirement cascade (explicit, per sub-phase + acceptance)
--------------------------------------------------------------------------------

Each row's authoritative text is ``ARCHITECTURE.md §15.3``; this maps
the *cascade* as re-baselined post-K11.6. The original
"all four retire inside K12" plan changed: W5/W9/W12 retired at **K11.6**;
W7 is the only PTY-axis row left for K12; W8/W10 deferred to a follow-on
(see §8.0 for the placement reasoning).

**R-K11-W5 (alloc_pid / attach_pid / find_task_by_vpid) — RETIRED at
K11.6.** PID-hash registration landed in ``__wasm32_alloc_process_task``
(natural ``alloc_pid`` + ``attach_pid`` for PID/TGID/PGID/SID).
``find_task_by_vpid`` resolves cross-Worker; ``getpid()``/``getppid()``
return real kernel pids. (Was the K12-A1 keystone; the split moved it.)

**R-K11-W9 (kill(-pgid) + getppid) — RETIRED at K11.6.** ``wasm32_sys_kill``
walks the real PGID hlist kernel-side; ``getppid`` routes via
``__NR_GETPPID``. The ``processGroupRegistry`` is NARROWED to a legacy
non-vmlinux fallback (NOT deleted — its full deletion is W10-gated, see
below). The §10 characterization demo reproduces identical observable
results kernel-side.

**R-K11-W12 (cross-Worker raise mis-target) — RETIRED at K11.6.** The
kernel pid is the single routing key; the signal ring delivers to the
Worker registered under that pid. Proven by the §10 demo (child→parent
cross-Worker ``SIGTERM``).

**R-K11-W7 (stdin → tty / N_TTY / ldisc) — K12 (the one PTY-axis row).**
Today stdin is a JS-direct per-tid queue (process-model.rst §6). The PTY
axis replaces it with the real tty input path: master-side input is
injected via ``tty_insert_flip_string`` into the N_TTY ldisc, and the
slave-fd ``read`` drains the line buffer (F3.5). *Acceptance:* a slave
``read`` returns a line assembled by N_TTY from master-injected bytes;
ICANON line-buffering + ECHO observed. Lands in **K12-A** (open path +
endpoints) and is exercised through **K12-B** (ldisc behaviors).

**R-K11-W8 (spawn-with-actions fd inheritance + pipe-DATA round-trip) —
DEFERRED to follow-on (K13), NOT K12.** Shell-pipeline need, not a tty
need; carries the ``spawn_with_actions`` JS-direct chicken-egg
(2nd-refutation risk); the PTY axis is demonstrable without it (§8.0).
*Follow-on acceptance:* ``pipeRegistry.ts`` + ``fdTableRegistry.ts``
deleted; ``cmd1 | cmd2`` cross-Worker pipe-DATA round-trip green
kernel-side.

**R-K11-W10 (K8-vitest migration + registry deletions) — DEFERRED to the
same follow-on (K13), NOT K12.** Pure test-migration + dead-code
retirement; the registries are already inert legacy fallbacks for
vmlinux Workers. *Follow-on acceptance:* the six K8 vitests
(``k8-c1-signals`` / ``-mt`` / ``-default-term`` / ``k8-b3-trampoline`` /
``k8-b4-default-action`` / ``k8-b5-mask``) migrated to ``vmlinuxModule``;
the ``dispatchSyscall`` kill/sigaction fallback arms + ``processGroupRegistry``
deleted; short-circuit set literally ``{}``.

**K12 end state (PTY-pure):** the pty pair opens and drives bytes through
N_TTY; ctty + fg/bg pgrp + control-char signals work (riding K11.6's
kernel ``kill_pgrp``); termios honored; W7 stdin is the real tty input
path. The three narrowed registries still EXIST (as inert legacy
fallbacks) until the K13 follow-on retires W8 + W10. (R-K11-W3 per-process
``sighand`` stays open as a narrow item unless a test forces it; it does
not block the PTY axis.)

--------------------------------------------------------------------------------
10. K12-A1 characterization-test ordering (the D1 demo's home)
--------------------------------------------------------------------------------

**DONE at K11.6.** Both halves of the ordering below were executed:
``hello-k12-a1-baseline{,-child}`` + ``k12-a1-characterization.test.ts``
recorded the JS-direct baseline GREEN (commit ``b4a252b``), then the
K11.6 kernel-side replacement reproduced **identical observable
behavior** (``getpid``/``getppid`` real kernel pids; child
``kill(0,SIGTERM)`` → parent caught it cross-Worker; ``WTERMSIG=15``) —
the diff is only the route (``wasmSyscallStats`` now shows the kill
family + getppid through ``wasm_syscall``). The section is retained as
the record of the discipline that was followed; no K12 action remains.

**Ordering is load-bearing:**

1. **BASELINE FIRST.** Before retiring any cross-process path
   kernel-side, write a Worker-spawning C demo (+ its vitest) that
   exercises the CURRENT JS-direct behavior:

   * ``getppid()`` from a spawned child returns the parent tid
     (``processGroupRegistry`` parentPid).
   * ``kill(-pgid, SIGTERM)`` from one Worker reaches the inherited
     pgrp members (``processGroupRegistry`` enumeration).
   * cross-Worker ``raise``/``kill(other_tid, sig)`` delivery (the
     R-K11-W12 exerciser).

   Capture the observable results (exit codes, which Worker's handler
   ran, dmesg ``wasmSyscallStats``) as the **golden baseline**. Commit
   it GREEN on the pre-A1 (JS-direct) code so the baseline is a real
   recording, not a prediction.

2. **THEN land the kernel-side replacement** (alloc_pid + kill_pgrp +
   find_task_by_vpid) and prove the SAME demo produces **identical
   observable behavior** — now with ``wasmSyscallStats`` showing the
   kill family routed through ``wasm_syscall`` and the JS registries
   removed. The diff in the demo is *only* the route taken, never the
   result.

This is a characterization test in the strict sense: it freezes
externally-observable behavior across an internal re-implementation.

--------------------------------------------------------------------------------
11. Sub-phase decomposition, acceptance, budget
--------------------------------------------------------------------------------

**Refutation budget: ``0 / ≤ 2`` for K12** (reset at the K11.6 split;
R-K12-1 resolved-by-split). The load-bearing pause condition is budget
overflow (a 3rd open risk surface); a clean within-budget pivot does not
pause. The process-identity foundation is DONE (§8.0), so this budget
covers ONLY the tty/ldisc/jobctrl surface below.

The decomposition is **PTY-pure**. Sub-phase A2 source-site reads re-pin
line numbers and decide any forks per §17.7 ("read bodies, not names").

* **K12-A — pty pair open path + the two ldisc endpoints (incl. W7).**

  - A.open: the F1.3 sequence — ``open("/dev/ptmx")`` →
    ``ioctl(TIOCSPTLCK=0)`` (unlockpt) → ``ioctl(TIOCGPTN)`` (ptsname)
    → ``open("/dev/pts/N")``. Needs ``openat`` + ``ioctl`` uaccess for
    the arg ints against ``current``'s ``env.user_memory`` (F2.2 — no
    ``copy_*_user_for``). *Acceptance:* a process obtains coupled
    master + slave fds resolving to one ``tty_struct`` pair.
  - A.endpoints: the two HardwareJS wiring points (F3.5) — a kernel→JS
    drain of master output and a JS→kernel inject of master input
    (``tty_insert_flip_string``). *Acceptance:* a byte injected at the
    master appears on a slave ``read`` and a slave ``write`` appears at
    the master-output drain, through N_TTY.
  - A.w7: re-point stdin from the JS-direct per-tid queue onto the
    master-input endpoint. **Retires R-K11-W7.** *Acceptance:* a slave
    ``read`` returns an N_TTY-assembled line from injected bytes.

* **K12-B — N_TTY line discipline + termios.**

  - B.canon: ICANON line buffering (EOL/EOF/erase/kill), raw-mode
    passthrough (VMIN/VTIME), ECHO, OPOST|ONLCR (F3.2–F3.4).
    *Acceptance:* canonical-vs-raw + echo-toggle demo through the pair.
  - B.termios: ``ioctl`` TCGETS/TCSETS[W|F] (tcgetattr/tcsetattr)
    reading/writing ``tty->termios`` (F7). *Acceptance:* a program
    toggles ICANON/ECHO and the ldisc behavior changes accordingly;
    ``-ENOTTY`` for a non-tty fd.

* **K12-C — controlling terminal + job control + control-char signals.**

  - C.ctty: ``setsid`` (kernel-side as of K11-C1.d) + ``TIOCSCTTY`` +
    ``open("/dev/tty")`` → ``current->signal->tty`` (F4). Builds on the
    session-leader ``/init`` K11.6 already established.
  - C.fgpgrp: ``tcsetpgrp`` / ``tcgetpgrp`` (``tty->pgrp``);
    background-read → SIGTTIN, background-write+TOSTOP → SIGTTOU via
    ``__tty_check_change`` → ``kill_pgrp`` (F5).
  - C.sig: ISIG control chars — Ctrl-C→SIGINT / Ctrl-Z→SIGTSTP /
    Ctrl-\\→SIGQUIT to ``tty->pgrp`` via ``__isig`` → ``kill_pgrp``
    (F6). **Rides K11.6's proven cross-Worker ``kill_pgrp``** — no new
    signal substrate.
  - *Acceptance:* Ctrl-C at the master interrupts the foreground child;
    a backgrounded reader gets SIGTTIN.

* **K12-D — close.**

  - D1: end-to-end smoke (a minimal interactive line: type, echo,
    Enter, Ctrl-C). D2: close ledger (R-row CLOSE entries; W-row audit
    incl. the W7 retirement + the W8/W10→K13 deferral; any deleted-file
    list); reconcile §13.5/§16 drift; tag ``v0.1.0-rc13`` (no push).

**§20.6.1 note:** the original scoping fork DID get refuted (R-K12-1),
but the resolution was the K11.6 SPLIT, not a redefinition of "K12 = PTY
axis" — and post-split K12 is now strictly the PTY axis. The §20.6.1
provenance lives in the K11.6 + K12 ledger rows + the R-K12-1 row; no
further note is required at this re-baseline.

--------------------------------------------------------------------------------
12. Pause-and-ask conditions (standing, K12)
--------------------------------------------------------------------------------

Per the K12 mandate, pause ONLY for: (1) refutation budget overflow
(3rd open risk surface); (2) a refutation against already-closed,
tagged work (rc12 / rc12.5 / **rc12.75**); (3) a user-visible contract
break (``bootKernel`` signature, ``hwjs:sync``, prestrike-web embedding
beyond ``num_vcpu`` / ``max_wasm_instances``); (4) a destructive /
irreversible action. The ``alloc_pid`` scoping fork is no longer a
pause surface (resolved by the K11.6 split). The one PTY-axis surface to
surface if it bites: a W8 fd-inheritance need that forces a
2nd-refutation-risk ``spawn_with_actions`` primitive (§8.0 escape).
