1213 lines
68 KiB
Markdown
1213 lines
68 KiB
Markdown
# 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:
|
||
|
||
```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 <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`:
|
||
|
||
```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:
|
||
|
||
```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 <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:
|
||
|
||
```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 <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:
|
||
|
||
```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 <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`:
|
||
|
||
```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 <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:
|
||
|
||
```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 <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:
|
||
|
||
```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/<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.
|