Files
LithosAnanake/docs/v4.0.0/plans/2026-10-16-mesh-step7-second-unit.md
T
2026-10-16 08:52:27 -04:00

68 KiB
Raw Blame History

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 <noreply@anthropic.com>.
  • 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=<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:
- **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:

  - `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:

**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
$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 <noreply@anthropic.com>"

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:

/* 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 <stdio.h>
#include <string.h>

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:
/* 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:
/* 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:
    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:

    CHECK(v4_manage_enter(&manage, 10, hera_place, 0), "the kernel enters her in its registry, as v3's enters Hera");
  1. 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
$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 <noreply@anthropic.com>"

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:

    /* ---- 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:
#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
$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 <noreply@anthropic.com>"

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:

    {
        /* 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:

    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:
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

    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
$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 <noreply@anthropic.com>"

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:
/* 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", ...):

    /* ---- 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
$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 <noreply@anthropic.com>"

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:
    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:
        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):
            { "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
$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 <noreply@anthropic.com>"

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/<TS>/amd64/, logs/<TS>/aarch64/, logs/<TS>/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:

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
$G add docs/v4.0.0/MESH.md v4/README.md logs/<TS-amd64> logs/<TS-aarch64> logs/<TS-riscv64>
$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 <noreply@anthropic.com>"
PATH="$PATH:/usr/share/gitkraken/resources/app.asar.unpacked/git/libexec/git-core" $G push -q origin StarForth-v4.0.0

Replace <TS-…> 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.