diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index 98f1cc31..97ac8d79 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -786,25 +786,42 @@ the engine and happens whole. | Operation | What it does | |---|---| -| 1, look | The seven words of the message at the front of the wire are copied to the word address in A. Nothing is taken. | -| 2, take | The message is copied out, its seven words to A and its text to B, and is off the wire. | -| 3, move | The message goes to the back of the wire of the port in A, with one fewer in its fourth word (how many more nodes may pass it on). | -| 4, drop | The message is off the wire and gone. | +| 1, look | The seven words of the message the wire's mark is at are copied to the word address in A. Nothing is taken. | +| 2, take | That message is copied out, its seven words to A and its text to B, and is off the wire. The mark is then at the one that followed it. | +| 3, move | That message goes to the back of the wire of the port in A, with one fewer in its fourth word (how many more nodes may pass it on). The mark is then at the one that followed it. | +| 4, drop | That message is off the wire and gone. The mark is then at the one that followed it. | | 5, put | The message whose seven words are at A and whose text is at B goes to the back of the wire. | -| 6, find | The first message on the wire that is to the node in B and of the type in A becomes the one that look, take and drop are about, wherever it is in the queue. | -| 7, next | The same, from after the one found last. | -| 8, sleep | The node is blocked until one of its wires has a message. | -| 9, sleep for room | The node is blocked until the wire has A cells free, or one of its wires has a message, whichever is first. | +| 6, first | The mark is put at the message at the front of the wire. | +| 7, next | The mark is put at the message after the one it is at. | +| 8, sleep | The node is blocked until a message has come to one of its wires since it last fetched "which wires have a message". | +| 9, sleep for room | The node is blocked until the wire has A cells free, or a message has come as in 8, whichever is first. | +| 10, room | Has the wire A cells free? It answers at once and changes nothing. | -- Look, take, move and drop are about the message at the front, unless - find or next has named another. +- Each wire has a mark for the node that reads it: the message that + look, take, move and drop are about. First and next move it; with no + message there, those four answer 3. - Only 8 and 9 block. A node blocked there executes nothing. +- **Sleep is woken by a message coming, not by one being there.** A node + may leave a message for itself on a wire -- text it will not begin + while it waits for an answer -- and must still sleep. - The ports themselves stay as they are, a word at a time, for the two things that are not messages: a newborn node being sent its nucleus (section 5), and a node's requests to the kernel (section 8). - The looking words of section 7a, and their lower-number rule, are no longer used by the nucleus. +*(Changed while the plan was written, 2026-10-08. As approved there was +"find, by type" where first and next are, sleep was "until a wire has a +message", and there was no room. Three reasons. A message a node leaves +for itself at the front of a wire must not keep back the ones behind it +that are for other nodes: so the node goes through a wire message by +message, and the fabric need not know what kind it is looking for. A +sleep that ended whenever a message was there would never sleep with one +left on a wire. And room is Captain Bob's probe -- "a fast, hot probe +into memory for enough space for an x byte message" -- at the one place +it can be certain: the wire a node itself writes to, which nothing else +fills. See 7d.4 and 7d.9.)* + ### 7d.4 The nucleus A node keeps no messages but the one it is doing. The ring of messages @@ -829,10 +846,18 @@ waiting, the words that turned it, the list of NACKs owed, and and sleeps again; text for itself from its console ends the text it is doing in error 21, "Interrupted"; anything else for itself stays on the wire. -- *`AWAIT`* sleeps (8) and, woken, passes on what is for other nodes and - looks for what would end its wait with find and next: the answer, a - NACK or a believed GONE about the node waited for, or text from its - console. Other messages for itself stay on the wire, in their order. +- *`AWAIT`* sleeps (8) and, woken, goes through each wire with first and + next: what is for other nodes is passed on; what would end its wait is + taken -- the answer, a NACK or a believed GONE about the node waited + for, or text from its console; other messages for itself stay on the + wire, in their order. +- *Room for the answer is kept.* Only a node puts messages on the wires + that lead from it, so room it has found there stays until it uses it. + A node does not begin text unless the wire back toward the sender has + room for the answer (8 cells), and while it does that text nothing + else it puts on that wire -- what it passes on, what it prints -- may + take those 8 cells. So how text ended is never refused at the node + that did the text. - *Before text begins any of these* it tries the stack it will need, as `(ROOM)` does now (7c.7). Between texts the rule of 28 values stands. @@ -886,6 +911,32 @@ requests stay a word at a time. commit before gives on the same input, but where 7d.6 says otherwise; `sanitize`, `hosted-check`, lint, three boots, one hash on all six. +### 7d.9 A ping: thought about, not ruled + +Captain Bob, 2026-10-08: "think about sending a ping that's a fast, hot +probe into memory for enough space for an x byte message." + +- *At one hop it is in the design:* operation 10, room. It is certain + there, because a node is the only writer of the wires that lead from + it, and 7d.4 uses it to keep room for an answer. +- *Across several hops* a ping would be a message of its own: it names a + length, each node that passes it on asks room for that length on the + next wire, and the node it is for answers that there is room all the + way, or a node on the way answers that there is not. It would let a + sender learn a long message will not get through before sending it. +- *What it cannot be without more:* a promise. Between the ping and the + message another sender may fill a wire on the way. To be a promise + each node would have to keep the room it reported for a while, and + there is no clock (7b.1). The room would have to be given back by a + message, or kept until used. +- *"Hot":* a message's fourth word is to carry heat as well as how many + nodes may pass it on (section 6); nothing acts on heat yet. A ping + that goes ahead of other messages on a wire would be the first thing + to. + +Not in step 6e. To be put to Captain Bob: whether a ping across hops is +wanted, and if so whether it promises room or only reports it. + ### 7d.8 A later step, not built: the wires in the system's blocks Captain Bob, 2026-10-08. The system has blocks set aside for it -- the diff --git a/docs/v4.0.0/plans/2026-10-08-step6e-messages-on-the-wire.md b/docs/v4.0.0/plans/2026-10-08-step6e-messages-on-the-wire.md new file mode 100644 index 00000000..24b3d29c --- /dev/null +++ b/docs/v4.0.0/plans/2026-10-08-step6e-messages-on-the-wire.md @@ -0,0 +1,166 @@ +# Step 6e: Messages on the Wire — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** A node that is stuck holds up no other node, by having the fabric move whole messages between neighbours and hold them on the wires. + +**Architecture:** The fabric keeps a queue of whole messages each way on every wire. A node asks the fabric, through a few addresses after its ports, to look at, take, move, drop or put a whole message; each is one engine step, all or nothing. A node holds nothing in transit: the nucleus's ring of messages waiting, the words that turned it, the list of NACKs owed and `(GATE)` are removed. Only two things block: sleep, and sleep for room on a wire, used by printing alone. + +**Tech Stack:** C99 (`v4/src`, `v4/include/v4`, `v4/system/boot.c`, `v4/tools/hosted.c`, tests), the v4 nucleus dialect (`v4/capsule/core.v4`, `quit.v4`), FORTH (`capsules/v4/hera.4th`), Python 3 (`v4/tools/depthsweep.py`), `make -C v4`, `make -f kernel/Makefile`. + +**Spec:** `docs/v4.0.0/MESH.md` section 7d (7d.3 the interface, 7d.4 the nucleus, 7d.6 the limits, 7d.7 the acceptance). Sections 7c.6 and 7c.7 say what the first attempt got wrong; read them before Task 1. + +## Global Constraints + +- Branch `StarForth-v4.0.0`, main checkout. No new branch, no stash, no worktree. End commit messages with `Co-Authored-By: Claude Opus 5.5 `. +- **Every commit is chained on its checks with `&&`**: `make -C v4 hosted-check` must pass in the same command that commits. A commit was once pushed with it failing. +- No bad code is released: a defect found on the way is fixed, test first, and reported. No stubs or stand-ins. Do not change v3. +- A statement about what v3 or v4 does today is checked in the code before it is relied on. A difference from the F18 is noted in a line and not held for ruling. +- Both stacks stay at 32 (`V4_DATA_RING=30`, `V4_RET_RING=31`). A text may leave 28 values on the stack. +- `V4_WIRE_CELLS` is 1,024, a build parameter set where `V4_PORTS` is. +- A message is seven words and its text, four characters to a word (MESH.md section 6); on a wire it takes `7 + (length + 3) / 4` cells. A length below 0 or above 1,024 is "not a message". +- Operation numbers and results are those of MESH.md 7d.3: 1 look, 2 take, 3 move, 4 drop, 5 put, 6 first, 7 next, 8 sleep, 9 sleep for room, 10 room; results 0 done, 1 no room, 2 nothing on that port, 3 no such message, 4 not a message. +- Message types: 1 text, 2 output, 3 done, 4 NACK, 5 GONE. Errors: 18 "No one on that port", 19 "Message refused", 20 "Node gone", 21 "Interrupted". +- **Every nucleus path that begins, takes or passes on a message is tested at every depth of both stacks, and with a node removed at every step of the exchange, before the task that adds it is called done.** A hang, a line not answered, or a node that does not come back is a failure; a clean error is not. +- After each task the hosted product is compared with the commit before on the same typed session (the session of `depthsweep.py` at shallow depth, plus `WORDS`, a 400-number loop, an error inside a definition after output, `QUIT`, `ABORT"`): any difference is explained by MESH.md 7d.6 or it is a defect. +- Before any QEMU run read `.claude/CLAUDE.md` "Running / Acceptance" and the memory note `acceptance-test-rules.md`. One QEMU at a time, `clean` before `qemu`, all three ISAs, logs kept and committed. Never delete a log. + +## Review Focus + +1. **A put or take whose addresses run off the node's memory**, or lie in its ports. Expected: result 4, nothing copied, nothing on the wire changed. Test in Task 1. +2. **A node that leaves a message for itself on a wire and sleeps.** Expected: it sleeps (the engine clock does not move) until another message comes; it does not go round. Tests in Task 1 (engine) and Task 3 (`AWAIT`, and printing that waits). +3. **A message for another node behind one the node has left for itself.** Expected: it is passed on; the one left stays where it was. Test in Task 3. +4. **A text that prints with the wire back holding exactly the room kept for its answer.** Expected: printing waits; how the text ended is never refused at the node that did it. Test in Task 3. +5. **A node removed, or a wire cut, with messages waiting both ways.** Expected: they are let go; no other node is left blocked or with a mark at a message that is gone. Tests in Task 1 and Task 4. + +--- + +### Task 1: The wire in the engine + +**Files:** Create `v4/include/v4/wire.h`, `v4/src/wire.c`, `v4/tests/test_wire.c`. Modify `v4/include/v4/node.h`, `v4/src/node.c`, `v4/include/v4/fabric.h`, `v4/src/fabric.c`, `v4/tests/test_fabric.c`. + +**Interfaces — Produces:** + +```c +/* wire.h -- a queue of whole messages, one way along a wire */ +#ifndef V4_WIRE_CELLS +#define V4_WIRE_CELLS 1024 +#endif +typedef struct { + v4_cell cell[V4_WIRE_CELLS]; /* the messages, one after another, going round */ + unsigned head; /* where the front message begins */ + unsigned used; /* how many cells they take */ + unsigned mark; /* how many cells from the front the reader's mark is; == used: at no message */ + unsigned arrived; /* not 0: a message has come since the reader last asked which wires have one */ +} v4_wire_queue; + +enum { V4_WIRE_DONE = 0, V4_WIRE_NO_ROOM = 1, V4_WIRE_NO_ONE = 2, V4_WIRE_NO_MESSAGE = 3, V4_WIRE_NOT_A_MESSAGE = 4 }; + +void v4_wire_reset(v4_wire_queue *q); +unsigned v4_wire_cells(v4_cell length); /* cells a message of that length takes; 0: not a message */ +int v4_wire_room(const v4_wire_queue *q, unsigned cells); +int v4_wire_put(v4_wire_queue *q, const v4_cell *header7, const v4_cell *text); /* a V4_WIRE_ result */ +void v4_wire_first(v4_wire_queue *q); +int v4_wire_next(v4_wire_queue *q); /* V4_WIRE_DONE, or V4_WIRE_NO_MESSAGE at the end */ +int v4_wire_look(const v4_wire_queue *q, v4_cell *header7); +int v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap); +int v4_wire_drop(v4_wire_queue *q); +int v4_wire_move(v4_wire_queue *from, v4_wire_queue *to); /* the marked message, its fourth word one fewer */ +``` + +- `node.h`: indexes `V4_PORT_WIRE (V4_PORTS + 4)`, `V4_PORT_A`, `V4_PORT_B`, `V4_PORT_DO`, `V4_PORT_HOW`, `V4_PORT_HAVE (V4_PORTS + 9)`. Node fields `unsigned wire; v4_cell wire_a, wire_b; int doing; v4_cell do_op; v4_cell how; unsigned have;`. A store to `V4_PORT_DO` records the operation and blocks the node (`doing = 1`) until whoever connects its ports has done it. +- `int v4_node_doing(const v4_node *n);` and `void v4_node_done(v4_node *n, v4_cell how);` for that connector; `void v4_node_have(v4_node *n, unsigned mask);`. +- `fabric.h`: `void v4_fabric_queues(v4_fabric *f, v4_wire_queue *pool, unsigned count);` — the owner gives the fabric the queues; wiring a port takes two, unwiring gives them back, and `v4_fabric_wire`/`v4_fabric_wire_device` fail when none are left. `int v4_fabric_device_put(v4_fabric *f, unsigned node, unsigned port, const v4_cell *header7, const v4_cell *text);` and `int v4_fabric_device_take(v4_fabric *f, unsigned node, unsigned port, v4_cell *header7, v4_cell *text, unsigned text_cap);` for what is on the other end of a device's wire. +- The word-at-a-time ports, `v4_device`'s `take`/`give`, and the looking words stay as they are in this task. + +- [ ] **Step 1: Write `v4/tests/test_wire.c`, failing.** The queue alone, no node: + - an empty queue: room for 1,024, look/take/drop answer 3; + - put of a message of 0, 1, 4, 5 and 1,024 characters: each takes `v4_wire_cells`; taken back, the seven words and every word of text are what was put, in order, across the end of the array (fill and empty it three times with lengths that do not divide 1,024); + - a put that does not fit answers 1 and changes nothing (compare `head`, `used`, and every cell); + - a length of -1 and of 1,025 answers 4 and changes nothing; + - first/next walk three messages and then answer 3; take or drop of the middle one leaves the first and third in order and the mark at the third; + - move takes the marked message from one queue to the back of another with its fourth word one fewer, and answers 1, changing neither queue, when the other has no room; + - `take` with `text_cap` too small answers 4 and takes nothing; + - `arrived` is set by a put and by a move into the queue. +- [ ] **Step 2: Run** `make -C v4 run-64-test_wire.c`. Expected: it does not compile, naming `v4/wire.h`. +- [ ] **Step 3: Write `wire.h` and `wire.c`.** No allocation; every function leaves the queue as it found it unless it answers `V4_WIRE_DONE`. `v4_wire_cells(length)` is `length < 0 || length > 1024 ? 0 : 7 + (length + 3) / 4`. +- [ ] **Step 4: Run** `make -C v4 run-64-test_wire.c run-32-test_wire.c` and both under `sanitize`. Expected: 0 failures. +- [ ] **Step 5: Write the fabric tests, failing**, in `test_fabric.c` (small assembled programmes, as there): + - a node puts a message on its wire 0 and a neighbour takes it from its wire 3: the words are what was put; "how it went" is 0 on both; each took one engine step blocked; + - a put with nothing on the port is 2; to a wire with no room is 1; neither changes the wire; + - addresses off the end of memory, or inside the ports, for look, take and put: 4, and nothing copied (Review Focus 1); + - "which wires have a message" has the bit of each wire with one, and fetching it clears `arrived`; + - sleep: a node with nothing arrived is blocked and the engine clock does not move for 500 steps; a put to one of its wires wakes it; **a node that looks, leaves the message there, fetches "which wires" and sleeps again stays asleep** until a second message comes (Review Focus 2); + - sleep for room: woken when the reader takes enough; woken by a message coming instead, and "how it went" says which; + - room answers at once and changes nothing; + - move from one of a node's wires to another; with the other full, 1 and nothing moved; + - a node removed with messages waiting both ways on two wires: the neighbours' queues are empty, their marks at no message, and a neighbour asleep for room on that wire is woken with 2 (Review Focus 5); + - a device's wire: `v4_fabric_device_put` and `_take` each way; + - wiring a third wire when the pool has queues for two fails and changes nothing. +- [ ] **Step 6: Run** `make -C v4 run-64-test_fabric.c`. Expected: it does not compile, naming `V4_PORT_WIRE`. +- [ ] **Step 7: Implement** the node indexes and fields, and in `v4_fabric_step`, after the nodes have executed, a loop that does each `doing` node's operation whole and calls `v4_node_done`, but for 8 and 9, which stay until their condition holds. Before the nodes execute, each awake node's "which wires" mask is set. +- [ ] **Step 8: Run** `make -C v4 -k test` and `sanitize`. Expected: 0 failures; the nucleus does not use any of it yet, and `hosted-check` is unchanged. +- [ ] **Step 9: Commit** `feat(v4.0.0): the wire in the engine -- whole messages, put and taken in one step`, chained on `hosted-check`, and push. + +### Task 2: The products' host and the test hosts speak whole messages + +**Files:** Modify `v4/system/boot.c`, `v4/tools/hosted.c`, `kernel/src/v4/sk_v4.c`, `v4/tests/test_host_quit.c`, `v4/tests/test_host_mesh.c`, `v4/tests/test_host_unit.c`, and any other `v4/tests/test_host_*.c` whose loop serves a node's console (find them with `grep -ln 'ask_port == CONSOLE_PORT\|console_take' v4/tests v4/system v4/tools kernel/src/v4`). + +**Interfaces — Consumes:** Task 1. **Produces:** in `boot.c`, for the lone node, the same operations done by the host: port 1 is the console's wire and port 0 the kernel's; `v4_boot` owns two `v4_wire_queue` for each. The test hosts' consoles use `v4_fabric_device_put`/`_take`. + +This task changes no behaviour by itself: it is done together with Task 3 and committed with it, because the nucleus and its hosts change over at once. It is a separate task so that the hosts are written, and read, before the nucleus is. + +- [ ] **Step 1:** In each host, replace the word-at-a-time console (a `v4_message` filled word by word from `asking` on the console port, and words given one at a time to a read) with whole messages: a typed line is put on the node's console wire; each step, every message on the wire from the node is taken and handled (output shown; done ends the line). +- [ ] **Step 2:** Kernel requests stay words: `asking` on port 0 is served as now. +- [ ] **Step 3:** In `boot.c` the lone node's operations are done by a function `boot_do(b)`, called each pass when `v4_node_doing(n)`; sleep ends when a line has been put on its wire; sleep for room cannot fail to find room once the host has taken the console's messages, which it does every pass. +- [ ] **Step 4:** Do not run the suite yet: it passes only with Task 3. + +### Task 3: The nucleus keeps no messages but the one it is doing + +**Files:** Modify `v4/capsule/core.v4`, `v4/capsule/quit.v4`, `v4/tests/host_map.h`, `v4/tools/mkimage.c` if it lists the cells, `capsules/v4/hera.4th` only if a word it uses changes its stack effect, `v4/tools/depthsweep.py`. Tests: `v4/tests/test_host_mesh.c`, `v4/tests/test_host_unit.c`, `v4/tests/test_host_quit.c`; create `v4/tests/test_host_depth.c`. + +**Interfaces — Consumes:** Tasks 1 and 2. **Produces (nucleus words):** +- `(DO) ( op -- how )` stores the operation and fetches how it went. `(WIRE!) ( port -- )` by its number. +- `(ROOM?) ( cells port -- flag )` operation 10. +- `(PUT) ( text-addr port -- how )` puts the message whose seven words are in `(OUT-HDR)` and whose text is at the word address given. +- `(WAY) ( node -- port | -1 )` the number of the port that leads to a node. +- `(REFUSE)` — the marked message on the wire in `(WIRE)` is dropped, counted, and a NACK put toward its sender (an answer's NACK is about the node that answered and goes toward whom the answer was for). +- `(SERVE) ( -- )` one pass over every wire that has a message, with first and next: what is for another node is moved or refused; what is for this node is left. `(IDLE)`, `AWAIT` and the wait for room each call it and then look for what is theirs. +- `(ROOM)` as now, tried before text begins any of this. +- Removed: `(MQ)` and its cells, `(MQ!)`, `(MQ@)`, `(TAKE-HDR)`, `(TAKE-KEEP)`, `(TAKE)`, `(GATE)`, `(OWE)`, `(PAY)`, `(PAY1)`, `(PAID)`, `(OWED)`, `(A-QUEUE)`, `(A-HDR)`, `(A-BACK)`, `(A-TEXT)`, `(LOW)`, and the use of `(WRITERS)`/`(READERS)`. `(NEAR)` and `NEIGHBOUR` stay, for `(CENTRE)`. +- Kept, with the same meaning to their callers: `SEND`, `SEND-ON`, `AWAIT`, `GONE`, `NO-ROUTE`, `ROUTE`, `DEFAULT-ROUTE`, `(REFUSED)`, `(LOST)`, `EMIT`, `(FLUSH-OUT)`. + +- [ ] **Step 1: Rewrite the tests that say what must hold, failing.** In `test_host_mesh.c` (three nodes; keep `row`-style fresh rows for each scene): + - everything in the file up to the flood that does not depend on waiting to write stays as it is and must pass unchanged; + - *refused at once (acceptance 2, 3):* node 11 stuck in `BEGIN 0 UNTIL`. Node 10 sends it short texts in a loop: `(REFUSED)` on node 10 is 0 while they fit on the wire and rises by one for each after; node 10's line ends; nothing waited. `tell(12, ...)` and `tell(10, ...)` answer throughout; + - *a stuck node on the way:* text for node 12 beyond stuck node 11 in a row is refused at node 10 once the wire to 11 is full, and the console is sent the NACK; + - *a refusal owed to a stuck node:* node 11 sends 55 a message by node 12, which has no way, and sticks: node 12's NACK goes on the wire (or is counted if it is full), and node 12 is asleep, not going round, and answers; + - *printing is not lost (acceptance 5):* node 12 does `: P 3000 0 DO I . LOOP ; P` with node 10 made slow to take (a word that loops between lines): every number arrives, in order, and then how the text ended; + - *printing held by a stuck node:* node 12's way to the console is by stuck node 11; `P` fills the wire and node 12 sleeps for room; meanwhile a message from node 10 for a fourth number by node 12 is passed on or refused, not kept; node 11 removed: node 12's text ends "No one on that port" and it goes on; + - *the answer has room kept (Review Focus 4):* the wire from node 12 back to node 10 is filled to within 8 cells by messages node 12 passes on; node 12 then does text from node 10 that prints: its printing waits, and how it ended arrives; + - *left for itself (Review Focus 2, 3):* node 10 in `12 AWAIT` is sent, on the same wire, text for itself from node 11, then a message for the console from node 11, then node 12's answer: the message for the console is passed on, the wait ends with the answer, the text is then done, in that order; and between arrivals node 10's engine clock does not move; + - *order (acceptance 8):* the `TRIO` scene of the first attempt ("DEF"); + - *`AWAIT`'s endings:* refused (NACK about the node), gone (GONE from the centre, and not from another), interrupted (text from the console), as now; + - *the flood:* 800 messages among three nodes comes to rest; every message sent is either done or counted once (`(LOST)` plus `(REFUSED)` accounts for it exactly — the check is exact again). +- [ ] **Step 2: In `test_host_unit.c`** (Hera and four): the scenes of step 6c for `KILL`, with these changed: text for a stuck node is refused once its wire is full, not kept; `KILL` of one stuck node with another stuck ends, and every node not stuck is told; Hera is typed to throughout (acceptance 4). +- [ ] **Step 3: Create `test_host_depth.c`** (acceptance 7 on the mesh): on a row of three, for the data stack of node 11 at each depth from 20 to 32 and from 20 to 32 calls deep, each of: a text that prints; `SEND` to node 12; `SEND` to a node there is no way to; `12 AWAIT` answered; `12 AWAIT` with node 12 stuck, ended from the console's node by a GONE; node 11 passing a message on while its own text waits for room. After each: every node answers `1 2 + .` and node 11's stack is as deep as it was or was emptied by an error it reported. +- [ ] **Step 4: Extend `depthsweep.py`** with: a loop that prints 400 numbers; `SEND` to a node with a route and nothing on the port. +- [ ] **Step 5: Run** the three tests. Expected: they do not build or fail throughout, as the nucleus still writes messages a word at a time. +- [ ] **Step 6: Write the nucleus** as MESH.md 7d.4 says, in this order, running `make -C v4 run-64-test_host_quit.c` after each: (a) `(DO)`, `(WIRE!)`, `(ROOM?)`, `(PUT)`, `(WAY)`; (b) `(FLUSH-OUT)` and `(FINISH)` — output and then how the text ended, put on the wire, with room for the answer kept and the wait for room; (c) `(IDLE)`: sleep, `(SERVE)`, take the first message for this node on any wire and do it; (d) `(REFUSE)`; (e) `SEND-ON`, `(SEND1)`; (f) `AWAIT`; (g) delete what is listed as removed, and the cells of `host_map.h` that go with it. Cells freed are noted in `host_map.h` as free. +- [ ] **Step 7: Stack depth.** `(SERVE)`, `(REFUSE)` and everything they call keep to four cells of the data stack and four entries of the return stack, with what they need between operations held in cells, not on the stack. `test_host_quit.c`'s "28 values may wait" must still say 28. +- [ ] **Step 8: Run** `make -C v4 -k test`, `sanitize`, `hosted-check` (which runs `depthsweep.py`). Expected: 0 failures; `test_host_depth.c` reports no node that did not come back. +- [ ] **Step 9: Compare the product with the commit before** on the session of Global Constraints (build the commit before into the scratchpad with `git archive`). Every difference is one MESH.md 7d.6 names. +- [ ] **Step 10: Mutation checks.** Each must make a named test fail: (a) sleep woken by a message being there, not coming; (b) the 8 cells for the answer not kept; (c) `(SERVE)` stopping at a message for itself; (d) a refused `SEND` not raising 19. +- [ ] **Step 11: Commit** Tasks 2 and 3 together, `feat(v4.0.0): messages are whole and wait on the wire -- a node keeps none but the one it is doing`, chained on `hosted-check`, and push. + +### Task 4: A node removed at every step; what is no longer used; the documents; the boots + +**Files:** Modify `v4/tests/test_host_mesh.c`, `v4/src/fabric.c`, `v4/include/v4/fabric.h`, `v4/src/node.c`, `v4/include/v4/node.h` (only what Step 2 finds unused), `docs/v4.0.0/MESH.md`, `v4/README.md`, `docs/v4.0.0/ENGINE.md` if it describes the ports. + +- [ ] **Step 1: Write the removal test, failing if anything is wrong** (acceptance 6). For each exchange — text from the console to node 12 through 11, and its printing and answer back; a `SEND` from 11 to 12 and its answer; a NACK from 12 to 10 — record how many engine steps it takes with no removal, then for every step from 0 to that many run it again on a fresh row with node 11 (and, again, node 12) removed at that step. After each: every node left comes to rest, answers `1 2 + .`, and is not blocked in any operation. +- [ ] **Step 2: Remove what nothing uses**, found with `grep`, not assumed: the `pending` hook of `v4_device` and anything of the first attempt if any is left; the word-at-a-time `give` of the console in hosts. The looking words stay in the engine if the engine's own tests use them. +- [ ] **Step 3: Run** `make -C v4 -k test`, `sanitize`, `hosted-check`, lint with the freshly built `mkcapsule`. +- [ ] **Step 4: Documents.** MESH.md: 4.1's table gains the six addresses; 7a and 7b say what of them still holds (the lower-number rule and the NACKs owed do not; the endings of `AWAIT` and Hera's `KILL` do); 7b.7's limit is closed with a pointer to 7d.6; step 6e is marked done with tests, logs and the hash. `v4/README.md` likewise. +- [ ] **Step 5: The three boots**, amd64, aarch64, riscv64, each `clean qemu` with `STARFORTH_V4=1` and the typed session of step 6d's last boots (29 values on a line; `WORDS` with 26; `AWAIT` with 27 and a line), plus a loop printing 400 numbers. Expected: POST 538 of 538, `PARITY:OK`, one `dict_hash` on all six, and the session's answers the same on all three. +- [ ] **Step 6: Commit** `feat(v4.0.0): step 6e -- a node removed at any step leaves the rest at rest; written up`, with the logs, chained on `hosted-check`, and push. +- [ ] **Step 7: Final review** by a fresh reviewer on the most capable model, with MESH.md 7c.6, 7c.7 and 7d, this plan's Review Focus, and the instruction to break it: faults at every depth, removal at every step, and what the products do that the commit before did not.