fix(v4.0.0): step 6c mended after its review -- what a stuck or gone node can and cannot hold up

The review of step 6c found it did not keep its rule.  Ruled: fix them
all; 16 hops.

- Paying a NACK no longer waits on a lower-numbered neighbour; a node
  that owes one does not sleep, and goes on taking in what it is sent.
- AWAIT looks first through the messages already waiting.
- A node waiting to write to a removed neighbour gets error 18: the
  word that says who is reading also says which ports have anything on
  them.
- A console line lets Hera go when she is blocked on the first word of
  a message (v4_fabric_interrupt_error, a device's `pending`).
- A refused answer is told to the node that waits, not the one that
  answered; one owed to the node itself goes to its own queue.
- A message is passed on by at most 16 nodes, then refused.
- A GONE is believed only when it says it is from the node's centre.
- Tests: the check that tested nothing is replaced; no room for a
  waiting sender; the limit that remains is tested as a limit.
- MESH.md 7b's rule reworded to what holds, 7b.7 says where it falls
  short; README likewise.

make -C v4 test, sanitize, hosted-check; three bare-metal boots.  POST
538 of 538, word_count=317, dict_hash=0xc0769523a47b7dc3 on all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 22:18:06 -04:00
co-authored by Claude Opus 5.5
parent d343001dea
commit 59727f762a
19 changed files with 1058 additions and 66 deletions
+103 -8
View File
@@ -295,9 +295,16 @@ kernel-Hermes's work in v3 and is not decided for the mesh.
## 7b. Refusals and waits (ruled 2026-10-07)
**The rule of this section: no message is lost without its sender being
told, and no node waits for ever.** It closes two defects found in what
was built: a message arriving at a node with no room for it was dropped
**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
killing that node, or from the console. A message that is refused is
told to whoever waits on it, while the node that refuses it has room to
remember that it owes the telling.** As first written the rule was "no
message is lost without its sender being told, and no node waits for
ever"; the review showed that was more than what was built could keep,
and section 7b.7 says exactly where it falls short. It closes two defects
found in what was built: a message arriving at a node with no room for it was dropped
and counted, and its sender never knew; and a node waiting for an answer
(`AWAIT`) waited for ever if none came. Captain Bob: "no bad code is ever
released".
@@ -354,6 +361,23 @@ Two types, beside text (1), output (2) and done (3):
to do: the moment it finds it has no room is the moment it is taking
messages in so as to be free to write (section 7a), and it cannot begin
a message of its own there.
- When the message refused is a node's answer (type 3), the NACK is owed
to the node the answer was for, which is the one that waits, and is
about the node that answered.
- A NACK owed to this node itself -- its own message came back to it and
was let go here -- is put with its messages waiting, as if it had come
by a port.
- Paying one: if the neighbour it goes by is reading, it is sent. If not,
and that neighbour's number is the higher, it is written all the same,
as any message to it is (section 7a). If its number is the lower, the
NACK stays owed; the node goes on taking in what is written to it and
tries again, and does not sleep while it owes one. One there is no way
for, or whose way is a port with nothing on it, is dropped and counted.
- A message is passed on by at most 16 nodes. Its fourth word, the TTL,
carries how many more may pass it on; the node that would be the
sixteenth refuses it as it would one it had no way for. (Ruled
2026-10-07: 16.) This is what stops a message going round for ever
between two nodes each of which believes the way is through the other.
- A NACK or a GONE that cannot be taken or passed on is dropped and
counted, and nothing is owed for it: there is no NACK for a NACK.
- Part of the queue is kept for NACK and GONE alone, so that ordinary
@@ -365,9 +389,18 @@ Two types, beside text (1), output (2) and done (3):
in an error: 19 "Message refused", or 20 "Node gone".
- If it is not, a NACK is counted in `(REFUSED)`, and a GONE makes it
forget the way to that node.
- It acts on a GONE only if it came by the port that leads to everything
the node has no other way to — its centre — so that only Hera can
declare a node gone. Hera herself has no such port and sends them.
- It acts on a GONE only if the message says it is from the node's centre:
the node on the port that leads to everything it has no other way to,
whose number it was told (`NEIGHBOUR`). One that comes by another port,
or by that port from another node, is not believed. Hera herself has no
centre and sends them. (As first built a node looked only at the port
the GONE came by, and so believed one that any node sent by way of its
centre.) A message's "from" word is written by its sender and nothing
yet proves it: that is the ACL tag's work, in the later step named in
section 3.
- What ends a wait is looked for first among the messages already
waiting: an answer, a NACK or a GONE may have been taken in before the
wait began.
### 7b.5 Hera
@@ -385,6 +418,18 @@ Two types, beside text (1), output (2) and done (3):
as usual.
- On the two products there is one node, and nobody but the console could
ever answer it: a line typed while it waits does the same.
- **A blocked write (ruled 2026-10-07).** Hera has the lowest number, so
she writes to a neighbour without looking and is blocked until it reads
(section 7a); if it never does, nothing could be typed to her again. So
when a node is blocked writing the first word of a message to another
node, and its console has a line for it, the fabric raises error 21 on
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`).
- **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
error 18, "No one on that port".
### 7b.7 Limits
@@ -394,6 +439,23 @@ Two types, beside text (1), output (2) and done (3):
console. v3's pool is finite too, 32 messages, and a send beyond it is
refused.
- A node whose neighbour is asleep waits until it is woken (section 4.1).
- **A node that is stuck can hold up its neighbours until it is killed.**
A node cannot tell a neighbour that is busy for a moment from one that
is stuck for good, and there is no clock (ruling 2). So:
- a node that owes a NACK to a stuck neighbour whose number is the
higher is blocked writing it;
- a node that owes one to a stuck neighbour whose number is the lower
goes on taking in what lower-numbered neighbours write to it, but does
not sleep, and a higher-numbered neighbour with something for it
waits;
- a node with a message for a stuck neighbour waits to write it.
Each ends when Hera kills the stuck node: the wire is then empty, the
waiting node is told so (error 18) and goes on. Hera herself can always
be typed to (7b.6). None of this can happen on a node that is gone.
- A node that is waiting for an answer keeps what comes for other nodes
and passes it on when its wait ends, not before.
- A node that was never told who is on its centre's port believes no GONE.
## 8. Storage
@@ -920,7 +982,8 @@ Each is tested, committed and pushed before the next.
6. *The suite.* `make -C v4 test` and `sanitize` pass at both widths;
`mkcapsule --lint capsules/` is clean.
**6c. Refusals and waits (section 7b).** **Done 2026-10-07.**
**6c. Refusals and waits (section 7b).** **Done 2026-10-07; reviewed
and mended the same day (below).**
- *In the nucleus* (`v4/capsule/core.v4`, `quit.v4`): `(OWE)` and
`(PAY)`; the room kept for NACK and GONE; `NO-ROUTE`; `GONE`; `AWAIT`
ending in errors 19, 20 and 21; `(REFUSED)`.
@@ -939,7 +1002,39 @@ Each is tested, committed and pushed before the next.
bare-metal boots, `logs/20261007-202737` (amd64), `-203230`
(aarch64), `-203616` (riscv64). On all six: POST 538 of 538,
`word_count=317`, `dict_hash=0x54520ade672566ad`.
- **Found on the way, and not mended: for ruling.**
- *Verified again after the review's mends.* `make -C v4 test`,
`sanitize` and `hosted-check`; three bare-metal boots,
`logs/20261007-220949` (amd64), `-221223` (aarch64), `-221549`
(riscv64). On all six: POST 538 of 538, `word_count=317`,
`dict_hash=0xc0769523a47b7dc3` (the nucleus changed, so the hash
did).
- **The review (2026-10-07) found the step did not keep its rule**, and
Captain Bob ruled: "fix them all; 16 hops". Mended, each with a test
that failed first or was shown to fail with the mend taken out:
- *A stuck node stalled a neighbour that owed it a NACK.* Paying one
no longer waits on a lower-numbered neighbour (7b.3). What is left
of this is in 7b.7.
- *`AWAIT` missed what had already come.* It looks through the
messages waiting first (7b.4).
- *A node waiting to write to a removed neighbour went round for
ever.* It gets error 18 (7b.6).
- *Hera blocked writing to a stuck node could not be typed to.* A
console line lets her go, on the first word of a message (7b.6).
- *A refused answer was told to the node that answered.* It is told
to the node that waits (7b.3).
- *A message could go round for ever.* 16 nodes may pass it on (7b.3).
- *Any node could forge a GONE by way of the centre.* The sender is
looked at, not only the port (7b.4).
- *A check that tested nothing*, the "GONE not from its centre" one,
is replaced by two that send one and see it disbelieved; and "no
room" is now tested for a sender that waits.
- *The rule claimed too much.* Reworded (7b); `v4/README.md` too.
The engine gained: the bits that say which ports have anything on
them; `v4_node_words_since_look`; a device's `pending`; and
`v4_fabric_interrupt_error`. Tests: `test_fabric.c` 78 checks,
`test_host_mesh.c` 109, `test_host_unit.c` 79.
- **Found on the way in the first pass (both mended above).**
- *Hera blocked writing to a stuck node.* Hera has the lowest number,
so by section 7a she writes to a neighbour without looking and is
blocked until it reads. If that neighbour is stuck she is blocked