feat(v4.0.0): step 6e -- a node removed at any step leaves the rest at rest; written up

test_host_mesh.c, 220 checks: three exchanges on the row of three -- text
from the console for node 12 with what it prints and how it ended; node 11
SENDing node 12 text and waiting for its answer; a message node 12 has no
way for and its NACK to node 10 -- are each played once for every engine
step they take with node 11 removed at that step, and again with node 12:
17,642 removals at 64 bits.  Every node left comes to rest asleep, blocked
at no port and in no operation, and answers.  What is typed is a word
defined beforehand, so that the steps are the exchange's and not the
dictionary being searched.

Removed from the engine, nothing using them: v4_fabric_interrupt_error, a
device's pending hook, v4_node_words_since_look, V4_ERROR_INTERRUPTED.

MESH.md: 4.1's table has the addresses after the ports; 7a and 7b say what
of them still holds; 7b.7's limit is closed; step 6e is written up.
v4/README.md likewise.

make -C v4 test and sanitize at both widths, hosted-check, lint.  Three
bare-metal boots with the typed session and a loop printing 400 numbers:
logs/20261013-165124 (amd64), -165428 (aarch64), -165818 (riscv64).  POST
538 of 538, word_count=317, dict_hash=0x0951d7e02e251554 on all six, and
the three sessions are the same character for character.  The boots were
built with the working tree's uncommitted change to kernel/Makefile that
adds the ISA to the kernel's version line; nothing else differs from this
commit.

