docs(rt2): track-wide brainstorm — all five iterations ready, graph remapped
- spec 2026-09-01-runtime-v2-design.md: the one principle (PULL — a child is fds, the net verbs drive them; runtime-v2 adds acquisition verbs, never transport), the full surface (ids 97+: spawn/spawn_pty/ wait_dl/signal/resize, signal.on delivering the sig number, term.raw/ restore with runtime-guaranteed restore, send_fd/recv_fd/connect_unix), actor-owned lifecycle, mechanics notes, refusals by name - push transport rejected with reasons recorded (mailbox-cap collision, new delivery machinery); death-notice verb refused (a two-line fiber composes wait_dl) - five stories flip readiness: ready; fork sections rewritten as settled - graph section 6 remapped: pull broke the 1->2->3 chain — only 1->2 remains; 3, 4, 5 and the VTE grid startable alone today - board section + registry follow; linkcheck 0 broken Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit d313cdeebbe53c83b83f31f4480631568d9d0743)
This commit is contained in:
parent
ab8ef64cd9
commit
3190b609af
10 changed files with 213 additions and 107 deletions
|
|
@ -322,7 +322,8 @@ exists to make visible. Streaming responses' runtime prerequisites
|
|||
The tmux study's gaps, remapped as buildable edges now that iteration 42
|
||||
landed. Every yellow node is an iteration of the
|
||||
[runtime-v2 track](stories/runtime-v2/00-story.md) ("the runtime beyond
|
||||
sockets", its own folder since 2026-09-01), brainstormed on demand —
|
||||
sockets"), ALL `readiness: ready` since the track-wide brainstorm
|
||||
([spec](superpowers/specs/2026-09-01-runtime-v2-design.md), 2026-09-01) —
|
||||
[1 streaming subprocess](stories/runtime-v2/01-streaming-subprocess.md) ·
|
||||
[2 PTY](stories/runtime-v2/02-pty.md) ·
|
||||
[3 signals as events](stories/runtime-v2/03-signals-as-events.md) ·
|
||||
|
|
@ -351,21 +352,22 @@ flowchart TD
|
|||
|
||||
I42w --> GSTREAM
|
||||
GSTREAM --> GPTY
|
||||
GPTY --> GSIG
|
||||
GSTREAM --> GVTE
|
||||
GPTY --> WMUX
|
||||
GSIG --> WMUX
|
||||
GTERMIOS --> WMUX
|
||||
GFDPASS --> WMUX
|
||||
GVTE --> WMUX
|
||||
TINFO -.settled at brainstorm.-> WMUX
|
||||
TINFO -.settled at wmux's brainstorm.-> WMUX
|
||||
TMONO -.v2.-> WMUX
|
||||
```
|
||||
|
||||
Sibling reuse, for the record: the alacritty study's Wayland stage D
|
||||
reuses GFDPASS + GVTE; the zen CDP driver shares GSTREAM only; skillhost
|
||||
(28) consumes GSTREAM's stdin transport. GTERMIOS and GFDPASS have no
|
||||
incoming edges — startable any time, alone.
|
||||
**Remapped by the 2026-09-01 track brainstorm** (pull transport: a child
|
||||
is fds, the net verbs drive them): the old 1→2→3 chain broke — signals
|
||||
never needed PTY, only the resize pairing, and the VTE grid needs no
|
||||
subprocess (a replay corpus feeds it). Only 1 → 2 remains chained;
|
||||
**3, 4, 5 and the grid are all startable alone, today.** Sibling reuse:
|
||||
the alacritty Wayland stage reuses GFDPASS + GVTE; the zen CDP driver
|
||||
shares GSTREAM only; skillhost (28) consumes GSTREAM's stdin transport.
|
||||
|
||||
## Maintenance rule
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ features cannot collide.
|
|||
| `query-corpus` | databasev2 query-grammar corpus #1 | on `dev` (`4c82461`). Conclusion was "no new grammar needed" |
|
||||
| `lang42` | iteration 42 — bounded subprocess: `proc.run` bounded + parked (pidfd, caps, ceiling, owner-bound reaping), `proc.run_dl`; carries the alacritty/tmux/zen parity studies and the porch dependency-graph section from the same sweep | ✅ on `master` 2026-09-01 |
|
||||
| `wmux` | the wmux track (`docs/stories/wmux/`, iteration 1 was language 43) — the terminal multiplexer, first of the softwares built with writeonce; story + gap-chain remap first, code follows gap by gap | on `dev` 2026-09-01 |
|
||||
| `rt2` | the runtime-v2 track (`docs/stories/runtime-v2/`) — the runtime beyond sockets: streaming subprocess, PTY, signals-as-events, termios, fd passing; five refine stories, wmux is the driving workload | on `dev` 2026-09-01 |
|
||||
| `rt2` | the runtime-v2 track (`docs/stories/runtime-v2/`) — the runtime beyond sockets: streaming subprocess, PTY, signals-as-events, termios, fd passing; all five `readiness: ready` (track-wide spec 2026-09-01), wmux is the driving workload | on `dev` 2026-09-01 |
|
||||
|
||||
## Cherry-picks onto master
|
||||
|
||||
|
|
|
|||
|
|
@ -1181,17 +1181,22 @@ this track adds the missing third — **processes, terminals, signals** —
|
|||
five builtin-sized seams, each `runtime/src/` work with a `types.ml` row
|
||||
as its whole compiler cost (the iteration 42 precedent). Iteration 42
|
||||
(bounded subprocess, ✅ on `master` 2026-09-01) opened the arc from the
|
||||
language track before it had a name. Build order 1 → 2 → 3; 4 and 5
|
||||
startable alone. All ⬜ `refine`; edges in
|
||||
[dependency graph section 6](../00-dependency-graph.md).
|
||||
language track before it had a name. **All five `readiness: ready`** —
|
||||
one track-wide brainstorm settled every fork
|
||||
([spec](../superpowers/specs/2026-09-01-runtime-v2-design.md),
|
||||
2026-09-01). The keystone decision: PULL transport — a child is fds and
|
||||
the existing net verbs drive them, so no new transport machinery exists
|
||||
anywhere in the track, and the old 1→2→3 chain broke. Only 1 → 2
|
||||
chained; 3, 4, 5 startable alone. Plan per iteration, written when it
|
||||
starts. Edges in [dependency graph section 6](../00-dependency-graph.md).
|
||||
|
||||
| # | Iteration | State |
|
||||
| --- | --- | --- |
|
||||
| 1 | [streaming subprocess](runtime-v2/01-streaming-subprocess.md) | ⬜ `refine` — 42's named follow-up: long-lived child, output as events, stdin, exit notice. Five forks (verb shape, push-vs-pull transport, the mailbox-cap collision, stdin backpressure, idle-deadline semantics). First up |
|
||||
| 2 | [PTY](runtime-v2/02-pty.md) | ⬜ `refine` — openpty + controlling terminal + resize; `.dev/reference/tmux` `spawn.c`/`fdforkpty.c` is the reading |
|
||||
| 3 | [signals as events](runtime-v2/03-signals-as-events.md) | ⬜ `refine` — SIGWINCH/SIGCHLD as mailbox messages; signalfd lean; the seam 42 deferred to its real consumer |
|
||||
| 4 | [termios adoption](runtime-v2/04-termios.md) | ⬜ `refine`, **startable alone** — raw mode + guaranteed restore on the process's own tty |
|
||||
| 5 | [fd passing](runtime-v2/05-fd-passing.md) | ⬜ `refine`, **startable alone** — SCM_RIGHTS over unix sockets; detach/attach's foundation |
|
||||
| 1 | [streaming subprocess](runtime-v2/01-streaming-subprocess.md) | ⬜ ready — `proc.spawn -> Child{id,in,out,err}` (fds driven by the net verbs; kernel pipe = backpressure), `proc.wait_dl`, `proc.signal`; actor-owned lifecycle, 42's sweeps. First up |
|
||||
| 2 | [PTY](runtime-v2/02-pty.md) | ⬜ ready, after 1 — `proc.spawn_pty(cmd, args, cols, rows)` (master raw, in==out), `proc.resize`; `-lutil` link check flagged |
|
||||
| 3 | [signals as events](runtime-v2/03-signals-as-events.md) | ⬜ ready, **startable alone** — `signal.on(sig, addr)` delivering the sig number as a scalar; signalfd on shard 0's plane; TERM/INT refused by name |
|
||||
| 4 | [termios adoption](runtime-v2/04-termios.md) | ⬜ ready, **startable alone** — `term.raw(fd)`/`term.restore(fd)`; restore is a runtime obligation (unwind/stop), no wrecked tty ever |
|
||||
| 5 | [fd passing](runtime-v2/05-fd-passing.md) | ⬜ ready, **startable alone** — `net.send_fd`/`net.recv_fd` (one fd, SCM_RIGHTS) + `net.connect_unix` (38 pending, verified) |
|
||||
|
||||
### ▸ wmux — the terminal multiplexer track
|
||||
|
||||
|
|
|
|||
|
|
@ -34,9 +34,14 @@ cross a unix socket. Five seams, each builtin-sized, each in
|
|||
| 4 | [termios adoption](04-termios.md) | the process's OWN tty into raw mode and back — adopting a terminal it was given |
|
||||
| 5 | [fd passing](05-fd-passing.md) | SCM_RIGHTS over unix sockets — detach/attach's foundation, and the Wayland stage's later |
|
||||
|
||||
Build order is 1 → 2 → 3 (each leans on the last); 4 and 5 have no
|
||||
incoming edges and are startable alone. Edges live in
|
||||
[dependency graph section 6](../../00-dependency-graph.md).
|
||||
All five are `readiness: ready` since the track-wide brainstorm
|
||||
([spec](../../superpowers/specs/2026-09-01-runtime-v2-design.md),
|
||||
2026-09-01), which also settled the build order: only 1 → 2 is chained
|
||||
(spawn_pty extends spawn's plumbing); **3, 4 and 5 are startable alone,
|
||||
today** — the pull-transport decision (a child is fds; the net verbs
|
||||
drive them) broke the old 1→2→3 chain. Edges live in
|
||||
[dependency graph section 6](../../00-dependency-graph.md); each
|
||||
iteration writes its own implementation plan when it starts.
|
||||
|
||||
## The driving workload
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
track: runtime-v2
|
||||
iteration: "1"
|
||||
status: pending
|
||||
readiness: refine
|
||||
readiness: ready
|
||||
---
|
||||
|
||||
# runtime-v2 1 — streaming subprocess: a long-lived child as a peer
|
||||
|
||||
> Part of [Story — runtime-v2: the runtime beyond sockets](00-story.md).
|
||||
> Spec: [`2026-09-01-runtime-v2-design.md`](../../superpowers/specs/2026-09-01-runtime-v2-design.md)
|
||||
> (track-wide brainstorm, approved 2026-09-01 — the forks below are settled).
|
||||
> Iteration [42](../language-runtime-database/42-bounded-subprocess.md)'s
|
||||
> named follow-up, promoted to this track's opening slice. 42 built the
|
||||
> machinery a streaming form reuses whole: the `wo_child` registry, the
|
||||
|
|
@ -21,33 +23,31 @@ readiness: refine
|
|||
> actor as it happens, stdin must reach the child, and exit must arrive
|
||||
> as an event — all without violating a single 42 guarantee.
|
||||
|
||||
## Info — the forks (open; why this is `refine`)
|
||||
## Info — the forks, settled (brainstorm 2026-09-01)
|
||||
|
||||
1. **Verb shape.** A new `proc.spawn(cmd, args, …)` returning a child
|
||||
HANDLE, versus spawn-options on `proc.run_dl`. Sub-fork: the handle
|
||||
as an actor address (the child looks like an actor — `send` to feed
|
||||
stdin, `monitor` for exit, iteration 24 machinery free) versus an
|
||||
opaque scalar with its own verb family.
|
||||
2. **Output transport.** PUSH — stdout/stderr chunks delivered as
|
||||
`Bytes` messages into the owner's mailbox (the fd-event pattern) —
|
||||
versus PULL — a `read`-style verb the fiber parks on, the
|
||||
`net.read_dl` mould. Push composes with actors; pull composes with
|
||||
backpressure.
|
||||
3. **The mailbox-cap collision** — the fork that decides whether push is
|
||||
viable at all: a chatty child versus `wo_mailbox_cap`'s fail-fast
|
||||
1024. Drop chunks? Kill the child by name? Or stop reading the pipe
|
||||
and let the KERNEL buffer be the backpressure (the lean — the child
|
||||
blocks on a full pipe exactly as it would under a slow tmux).
|
||||
4. **stdin and its mirror.** Child not reading, pipe full: park the
|
||||
writing fiber with a deadline (the `net.write_dl` mould) versus
|
||||
refuse at a byte cap.
|
||||
5. **Bounds semantics shift.** Total-output cap and total deadline stop
|
||||
meaning anything for a shell that runs for days — per-chunk caps and
|
||||
an IDLE deadline replace them; exit notification as a monitor-style
|
||||
death notice carrying the code, versus a blocking `wait` verb.
|
||||
1. **Verb shape: `proc.spawn(cmd, args) -> ?Child`** — a new verb, and
|
||||
the handle is a RECORD, not an actor: `Child {id, in, out, err}`,
|
||||
all Int-shaped, joining the predeclared records.
|
||||
2. **Transport: PULL, and it already exists.** The child's fds are
|
||||
ordinary conn-like values; `net.read_dl`/`net.write_dl`/`net.close`
|
||||
drive them unchanged through the park plane. This iteration adds NO
|
||||
transport code at all — only acquisition, ownership and exit.
|
||||
3. **The mailbox-cap collision never starts** — nothing is pushed. The
|
||||
kernel pipe is the backpressure: an owner that stops reading makes
|
||||
the child block on write, exactly tmux under a slow client.
|
||||
4. **stdin mirror settled by the same move**: `net.write_dl` on
|
||||
`child.in` parks with a deadline; torn-write semantics as on sockets.
|
||||
5. **Bounds:** the idle deadline is `read_dl`'s per-call ms; output caps
|
||||
are the caller's read sizes (no runtime buffer exists); exit is
|
||||
`proc.wait_dl(id, ms) -> ?Int` parking on the slot's pidfd — one
|
||||
waiter per id, a second refuses by name. `proc.signal(id, sig)`
|
||||
completes the surface. A death-notice verb is refused: a two-line
|
||||
fiber composes `wait_dl` into an event.
|
||||
|
||||
Ownership does not fork: 42's rule stands — the owner unwinding kills
|
||||
the child; engine stop kills them all; a zombie or an orphan is a bug.
|
||||
Ownership (stated, not forked): the spawning ACTOR owns the child (the
|
||||
program, when spawned outside one). Actor death, engine stop and
|
||||
`wo_vm_destroy` kill, reap and close — 42's doctrine, streaming edition.
|
||||
Ceiling stays 32 per shard, failing closed by name.
|
||||
|
||||
## Acceptance sketch (firmed at brainstorm)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
track: runtime-v2
|
||||
iteration: "2"
|
||||
status: pending
|
||||
readiness: refine
|
||||
readiness: ready
|
||||
---
|
||||
|
||||
# runtime-v2 2 — PTY: a child that believes it owns a terminal
|
||||
|
|
@ -19,19 +19,19 @@ readiness: refine
|
|||
> `compat/fdforkpty.c` (~450 lines of C covering five platforms; Linux
|
||||
> alone is far smaller).
|
||||
|
||||
## Info — the forks (open)
|
||||
## Info — the forks, settled (brainstorm 2026-09-01; spec:
|
||||
[`2026-09-01-runtime-v2-design.md`](../../superpowers/specs/2026-09-01-runtime-v2-design.md))
|
||||
|
||||
1. **Surface.** A `pty: true` option on iteration 1's spawn verb (the
|
||||
lean — one spawn shape, two transports) versus a separate
|
||||
`proc.spawn_pty`.
|
||||
2. **Resize.** A verb on the handle (`resize(cols, rows)` → TIOCSWINSZ)
|
||||
— needed the moment [3](03-signals-as-events.md) delivers SIGWINCH.
|
||||
The fork is only whether it ships here or with 3.
|
||||
3. **The master fd's transport** is settled by iteration 1's fork 2 —
|
||||
whatever won there carries the PTY master identically.
|
||||
4. **Encoding edge.** A PTY master delivers the child's output with tty
|
||||
post-processing (ONLCR and friends): raw the master by default versus
|
||||
expose termios knobs. Lean: raw, no knobs, until a consumer asks.
|
||||
1. **Separate verb**: `proc.spawn_pty(cmd, args, cols, rows) -> ?Child`
|
||||
— its own builtin id (static arity table stays static), same Child
|
||||
record with `in` and `out` both the master fd, `err` nil.
|
||||
2. **Resize ships HERE**: `proc.resize(id, cols, rows)` → TIOCSWINSZ on
|
||||
the slot; refusal by name on a pipe child.
|
||||
3. **Transport settled by iteration 1's pull decision** — the master fd
|
||||
is driven by the existing net verbs, nothing new.
|
||||
4. **Master raw by default, no knobs** — until a consumer asks by name.
|
||||
(`openpty` may need `-lutil` — the plan verifies the link line
|
||||
before assuming.)
|
||||
|
||||
## Acceptance sketch
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
track: runtime-v2
|
||||
iteration: "3"
|
||||
status: pending
|
||||
readiness: refine
|
||||
readiness: ready
|
||||
---
|
||||
|
||||
# runtime-v2 3 — signals as events: SIGWINCH into a mailbox
|
||||
|
|
@ -21,25 +21,23 @@ readiness: refine
|
|||
> once: the stop flag is a signal made safe by latching. This iteration
|
||||
> generalizes that shape without handing user code a signal handler.
|
||||
|
||||
## Info — the forks (open)
|
||||
## Info — the forks, settled (brainstorm 2026-09-01; spec:
|
||||
[`2026-09-01-runtime-v2-design.md`](../../superpowers/specs/2026-09-01-runtime-v2-design.md))
|
||||
|
||||
1. **Registration surface.** `signal.on(SIGWINCH, addr, msg)` in the
|
||||
`time.after` mould (the msg MOVES to the runtime, delivered on
|
||||
arrival) versus a process-level subscription table in `wo.toml`.
|
||||
Lean: the builtin — dynamic, one consumer today.
|
||||
2. **Delivery mechanics.** `signalfd` on shard 0's plane (a signal
|
||||
becomes an fd event — no async-signal-safety questions at all, the
|
||||
kernel-primitive taste) versus a latch array swept like deadlines.
|
||||
Lean: signalfd; the runtime is Linux-first and the plane already
|
||||
multiplexes fds.
|
||||
3. **Which signals are offerable.** SIGWINCH and SIGCHLD certainly;
|
||||
SIGTERM/SIGINT stay the ENGINE's (the stop latch is load-bearing —
|
||||
iteration 40's drain). The fork is whether user registration for the
|
||||
stop signals is refused by name or layered before the latch.
|
||||
4. **Coalescing.** Signals coalesce in the kernel; a mailbox message per
|
||||
delivery can't promise one-per-resize. Disclose coalescing (lean —
|
||||
it is what SIGWINCH consumers expect anyway) versus sequence-number
|
||||
them.
|
||||
1. **`signal.on(sig, addr)`** — a standing subscription delivering the
|
||||
SIGNAL NUMBER as a scalar message. No moved-message ownership rules:
|
||||
scalars copy, so a repeating SIGWINCH needs no per-delivery payload.
|
||||
2. **signalfd on shard 0's plane** — one more registered fd with a
|
||||
sentinel `user_data` (the wake-eventfd precedent); reads drain
|
||||
`signalfd_siginfo` records and fan out ordinary sends.
|
||||
3. **Offerable set: SIGWINCH, SIGCHLD, SIGHUP, SIGUSR1, SIGUSR2.**
|
||||
SIGTERM/SIGINT registration is refused by name — the stop latch
|
||||
stays the engine's, iteration 40 is load-bearing.
|
||||
4. **Coalescing disclosed, not sequenced** — what SIGWINCH consumers
|
||||
expect anyway.
|
||||
|
||||
Also settled: this iteration is STANDALONE — the old edge from PTY was
|
||||
only the resize pairing; signalfd needs nothing from iteration 2.
|
||||
|
||||
## Acceptance sketch
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
track: runtime-v2
|
||||
iteration: "4"
|
||||
status: pending
|
||||
readiness: refine
|
||||
readiness: ready
|
||||
---
|
||||
|
||||
# runtime-v2 4 — termios adoption: raw mode on a terminal we were given
|
||||
|
|
@ -17,20 +17,18 @@ readiness: refine
|
|||
> including a trap. A terminal left raw is the classic way a program
|
||||
> makes a user's shell unusable.
|
||||
|
||||
## Info — the forks (open)
|
||||
## Info — the forks, settled (brainstorm 2026-09-01; spec:
|
||||
[`2026-09-01-runtime-v2-design.md`](../../superpowers/specs/2026-09-01-runtime-v2-design.md))
|
||||
|
||||
1. **Surface size.** Exactly two verbs — `term.raw()` returning a
|
||||
restore token and `term.restore(token)` (the lean: wmux needs
|
||||
nothing else; tmux itself uses little more than `cfmakeraw`) —
|
||||
versus exposing termios flag knobs. YAGNI says two verbs and a
|
||||
refusal for the rest.
|
||||
2. **Restore guarantee.** Tie restoration to the unwind machinery (the
|
||||
42 ownership pattern: fiber dies, terminal restored — a runtime
|
||||
obligation) versus caller's-problem-with-a-doc-note. Lean: runtime
|
||||
obligation; "no orphan" has a terminal-state sibling: no wrecked tty.
|
||||
3. **Scope.** stdin only, versus any tty fd (a client adopting a tty it
|
||||
received via [5](05-fd-passing.md) — the wmux SERVER's need). This
|
||||
fork decides whether the verb takes an fd argument now or grows one
|
||||
1. **Exactly two verbs**: `term.raw(fd)` and `term.restore(fd)`. Saved
|
||||
termios live in a per-shard table keyed by fd (one thread — no
|
||||
locks); double-raw on one fd refuses by name; no flag knobs.
|
||||
2. **Restore is a RUNTIME OBLIGATION.** Fiber unwind, engine stop and
|
||||
`wo_vm_destroy` restore every saved tty, newest first — "no orphan"
|
||||
has its terminal-state sibling: no wrecked tty, ever, trap paths
|
||||
included.
|
||||
3. **fd-taking from day one** — a tty received via
|
||||
[5](05-fd-passing.md) works without the verb growing an argument
|
||||
later.
|
||||
|
||||
## Acceptance sketch
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
track: runtime-v2
|
||||
iteration: "5"
|
||||
status: pending
|
||||
readiness: refine
|
||||
readiness: ready
|
||||
---
|
||||
|
||||
# runtime-v2 5 — fd passing: SCM_RIGHTS over the unix socket
|
||||
|
|
@ -22,21 +22,19 @@ readiness: refine
|
|||
> SCM_RIGHTS ancillary data is the only mechanism, and it is runtime
|
||||
> work by nature.
|
||||
|
||||
## Info — the forks (open)
|
||||
## Info — the forks, settled (brainstorm 2026-09-01; spec:
|
||||
[`2026-09-01-runtime-v2-design.md`](../../superpowers/specs/2026-09-01-runtime-v2-design.md))
|
||||
|
||||
1. **Surface.** `net.send_fd(conn, fd)` / `net.recv_fd(conn)` — two
|
||||
verbs, fd travels alone (the lean; tmux sends its imsg header as
|
||||
ordinary bytes beside it) — versus fd-attached-to-a-message framing
|
||||
in the runtime.
|
||||
2. **What arrives.** The received fd as an opaque scalar the existing
|
||||
`net.read`/`net.write`/termios verbs accept (lean — every fd verb
|
||||
already takes an Int-shaped conn) versus a new wrapped type.
|
||||
3. **The unix-socket client side.** Iteration 38's `net.connect` covers
|
||||
outbound TCP; the CLIENT half of a unix-socket connection may or may
|
||||
not exist by then — this iteration carries `net.connect_unix` if 38
|
||||
has not landed it first. Verify at brainstorm, not assumed.
|
||||
4. **Bounds.** One fd per message, refusal by name past it (lean),
|
||||
versus SCM_RIGHTS' multi-fd arrays. YAGNI: no consumer sends two.
|
||||
1. **Two verbs, fd travels alone**: `net.send_fd(conn, fd) -> Bool` /
|
||||
`net.recv_fd(conn) -> ?Int` — `sendmsg` + SCM_RIGHTS, fixed
|
||||
ancillary buffer; framing stays the caller's ordinary bytes (the
|
||||
tmux imsg shape, composed in `.wo`).
|
||||
2. **A received fd is a plain Int** every existing fd verb accepts —
|
||||
net reads/writes, termios adoption included.
|
||||
3. **This iteration CARRIES `net.connect_unix(path) -> Int`** —
|
||||
iteration 38 verified pending at brainstorm time, not assumed.
|
||||
4. **One fd per message**, refusal by name past it; non-unix sockets
|
||||
refuse by name; multi-fd arrays wait for a consumer.
|
||||
|
||||
## Acceptance sketch
|
||||
|
||||
|
|
|
|||
100
docs/superpowers/specs/2026-09-01-runtime-v2-design.md
Normal file
100
docs/superpowers/specs/2026-09-01-runtime-v2-design.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# runtime-v2 — the runtime beyond sockets, all five iterations settled
|
||||
|
||||
> Brainstormed and approved 2026-09-01, one pass across the whole track:
|
||||
> [runtime-v2](../../stories/runtime-v2/00-story.md) iterations 1–5.
|
||||
> Scope settled: PULL transport (a child is fds; the net verbs already
|
||||
> drive them), `wait_dl` over death notices, PTY as a spawn variant,
|
||||
> signals via signalfd delivering the signal number, termios as two
|
||||
> fd-taking verbs with runtime-guaranteed restore, fd passing as two
|
||||
> verbs plus `net.connect_unix`. This spec is track-wide; each iteration
|
||||
> gets its own implementation plan when it starts.
|
||||
|
||||
## The one principle
|
||||
|
||||
**A child is fds plus a registry slot — runtime-v2 adds ACQUISITION
|
||||
verbs, never new transport.** `net.read_dl`, `net.write_dl` and
|
||||
`net.close` already park on any fd through the shard plane (POLL_ADD is
|
||||
fd-agnostic; pipes and PTY masters qualify today). So:
|
||||
|
||||
- Backpressure is the kernel pipe. An owner that stops reading makes
|
||||
the child block on write — exactly tmux under a slow client. The
|
||||
mailbox-cap collision never starts because nothing is pushed.
|
||||
- The idle deadline is `read_dl`'s own per-call ms. Total-output caps
|
||||
disappear because no runtime buffer exists — the caller's read sizes
|
||||
ARE the bound.
|
||||
- A wmux pane actor is chat's Reader actor with a different fd.
|
||||
|
||||
The rejected alternative, recorded: PUSH (child output as mailbox
|
||||
messages) required new delivery machinery in `vm.c`, a policy for the
|
||||
`wo_mailbox_cap` collision (drop, kill, or park — each wrong somewhere),
|
||||
ordering rules across two streams, and per-chunk allocation. Everything
|
||||
pull gets from three landed iterations, push would rebuild.
|
||||
|
||||
## The surface
|
||||
|
||||
New builtin ids from 97 up. Every id is FOUR registrations — `wob.h`
|
||||
enum, `loader.c` arity table, `builtin.c` dispatch range, `types.ml`
|
||||
stdlib row — the iteration 42 lesson; missing one reads as "unknown
|
||||
stdlib builtin" from a valid image. No `.wob` version bump (the 38/42
|
||||
precedent). `Child` joins the predeclared records (`types.ml`
|
||||
`stdlib_records`): `id`, `in`, `out`, `err` — all Int-shaped; the fds
|
||||
are ordinary conn-like values every existing fd verb accepts.
|
||||
|
||||
| Verb | Contract |
|
||||
| --- | --- |
|
||||
| `proc.spawn(cmd, args) -> ?Child` | iteration 1. Pipes on all three stdio fds, parent ends `O_NONBLOCK`; the registry slot is claimed (ceiling 32/shard, fails closed by name) and OWNED (below) |
|
||||
| `proc.wait_dl(id, ms) -> ?Int` | iteration 1. Parks on the slot's pidfd (the 42 machinery); the exit code, or nil at the deadline. ONE waiter per id — a second concurrent wait refuses by name |
|
||||
| `proc.signal(id, sig)` | iteration 1. `pidfd_send_signal` through the slot — no pid-reuse race |
|
||||
| `proc.spawn_pty(cmd, args, cols, rows) -> ?Child` | iteration 2. openpty; child on the slave as controlling terminal; `in` and `out` are both the master, `err` is nil; master raw by default, no knobs |
|
||||
| `proc.resize(id, cols, rows)` | iteration 2. TIOCSWINSZ on the slot's master; refusal by name on a pipe child |
|
||||
| `signal.on(sig, addr)` | iteration 3. Standing subscription; delivery is an ordinary send of the SIGNAL NUMBER as a scalar (scalars copy — no moved-message ownership rules). Offerable: SIGWINCH, SIGCHLD, SIGHUP, SIGUSR1, SIGUSR2. SIGTERM/SIGINT refused by name — the stop latch stays the engine's (iteration 40 is load-bearing). Kernel coalescing disclosed, not sequenced |
|
||||
| `term.raw(fd)` / `term.restore(fd)` | iteration 4. Save termios into the shard table, `cfmakeraw`, restore from the table. Restore is a RUNTIME OBLIGATION: fiber unwind, engine stop and `wo_vm_destroy` restore every saved tty, newest first. fd-taking from day one — a tty received via iteration 5 works |
|
||||
| `net.send_fd(conn, fd) -> Bool` | iteration 5. `sendmsg` + SCM_RIGHTS, exactly one fd, fixed ancillary buffer; refusal by name on a non-unix socket or a second fd |
|
||||
| `net.recv_fd(conn) -> ?Int` | iteration 5. The received fd as a plain Int; nil when the peer sent none |
|
||||
| `net.connect_unix(path) -> Int` | iteration 5 carries it — iteration 38 (`net.connect`) has not landed; verified pending, not assumed |
|
||||
|
||||
## Lifecycle — 42's doctrine, streaming edition
|
||||
|
||||
The `wo_child` slot grows: pid, pidfd, owner, and the fds the runtime
|
||||
must close if the owner never does. **Owner is the spawning ACTOR when
|
||||
one exists, else the program.** Actor death (iteration 24's dead mark),
|
||||
engine stop, and `wo_vm_destroy` each sweep: kill via pidfd, reap,
|
||||
close the slot's fds, restore any termios the shard saved. The
|
||||
per-shard ceiling stays 32; a zombie, an orphan, or a wrecked tty is a
|
||||
bug by definition. `wait_dl` parking reuses `fb->dl_active`/`dl_at`
|
||||
and parks on the pidfd directly — no epoll bundle needed for a single
|
||||
fd.
|
||||
|
||||
## Mechanics notes (for the plans)
|
||||
|
||||
- signalfd joins shard 0's plane as one more registered fd with a
|
||||
sentinel `user_data`, the wake-eventfd precedent in `park.c`; reads
|
||||
drain `signalfd_siginfo` records and fan out sends.
|
||||
- The termios table is per-shard (one thread — no locks), keyed by fd;
|
||||
double-raw on one fd refuses by name.
|
||||
- Raw syscalls wherever glibc 2.35 lacks wrappers (the 42 rule);
|
||||
`signalfd` and `openpty` are fine (`openpty` needs `-lutil` — check
|
||||
the Makefile link line before assuming).
|
||||
- Each iteration's gate legs: the 42 batteries repeat — fd-flat churn,
|
||||
stop/unwind reaping, refusals asserted verbatim — plus the
|
||||
iteration's own proof (PTY prompt bytes, SIGWINCH round trip, termios
|
||||
restore-after-trap, fd-across-socket byte echo).
|
||||
|
||||
## Sequencing (the graph remap this settles)
|
||||
|
||||
Pull transport cuts the old 1→2→3 chain: signals never needed PTY, only
|
||||
the resize PAIRING. Edges now: **1 → 2** (spawn_pty extends spawn's
|
||||
plumbing); **3, 4, 5 startable alone, today**; the VTE grid is wmux's
|
||||
own `.wo` work, also standalone (a replay corpus needs no subprocess).
|
||||
wmux 1 consumes all five plus the grid.
|
||||
|
||||
## Out of scope, by name
|
||||
|
||||
- PUSH delivery of child output — rejected above, revisit only with a
|
||||
consumer pull cannot serve.
|
||||
- Death notices (`proc.watch`) — a two-line fiber composes `wait_dl`
|
||||
into an event; build it in `.wo`, not in C.
|
||||
- termios knobs beyond raw/restore; multi-fd SCM_RIGHTS arrays;
|
||||
sequence-numbered signals; PTY termios shaping — each waits for a
|
||||
named consumer.
|
||||
- SIGTERM/SIGINT user handling — permanently the engine's.
|
||||
Loading…
Reference in a new issue