docs(v4.0.0): step 6c -- refusals and waits: rulings, design, acceptance and plan
MESH.md 7b: no message is lost without its sender being told, and no node waits for ever. What v3 does, from the code, and what could not be established. A refusal travels back as a NACK; Hera ends a wait on a stuck node by killing it and tells the others it is gone; a line from the console breaks a wait. Step 6c has the acceptance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a425f738a4
commit
6e1ac157c6
@@ -294,6 +294,108 @@ that was to start a node sending). Six from each to each, at once, all
|
||||
arrive. Nothing here makes a sender slow down or send again; that is
|
||||
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
|
||||
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".
|
||||
|
||||
**v3, as built** (`kernel/include/starkernel/vm/kernel_hermes.h`,
|
||||
`kernel/src/vm/kernel_hermes.c`; `FABRIC-3.5.md` XLIII, XLV):
|
||||
|
||||
- Sending never blocks the sender: kernel-Hermes copies the message into
|
||||
its own pool and queues it for the target, which drains its own queue.
|
||||
- A send that cannot be accepted is refused to the sender at once, with
|
||||
nothing changed: `sk_hermes_send_one` returns failure "on any refusal
|
||||
(bound, reservoir, arena, or destination queue full)".
|
||||
- Nobody waits on a message for an answer. What Hera needs done in another
|
||||
VM now she does with `VM-EXEC`, which the kernel runs there directly.
|
||||
- "A deny is a NACK", message type 23; in the code it is sent in one place,
|
||||
refusing a private-channel request.
|
||||
- Not established from the code: what becomes of messages queued for a VM
|
||||
that is killed; and any live path that expires a queued message (there
|
||||
is a `ttl` field, set to 0, and a decay function called only by boot
|
||||
self-tests).
|
||||
|
||||
**Scope (ruled):** only what closes the two defects. Heat and its ledger,
|
||||
channels, and the ACL question on opening a channel stay for the later
|
||||
step named in section 3; a message still carries its heat, TTL and ACL
|
||||
tag, and nothing acts on them.
|
||||
|
||||
### 7b.1 The rulings
|
||||
|
||||
1. **A refusal travels back as a NACK.** A node that has no room for a
|
||||
message, or is asked to pass one on and knows no way to where it is
|
||||
going, tells the node the message came from. In v3 the sender is
|
||||
refused at once; here the sender may be several nodes away and has
|
||||
finished writing, so the refusal has to travel.
|
||||
2. **Hera ends a wait on a node that is alive and never answers, by
|
||||
killing it.** Only Hera decides a node is gone, as in v3. There is no
|
||||
clock and no time limit.
|
||||
3. **A line from the console breaks a wait.** It is the one way to end
|
||||
Hera's own wait, when she is waiting on a node during a birth or a send.
|
||||
|
||||
### 7b.2 The messages
|
||||
|
||||
Two types, beside text (1), output (2) and done (3):
|
||||
|
||||
| Type | Text | Meaning |
|
||||
|---|---|---|
|
||||
| 4, NACK | one word: a node's number | your message for that node was refused |
|
||||
| 5, GONE | one word: a node's number | that node is gone |
|
||||
|
||||
### 7b.3 A node that cannot take a message, or pass it on
|
||||
|
||||
- It reads the message to its end and counts it in `(LOST)`, as before.
|
||||
- It notes that it owes a NACK, to the message's sender and about the node
|
||||
the message was for. It sends what it owes when it next has nothing else
|
||||
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.
|
||||
- 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
|
||||
messages cannot take the last of the room.
|
||||
|
||||
### 7b.4 A node that is sent a NACK or a GONE
|
||||
|
||||
- If it is waiting for an answer from that node (`AWAIT`), the wait ends
|
||||
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.
|
||||
|
||||
### 7b.5 Hera
|
||||
|
||||
- She keeps a table of the nodes she has had born: number and place.
|
||||
- `KILL ( n -- )`: she asks the kernel to remove node *n*, forgets her own
|
||||
way to it, and sends GONE about it to every other node in her table.
|
||||
(`KILL` is v3's word and meaning; v3's takes a name, and a mesh node has
|
||||
a number. Ruled 2026-10-07. `SLEEP`, `WAKE` and a second unit are step
|
||||
7.)
|
||||
|
||||
### 7b.6 Breaking a wait from the console
|
||||
|
||||
- Text from the console that reaches a node while it waits for an answer
|
||||
ends the wait in error 21, "Interrupted". The text is then interpreted
|
||||
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.
|
||||
|
||||
### 7b.7 Limits
|
||||
|
||||
- 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
|
||||
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).
|
||||
|
||||
## 8. Storage
|
||||
|
||||
**Ruled 2026-10-06 and 2026-10-07** (Captain Bob). On 2026-10-07 he found
|
||||
@@ -819,6 +921,28 @@ 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).** Acceptance, approved
|
||||
2026-10-07:
|
||||
1. *No room.* A node's queue is filled; the next message for it is
|
||||
refused, and the sender's `(REFUSED)` count rises by one. A sender
|
||||
that was waiting has its line end "Message refused".
|
||||
2. *No way.* A message for a node nobody has a way to comes back as a
|
||||
NACK in the same way.
|
||||
3. *Killed.* Hera kills a node. A node that was waiting on it, and is
|
||||
not its neighbour, has its line end "Node gone". Afterwards no node
|
||||
has a way to it.
|
||||
4. *Stuck.* A node waits on a node stuck in a loop. Hera kills the
|
||||
stuck node and the wait ends.
|
||||
5. *Hera's own wait.* Hera waits on a stuck node. A line typed at the
|
||||
console ends her wait with "Interrupted", and that line, which kills
|
||||
the stuck node, is then run.
|
||||
6. *The products.* Hosted and bare metal: `5 AWAIT` followed by another
|
||||
line gives "Interrupted" and then runs that line.
|
||||
7. *More refusals than room.* The extra NACK is dropped and counted in
|
||||
`(LOST)`; nothing else goes wrong.
|
||||
8. *Nothing else changes.* POST 538 of 538, the existing tests, and the
|
||||
same hashes on all six systems.
|
||||
|
||||
7. **A second unit; scaling while running; sleep, wake and kill by
|
||||
command.**
|
||||
8. **The hosted product is the five nodes**, on three ISAs: acceptance 1
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Step 6c: Refusals and Waits — 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:** No message is lost without its sender being told, and no node waits for ever.
|
||||
|
||||
**Architecture:** Two message types, NACK and GONE, handled in the nucleus where messages are taken in, passed on and waited for (`v4/capsule/core.v4`, `quit.v4`). A node that drops a message records a NACK it owes and sends it when idle. `AWAIT` ends in an error on a NACK or GONE about the node it waits for, or on text from the console. Hera keeps a table of the nodes she has had born and gets `KILL`. The lone-node boot hands a waiting node the next console line.
|
||||
|
||||
**Tech Stack:** the v4 nucleus dialect, FORTH (`capsules/v4/hera.4th`), C99 (`v4/system/boot.c`, tests), `make -C v4`, `make -f kernel/Makefile`.
|
||||
|
||||
**Spec:** `docs/v4.0.0/MESH.md` section 7b; acceptance in step 6c of section 10.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Branch `StarForth-v4.0.0`, main checkout. No new branch, no stash, no worktree. Commit and push after every task; end messages with `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
|
||||
- v4 follows the OS as designed: a statement about what v3 or v4 does today is checked in the code, and where it can be, tried on the binary, before it is relied on.
|
||||
- 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.
|
||||
- Message types: 4 NACK, 5 GONE; each has one word of text, a node's number, and a length of 4.
|
||||
- Errors: 19 "Message refused", 20 "Node gone", 21 "Interrupted".
|
||||
- New cells, word addresses relative to `BUF0_W` in `v4/tests/host_map.h`: `(REFUSED)` at -7, `(OWED#)` at -8, `(OWED)` at -112 (8 pairs: to, about), `(AWAITING)` is the existing `(AWAIT-FROM)`, which is 0 when the node is not waiting.
|
||||
- The queue keeps 36 cells that only types 4 and 5 may use (four of them: seven words, the port, one word of text).
|
||||
- `capsules/v4/hera.4th`: every block at most 16 lines of at most 64 characters; `v4/build/mkcapsule --lint capsules/` clean.
|
||||
- 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. Never delete a log.
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. **A NACK that meets a full node.** Expected: dropped, counted in `(LOST)`, no NACK owed for it, nothing loops. Test in Task 1.
|
||||
2. **Two nodes each owing the other a NACK while both have full queues.** Expected: neither waits for ever; section 7a's rule still holds. Test in Task 1.
|
||||
3. **A GONE that does not come from the centre.** Expected: ignored; the way to that node is kept. Test in Task 1.
|
||||
4. **Console text for another node passing through a node that is waiting.** Expected: it does not interrupt the node it passes through; only text for that node does. Test in Task 2.
|
||||
5. **`KILL` of a node Hera never had born, of herself, or twice.** Expected: an error with a message, nothing removed. Test in Task 3.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: NACK and GONE in the nucleus
|
||||
|
||||
**Files:** Modify `v4/capsule/core.v4` (`(TAKE-KEEP)`, the queue's room, the errors list), `v4/capsule/quit.v4` (`(IDLE)`, `(PASS-ON)`, `AWAIT`, the messages for 19 to 21, `NO-ROUTE`, headers for `(REFUSED)`), `v4/tests/host_map.h`, `v4/tools/mkimage.c`, `v4/include/v4/message.h` (the two types), `v4/tests/test_host_mesh.c`
|
||||
|
||||
**Interfaces — Produces:** `V4_MSG_NACK 4`, `V4_MSG_GONE 5`; nucleus words `(OWE) ( to about -- )`, `(PAY) ( -- )` sends one owed NACK if there is one, `NO-ROUTE ( node -- )` forgets the way to a node; variable `(REFUSED)`; `AWAIT ( node -- how )` as before when the answer comes, and otherwise ends in error 19, 20 or 21.
|
||||
|
||||
- [ ] **Step 1: Write the failing tests** in `test_host_mesh.c` (three nodes in a line, 10 with the console, 11, 12):
|
||||
- *No way:* node 10 sends text to node 77, which nobody has a way to and for which 10's way is "toward 11": 11's `(LOST)` rises by one, and 10's `(REFUSED)` rises by one.
|
||||
- *No room:* node 12 is made busy (a word that loops a counted number of times without reading) while 10 sends it messages until its queue is full; the next is refused: 12's `(LOST)` rises and 10's `(REFUSED)` rises by one, and when 12 is free every message it did take is done in order.
|
||||
- *A waiting sender:* 10 does `: W S" 1 DROP" 77 SEND 77 AWAIT ; W` and its line ends "Message refused" with an error; 10 then answers the next line.
|
||||
- *GONE:* a GONE about 12 arriving at 11 from the port toward 10, 11's centre here, makes 11 forget the way to 12 (`12 (PORT-FOR)` gives the default); one arriving from 12's side is ignored (Review Focus 3). A node waiting on 12 ends "Node gone".
|
||||
- *A NACK meets a full node* (Review Focus 1), and *two full nodes owing each other* (Review Focus 2): run to quiet; `(LOST)` accounts for every message not delivered; every node is back waiting at its ports.
|
||||
- *More refusals than room* (acceptance 7): nine messages refused at one node in a burst: eight NACKs are owed and sent, the ninth is counted in `(LOST)`.
|
||||
- [ ] **Step 2: Run** `make -C v4 run-64-test_host_mesh.c`: the new checks fail (nothing sends a NACK).
|
||||
- [ ] **Step 3: Implement.**
|
||||
- `(TAKE-KEEP)`: an ordinary message is kept only if it leaves the 36 reserved cells free; types 4 and 5 may use them. On no room: an ordinary message calls `(OWE)` with its `from` and `to`; types 4 and 5 are only counted.
|
||||
- `(OWE)`: append the pair at `(OWED)` if `(OWED#)` is below 8; otherwise add one to `(LOST)`.
|
||||
- `(IDLE)`: before it reads its ports, `(PAY)`: if anything is owed, send the oldest as a message of type 4, length 4, one word, by `(PORT-FOR)` of its `to` (the default way if none; no way at all: count it and go on).
|
||||
- `(PASS-ON)` with no way: `(OWE)` for an ordinary message, count for types 4 and 5.
|
||||
- `(IDLE)` for this node, type 4: add one to `(REFUSED)`. Type 5: if it came by the port of `(ROUTE-DEFAULT)`, `NO-ROUTE` for the node named. Both are then let go.
|
||||
- `AWAIT`: sets `(AWAIT-FROM)`; in its loop, a message for this node of type 4 or 5 whose word of text is the awaited node clears `(AWAIT-FROM)` and raises 19 or 20 (a GONE only by the centre's port; it also does `NO-ROUTE`). On the answer, or any ending, `(AWAIT-FROM)` is 0.
|
||||
- Errors 19, 20, 21 in `(RAISED)` and in `core.v4`'s list.
|
||||
- [ ] **Step 4: Run** the mesh test at 64 and 32 bits and under the sanitizers; then `make -C v4 -k test`. 0 failures; `test_host_unit.c` still 538 of 538.
|
||||
- [ ] **Step 5: Commit** `feat(v4.0.0): a refused message is told to its sender -- NACK and GONE` and push.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: A line from the console breaks a wait
|
||||
|
||||
**Files:** `v4/capsule/quit.v4` (`AWAIT`), `v4/system/boot.c`, `v4/Makefile` (`hosted-check`), `v4/tests/test_host_mesh.c`
|
||||
|
||||
- [ ] **Step 1: Failing tests.** In the mesh test: node 12 loops for ever; node 10 does `12 AWAIT`; a line typed at the console, `65 EMIT`, ends 10's wait with "Interrupted" and an error, and then prints `A`. Text from the console for node 11, passing through 10 while 10 waits, does not end 10's wait and is done by 11 once 10 is free (Review Focus 4). In `hosted-check`: `printf '5 AWAIT\n1 2 + .\n'` prints `Interrupted`, then `3`.
|
||||
- [ ] **Step 2: Run** and see them fail (the hosted program ends on `5 AWAIT`).
|
||||
- [ ] **Step 3: Implement.** In `AWAIT`'s loop a message of type 1 for this node whose `from` is the node's `(CONSOLE)` is kept with the messages waiting, as any other is, and then the wait ends in error 21. In `boot.c`, a node reading "any port" in the middle of a line, with nothing to give it, returns a new result, `V4_BOOT_LINE_WAITING`; `v4_boot_line` called again with the next line sends it, which interrupts. `v4/tools/hosted.c` and `kernel/src/v4/sk_v4.c` treat `WAITING` as "read the next line", printing nothing.
|
||||
- [ ] **Step 4: Run** the mesh test, `make -C v4 -k test`, `sanitize`, `hosted-check`.
|
||||
- [ ] **Step 5: Commit** `feat(v4.0.0): a line from the console breaks a wait` and push.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Hera's table and KILL
|
||||
|
||||
**Files:** `capsules/v4/hera.4th`, `v4/tests/test_host_unit.c`
|
||||
|
||||
- [ ] **Step 1: Failing tests** in the unit of five:
|
||||
- *Killed* (acceptance 3): node 11 does `: W 14 AWAIT ; W` (it is not wired to 14); Hera does `14 KILL`; 11's line ends "Node gone"; afterwards `14 (PORT-FOR)` on 11, 12 and 13 gives each its default way, and on Hera gives 0; a `SEND` to 14 from 12 comes back refused.
|
||||
- *Stuck* (acceptance 4): node 13 loops for ever; node 12 waits on it; Hera does `13 KILL`; 12's wait ends.
|
||||
- *Hera's own wait* (acceptance 5): node 12 loops for ever; the console types `12 AWAIT` to Hera and then `12 KILL`: the first ends "Interrupted", the second is run, and 12 is gone.
|
||||
- Review Focus 5: `99 KILL`, `10 KILL`, and `14 KILL` a second time each print a message and end in an error; `born_count` and the nodes there are unchanged.
|
||||
- [ ] **Step 2: Run** and see them fail (`KILL` is not a word).
|
||||
- [ ] **Step 3: Implement** in `hera.4th`, in a new block: a table of 16 pairs filled by `BIRTH`; `(PLACE) ( n -- place | -1 )`; `KILL ( n -- )`: not in the table, or this node's own number: `." KILL: no such node"` and error -1; otherwise `NODE-KILL`, the pair removed, `NO-ROUTE`, and for every other pair a message of type 5 about *n* by `SEND`'s way. The words for sending a message of a given type with one word of text are the nucleus's (Task 1).
|
||||
- [ ] **Step 4: Run** the unit test, `make -C v4 -k test`, `sanitize`, the capsule lint.
|
||||
- [ ] **Step 5: Commit** `feat(v4.0.0): Hera kills a node by number and tells the others it is gone` and push.
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Bare metal, and the write-up
|
||||
|
||||
- [ ] **Step 1:** Three v4 boots with the typed session of the last step and, added to it, `5 AWAIT`, `1 2 + .`. Each log: POST 538 of 538; `Interrupted`; `3`; the `PARITY:V4_SYSTEM` line equal to hosted's.
|
||||
- [ ] **Step 2:** `MESH.md` step 6c as done, with what is not as intended yet; the note under section 4.1 about `AWAIT`; `v4/README.md`.
|
||||
- [ ] **Step 3: Commit** with the three logs and push.
|
||||
Reference in New Issue
Block a user