The plan's final review is not done yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-13 17:47:05 -04:00
co-authored by Claude Opus 5.5
parent ab96a9f2be
commit 06da5700bc
17 changed files with 893 additions and 142 deletions
+119 -4
View File
@@ -104,6 +104,19 @@ word address `base`:
| `base` … `base + V4_PORTS − 1` | Port 0 … port `V4_PORTS − 1` |
| `base + V4_PORTS` | Any port: a read here takes from whichever port has a neighbour writing |
| `base + V4_PORTS + 1` | Which port the last read from "any" came from. Read only. |
| `base + V4_PORTS + 2` | Which ports have a neighbour waiting to write to this node, a bit to a port. Read only. (Section 7a.) |
| `base + V4_PORTS + 3` | Which have one waiting to read from it; and, in the bits above those, which have anything on them at all. Read only. (Section 7a.) |
| `base + V4_PORTS + 4` | Wire: the port that a whole-message operation is about (section 7d.3) |
| `base + V4_PORTS + 5` | A: a value the operation needs |
| `base + V4_PORTS + 6` | B: a second |
| `base + V4_PORTS + 7` | Do: the number of the operation; storing it does the operation |
| `base + V4_PORTS + 8` | How it went. Read only. |
| `base + V4_PORTS + 9` | Which wires have a message waiting, a bit to a port. Read only. |
The first two rows after "which port" are for words written and read one at
a time; the six after them are for whole messages, which is how nodes talk
to each other since step 6e (section 7d). Words one at a time are still
how a newborn node is sent its nucleus and how a node asks its kernel.
- **A write to a port blocks the node until the neighbour has read it.**
Built 2026-10-05 for one port; the opcode after the write runs when the
@@ -159,6 +172,12 @@ time passing for all of them at once.
a word when the node reads, each able to say "not yet". The console, a
disk, and the kernel that serves a node's requests (`ENGINE.md` 3.3) are
devices.
- **The wires' queues** (step 6e, section 7d.2). Each wire between two
nodes, or a node and a device that speaks whole messages, has a queue of
messages each way, in memory the fabric's owner gives it
(`v4_fabric_queues`). A device puts and takes whole messages through the
fabric (`v4_fabric_device_put`, `v4_fabric_device_take`). The kernel's
port has none (7d.5).
- **A step.** Every node that is awake and not blocked executes one
instruction word. Then every write that has a reader waiting on the
other end of its wire is handed over, and both nodes are unblocked. That
@@ -240,9 +259,17 @@ and these tables. Nothing else changes.
## 7a. Two neighbours writing to each other (ruled 2026-10-06)
*(The wait of section 7c was to replace the lower-number rule and the
looking again of this section. It was backed out on 2026-10-08; this
section is what the code does.)*
*(**A record since step 6e, 2026-10-13.** Messages now wait on the wires
(section 7d) and a node writes none a word at a time, so two neighbours
can no longer block each other and nothing below is needed to prevent it.
What does not hold any more: looking before writing and `(GATE)`; the
lower-number rule; the ring of messages waiting, `(MQ)`. What still
holds: the two status addresses are in the engine and tested there
(`v4/tests/test_fabric.c`), though the nucleus does not use them;
`NEIGHBOUR` stays, because it is how a node knows who its centre is and
so whose GONE to believe (7b.4); and `(LOST)` still counts what a node
lets go. The cost at the end of this section is now 7d.6's: a message for
a full wire is refused at once and its sender told.)*
The fault is in section 10, step 4. **Ruled: a node writes only to a
neighbour that is already reading; a node keeps the messages it takes in
@@ -299,6 +326,20 @@ kernel-Hermes's work in v3 and is not decided for the mesh.
## 7b. Refusals and waits (ruled 2026-10-07)
*(**What of this section holds since step 6e, 2026-10-13.** It holds: the
two messages, NACK and GONE (7b.2); that a message is passed on by at
most 16 nodes, and that there is no NACK for a NACK (7b.3); what a node
does when it is sent a NACK or a GONE, and the endings of `AWAIT` in
errors 19, 20 and 21 (7b.4); Hera's table and `KILL` (7b.5); and a line
from the console ending an `AWAIT` (7b.6). It does not hold: a node no
longer notes a NACK it owes and pays it later, because it keeps nothing:
the NACK is put on the wire at once or counted in `(LOST)` (7d.4); no
room is kept in a node for NACK and GONE, there being no queue in a node;
what ends a wait is looked for on the wires, not among messages a node
has taken in; and the blocked write of 7b.6 cannot happen. The limit of
7b.7, that a stuck node holds up its neighbours, is closed: 7d.6 says
what the limits are now.)*
**The rule of this section (reworded 2026-10-07, after the review of step
6c): a node is never left waiting, or going round, on a node that is
gone; and a wait on a node that is alive and stuck is ended by Hera's
@@ -430,6 +471,11 @@ Two types, beside text (1), output (2) and done (3):
it: it gives that message up and goes on to read the line. Only on the
first word, so that half a message is never left on a wire
(`v4_fabric_interrupt_error`, `v4/include/v4/fabric.h`).
*(Removed at step 6e, 2026-10-13: no node writes a message a word at a
time, so none is blocked writing one. `v4_fabric_interrupt_error`, a
device's `pending` and `v4_node_words_since_look` are gone from the
engine. Hera can be typed to whatever any node is doing:
`v4/tests/test_host_unit.c`.)*
- **A node waiting to write to a neighbour that has been removed** is told:
the word that says which neighbours are reading also says which ports
have anything on them at all, and with nothing there the wait ends in
@@ -437,6 +483,12 @@ Two types, beside text (1), output (2) and done (3):
### 7b.7 Limits
*(**Closed at step 6e, 2026-10-13**, but for the last of these, which
stands. A node that is stuck holds up no other node, and neither does
one that is asleep: what is for it waits on its wire. A node that waits
for an answer passes on what is for other nodes while it waits. What a
stuck node costs now is in section 7d.6.)*
- The room kept for NACK and GONE is finite, and so is the list of NACKs
a node owes. Past them one is dropped and counted in `(LOST)`. A sender
left waiting by that is ended as any stuck wait is: by Hera, or from the
@@ -1612,7 +1664,70 @@ Each is tested, committed and pushed before the next.
and `-06*` are of the wait as built and are kept as its record.
**6e. Messages on the wire (section 7d).** Ruled 2026-10-08: the
second attempt at 6d. Not built.
second attempt at 6d. **Built and verified 2026-10-13; its final
review is still to come** (last item below).
- *The wire* (`v4/include/v4/wire.h`, `v4/src/wire.c`, `d5cd6460`,
`83694b18`): a queue of whole messages, one way; put, look, take,
move and drop, each all or nothing. `v4/tests/test_wire.c`, 112
checks.
- *The engine* (`v4/src/node.c`, `v4/src/fabric.c`): the six addresses
of 7d.3 and the ten operations; the wires' queues given by the
fabric's owner; a device that puts and takes whole messages; the
kernel's port, which has no queue. `v4/tests/test_fabric.c`, 306
checks.
- *The nucleus* (`v4/capsule/core.v4`, `quit.v4`, `b1d09af0`): `(DO)`,
`(WAY)`, `(ROOM?)`, `(SERVE)`, `(REFUSE)`; `(IDLE)`, `SEND`, `AWAIT`,
printing and how text ended, all on the wires. `(MQ)`, `(GATE)`, `(OWE)` and
`(PAY)` are gone.
- *The hosts* (`v4/system/boot.c`, which both products' lone node is
run by, and the tests' consoles) speak whole messages.
- *Two reviews of the nucleus, each mended* (`6a7d0aa2`, `f1419e73`):
7d.3 and 7d.4 carry what they changed, marked "fix round".
- *A node removed at every step* (acceptance 6):
`v4/tests/test_host_mesh.c`, 220 checks. Three exchanges on the row
of three -- text from the console for node 12, what it prints and how
it ended; node 11 `SEND`ing node 12 text and waiting for its answer;
a message node 12 has no way for, and its NACK to node 10 -- are each
played whole, and then once for every engine step they take (3,579,
4,214 and 1,025 at 64 bits) with node 11 removed at that step, and
again with node 12: 17,642 removals. After each, every node left came
to rest asleep, blocked at no port and in no operation, and answered
`1 2 + .`; with node 11 gone, node 12 is wired to node 10 first, as
whoever removed it would, so that it can be asked. None failed. The
test was shown to fail, in 4,376 of them, with the engine changed so
that a wait for room on a cut wire never ends.
*What is typed in that test is a word defined beforehand, not a line
of several words:* a line is mostly the dictionary being searched,
tens of thousands of steps in which no message moves, and each would
be a removal played to its end. The first exchange as the line
`7 8 * .` takes 38,234 steps; removal was tried at its first 300 and
none failed, and it is not in the suite.
- *Removed from the engine, nothing using them:*
`v4_fabric_interrupt_error`, a device's `pending`,
`v4_node_words_since_look`, and `V4_ERROR_INTERRUPTED` (the nucleus
raises error 21 itself). A device's `take` and `give`, and the two
status addresses of 7a, stay: the kernel's port and the engine's own
tests use them.
- *Other tests.* `v4/tests/test_host_unit.c`, 81 checks (Hera and
four); `v4/tests/test_host_depth.c`, 397 checks (acceptance 7 on the
mesh); `v4/tools/depthsweep.py`, 230 runs on the hosted product.
- *Verified.* `make -C v4 test` and `sanitize` at both widths,
`hosted-check`, and `mkcapsule --lint capsules/`: no failures. Three
bare-metal boots, `logs/20261013-165124` (amd64), `-165428`
(aarch64), `-165818` (riscv64), each typing the session of step 6d's
last boots and a loop that prints 400 numbers: 29 values on a line
("Stack overflow"); `WORDS` with 26 on the stack; `AWAIT` with 27,
and `5 AWAIT` ended by the next line ("Interrupted"); all 400
numbers, in order. On all six systems: POST 538 of 538,
`word_count=317`, `dict_hash=0x0951d7e02e251554`; and what the three
boots answered at the prompt is the same, character for character.
- *Seen in the older logs, and not a fault of the node.* In the boots
of 2026-10-08 `WORDS` shows a character twice here and there on
amd64 and riscv64 (`LOG-INFFO-STR`, `QQ.-`). The script that typed
those sessions read QEMU's serial socket only between lines; with
the socket read as it comes, as these three were, there is none.
- **Not done:** the final review of the whole step by a fresh
reviewer (the plan's last step).
7. **A second unit; scaling while running; sleep, wake and kill by
command.**