# 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.