From f4f566bb37a9f70358f4bd9fbc347b129aa8bbea Mon Sep 17 00:00:00 2001 From: rajames Date: Fri, 16 Oct 2026 08:52:27 -0400 Subject: [PATCH] docs(v4): the implementation plan for MESH step 7 Co-Authored-By: Claude Opus 5.5 --- .../2026-10-16-mesh-step7-second-unit.md | 1212 +++++++++++++++++ 1 file changed, 1212 insertions(+) create mode 100644 docs/v4.0.0/plans/2026-10-16-mesh-step7-second-unit.md diff --git a/docs/v4.0.0/plans/2026-10-16-mesh-step7-second-unit.md b/docs/v4.0.0/plans/2026-10-16-mesh-step7-second-unit.md new file mode 100644 index 00000000..b81c0cfa --- /dev/null +++ b/docs/v4.0.0/plans/2026-10-16-mesh-step7-second-unit.md @@ -0,0 +1,1212 @@ +# MESH Step 7 — A Second Unit, PAUSE, RESUME and KILL — 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:** Births follow v3 on the mesh: any node may birth on its own ports; a second unit is born by its own centre; Hera alone pauses, resumes and kills, by number, through a registry the kernel keeps; and GONE goes down the birth graph. + +**Architecture:** The kernel side (`v4/src/manage.c`) gains a registry of live nodes (number, place, parent), fixed request numbers, and a check of who may ask what. The system (`capsules/v4/sys/quit.f18`) gains one variable, `(GONE-HOOK)`: when a node believes a GONE it hands the node's number to the word there. `hera.4th` is split: `centre.4th` holds what every birthing node needs (`BIRTH-AS`, `BIRTH`, `UNIT`, `GONE-DOWN`, its table of children); `hera.4th` keeps `KILL`, `PAUSE`, `RESUME`, `UNIT-KILL`, `CENTRE`. Proven in the fabric under test (`v4/tests/test_host_unit.c`), as step 5 was. + +**Tech Stack:** C99 (`-Wall -Wextra -Wpedantic -Werror`), the system's F18 assembler text (`capsules/v4/sys/*.f18`), FORTH-79 capsules (`capsules/v4/*.4th`, 64 characters by 16 lines a block), GNU make, QEMU for acceptance. + +**Spec:** `docs/v4.0.0/MESH.md` section 9a (commit `5283e67d`), with section 9 (births follow v3, `9842b9cd`). + +## Global Constraints + +- Work on branch `StarForth-v4.0.0` in the main checkout. Never create a branch, never stash, never `--no-verify`. +- Commit with `G="git -c filter.lfs.process= -c filter.lfs.clean=cat -c filter.lfs.smudge=cat -c filter.lfs.required=false"`; push with `PATH="$PATH:/usr/share/gitkraken/resources/app.asar.unpacked/git/libexec/git-core" $G push -q origin StarForth-v4.0.0`. Commit messages end `Co-Authored-By: Claude Opus 5.5 `. +- Never apply a fix that is not in this plan; report it. +- Births follow v3 (`MESH.md` 9): any node may birth on its own ports; its child's parent is the birther; `KILL` is Hera's alone. +- Capsule blocks: lines of at most 64 characters, at most 16 lines a block, block numbers 2048 or more; check with `tools/mkcapsule --lint capsules/`. **No parentheses inside a `( ... )` comment**: the first `)` ends it. +- `centre.4th` takes blocks 8100 up; `hera.4th` keeps 8000 up. +- A refused request is error 12 ("Argument out of range") on the asking node, and changes nothing. +- The suite for every task: `make -C v4 test test-32 sanitize hosted-check`. +- Acceptance (`.claude/CLAUDE.md`): `make -f kernel/Makefile ARCH= clean qemu` for amd64, aarch64 and riscv64, one QEMU at a time in the foreground, each to `ok>` with `PARITY:V4_POST tests=538 pass=538 fail=0` and `POST: PASSED`, `PARITY:V4_SYSTEM dict_hash=` equal across the three; and `make -C v4 test-32` with 0 failures. Logs are committed plain. `git status --short disk` must be clean before boots. + +## Rulings made while planning (for Captain Bob's review before execution) + +Each is something section 9a left open, with the reason and the cost if wrong. Task 1 writes the ones approved into `MESH.md` 9a. + +1. **The manage requests get fixed numbers, -6 to -18, below the block requests' -1 to -5** (`v4/include/v4/blocks.h`), the same for every node. Today they are `FIRST_REQUEST + n`, a number the host chooses, typed into Hera with `KERNEL-WORD` by the test. A centre Hera births cannot be told the host's numbers; fixed numbers let `centre.4th` and `hera.4th` make the words themselves (`-7 KERNEL-WORD NODE-BORN`). Cost if wrong: the numbers in two capsules and `manage.h`. +2. **`NODE-BORN` takes the number: `( number -- place | -1 )`.** The kernel enters the node in its registry at birth, with the asker as parent, and refuses a number that is live -- before anything is streamed, as v3 refuses a live name before it births. Section 9a.4 had the entry made at `NODE-PARITY`, after the whole birth. Cost if wrong: one argument. +3. **GONE down the graph is a hook in the system, `(GONE-HOOK)`, and the forwarding is FORTH in `centre.4th`** (`GONE-DOWN`). The belief happens in the system (`(GONE?)`, `quit.f18`), so the system must hand it on; the word that knows the children is the capsule's. One variable (`BUF0_W - 21`, free today) and five lines of F18. **The system changes, so every product's `dict_hash` changes** -- still equal on all three ISAs. Section 9a.6 item 7 said it must not move; that was wrong and is corrected in Task 1. COLD clears a hook set above the sealed system, so it never points at a forgotten word. Cost if wrong: the hook and `COLD`'s three lines. +4. **The capsule reader is the asker's** (`open`, `cell`, `line` take the asking node's place). Two nodes birthing at once would otherwise read one capsule between them. Cost if wrong: one argument on three callbacks. +5. **`KILL`'s messages stay as v4 has them** ("KILL: no such node", an error, ruled 2026-10-07 and tested), not v3's "not found" / "already dead" lines. `PAUSE` and `RESUME` say the same of themselves. +6. **The capsule a node is born from is still called `v4:nucleus-64.f18`** in `(NUCLEUS)`. Renaming it is the births correction for the products (`MACHINE.md` 2.2, steps 8/9), not this step. + +## Review Focus + +1. **The hook runs on the stacks of whatever text is waiting** -- `AWAIT` takes a GONE about the node it waits on and believes it there, deep in a text. The forwarding sends messages from inside that. Pinned by a new depth-sweep path in Task 6. +2. **The hook's `GONE` may disturb what `AWAIT` or `(IDLE)` keep in variables** (`(ONE)`, `(OUT-HDR)`, `(W-PORT)`) while they are mid-walk of a wire. Pinned by Task 6's removal sweep and its depth path, whose "works" requires the wait to end in "Node gone" and every node to answer afterwards. +3. **A centre killed while it is still birthing its unit** leaves children half made. Not tested; recorded as a limit in Task 7's documentation (as 7b.7's limits are), since v3 has no such state. +4. **Registry and the node's own table disagree** (the kernel freed a number the centre still lists). `GONE-DOWN` drops a child from the table when told it is gone; Task 5 checks that after `UNIT-KILL` no centre still lists a removed node (`GONE` to it would be "Argument out of range"). +5. **A refused request leaves the stack as the word left it**, and the line ends. Task 2 checks each refusal leaves nothing pushed. + +--- +### Task 1: Section 9a carries the planning rulings + +**Files:** +- Modify: `docs/v4.0.0/MESH.md` (section 9a) + +**Interfaces:** none (documentation). + +- [ ] **Step 1: Amend 9a.3, 9a.4 and 9a.6.** Under 9a.4 add, after the table of who may ask: + +```markdown +- **The requests' numbers are fixed** (ruled 2026-10-16, planning): -6 to -18, below the block + requests' -1 to -5 (`v4/include/v4/blocks.h`), the same for every node. The capsules make the + words themselves (`-7 KERNEL-WORD NODE-BORN`); no host types them in. + + | -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | + |---|---|---|---|---|---|---|---|---|---|---|---|---| + | `NODE-ME` | `NODE-BORN` | `NODE-WIRE` | `NODE-UNWIRE` | `NODE-SLEEP` | `NODE-WAKE` | `NODE-KILL` | `NODE-PARITY` | `CAPSULE-OPEN` | `CAPSULE-CELL` | `CAPSULE-LINE` | `NODE-CHILD` | `NODE-PLACE` | + +- **`NODE-BORN ( number -- place | -1 )`.** The node is entered in the registry when it is born, + with the asker as its parent; a number that is live is refused before anything is sent. -1: no room. +- **A capsule is read by the node that opened it**: two birthers do not share one reader. +``` + +Under 9a.3, replace the bullet "Passing a believed GONE on to the nodes this node birthed." with: + +```markdown + - `GONE-DOWN ( n -- )`: n is gone; every node this one birthed is told, as from this one, and n + leaves its table if it was one of them. It is put in the system's `(GONE-HOOK)` when the + capsule is loaded, before the node seals. +``` + +and add a paragraph after 9a.3: + +```markdown +**The system's part (ruled 2026-10-16, planning).** A node believes a GONE in the system +(`(GONE?)`, `capsules/v4/sys/quit.f18`), so the system hands it on: a variable, `(GONE-HOOK)`, +holds the xt of a word that is given the number of the node that is gone; 0, as at switch-on, +does nothing. `COLD` clears it if it points at or above the sealed system. +``` + +In 9a.6 item 7 replace "The products load neither capsule, so their `dict_hash` must not move." with "The system changes (`(GONE-HOOK)`), so the products' `dict_hash` changes; it must be the same on all three ISAs." + +- [ ] **Step 2: Commit** + +```bash +$G add docs/v4.0.0/MESH.md +$G commit -m "docs(v4): MESH.md 9a, the rulings made while planning step 7 + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 2: The kernel's registry and who may ask + +**Files:** +- Modify: `v4/include/v4/manage.h` (whole interface) +- Modify: `v4/src/manage.c` (whole file) +- Create: `v4/tests/test_manage.c` +- Modify: `v4/tests/test_host_unit.c` (only so that it compiles against the new interface: Step 6) + +**Interfaces:** +- Produces: `V4_REQ_NODE_ME` (-6) … `V4_REQ_NODE_PLACE` (-18); `V4_MANAGE_WORDS` 13; `v4_manage_entry { v4_cell number; v4_cell parent; unsigned place; }` (number 0: free); `v4_manage` with `reg`, `reg_cap`, `hera`, and `open/cell/line` taking `unsigned asker`; `int v4_manage_enter(const v4_manage *m, v4_cell number, unsigned place, v4_cell parent)`; `int v4_manage_serve(const v4_manage *m, unsigned place, v4_cell request)` -- 0 if `request` is not one of these, else 1 (served, or refused with error 12 raised on the asker). + +- [ ] **Step 1: Write the failing test** `v4/tests/test_manage.c`: + +```c +/* test_manage.c -- the kernel's side of births (docs/v4.0.0/MESH.md 9a.4): + * the registry of live nodes, and who may ask what. At the level of C: a + * request's arguments are put on the asking node's stack and the request is + * served, as the kernel does when the node writes its number to port 0. */ +#include "v4/fabric.h" +#include "v4/manage.h" +#include +#include + +static int failures = 0, checks = 0; +#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0) + +#define PB ((v4_cell)(V4_NODE_WORDS - 24u)) /* the ports */ +#define ERR ((v4_cell)(V4_NODE_WORDS - 40u)) /* NODE-ERROR */ +#define NODES 8 +static v4_fabric_node pool[NODES]; +static int used[NODES]; +static v4_place places[NODES]; +static v4_fabric f; +static v4_manage_entry reg[NODES]; +static unsigned gone_count, parity_count; +static v4_cell parity_number; +static v4_manage m; + +static int born(void *self) +{ + unsigned k; + int place; + (void)self; + for (k = 0; k < NODES && used[k]; k++) { } + if (k == NODES) return -1; + place = v4_fabric_add(&f, &pool[k], PB); + if (place < 0) return -1; + used[k] = 1; + v4_node_error_attach(&pool[k].n, ERR); + return place; +} +static void gone(void *self, unsigned place, v4_fabric_node *was) { (void)self; (void)place; used[was - pool] = 0; gone_count++; } +static int open_(void *self, unsigned asker, const char *name, unsigned len) { (void)self; (void)asker; return len == 5 && memcmp(name, "v4:ok", 5) == 0; } +static int cell_(void *self, unsigned asker, v4_cell *w) { (void)self; *w = (v4_cell)asker; return 1; } +static int line_(void *self, unsigned asker, char *text) { (void)self; (void)asker; text[0] = 'O'; text[1] = 'K'; return 2; } +static void parity_(void *self, unsigned place, v4_cell number, const v4_node *n) { (void)self; (void)place; (void)n; parity_count++; parity_number = number; } + +static v4_node *N(unsigned place) { return &v4_fabric_node_at(&f, place)->n; } +static void push(unsigned place, v4_cell x) { v4_dstack_push(&N(place)->ds, x); } +static v4_cell pop(unsigned place) { return v4_dstack_pop(&N(place)->ds); } +static unsigned depth(unsigned place) { return N(place)->ds.depth; } +/* serve a request; 1 if it raised error 12 on the asker */ +static int refused(unsigned place, v4_cell request) +{ + unsigned before = N(place)->faults; + CHECK(v4_manage_serve(&m, place, request) == 1, "request %ld is served or refused", (long)request); + return N(place)->faults == before + 1 && N(place)->fault_kind == V4_FAULT_RAISED && N(place)->fault_addr == 12; +} +static v4_cell place_of(unsigned asker, v4_cell number) { push(asker, number); (void)v4_manage_serve(&m, asker, V4_REQ_NODE_PLACE); return pop(asker); } +static v4_cell child(unsigned asker, v4_cell number, v4_cell i) { push(asker, number); push(asker, i); (void)v4_manage_serve(&m, asker, V4_REQ_NODE_CHILD); return pop(asker); } +/* asker births `number`: its place, or -1 */ +static v4_cell birth(unsigned asker, v4_cell number) { push(asker, number); (void)v4_manage_serve(&m, asker, V4_REQ_NODE_BORN); return depth(asker) ? pop(asker) : -2; } +static void put_chars(unsigned place, v4_cell baddr, const char *s) +{ + unsigned i; + for (i = 0; s[i]; i++) { + v4_cell a = baddr + (v4_cell)i; + unsigned shift = 8u * (unsigned)(a & 3); + v4_ucell c = (v4_ucell)N(place)->mem[a >> 2]; + c = (c & ~((v4_ucell)0xFFu << shift)) | ((v4_ucell)(unsigned char)s[i] << shift); + N(place)->mem[a >> 2] = (v4_cell)c; + } +} + +int main(void) +{ + unsigned h, a, b, x; + v4_cell r; + + printf("v4 manage tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); + v4_fabric_init(&f, places, NODES); + memset(&m, 0, sizeof m); + m.f = &f; m.reg = reg; m.reg_cap = NODES; m.born = born; m.gone = gone; + m.open = open_; m.cell = cell_; m.line = line_; m.parity = parity_; + h = (unsigned)born(0); + m.hera = h; + + CHECK(v4_manage_serve(&m, h, -1) == 0 && v4_manage_serve(&m, h, 1) == 0 && v4_manage_serve(&m, h, -19) == 0, "a block request, a host's own word and a number past the last are not this file's"); + CHECK(v4_manage_enter(&m, 10, h, 0) == 1, "Hera is entered by whoever made her, as v3's kernel enters Hera at boot"); + CHECK(v4_manage_enter(&m, 10, h, 0) == 0 && v4_manage_enter(&m, 0, h, 0) == 0, "a number that is live, or 0, is not entered again"); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_ME) == 1 && pop(h) == (v4_cell)h, "NODE-ME is her place"); + + /* births */ + r = birth(h, 11); + CHECK(r >= 0 && place_of(h, 11) == r && child(h, 10, 0) == 11 && child(h, 10, 1) == 0, "Hera births 11: the registry has it, with her as its parent"); + a = (unsigned)r; + push(h, 11); + CHECK(refused(h, V4_REQ_NODE_BORN) && depth(h) == 0 && used[2] == 0, "a birth of a number that is live is refused, and nothing is born"); + push(h, 0); + CHECK(refused(h, V4_REQ_NODE_BORN) && depth(h) == 0, "and so is a birth of 0"); + r = birth(a, 12); + CHECK(r >= 0 && child(a, 11, 0) == 12 && child(h, 10, 0) == 11 && child(h, 10, 1) == 0, "node 11 births 12, as v3: its parent is 11, not Hera"); + b = (unsigned)r; + x = (unsigned)v4_fabric_add(&f, &pool[7], PB); used[7] = 1; v4_node_error_attach(&pool[7].n, ERR); + push(x, 13); + CHECK(refused(x, V4_REQ_NODE_BORN) && depth(x) == 0, "a node the kernel has no record of may not birth"); + + /* wiring: its own ports, or those of a node it birthed */ + push(a, (v4_cell)a); push(a, 3); push(a, (v4_cell)b); push(a, 2); + CHECK(v4_manage_serve(&m, a, V4_REQ_NODE_WIRE) == 1 && pop(a) == -1, "node 11 wires itself to its child"); + push(b, (v4_cell)a); push(b, 4); push(b, (v4_cell)h); push(b, 5); + CHECK(refused(b, V4_REQ_NODE_WIRE) && depth(b) == 0, "node 12 may not wire its parent to Hera"); + push(a, (v4_cell)h); push(a, 5); push(a, (v4_cell)a); push(a, 5); + CHECK(refused(a, V4_REQ_NODE_WIRE) && depth(a) == 0, "node 11 may not wire Hera"); + push(a, (v4_cell)h); push(a, 2); + CHECK(refused(a, V4_REQ_NODE_UNWIRE) && depth(a) == 0, "nor cut a wire of hers"); + push(a, (v4_cell)a); push(a, 3); + CHECK(v4_manage_serve(&m, a, V4_REQ_NODE_UNWIRE) == 1 && depth(a) == 0, "and cuts its own"); + + /* parity: of a node it birthed */ + push(a, (v4_cell)a); push(a, 11); + CHECK(refused(a, V4_REQ_NODE_PARITY) && parity_count == 0, "node 11 may not report its own parity"); + push(h, (v4_cell)a); push(h, 12); + CHECK(refused(h, V4_REQ_NODE_PARITY) && parity_count == 0, "nor Hera that of her child under another number"); + push(h, (v4_cell)a); push(h, 11); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_PARITY) == 1 && parity_count == 1 && parity_number == 11 && depth(h) == 0, "Hera reports her child's"); + + /* capsules: the asker's own */ + put_chars(a, 400, "v4:ok"); + push(a, 400); push(a, 5); + CHECK(v4_manage_serve(&m, a, V4_REQ_CAPSULE_OPEN) == 1 && pop(a) == -1, "node 11 opens a capsule"); + CHECK(v4_manage_serve(&m, a, V4_REQ_CAPSULE_CELL) == 1 && pop(a) == -1 && pop(a) == (v4_cell)a, "and reads its cells: the reader is the asker's"); + push(a, 400); + CHECK(v4_manage_serve(&m, a, V4_REQ_CAPSULE_LINE) == 1 && pop(a) == 2 && (N(a)->mem[100] & 0xFFFF) == ('O' | 'K' << 8), "and its lines"); + + /* sleep, wake and kill: Hera's alone, by number */ + push(a, 12); + CHECK(refused(a, V4_REQ_NODE_SLEEP) && !places[b].asleep && depth(a) == 0, "node 11 may not put 12 to sleep"); + push(h, 12); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_SLEEP) == 1 && places[b].asleep, "Hera may, by its number"); + push(a, 12); + CHECK(refused(a, V4_REQ_NODE_WAKE) && places[b].asleep, "node 11 may not wake it"); + push(h, 12); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_WAKE) == 1 && !places[b].asleep, "Hera may"); + push(a, 12); + CHECK(refused(a, V4_REQ_NODE_KILL) && v4_fabric_node_at(&f, b) != NULL, "node 11 may not kill its own child: KILL is Hera's, as v3"); + { + unsigned faults = N(h)->faults; + push(h, 10); (void)v4_manage_serve(&m, h, V4_REQ_NODE_KILL); + push(h, 99); (void)v4_manage_serve(&m, h, V4_REQ_NODE_KILL); + CHECK(N(h)->faults == faults && gone_count == 0 && depth(h) == 0, "Hera's kill of herself, or of a number not live, does nothing"); + } + push(h, 12); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_KILL) == 1 && gone_count == 1 && place_of(h, 12) == -1 && child(h, 11, 0) == 0, "Hera kills 12, which she did not birth: the registry forgets it"); + r = birth(a, 13); + CHECK(r >= 0, "node 11 births 13"); + push(h, 11); + CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_KILL) == 1 && place_of(h, 13) == r && child(h, 11, 0) == 13, "killing 11 does not kill its child, as in v3"); + CHECK(place_of(h, 12) == -1 && place_of(h, 10) == (v4_cell)h, "NODE-PLACE of a number not live is -1"); + + printf(" %d checks, %d failures\n", checks, failures); + return failures != 0; +} +``` + +- [ ] **Step 2: Run it to see it fail** + +Run: `make -C v4 run-64-test_manage.c` +Expected: FAIL to compile: `V4_REQ_NODE_ME`, `v4_manage_entry`, `v4_manage_enter` undeclared. + +- [ ] **Step 3: Write `v4/include/v4/manage.h`.** Keep the file's opening comment, rewritten for this step, and replace the declarations: + +```c +/* manage.h -- what a node asks of whoever holds the fabric about nodes and + * capsules. docs/v4.0.0/MESH.md sections 9 and 9a. + * + * Births follow v3 (MESH.md 9): any node may birth, on its own ports, and is + * its child's parent; only Hera may put a node to sleep, wake it or remove + * it. The kernel keeps a registry of the live nodes -- each one's number, + * place and parent -- as v3's kernel keeps its registry of VMs, and checks + * every request against it. A request that is refused is error 12 on the + * node that asked, and changes nothing. + * + * Each request is a word on the node made with KERNEL-WORD (capsules/v4/ + * centre.4th, hera.4th), whose number is fixed, below the block requests' + * (v4/blocks.h): + * + * -6 NODE-ME ( -- place ) any node + * -7 NODE-BORN ( number -- place | -1 ) any node in the registry: a node + * is born, empty, and entered with + * the asker as its parent; a number + * that is live, or below 1, is + * refused; -1: no room + * -8 NODE-WIRE ( a pa b pb -- flag ) a and b each the asker or a node + * it birthed + * -9 NODE-UNWIRE ( a pa -- ) a the asker or a node it birthed + * -10 NODE-SLEEP ( number -- ) Hera + * -11 NODE-WAKE ( number -- ) Hera + * -12 NODE-KILL ( number -- ) Hera; not herself; a number not + * live does nothing. The node's + * children are not removed, as v3. + * -13 NODE-PARITY ( place number -- ) for a node the asker birthed, + * under the number it was born as + * -14 CAPSULE-OPEN ( baddr u -- flag ) any node in the registry: the + * capsule is found, checked and + * read from its start by that node + * -15 CAPSULE-CELL ( -- w flag ) the next cell of the asker's + * -16 CAPSULE-LINE ( baddr -- u | -1 ) the next line of the asker's + * -17 NODE-CHILD ( number i -- child | 0 ) any node: the i-th live child + * -18 NODE-PLACE ( number -- place | -1 ) any node + * + * A place is where a node is in the fabric (fabric.h): the host's name for + * it. A number is the nodes' own name for it, which messages go by. + * + * Nothing here uses the C library: the bare-metal kernel links this file. + */ +#ifndef V4_MANAGE_H +#define V4_MANAGE_H + +#include "v4/fabric.h" + +#define V4_REQ_NODE_ME (-6) +#define V4_REQ_NODE_BORN (-7) +#define V4_REQ_NODE_WIRE (-8) +#define V4_REQ_NODE_UNWIRE (-9) +#define V4_REQ_NODE_SLEEP (-10) +#define V4_REQ_NODE_WAKE (-11) +#define V4_REQ_NODE_KILL (-12) +#define V4_REQ_NODE_PARITY (-13) +#define V4_REQ_CAPSULE_OPEN (-14) +#define V4_REQ_CAPSULE_CELL (-15) +#define V4_REQ_CAPSULE_LINE (-16) +#define V4_REQ_NODE_CHILD (-17) +#define V4_REQ_NODE_PLACE (-18) + +#define V4_MANAGE_WORDS 13 /* -6 down to -18 */ +extern const char *const v4_manage_words[V4_MANAGE_WORDS]; /* [k] is request -6 - k */ +#define V4_MANAGE_LINE 64 /* the most characters in a line of a block */ + +/* A live node: number 0 marks a free entry. */ +typedef struct { + v4_cell number; + v4_cell parent; /* the number of the node that birthed it; 0: none */ + unsigned place; +} v4_manage_entry; + +typedef struct { + v4_fabric *f; + v4_manage_entry *reg; /* the registry: room the owner gives */ + unsigned reg_cap; + unsigned hera; /* Hera's place: the one node that may sleep, wake and kill */ + /* A node is born: the host finds it room, adds it to the fabric and gives + * it what a node's hardware gives it. Its place, or -1. */ + int (*born)(void *self); + /* The node that was at `place` has been removed: its room is free. */ + void (*gone)(void *self, unsigned place, v4_fabric_node *was); + /* For the node at `asker`: the capsule called `name`, found, its hash + * and signature checked, and to be read from its start. 1, or 0. */ + int (*open)(void *self, unsigned asker, const char *name, unsigned len); + /* The next cell of the capsule `asker` has open: 1 with it in *w, 0 at its end. */ + int (*cell)(void *self, unsigned asker, v4_cell *w); + /* The next line of it that is part of a block, at most V4_MANAGE_LINE characters: its length, or -1. */ + int (*line)(void *self, unsigned asker, char *text); + /* The node at `place`, numbered `number`: record its parity. */ + void (*parity)(void *self, unsigned place, v4_cell number, const v4_node *n); + void *self; +} v4_manage; + +/* Enter a node made by the owner -- Hera -- in the registry. 1; 0 if the + * number is below 1, already live, or there is no room. */ +int v4_manage_enter(const v4_manage *m, v4_cell number, unsigned place, v4_cell parent); + +/* The node at `place` has written `request` to the port its requests go to. + * 0 if it is not one of these, and nothing is done; otherwise 1: it was + * served, its arguments taken from the node's data stack and its results + * left there, or it was refused and error 12 raised on the node. */ +int v4_manage_serve(const v4_manage *m, unsigned place, v4_cell request); + +#endif /* V4_MANAGE_H */ +``` + +- [ ] **Step 4: Write `v4/src/manage.c`:** + +```c +/* manage.c -- what a node asks of whoever holds the fabric. See manage.h. + * + * Nothing here uses the C library: the bare-metal kernel links this file. + */ +#include "v4/manage.h" + +const char *const v4_manage_words[V4_MANAGE_WORDS] = { + "NODE-ME", "NODE-BORN", "NODE-WIRE", "NODE-UNWIRE", "NODE-SLEEP", "NODE-WAKE", "NODE-KILL", "NODE-PARITY", + "CAPSULE-OPEN", "CAPSULE-CELL", "CAPSULE-LINE", "NODE-CHILD", "NODE-PLACE" +}; + +/* A character of the node's memory: four to a cell, the first lowest, as + * the system keeps text (capsules/v4/sys/core.f18). */ +static int in_memory(v4_cell baddr) { return baddr >= 0 && (baddr >> 2) < (v4_cell)V4_NODE_WORDS; } +static char char_at(const v4_node *n, v4_cell baddr) +{ + return (char)(((v4_ucell)n->mem[baddr >> 2] >> (8 * (unsigned)(baddr & 3))) & 0xFFu); +} +static void char_put(v4_node *n, v4_cell baddr, char c) +{ + unsigned shift = 8 * (unsigned)(baddr & 3); + v4_ucell cell = (v4_ucell)n->mem[baddr >> 2]; + cell = (cell & ~((v4_ucell)0xFFu << shift)) | ((v4_ucell)(unsigned char)c << shift); + n->mem[baddr >> 2] = (v4_cell)cell; +} + +static unsigned place_of(v4_cell x) { return x < 0 ? ~0u : (unsigned)x; } + +static v4_manage_entry *by_number(const v4_manage *m, v4_cell number) +{ + unsigned k; + if (number < 1) return 0; + for (k = 0; k < m->reg_cap; k++) if (m->reg[k].number == number) return &m->reg[k]; + return 0; +} +static v4_manage_entry *by_place(const v4_manage *m, unsigned place) +{ + unsigned k; + for (k = 0; k < m->reg_cap; k++) if (m->reg[k].number != 0 && m->reg[k].place == place) return &m->reg[k]; + return 0; +} +/* the place is the asker's own, or that of a node it birthed */ +static int mine(const v4_manage *m, const v4_manage_entry *asker, unsigned place) +{ + const v4_manage_entry *e = by_place(m, place); + return asker && e && (e == asker || e->parent == asker->number); +} +/* error 12 on the node that asked: a store to NODE-ERROR raises it (D-18) */ +static int refuse(v4_node *n) { v4_node_store(n, n->error_reg, 12); return 1; } + +int v4_manage_enter(const v4_manage *m, v4_cell number, unsigned place, v4_cell parent) +{ + unsigned k; + if (number < 1 || by_number(m, number)) return 0; + for (k = 0; k < m->reg_cap; k++) { + if (m->reg[k].number == 0) { + m->reg[k].number = number; m->reg[k].parent = parent; m->reg[k].place = place; + return 1; + } + } + return 0; +} + +int v4_manage_serve(const v4_manage *m, unsigned place, v4_cell request) +{ + v4_fabric_node *at = v4_fabric_node_at(m->f, place); + v4_manage_entry *me, *e; + v4_node *n; + v4_cell a, b, c, d; + + if (!at || request > V4_REQ_NODE_ME || request < V4_REQ_NODE_PLACE) return 0; + n = &at->n; + me = by_place(m, place); + + switch (request) { + case V4_REQ_NODE_ME: /* ( -- place ) */ + v4_dstack_push(&n->ds, (v4_cell)place); + return 1; + case V4_REQ_NODE_BORN: { /* ( number -- place | -1 ) */ + int p; + a = v4_dstack_pop(&n->ds); + if (!me || a < 1 || by_number(m, a)) return refuse(n); + p = m->born ? m->born(m->self) : -1; + if (p >= 0 && !v4_manage_enter(m, a, (unsigned)p, me->number)) { /* no room in the registry: the node goes again */ + v4_fabric_node *was = v4_fabric_remove(m->f, (unsigned)p); + if (was && m->gone) m->gone(m->self, (unsigned)p, was); + p = -1; + } + v4_dstack_push(&n->ds, (v4_cell)p); + return 1; + } + case V4_REQ_NODE_WIRE: /* ( a pa b pb -- flag ) */ + d = v4_dstack_pop(&n->ds); c = v4_dstack_pop(&n->ds); b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + if (!mine(m, me, place_of(a)) || !mine(m, me, place_of(c))) return refuse(n); + v4_dstack_push(&n->ds, v4_fabric_wire(m->f, place_of(a), place_of(b), place_of(c), place_of(d)) ? -1 : 0); + return 1; + case V4_REQ_NODE_UNWIRE: /* ( a pa -- ) */ + b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + if (!mine(m, me, place_of(a))) return refuse(n); + (void)v4_fabric_unwire(m->f, place_of(a), place_of(b)); + return 1; + case V4_REQ_NODE_SLEEP: /* ( number -- ) */ + case V4_REQ_NODE_WAKE: + case V4_REQ_NODE_KILL: + a = v4_dstack_pop(&n->ds); + if (place != m->hera) return refuse(n); + e = by_number(m, a); + if (!e || e->place == place) return 1; /* not live, or Hera herself: nothing */ + if (request == V4_REQ_NODE_SLEEP) v4_fabric_sleep(m->f, e->place); + else if (request == V4_REQ_NODE_WAKE) v4_fabric_wake(m->f, e->place); + else { + unsigned gone_place = e->place; + v4_fabric_node *was = v4_fabric_remove(m->f, gone_place); + e->number = 0; + if (was && m->gone) m->gone(m->self, gone_place, was); + } + return 1; + case V4_REQ_NODE_PARITY: { /* ( place number -- ) */ + v4_fabric_node *it; + b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + e = by_place(m, place_of(a)); + if (!me || !e || e == me || e->parent != me->number || e->number != b) return refuse(n); + it = v4_fabric_node_at(m->f, e->place); + if (it && m->parity) m->parity(m->self, e->place, b, &it->n); + return 1; + } + case V4_REQ_CAPSULE_OPEN: { /* ( baddr u -- flag ) */ + char name[64]; + unsigned i, len; + b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + if (!me) return refuse(n); + len = b < 0 ? 0u : (unsigned)b; + if (len == 0 || len > sizeof name || !in_memory(a) || !in_memory(a + (v4_cell)len - 1) || !m->open) { + v4_dstack_push(&n->ds, 0); + return 1; + } + for (i = 0; i < len; i++) name[i] = char_at(n, a + (v4_cell)i); + v4_dstack_push(&n->ds, m->open(m->self, place, name, len) ? -1 : 0); + return 1; + } + case V4_REQ_CAPSULE_CELL: /* ( -- w flag ) */ + if (!me) return refuse(n); + a = 0; + if (m->cell && m->cell(m->self, place, &a)) { v4_dstack_push(&n->ds, a); v4_dstack_push(&n->ds, -1); } + else { v4_dstack_push(&n->ds, 0); v4_dstack_push(&n->ds, 0); } + return 1; + case V4_REQ_CAPSULE_LINE: { /* ( baddr -- u | -1 ) */ + char text[V4_MANAGE_LINE]; + int len, i; + a = v4_dstack_pop(&n->ds); + if (!me) return refuse(n); + len = m->line ? m->line(m->self, place, text) : -1; + if (len > V4_MANAGE_LINE) len = V4_MANAGE_LINE; + if (len > 0 && (!in_memory(a) || !in_memory(a + len - 1))) len = -1; + for (i = 0; i < len; i++) char_put(n, a + i, text[i]); + v4_dstack_push(&n->ds, (v4_cell)len); + return 1; + } + case V4_REQ_NODE_CHILD: { /* ( number i -- child | 0 ) */ + unsigned k; + v4_cell found = 0; + b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds); + for (k = 0; k < m->reg_cap && !found; k++) + if (m->reg[k].number != 0 && m->reg[k].parent == a && b-- == 0) found = m->reg[k].number; + v4_dstack_push(&n->ds, found); + return 1; + } + default: /* V4_REQ_NODE_PLACE ( number -- place | -1 ) */ + a = v4_dstack_pop(&n->ds); + e = by_number(m, a); + v4_dstack_push(&n->ds, e ? (v4_cell)e->place : -1); + return 1; + } +} +``` + +Note on `NODE-CHILD`: `b` counts down to the i-th match; `a` of 0 or below matches nothing live with that parent, so Hera (parent 0) is never a "child". + +- [ ] **Step 5: Run the new test at both widths** + +Run: `make -C v4 run-64-test_manage.c run-32-test_manage.c` +Expected: both print `0 failures`. + +- [ ] **Step 6: Keep the unit test whole on the new interface.** Three changes in `v4/tests/test_host_unit.c` and two in `capsules/v4/hera.4th`, so the suite passes at this commit: + +1. `host_open`, `host_cell`, `host_line` take `unsigned asker` after `self` (`(void)asker;` -- one reader still serves, since only Hera births here until Task 5). +2. Replace `static const v4_manage manage = { &f, host_born, ... };` with `static v4_manage manage;` and `static v4_manage_entry registry[PLACES];`, and at the top of `main` add: + +```c + manage.f = &f; manage.reg = registry; manage.reg_cap = PLACES; + manage.born = host_born; manage.gone = host_gone; manage.open = host_open; + manage.cell = host_cell; manage.line = host_line; manage.parity = host_parity; +``` + + After `place = host_born(0); ... hera_place = (unsigned)place;` add `manage.hera = hera_place;`, and after the check "it is told it is 10, and where the console is" add: + +```c + CHECK(v4_manage_enter(&manage, 10, hera_place, 0), "the kernel enters her in its registry, as v3's enters Hera"); +``` + +3. In `kernel_take`, serve by the request's own number: replace + `if (value < FIRST_REQUEST || !v4_manage_serve(&manage, hera_place, (unsigned)(value - FIRST_REQUEST))) refused++;` + with `if (!v4_manage_serve(&manage, hera_place, value)) refused++;`, and in the loop that types the words, make each one's number the fixed one: + `snprintf(text + at, sizeof text - at, "%d KERNEL-WORD %s ", (int)(V4_REQ_NODE_ME - (v4_cell)i), v4_manage_words[i]);` + +In `capsules/v4/hera.4th`, the two words whose arguments changed: +- `BIRTH`: `NODE-BORN DUP 0< IF (FAIL) THEN (KID) !` becomes `(KID#) @ NODE-BORN DUP 0< IF (FAIL) THEN (KID) !`. +- `KILL`: `DUP (KID@) 1+ @ NODE-KILL (UNLIST)` becomes `OVER NODE-KILL (UNLIST)` (the stack there is `n i`: the kernel now takes the number). + +Run: `make -C v4 test test-32 sanitize hosted-check` +Expected: all pass; `test_host_unit` at 64 bits still reports its 81 checks with 0 failures. (The check "an outer node's request for a node is not honoured" still passes here: its `ASK-BORN` was made with the host's old number 2, which is not a manage request. Task 5 replaces it.) + +- [ ] **Step 7: Commit** + +```bash +$G add v4/include/v4/manage.h v4/src/manage.c v4/tests/test_manage.c v4/tests/test_host_unit.c capsules/v4/hera.4th +$G commit -m "feat(v4): the kernel keeps a registry of nodes and checks who may ask (MESH.md 9a.4) + +Any node may birth and wire its own; only Hera sleeps, wakes and kills, by +number. Requests have fixed numbers, -6 to -18. NODE-BORN takes the +number; NODE-KILL a number. + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 3: The system hands a believed GONE on: `(GONE-HOOK)` + +**Files:** +- Modify: `v4/tests/host_map.h` (a variable, its map entry, the FREE comment) +- Modify: `capsules/v4/sys/quit.f18` (`(DOWN)`, `(GONE?)`, the header of `(GONE-HOOK)`) +- Modify: `capsules/v4/sys/system.f18` (`COLD`) +- Regenerate: `capsules/v4/sys/map-64.f18`, `capsules/v4/sys/map-32.f18` (by the build: `make -C v4` writes them from `host_map.h`) +- Test: `v4/tests/test_host_mesh.c` + +**Interfaces:** +- Produces: the FORTH word `(GONE-HOOK)` ( -- addr ), a variable: the xt of a word `( n -- )` that a node runs, with the number of the node that is gone, each time it believes a GONE; 0 does nothing. `COLD` sets it to 0 if it is at or above the sealed system. + +- [ ] **Step 1: Write the failing tests.** In `v4/tests/test_host_mesh.c`, just before the comment block that begins `/* ---- A NODE REMOVED, OR A WIRE CUT, AT EVERY STEP`, add: + +```c + /* ---- A BELIEVED GONE IS HANDED ON (MESH.md 9a.2, ruling 7; 9a.3) ---- + * The word in (GONE-HOOK) is given the number of the node that is gone, each time a GONE is believed: + * centre.4th puts GONE-DOWN there, which tells the nodes the node birthed. Here node 11 has a word that + * tells node 12; node 12 believes it, since node 11 is its centre, and forgets its way to 44. */ + CHECK(row() && told(12, "44 2 ROUTE") && told(11, "44 3 ROUTE"), "a row; nodes 11 and 12 each know a way to 44"); + { + v4_cell ways11 = pool[mid].n.mem[ROUTE_COUNT], ways12 = pool[far].n.mem[ROUTE_COUNT]; + CHECK(told(10, "44 11 GONE") && pool[mid].n.mem[ROUTE_COUNT] == ways11 - 1 && pool[far].n.mem[ROUTE_COUNT] == ways12 && asleep(far), + "with nothing in (GONE-HOOK) node 11 believes a GONE from its centre and tells no one"); + CHECK(told(11, "44 3 ROUTE : DOWN 12 GONE ; ' DOWN (GONE-HOOK) !"), "node 11 is given a word to hand a GONE on with"); + CHECK(told(10, "44 11 GONE") && pool[mid].n.mem[ROUTE_COUNT] == ways11 - 1 && pool[far].n.mem[ROUTE_COUNT] == ways12 - 1 && asleep(far) && asleep(mid), + "now node 11 believes it and hands it on: node 12 believes it from its centre and forgets 44 too"); + CHECK(told(12, "44 2 ROUTE") && told(11, "44 3 ROUTE") && told(12, "44 12 GONE") && pool[mid].n.mem[ROUTE_COUNT] == ways11, + "a GONE that is not believed -- node 12 is not node 11's centre -- is not handed on"); + CHECK(strstr(tell(11, "COLD"), "Cold Start") != NULL && strcmp(tell(11, "(GONE-HOOK) @ ."), "0 ") == 0, + "COLD forgets DOWN, so (GONE-HOOK) no longer points at it: \"%s\"", printed); + CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && strcmp(tell(11, "1 2 + ."), "3 ") == 0 && strcmp(tell(12, "1 2 + ."), "3 ") == 0, "and every node answers"); + } +``` + +- [ ] **Step 2: Run to see it fail** + +Run: `make -C v4 run-64-test_host_mesh.c` +Expected: FAIL at "node 11 is given a word to hand a GONE on with" (`(GONE-HOOK)` is an unknown word). + +- [ ] **Step 3: The variable.** In `v4/tests/host_map.h`, after the `#define WORD_FORGOTTEN` line, add: + +```c +#define GONE_HOOK (BUF0_W - 21) /* the xt of the word a believed GONE is handed to, or 0 (MESH.md 9a.2, ruling 7) */ +``` + +In the FREE comment, `BUF0_W - 22 .. - 21` becomes `BUF0_W - 22`. In the map list, after `X("(WORD-FORGOTTEN)", WORD_FORGOTTEN) \` add `X("(GONE-HOOK)", GONE_HOOK) \`. + +- [ ] **Step 4: The hand-on, in `capsules/v4/sys/quit.f18`.** Immediately before the comment above `: (GONE?)` insert: + +``` +\ ( -- ) a GONE has been believed: the word whose xt is in (GONE-HOOK), if +\ there is one, is given the number of the node that is gone, in (ONE), so +\ that the GONE goes on down the birth graph (MESH.md 9a.2, ruling 7; +\ capsules/v4/centre.4th, GONE-DOWN). 0 there, as at switch-on: nothing. +\ As (DEFINED) does (dict.f18): the xt goes on the return stack and ; +\ goes there, so the word returns to whoever called this. +: (DOWN) + (GONE-HOOK) b! @b if NONE (ONE) a! @ SWAP push ; + NONE: drop ; +``` + +and in `(GONE?)` change the last line + +``` + YES: drop (ONE) a! @ NO-ROUTE -1 ; +``` + +to + +``` + YES: drop (ONE) a! @ NO-ROUTE (DOWN) -1 ; +``` + +and add to the comment above `(GONE?)` the sentence: `Believed, it is handed on: (DOWN).` + +After the header line for `(WORD-FORGOTTEN)'` (the line that ends `: (WORD-FORGOTTEN)' (WORD-FORGOTTEN) ;`) add: + +``` +set HDR-NFA here ", (GONE-HOOK) #, 8 #, HDR-NFA #, HDR-LINK set HDR-LINK here : (GONE-HOOK)' (GONE-HOOK) ; +``` + +- [ ] **Step 5: `COLD` forgets a hook above the sealed system.** In `capsules/v4/sys/system.f18`, in `COLD`, after the line that begins `(BOOT) a! @ 2/ 2/ (FORGOTTEN)` add (`-if` jumps when T ≥ 0: `DATASHEET-F18.md` §7): + +``` + (GONE-HOOK) a! @ (BOOT) b! @b 2/ 2/ inv 1 + + \ the hook's xt less the sealed HERE, in cells + -if CLEAR drop jump KEPT \ below the sealed system: it stays (0 is below) + CLEAR: drop 0 (GONE-HOOK) a! ! \ at or above it: forgotten with what COLD forgets + KEPT: +``` + +and add to the comment above `COLD`: `A word in (GONE-HOOK) that COLD forgets is taken out of it (quit.f18, (DOWN)).` + +- [ ] **Step 6: Run the suite** + +Run: `make -C v4 test test-32 sanitize hosted-check` +Expected: all pass, including the new checks; `hosted-check` reports the three hosted ISAs identical (their `dict_hash` has changed from `0xfec8c47cfc6f437f`, and is the same on all three: note it). + +- [ ] **Step 7: Commit** + +```bash +$G add v4/tests/host_map.h capsules/v4/sys/quit.f18 capsules/v4/sys/system.f18 capsules/v4/sys/map-64.f18 capsules/v4/sys/map-32.f18 v4/tests/test_host_mesh.c +$G commit -m "feat(v4): a believed GONE is handed to the word in (GONE-HOOK) (MESH.md 9a) + +COLD takes out a hook it forgets. The system changed, so dict_hash did. + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 4: `centre.4th` and `hera.4th`; every node's kernel serves its requests + +**Files:** +- Create: `capsules/v4/centre.4th` (blocks 8100 to 8108) +- Rewrite: `capsules/v4/hera.4th` (blocks 8000 to 8001) +- Modify: `v4/tests/test_host_unit.c` (the host: readers, kernels, load order, one check replaced) +- Regenerated by the build: `capsules/BLOCK_MAP.md` (commit it with this task: it records the new capsule's blocks) + +**Interfaces:** +- Consumes: Task 2's request numbers; Task 3's `(GONE-HOOK)`. +- Produces (FORTH): in `centre.4th` -- `BIRTH-AS ( number port childport baddr u -- place )`, `BIRTH ( number port childport -- place )`, `UNIT ( n -- )`, `GONE-DOWN ( n -- )`, the request words `NODE-ME NODE-BORN NODE-WIRE NODE-UNWIRE NODE-PARITY CAPSULE-OPEN CAPSULE-CELL CAPSULE-LINE NODE-CHILD NODE-PLACE`; in `hera.4th` -- `KILL ( n -- )`, `PAUSE ( n -- )`, `RESUME ( n -- )`, `UNIT-KILL ( n -- )`, `CENTRE ( n -- )`, `NODE-SLEEP NODE-WAKE NODE-KILL`. + +- [ ] **Step 1: Write the failing test.** In `v4/tests/test_host_unit.c`, replace the `{ ... }` block that holds the check `"an outer node's request for a node is not honoured: only its blocks are"` (just before the comment `/* two nodes use the store at once */`) with: + +```c + { + /* Births follow v3 (MESH.md 9): an outer node births, and is its child's parent; it may not kill, + * put to sleep or wire what it did not birth. Hera kills the node it birthed, by number. */ + unsigned born = born_count; + CHECK(told(12, "-7 KERNEL-WORD ASK-BORN -12 KERNEL-WORD ASK-KILL -10 KERNEL-WORD ASK-SLEEP") + && told(12, "-8 KERNEL-WORD ASK-WIRE -18 KERNEL-WORD ASK-PLACE"), "node 12 is given words that ask the kernel"); + CHECK(strstr(tell(12, "30 ASK-BORN ."), "-1") == NULL && born_count == born + 1 && ended_how == V4_TEXT_COMPLETED, "node 12 births node 30, as any v3 VM may: \"%s\"", printed); + CHECK(strstr(tell(12, "11 ASK-KILL"), "Argument out of range") != NULL && node_numbered(11) != NULL, "it may not kill node 11: KILL is Hera's: \"%s\"", printed); + CHECK(strstr(tell(12, "11 ASK-SLEEP"), "Argument out of range") != NULL && waiting(11), "nor put it to sleep"); + CHECK(strstr(tell(12, "11 ASK-PLACE 5 10 ASK-PLACE 6 ASK-WIRE"), "Argument out of range") != NULL, "nor wire Hera and node 11, neither of which it birthed"); + CHECK(told(10, "30 KILL") && born_count == born + 1, "Hera kills node 30, which she did not birth, by its number"); + CHECK(strcmp(tell(12, "30 ASK-PLACE ."), "-1 ") == 0, "and the kernel no longer has it: \"%s\"", printed); + } +``` + +and, after the check `"and its own capsule, which makes it Hera"`, add: + +```c + CHECK(strcmp(tell(10, "(GONE-HOOK) @ 0= ."), "0 ") == 0, "centre.4th has put GONE-DOWN in her (GONE-HOOK)"); + CHECK(strcmp(tell(10, "NODE-ME ."), "0 ") == 0 && refused == 0, "she asks where she is, by the word centre.4th made, and is answered"); +``` + +Run: `make -C v4 run-64-test_host_unit.c` +Expected: FAIL (`centre.4th` does not exist; "and its own capsule" fails). + +- [ ] **Step 2: Write `capsules/v4/centre.4th`:** + +``` +Block 8100 +( centre.4th -- what a node that births others needs. ) +( docs/v4.0.0/MESH.md 9a. Births follow v3: any node may ) +( birth on its own ports, and is its children's parent. ) +( The kernel's requests, by their numbers in manage.h: ) +-6 KERNEL-WORD NODE-ME -7 KERNEL-WORD NODE-BORN +-8 KERNEL-WORD NODE-WIRE -9 KERNEL-WORD NODE-UNWIRE +-13 KERNEL-WORD NODE-PARITY -14 KERNEL-WORD CAPSULE-OPEN +-15 KERNEL-WORD CAPSULE-CELL -16 KERNEL-WORD CAPSULE-LINE +-17 KERNEL-WORD NODE-CHILD -18 KERNEL-WORD NODE-PLACE +( Text for another node is built in PAD. ) +VARIABLE (T#) +: (T0) ( -- ) 0 (T#) ! ; +: (TC) ( c -- ) PAD (T#) @ + C! 1 (T#) +! ; +: (TS) ( baddr u -- ) + DUP IF 0 DO DUP I + C@ (TC) LOOP DROP ELSE DROP DROP THEN ; +Block 8101 +: (TN) ( n -- ) 0 <# #S #> (TS) 32 (TC) ; +: (T) ( -- baddr u ) PAD (T#) @ ; +( The node being born: its number, its place, the port of ) +( this node that leads to it, its own port that leads here, ) +( and its personality: a capsule's name, or none. ) +VARIABLE (KID) VARIABLE (KID#) VARIABLE (KIDP) +VARIABLE (KIDC) VARIABLE (PERS) VARIABLE (PERS#) +: (FAIL) ( -- ) + ." BIRTH: node " (KID#) @ . ." did not do as it was told" CR + -1 NODE-ERROR ! ; +: (TOLD) ( how -- ) 1 - IF (FAIL) THEN ; +: (TELL) ( baddr u -- ) (KID#) @ SEND (KID#) @ AWAIT (TOLD) ; +Block 8102 +( the system, a capsule of F18 code, cell by cell to its port ) +: (NUCLEUS) ( -- ) + S" v4:nucleus-64.f18" CAPSULE-OPEN 0= IF (FAIL) THEN + BEGIN CAPSULE-CELL WHILE (KIDP) @ PORT! REPEAT DROP ; +( a capsule of FORTH blocks, a line at a time ) +: (LINES) ( baddr u -- ) CAPSULE-OPEN 0= IF (FAIL) THEN + BEGIN PAD CAPSULE-LINE DUP 0< 0= WHILE PAD SWAP (TELL) REPEAT + DROP ; +Block 8103 +( The nodes this one has had born: for each its number and ) +( the port of this node it is on. Sixteen at most. ) +CREATE (KIDS) 32 ALLOT VARIABLE (KIDS#) 0 (KIDS#) ! +: (KID@) ( i -- addr ) 2 * (KIDS) + ; +: (NOTE) ( number port -- ) + (KIDS#) @ 16 < IF + (KIDS#) @ (KID@) SWAP OVER 1+ ! ! 1 (KIDS#) +! + ELSE DROP DROP THEN ; +: (WHICH) ( number -- i | -1 ) + -1 SWAP (KIDS#) @ DUP IF 0 DO + DUP I (KID@) @ = IF SWAP DROP I SWAP THEN + LOOP ELSE DROP THEN DROP ; +: (UNLIST) ( i -- ) + (KIDS#) @ 1- DUP (KIDS#) ! (KID@) DUP @ SWAP 1+ @ + ROT (KID@) SWAP OVER 1+ ! ! ; +Block 8104 +( Who it is, where its printing goes, which of its ports ) +( leads to everything it is not told of, and who is there. ) +: (WHO) ( -- ) + (T0) (KID#) @ (TN) S" (ME) ! " (TS) + (CONSOLE) @ (TN) S" (CONSOLE) ! " (TS) + (KIDC) @ (TN) S" DEFAULT-ROUTE " (TS) + (ME) @ (TN) (KIDC) @ (TN) S" NEIGHBOUR" (TS) + (T) 0 (KIDP) @ SEND-ON (KID#) @ AWAIT (TOLD) ; +Block 8105 +( number port childport baddr u -- place A NODE IS BORN, ) +( its port childport joined to this one's port: empty, then ) +( the system, FORTH-79, and its personality if u is not 0. ) +: BIRTH-AS + (PERS#) ! (PERS) ! (KIDC) ! (KIDP) ! (KID#) ! + (KID#) @ NODE-BORN DUP 0< IF (FAIL) THEN (KID) ! + NODE-ME (KIDP) @ (KID) @ (KIDC) @ NODE-WIRE 0= IF (FAIL) THEN + (NUCLEUS) + (KID#) @ (KIDP) @ ROUTE (KID#) @ (KIDP) @ NEIGHBOUR (WHO) + S" v4:forth79.4th" (LINES) + (PERS#) @ IF (PERS) @ (PERS#) @ (LINES) THEN + S" (SEAL)" (TELL) + (KID) @ (KID#) @ NODE-PARITY + (KID#) @ (KIDP) @ (NOTE) + (KID) @ ; +Block 8106 +( number port childport -- place a node with no capsule ) +: BIRTH 0 0 BIRTH-AS ; +( Two outer nodes are joined: port 3 of X to port 4 of Y, ) +( and each is told the other is there. ) +VARIABLE (XP) VARIABLE (XN) VARIABLE (YP) VARIABLE (YN) +: (X) ( place number -- ) (XN) ! (XP) ! ; +: (Y) ( place number -- ) (YN) ! (YP) ! ; +: (SAY) ( them port me -- ) (KID#) ! + (T0) OVER (TN) DUP (TN) S" ROUTE " (TS) + SWAP (TN) (TN) S" NEIGHBOUR" (TS) (T) (TELL) ; +: (JOIN) ( -- ) + (XP) @ 3 (YP) @ 4 NODE-WIRE 0= IF (FAIL) THEN + (YN) @ 3 (XN) @ (SAY) (XN) @ 4 (YN) @ (SAY) ; +Block 8107 +( n -- A UNIT: four outer nodes, n+1 to n+4, on ports 2 ) +( to 5 of this one, each by its own port 2, and joined in a ) +( square: 1-2, 2-4, 4-3, 3-1. ) +VARIABLE (U) +VARIABLE (P1) VARIABLE (P2) VARIABLE (P3) VARIABLE (P4) +: (N) ( k -- number ) (U) @ + ; +: UNIT ( n -- ) (U) ! + 1 (N) 2 2 BIRTH (P1) ! 2 (N) 3 2 BIRTH (P2) ! + 3 (N) 4 2 BIRTH (P3) ! 4 (N) 5 2 BIRTH (P4) ! + (P1) @ 1 (N) (X) (P2) @ 2 (N) (Y) (JOIN) + (P2) @ 2 (N) (X) (P4) @ 4 (N) (Y) (JOIN) + (P4) @ 4 (N) (X) (P3) @ 3 (N) (Y) (JOIN) + (P3) @ 3 (N) (X) (P1) @ 1 (N) (Y) (JOIN) ; +Block 8108 +( n -- NODE n IS GONE: every node this one birthed is told, ) +( as from this one, and n leaves the list if it was there. ) +( The system calls this when it believes a GONE, through ) +( GONE-HOOK in quit.f18; Hera calls it from KILL. ) +: GONE-DOWN ( n -- ) + DUP (WHICH) DUP 0< IF DROP ELSE (UNLIST) THEN + (KIDS#) @ DUP IF 0 DO DUP I (KID@) @ GONE LOOP + ELSE DROP THEN DROP ; +' GONE-DOWN (GONE-HOOK) ! +``` + +Check every line against the rules before going on: `tools/mkcapsule --lint capsules/` (built by `make -f kernel/Makefile` or `cc -O2 tools/mkcapsule.c -o build/mkcapsule`) must report 0 violations for `v4:centre.4th`. + +- [ ] **Step 3: Rewrite `capsules/v4/hera.4th`:** + +``` +Block 8000 +( hera.4th -- what is Hera's alone, beside centre.4th, which ) +( she loads first. docs/v4.0.0/MESH.md 9a. She decides ) +( which nodes there are and which are awake: KILL, PAUSE and ) +( RESUME, by number, through the kernel's registry, are hers ) +( and no other node's, as v3's KILL is Hera's alone. ) +-10 KERNEL-WORD NODE-SLEEP -11 KERNEL-WORD NODE-WAKE +-12 KERNEL-WORD NODE-KILL +( n -- n flag a node there is, and not Hera herself ) +: (THERE?) DUP (ME) @ = 0= OVER NODE-PLACE 0< 0= AND ; +: (NO-NODE) ( n -- ) DROP ." no such node" CR -1 NODE-ERROR ! ; +( n -- KILL: node n is removed, whatever it was doing; this ) +( node forgets the way to it, and GONE goes down the graph. ) +: KILL ( n -- ) + (THERE?) 0= IF ." KILL: " (NO-NODE) THEN + DUP NODE-KILL DUP NO-ROUTE GONE-DOWN ; +Block 8001 +: PAUSE ( n -- ) + (THERE?) 0= IF ." PAUSE: " (NO-NODE) THEN NODE-SLEEP ; +: RESUME ( n -- ) + (THERE?) 0= IF ." RESUME: " (NO-NODE) THEN NODE-WAKE ; +( n -- a unit is removed: each of n's children, then n ) +: UNIT-KILL ( n -- ) + BEGIN DUP 0 NODE-CHILD ?DUP WHILE KILL REPEAT KILL ; +( n -- A SECOND CENTRE, node n, from its port 6 to Hera's ) +( port 6, given centre.4th; it births its unit, n+1 to n+4. ) +: CENTRE ( n -- ) + DUP 6 6 S" v4:centre.4th" BIRTH-AS DROP + 5 1 DO DUP I + 6 ROUTE LOOP DROP + (T0) (KID#) @ (TN) S" UNIT" (TS) (T) (TELL) ; +``` + +(The table of children Hera had, `(KIDS)`, and the old `BIRTH`, `UNIT` and the joining words now come from `centre.4th`. `KILL` of a node she never had, of herself, or of one already gone still ends "KILL: no such node", as ruled 2026-10-07.) + +- [ ] **Step 4: The host, in `v4/tests/test_host_unit.c`:** + +1. `#define PLACES 12u`; `parity_number[16]`, `parity_hash[16]` and the `parity_count < 8` guard becomes `< 16`. +2. One reader per place: `static reader reads[PLACES];` replaces `hera_reads`. `host_open` uses `reader *r = &reads[asker];` and maps `"v4:nucleus-64.f18"` (as now), `"v4:forth79.4th"` → `read_file(r, "forth79.4th")`, and add `"v4:centre.4th"` (13 characters) → `read_file(r, "centre.4th")`. `host_cell` and `host_line` use `&reads[asker]`. `read_file` must reset `r->cells_at = 0` and `r->is_f18 = 0` (it sets `is_f18 = 0` already). +3. Every node's kernel serves its requests: keep `static unsigned pool_place[PLACES];`, set `pool_place[k] = (unsigned)place;` in `host_born`; `node_kernel_take` becomes: + +```c +static int node_kernel_take(void *self, v4_cell value) +{ + v4_fabric_node *x = (v4_fabric_node *)self; + if (v4_blocks_serve(&node_blocks[x - pool], &x->n, value)) return 1; + if (!v4_manage_serve(&manage, pool_place[x - pool], value)) refused++; + return 1; +} +``` + + (`manage` must be declared above it: move `static v4_manage manage;` and `registry` up to the top of the file, after `places`.) +4. Delete the block that types the manage words into Hera (the `for (i = 0; i < V4_MANAGE_WORDS; i++) ... KERNEL-WORD ...` and its CHECK) and the check `"it asks where it is, and is answered"` that follows it (Step 1 added the same check after her capsules). +5. Load order: replace `CHECK(load(10, "hera.4th") == 0, "and its own capsule, which makes it Hera");` with + +```c + CHECK(load(10, "centre.4th") == 0, "she takes in what a node that births needs"); + CHECK(load(10, "hera.4th") == 0, "and her own capsule, which makes it Hera"); +``` + + and change the expected message of the check in Step 1 that says "and its own capsule" accordingly (it is the line just above Step 1's two added checks). + +- [ ] **Step 5: Run the suite** + +Run: `make -C v4 test test-32 sanitize hosted-check` +Expected: all pass. `test_host_unit` (64 bits): every earlier check still passes -- `10 UNIT`, the four parities with one hash, the kills of 14, 13, 12, 11 with "Node gone" and "Interrupted", "KILL: no such node" three times -- and the seven checks from Step 1. + +- [ ] **Step 6: Commit** + +```bash +$G add capsules/v4/centre.4th capsules/v4/hera.4th capsules/BLOCK_MAP.md v4/tests/test_host_unit.c +$G commit -m "feat(v4): centre.4th and hera.4th; every node's kernel serves it (MESH.md 9a.3) + +What a birthing node needs is centre.4th; KILL, PAUSE, RESUME, UNIT-KILL and +CENTRE are Hera's. An outer node births, as a v3 VM may, and may not kill. + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 5: A second unit, PAUSE and RESUME, GONE down the graph (acceptance 1 to 4) + +**Files:** +- Test: `v4/tests/test_host_unit.c` (a new section at the end of `main`, and one helper) + +**Interfaces:** +- Consumes: Task 4's `CENTRE`, `UNIT-KILL`, `PAUSE`, `RESUME`, `KILL`, `NODE-PLACE`; Task 2's registry. + +These tests go **at the end** of `main`, after the existing kill tests. By then Hera has killed 11 to 14, so her ports 2 to 5 are free and the numbers 11 to 14 are no longer live: she births her unit again and the second unit is born while it runs. (Putting them earlier would take nodes the existing kill tests need.) + +- [ ] **Step 1: Write the tests.** Add the helper after `going_from`: + +```c +/* 1 if the node numbered `number` has been put to sleep by the fabric (NODE-SLEEP) */ +static int paused(v4_cell number) +{ + unsigned k; + for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return places[k].asleep; + return 0; +} +``` + +and, at the end of `main`, before `printf(" %d checks, %d failures\n", ...)`: + +```c + /* ---- STEP 7 (MESH.md 9a.6): A SECOND UNIT WHILE THE FIRST RUNS; PAUSE, RESUME; GONE DOWN THE GRAPH ---- */ + step_limit = 400000000ul; + { + unsigned born = born_count; + CHECK(told(10, "10 UNIT") && born_count == born + 4, "Hera births her unit again: 11 to 14 are free once more: \"%s\"", printed); + } + CHECK(strstr(tell(10, "11 6 6 BIRTH"), "Argument out of range") != NULL && ended_how == V4_TEXT_ERROR, "a birth of a number that is live is refused: \"%s\"", printed); + + /* acceptance 1: it scales while running */ + spinning = 1u << 11; + (void)tell(11, ": BUSY 3000000 0 DO LOOP ; BUSY"); + CHECK(!waiting(11) && ended == 0, "node 11 of the first unit is busy"); + { + unsigned born = born_count, p = parity_count, k; + const char *r = tell(10, "20 CENTRE"); + CHECK(ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10, "Hera's 20 CENTRE: a second centre, and it births its own unit: \"%s\"", r); + CHECK(born_count == born + 5 && parity_count == p + 5 && parity_number[p] == 20 && parity_number[p + 1] == 21 && parity_number[p + 4] == 24, + "five nodes were born and reported, 20 by Hera and 21 to 24 by node 20: %u", born_count - born); + for (k = 1; k <= 4 && parity_hash[p + k] == parity_hash[0]; k++) { } + CHECK(k == 5, "the four outer nodes of the second unit have the dictionary hash of the first's"); + CHECK(node_numbered(20)->rom == MESH_ROM && node_numbered(24)->rom == MESH_ROM, "they carry a mesh node's ROM"); + } + CHECK(!waiting(11), "node 11 was busy all the while"); + spinning = 0; + CHECK(strcmp(tell(11, "11 100 * ."), "1100 ") == 0, "and goes on when its loop is done"); + for (i = 21; i <= 24; i++) { + char text[32], want[16]; + snprintf(text, sizeof text, "%u 100 * .", i); + snprintf(want, sizeof want, "%u ", i * 100); + CHECK(strcmp(tell((v4_cell)i, text), want) == 0 && printed_from == (v4_cell)i, "text for node %u goes by Hera and node 20, and what it prints comes back: \"%s\"", i, printed); + } + CHECK(strstr(tell(12, ": X22 S\" 7 6 * .\" 22 SEND 22 AWAIT . ; X22"), "42 ") != NULL && strstr(printed, "1 ") != NULL && ended_how == V4_TEXT_COMPLETED, + "node 12 of the first unit sends node 22 of the second text and waits for it: it is done, and its answer comes back: \"%s\"", printed); + CHECK(strcmp(tell(23, ": T S\" 65 EMIT\" 24 SEND ; T"), "A") == 0 && printed_from == 24, "the second unit's nodes talk across their own square: \"%s\"", printed); + + /* acceptance 2: Hera manages */ + CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14) && waiting(20) && waiting(21) && waiting(22) && waiting(23) && waiting(24), "all ten are waiting"); + { + v4_uheat_t c[10]; + static const v4_cell who[10] = { 10, 11, 12, 13, 14, 20, 21, 22, 23, 24 }; + for (i = 0; i < 10; i++) c[i] = clock_of(who[i]); + for (i = 0; i < 5000; i++) (void)v4_fabric_step(&f); + for (i = 0; i < 10 && clock_of(who[i]) == c[i]; i++) { } + CHECK(i == 10, "and none executes anything while it waits: the anti-clocks do not move"); + } + spinning = 1u << 13; + (void)tell(13, ": LONG 2000000 0 DO LOOP ; LONG"); + CHECK(!waiting(13) && ended == 0, "node 13 is in a long loop"); + CHECK(told(10, "13 PAUSE") && paused(13), "Hera puts it to sleep, by its number"); + { + v4_uheat_t c = clock_of(13); + for (i = 0; i < 100000; i++) (void)v4_fabric_step(&f); + CHECK(clock_of(13) == c, "asleep, it executes nothing"); + CHECK(told(11, ": T13 S\" 5 .\" 13 SEND ; T13") && going_from(11, 4) > 0, "text node 11 sends it waits on the wire to it: %u cells", going_from(11, 4)); + CHECK(told(10, "13 RESUME") && !paused(13), "Hera wakes it"); + spinning = 0; + CHECK(strstr(tell(10, "1 DROP"), "5 ") != NULL && clock_of(13) != c && waiting(13), "its loop goes on to its end, and then the text is done: \"%s\"", printed); + } + CHECK(strstr(tell(10, "99 PAUSE"), "PAUSE: no such node") != NULL && strstr(tell(10, "10 RESUME"), "RESUME: no such node") != NULL, "PAUSE and RESUME of what is not a node, or of Hera, say so"); + + /* acceptance 4: GONE down the graph */ + (void)tell(21, ": W12 12 AWAIT . ; W12"); + CHECK(ended == 0 && node_numbered(21)->mem[AWAIT_FROM] == 12 && waiting(21), "node 21 of the second unit waits on node 12 of the first"); + (void)tell(10, "12 KILL"); + CHECK(node_numbered(12) == NULL && strstr(printed, "Node gone") != NULL && node_numbered(21)->mem[AWAIT_FROM] == 0, + "Hera kills node 12: her GONE goes to node 20, which tells its own, and node 21's wait ends: \"%s\"", printed); + { + v4_cell ways = node_numbered(22)->mem[ROUTE_COUNT]; + CHECK(told(22, "88 3 ROUTE") && told(21, "88 22 GONE") && node_numbered(22)->mem[ROUTE_COUNT] == ways + 1, + "a GONE from node 21, which is not node 22's centre, is not believed"); + } + + /* acceptance 1, the end: the second unit is removed and the first carries on */ + { + unsigned born = born_count; + CHECK(told(10, "20 UNIT-KILL") && node_numbered(20) == NULL && node_numbered(21) == NULL && node_numbered(24) == NULL && born_count == born, + "Hera's 20 UNIT-KILL: node 20's children and then node 20 are removed: \"%s\"", printed); + CHECK(strcmp(tell(10, "20 NODE-PLACE . 21 NODE-PLACE . 24 NODE-PLACE ."), "-1 -1 -1 ") == 0, "the kernel has none of them: \"%s\"", printed); + CHECK(strstr(tell(10, ": H21 S\" 1\" 21 SEND ; H21"), "Argument out of range") != NULL, "Hera has no way to them"); + } + CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && strcmp(tell(11, "1 2 + ."), "3 ") == 0 && strcmp(tell(13, "1 2 + ."), "3 ") == 0 + && strcmp(tell(14, "1 2 + ."), "3 ") == 0, "and the first unit carries on: every node answers"); + CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && waiting(10) && waiting(11) && waiting(13) && waiting(14), "nothing was let go, and all wait again"); +``` + +- [ ] **Step 2: Run, and see what fails** + +Run: `make -C v4 run-64-test_host_unit.c` +Expected: PASS if Tasks 2–4 are right. A test written after the code it tests must still be shown to fail: comment out `' GONE-DOWN (GONE-HOOK) !` in `centre.4th`, run again, and confirm "node 21's wait ends" FAILS; put the line back. Then comment out the `if (place != m->hera) return refuse(n);` line in `manage.c`, run `make -C v4 run-64-test_host_unit.c run-64-test_manage.c`, and confirm "it may not kill node 11" and the test_manage "node 11 may not kill its own child" FAIL; put it back. + +- [ ] **Step 3: Run the suite** + +Run: `make -C v4 test test-32 sanitize hosted-check` +Expected: all pass. + +- [ ] **Step 4: Commit** + +```bash +$G add v4/tests/test_host_unit.c +$G commit -m "test(v4): a second unit while the first runs; PAUSE, RESUME; GONE down the graph (MESH.md 9a.6) + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 6: The new message path at every depth, and disturbed at every step + +**Files:** +- Test: `v4/tests/test_host_depth.c` (a path: a GONE believed in `AWAIT` and handed on) +- Test: `v4/tests/test_host_mesh.c` (an exchange: a GONE node 11 hands on to node 12) + +**Interfaces:** +- Consumes: Task 3's `(GONE-HOOK)`. + +A GONE is handed on from inside `(GONE?)`, which runs in `(IDLE)` and inside `AWAIT`, on the stacks of the text that waits. The memory of 2026-10-07 (v4 nucleus faults mid-protocol) asks every new message path to be swept at every stack depth and with a node removed at every step before it is called done. + +- [ ] **Step 1: The depth path.** In `v4/tests/test_host_depth.c`: + - `#define PATHS 11u`; add `"AWAIT ended by a GONE that is handed on"` to the end of `path_name`, and `": ACT 12 AWAIT DROP ;",` to the end of `act`. + - In `made()`, after the `if (path == 4 && ...)` line, add: + +```c + if (path == 10 && !(told(12, ": LL 400000 0 DO LOOP ; 44 2 ROUTE") + && told(11, "VARIABLE FWD : DOWN DROP 44 12 GONE 1 FWD +! ; ' DOWN (GONE-HOOK) !"))) return 0; +``` + + - In `one()`, next to `if (path == 8 && ...)` add `if (path == 10 && !told(11, "0 FWD !")) why = "node 11 did not clear FWD";`; make the `else if (path == 4)` branch read `else if (path == 4 || path == 10)`; and in the `switch`, add: + +```c + case 10: { + int down = pool[far].n.mem[ROUTE_COUNT] == ways12 - 1; /* node 12 believed the GONE node 11 handed on */ + works = how11 == V4_TEXT_ERROR && strcmp(printed, "Node gone\n") == 0 && left == values && down; + clean_error = over && strcmp(printed, over) == 0; + if (works) works = strcmp(tell(11, "FWD @ ."), "1 ") == 0; + else if (clean_error) { + int fwd1 = strcmp(tell(11, "FWD @ ."), "1 ") == 0, fwd0 = strcmp(printed, "0 ") == 0; + clean_error = down ? (fwd1 || fwd0) : fwd0; + } + break; + } +``` + + (`ways12` is read before the run, as now. The second line of the clean case says: if node 12 forgot 44, node 11's word ran far enough to hand it on -- FWD may or may not have been counted after; if node 12 did not, nothing was handed on and FWD is 0. Anything else is half done.) + - The `must` rule for calls deep: path 10 goes deeper than path 4 by the hook's word and `GONE`; use `k + 14u <= V4_RET_DEPTH` for path 10 (and keep 11u for the others): change the expression `((path == 5 || path == 7 || path == 9) ? 16u : 11u)` to `((path == 5 || path == 7 || path == 9) ? 16u : path == 10 ? 14u : 11u)`. + +Run: `make -C v4 run-64-test_host_depth.c` +Expected: PASS, with a line `AWAIT ended by a GONE that is handed on 20..32 values: works to N ...; calls: works to M deep; ... 0 failures`. Note N and M in the commit message. If it fails, the failure says which depth and why: that is a defect in Task 3's `(DOWN)` or in what the hook's `GONE` disturbs -- report it, do not loosen the test. + +- [ ] **Step 2: The removal exchange.** In `v4/tests/test_host_mesh.c`, in the array `ex` of the section `A NODE REMOVED, OR A WIRE CUT, AT EVERY STEP`, make it `ex[4]` and insert, **before** the last entry (the NACK one, which the check after the loop depends on being last): + +```c + { "a GONE node 10 sends node 11, which node 11 hands on to node 12", ": G 44 11 GONE ;", ": DOWN 12 GONE ; ' DOWN (GONE-HOOK) ! 44 3 ROUTE", "44 2 ROUTE", 10, "G" }, +``` + +and make the loop `for (e = 0; e < 4; e++)`. + +Run: `make -C v4 run-64-test_host_mesh.c run-32-test_host_mesh.c` +Expected: PASS: "a GONE node 10 sends node 11, which node 11 hands on to node 12, in N steps: with node 11 removed / node 12 removed / the wire ... cut and made again at each of them the rest come to rest and answer: 0 did not". + +- [ ] **Step 3: The suite, sanitizers included** + +Run: `make -C v4 test test-32 sanitize hosted-check` +Expected: all pass. + +- [ ] **Step 4: Commit** + +```bash +$G add v4/tests/test_host_depth.c v4/tests/test_host_mesh.c +$G commit -m "test(v4): a GONE handed on, at every stack depth and disturbed at every step + +AWAIT ended by a GONE that is handed on: works to N values and M calls deep, +then a stack error and nothing else. + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 7: Documents, and acceptance + +**Files:** +- Modify: `docs/v4.0.0/MESH.md` (step 7 in section 10: done; 9a's limits) +- Modify: `v4/README.md` (births follow v3; the second unit) +- Modify: `docs/v4.0.0/DATASHEET-F18.md` only if it names the manage requests' numbers (check with `grep -n "NODE-BORN\|FIRST_REQUEST" docs/v4.0.0/DATASHEET-F18.md`; if nothing, no change) +- Add: `logs//amd64/`, `logs//aarch64/`, `logs//riscv64/` (the three acceptance boots) + +- [ ] **Step 1: MESH.md.** Under step 7 in section 10, after "Designed 2026-10-16: section 9a." add a **Done 2026-10-16** paragraph in the style of steps 6b–6e: what was built (the registry and who may ask, `manage.c`; `(GONE-HOOK)`, `quit.f18`, and `COLD`; `centre.4th`, `hera.4th`); the tests and their check counts (`test_manage.c`, `test_host_unit.c`, `test_host_mesh.c`, `test_host_depth.c` with path 10's depths); the three boots' log directories; the new `dict_hash`, the same on all six systems. Add to 9a.5 the limit Review Focus 3 names: "A centre killed while it is still birthing its unit leaves the nodes it had asked for, not yet given the system; they wait, empty, until Hera kills them by number." +- [ ] **Step 2: v4/README.md.** In the paragraph that begins `**Five nodes from nothing**`, add one sentence: births follow v3 since 2026-10-16 -- any node may birth on its own ports; Hera alone kills, pauses and resumes, by number, through the kernel's registry; a second unit is born by its own centre (`MESH.md` 9a). Correct "The two products are not yet this" if the wording now misleads (it does not: the products are still one node). +- [ ] **Step 3: Lint and suite.** `tools/mkcapsule --lint capsules/` (0 violations); `make -C v4 test test-32 sanitize hosted-check` (all pass). +- [ ] **Step 4: The three boots**, as `.claude/CLAUDE.md` says: `git status --short disk` clean; then, one at a time, in the foreground: + +```bash +make -f kernel/Makefile ARCH=amd64 clean qemu +make -f kernel/Makefile ARCH=aarch64 clean qemu +make -f kernel/Makefile ARCH=riscv64 clean qemu +make -C v4 test-32 +``` + +Expected in each serial log: `ok>`, `PARITY:V4_POST tests=538 pass=538 fail=0`, `POST: PASSED`, and `PARITY:V4_SYSTEM ... dict_hash=0x...` the same on all three (and the same as `hosted-check`'s). `test-32`: 0 failures. Afterwards put back `v3/include/version.h` and `capsules/BLOCK_MAP.md` if the boots rewrote them with nothing new (`git diff` them first: `BLOCK_MAP.md` gained `centre.4th`'s blocks in Task 4 and is committed already). + +- [ ] **Step 5: Commit and push** + +```bash +$G add docs/v4.0.0/MESH.md v4/README.md logs/ logs/ logs/ +$G commit -m "docs(v4): MESH step 7 done -- a second unit, PAUSE, RESUME, KILL; the three boots + +Co-Authored-By: Claude Opus 5.5 " +PATH="$PATH:/usr/share/gitkraken/resources/app.asar.unpacked/git/libexec/git-core" $G push -q origin StarForth-v4.0.0 +``` + +Replace `` with the directories the boots wrote (`ls -dt logs/2026* | head -3`). Push every earlier task's commit as it is made, the same way.