feat(kernel): Hera in the kernel's fabric and registry; centre.4th and hera.4th on her

#189, ENGINE step 4 Phase 1, Task 5a. Captain Bob, 2026-11-13: "Full
births now"; "Hera as now, in a fabric".

- sk_v4.c: the kernel's fabric (v4/fabric.h), Hera's node at its one
  place, entered in its registry (v4/manage.h) as node 10. The registry's
  answers are real: a birth is "no room" -- the fabric has one place
  until 5b; CAPSULE-OPEN/-LINE read the kernel's capsule directory,
  checked as the boot checks (no capsule of F18 code is there until 5b);
  a born node's parity line. She is not told her number: the boot's
  console addresses node 0 (told 10, the boot stopped after POST -- kept,
  labelled, logs/20261113-132712).
- boot.h/boot.c: v4_boot's `request` hook (the registry's requests) and
  `capsules` (the kernel's own, loaded before the seal, so POST runs on
  them). hosted.c sets neither: the hosted product's boot is as it was.
- kernel/Makefile: manage.c in the kernel's v4 engine.
- qemu_session.py: a v4 acceptance boot asks NODE-ME . and wants 0.
- v4-dictionary.tsv: the 55 words of centre.4th and hera.4th, written
  from their source and manage.h; mkdictionary.py loads the kernel's
  capsules into the hosted system before WORDS, so the live list is the
  kernel's -- 375 words.
- .claude/memory/rules.md, rule 6: every boot command under timeout 570.

Seen to fail first (logs/20261113-132410): NODE-ME was an unknown word.
After: amd64 NODE-ME . gives 0 ok; POST 539/539; word_count 375. v4
suite: test, test-32 99 programs 0 failures; v4-checks 79 under the
sanitizers 0 failures, the hosted builds alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
Claude
2026-11-13 14:24:27 +00:00
parent c32cd04388
commit f10d2bc835
21 changed files with 348 additions and 24 deletions
+4
View File
@@ -13,6 +13,10 @@
before acting.** (Bob, 2026-11-12: "you are supposed to stop and ask when something comes up ... What before acting.** (Bob, 2026-11-12: "you are supposed to stop and ask when something comes up ... What
if I had missed that message?") I installed qemu-user unasked when hosted-check could not find if I had missed that message?") I installed qemu-user unasked when hosted-check could not find
qemu-aarch64, and only said so afterwards, in a summary. Telling him after is not asking him first. qemu-aarch64, and only said so afterwards, in a summary. Telling him after is not asking him first.
6. **Every foreground boot command is wrapped in `timeout 570`.** A boot that hangs past the tool's
10-minute limit is moved to the background by the tool -- an acceptance run in the background,
against rule 1. It happened 2026-11-13 (a v4 boot that stopped after POST); QEMU was stopped at
once with SIGTERM. `timeout` ends it in the foreground instead.
Source of authority: `.claude/CLAUDE.md` (wins), then the FABRIC record (`docs/fabric/`, FABRIC-3 living), Source of authority: `.claude/CLAUDE.md` (wins), then the FABRIC record (`docs/fabric/`, FABRIC-3 living),
then `docs/v4.0.0/`, `docs/reference/ROADMAP.md`, `boards/BRINGUP.md`. Code wins over all documents. then `docs/v4.0.0/`, `docs/reference/ROADMAP.md`, `boards/BRINGUP.md`. Code wins over all documents.
+55
View File
@@ -321,3 +321,58 @@ XOR ( x1 x2 -- x3 ) Bitwise exclusive or.
[LITERAL] ( x -- ) Another name for LITERAL, as v3 has it. [LITERAL] ( x -- ) Another name for LITERAL, as v3 has it.
\ ( -- ) \ text: a comment to the end of the line; immediate. \ ( -- ) \ text: a comment to the end of the line; immediate.
] ( -- ) Start compiling what follows. ] ( -- ) Start compiling what follows.
(FAIL) ( -- ) A birth went wrong: print "BIRTH: node n did not do as it was told" and raise an error (centre.4th).
(JOIN) ( -- ) Join two outer nodes of a unit: port 3 of X to port 4 of Y, each told the other is there (centre.4th).
(KID#) ( -- addr ) Variable: the number of the node being born (centre.4th).
(KID) ( -- addr ) Variable: the place of the node being born (centre.4th).
(KIDC) ( -- addr ) Variable: the newborn node's own port that leads back to its parent (centre.4th).
(KIDP) ( -- addr ) Variable: this node's port that leads to the node being born (centre.4th).
(LINES) ( baddr u -- ) Send the newborn node the named capsule of FORTH blocks, a line at a time, each to be completed (centre.4th).
(N) ( k -- number ) The number of the unit's k-th outer node: the unit's number plus k (centre.4th).
(NO-NODE) ( n -- ) Drop n, print "no such node" and raise an error (hera.4th).
(NUCLEUS) ( -- ) Send the newborn node the system, the capsule of F18 code, cell by cell to its port (centre.4th).
(P1) ( -- addr ) Variable: the place of a unit's first outer node (centre.4th).
(P2) ( -- addr ) Variable: the place of a unit's second outer node (centre.4th).
(P3) ( -- addr ) Variable: the place of a unit's third outer node (centre.4th).
(P4) ( -- addr ) Variable: the place of a unit's fourth outer node (centre.4th).
(PERS#) ( -- addr ) Variable: the length of the newborn node's personality capsule's name; 0: none (centre.4th).
(PERS) ( -- addr ) Variable: the byte address of the newborn node's personality capsule's name (centre.4th).
(SAY) ( them port me -- ) Tell node me that the port, by its number, leads to node them, and that node them is its neighbour there (centre.4th).
(T#) ( -- addr ) Variable: how many characters of text for another node have been built in PAD (centre.4th).
(T) ( -- baddr u ) The text built in PAD for another node (centre.4th).
(T0) ( -- ) Begin building text for another node in PAD: empty (centre.4th).
(TC) ( c -- ) Add a character to the text being built in PAD (centre.4th).
(TELL) ( baddr u -- ) Send the newborn node the text to interpret, wait for how it ended, and fail the birth if it did not complete (centre.4th).
(THERE?) ( n -- n flag ) True if node n is live and is not Hera herself (hera.4th).
(TN) ( n -- ) Add a number and a space to the text being built in PAD (centre.4th).
(TOLD) ( how -- ) Fail the birth unless how is 1: the text completed (centre.4th).
(TS) ( baddr u -- ) Add a string to the text being built in PAD (centre.4th).
(U) ( -- addr ) Variable: the number of the unit being born; its outer nodes are this plus 1 to 4 (centre.4th).
(WHO) ( -- ) Tell the newborn node its number, where its printing goes, its default route and who is at its port back (centre.4th).
(X) ( place number -- ) Set X, the first of the two outer nodes (JOIN) joins (centre.4th).
(XN) ( -- addr ) Variable: X's number (centre.4th).
(XP) ( -- addr ) Variable: X's place (centre.4th).
(Y) ( place number -- ) Set Y, the second of the two outer nodes (JOIN) joins (centre.4th).
(YN) ( -- addr ) Variable: Y's number (centre.4th).
(YP) ( -- addr ) Variable: Y's place (centre.4th).
BIRTH ( number port childport -- place ) A node is born, numbered number, its port childport joined to this node's port: given the system and FORTH-79; its place.
BIRTH-AS ( number port childport baddr u -- place ) BIRTH, and given the capsule named by the string as its personality when u is not 0.
CAPSULE-CELL ( -- w flag ) The next cell of the capsule of F18 code this node has open, and true; 0 0 at its end. A kernel request.
CAPSULE-LINE ( baddr -- u | -1 ) The next line of the capsule of FORTH blocks this node has open, copied to baddr: its length, or -1 at its end. A kernel request.
CAPSULE-OPEN ( baddr u -- flag ) Find the named capsule, check its hash and signature, and open it to be read from its start by this node. A kernel request.
CENTRE ( n -- ) Hera only: a second centre, node n, from its port 6 to Hera's port 6, given centre.4th; it births its own unit, n+1 to n+4.
KILL ( n -- ) Hera only: node n is removed, whatever it was doing; every node is told it is gone, and Hera forgets the way to it.
NODE-BORN ( number -- place | -1 ) A node is born, empty, and entered in the registry with this node as its parent: its place, or -1 if there is no room. A kernel request.
NODE-CHILD ( number i -- child | 0 ) The number of node number's i-th live child, or 0. A kernel request.
NODE-KILL ( number -- ) Hera only: remove the node; its children become hers. A kernel request; KILL is the word to use.
NODE-ME ( -- place ) This node's place: where it is in the kernel's fabric. A kernel request.
NODE-PARITY ( place number -- ) Record the parity of a node this one birthed, under the number it was born as. A kernel request.
NODE-PLACE ( number -- place | -1 ) The node's place in the kernel's fabric, or -1: not live. A kernel request.
NODE-SLEEP ( number -- ) Hera only: put the node to sleep. A kernel request; PAUSE is the word to use.
NODE-UNWIRE ( a pa -- ) Cut the wire on port pa of node a, this node or one it birthed. A kernel request.
NODE-WAKE ( number -- ) Hera only: wake the node. A kernel request; RESUME is the word to use.
NODE-WIRE ( a pa b pb -- flag ) Join port pa of node a to port pb of node b, each this node or one it birthed: true if done. A kernel request.
PAUSE ( n -- ) Hera only: put node n to sleep; it executes nothing until RESUME.
RESUME ( n -- ) Hera only: wake node n.
UNIT ( n -- ) Birth a unit: four outer nodes, n+1 to n+4, on ports 2 to 5 of this one, joined in a square.
UNIT-KILL ( n -- ) Hera only: remove node n's children, one by one, then n.
Can't render this file because it contains an unexpected character in line 10 and column 3.
@@ -139,10 +139,17 @@ own sources.
Each its own increment, each test first, each a boot: Each its own increment, each test first, each a boot:
- [ ] **5a. A fabric in the kernel, Hera in it.** `v4/src/fabric.c` and `manage.c` into the kernel - [x] **5a. A fabric in the kernel, Hera in it.** Ruled 2026-11-13 (Captain Bob): **"Hera as now, in
build; Hera, node 10, at a place of a kernel fabric, entered in the registry; the prompt's lines go a fabric"** -- she boots by `v4_boot_run` as before (the hosted product's parity kept), and her node
to her through it. `centre.4th` and `hera.4th` loaded on her: `NODE-ME` answers her place (word is place 0 of the kernel's fabric (`sk_v4.c`), entered in the registry as node 10; her registry
count and `dict_hash` change). requests go to `v4_manage_serve` through `v4_boot`'s new `request` hook, and `centre.4th` and
`hera.4th` are loaded on her through its new `capsules` list -- before the seal, so POST runs on
them. The fabric has one place: `NODE-BORN` is answered "no room" until 5b. `CAPSULE-OPEN/-LINE`
read the kernel's capsule directory, checked as the boot checks; no capsule of F18 code is there
until 5b. She is not told her number ((ME) stays 0): the boot's console addresses node 0. Word
count 375 (55 from the two capsules), each in the dictionary; `mkdictionary.py` loads the kernel's
capsules into the hosted system before `WORDS`, so the live list is the kernel's.
Seen to fail first: the acceptance check `NODE-ME .` gave UNKNOWN WORD; then `0 ok`.
- [ ] **5b. Births.** `NODE-BORN`: room for a node from the kernel's heap, a place in the fabric, a - [ ] **5b. Births.** `NODE-BORN`: room for a node from the kernel's heap, a place in the fabric, a
Stadium quota granted from Hera's under the new node's key (`stadium_node_key(number)`), its Stadium quota granted from Hera's under the new node's key (`stadium_node_key(number)`), its
opcode patrons; `NODE-KILL` gives the room and the quota back. The capsule words opcode patrons; `NODE-KILL` gives the room and the quota back. The capsule words
+1 -1
View File
@@ -727,7 +727,7 @@ V4_DEFS := -DSTARFORTH_V4=1 -DSK_V4_DEFAULT=$(V4_DEFAULT) -Iv4/include \
KERNEL_CFLAGS += $(V4_DEFS) KERNEL_CFLAGS += $(V4_DEFS)
LOADER_CFLAGS += $(V4_DEFS) LOADER_CFLAGS += $(V4_DEFS)
V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c wire.c) V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c wire.c manage.c)
V4_ROM_C := $(BUILD_DIR)/v4_rom_64.c V4_ROM_C := $(BUILD_DIR)/v4_rom_64.c
V4_MAP_C := $(BUILD_DIR)/v4_map_64.c V4_MAP_C := $(BUILD_DIR)/v4_map_64.c
+142 -5
View File
@@ -32,7 +32,13 @@
#include "starkernel/arch.h" #include "starkernel/arch.h"
#include "starkernel/timer.h" #include "starkernel/timer.h"
#include "v4/boot.h" #include "v4/boot.h"
#include "v4/fabric.h"
#include "v4/manage.h"
#include "block_subsystem.h" #include "block_subsystem.h"
#include "starkernel/capsule.h"
#include "starkernel/capsule_generated.h"
#include "starkernel/capsule_sig.h"
#include "starkernel/capsule_blocks.h"
#ifdef STARFORTH_ENABLE_VM #ifdef STARFORTH_ENABLE_VM
#include "starkernel/vm/stadium.h" #include "starkernel/vm/stadium.h"
#include "starkernel/vm/stadium_ops.h" #include "starkernel/vm/stadium_ops.h"
@@ -44,10 +50,119 @@
* console's own "[zuse@Hera] " line prefix. */ * console's own "[zuse@Hera] " line prefix. */
#define SK_V4_PROMPT "\x1b[38;2;85;255;255mok> \x1b[39m" #define SK_V4_PROMPT "\x1b[38;2;85;255;255mok> \x1b[39m"
static v4_node sk_v4_node; /* THE KERNEL'S FABRIC (#189, ENGINE step 4 Phase 1, Task 5a; Captain Bob, 2026-11-13: "Hera as now,
static v4_exec_state sk_v4_es; * in a fabric"). Hera -- node 10, the one the kernel brings up -- is booted as before (v4_boot_run)
static v4_heat sk_v4_heat; * and is the node at place 0 of the kernel's fabric, entered in its registry (v4/manage.h,
static v4_image sk_v4_image; * MESH.md 9a): her registry requests (centre.4th, hera.4th) are served by v4_manage_serve. The
* fabric has her one place: a birth is refused, "no room" (-1), until the fabric has room for more
* (Task 5b). */
#define SK_V4_PLACES 1u
static v4_fabric_node sk_v4_hera; /* her node, its exec state and its heat */
static v4_place sk_v4_places[SK_V4_PLACES];
static v4_fabric sk_v4_fabric;
static v4_manage sk_v4_manage;
static v4_manage_entry sk_v4_registry[SK_V4_PLACES];
static unsigned sk_v4_hera_place;
static v4_image sk_v4_image;
#define sk_v4_heat (sk_v4_hera.h)
/* What each place has open of a capsule (CAPSULE-OPEN, -LINE): a FORTH capsule's payload, read a
* block line at a time, as the boot reads one (v4/system/boot.c, load_capsule). */
typedef struct { const uint8_t *p, *end; int in_block; } sk_v4_reader;
static sk_v4_reader sk_v4_reads[SK_V4_PLACES];
static int sk_v4_born(void *self)
{
(void)self;
return -1; /* the fabric has no room beyond Hera's place (Task 5b gives it room) */
}
static void sk_v4_gone(void *self, unsigned place, v4_fabric_node *was)
{
(void)self; (void)was;
if (place < SK_V4_PLACES) sk_v4_reads[place].p = sk_v4_reads[place].end = 0;
}
/* The capsule `name`: found in the kernel's directory, its content and signature checked as the
* boot checks them (a signature that does not verify refuses it), and opened for `asker`. Only a
* capsule of FORTH blocks can be opened: there is none of F18 code in the directory to read a cell
* at a time until Task 5b puts the nucleus there. */
static int sk_v4_open(void *self, unsigned asker, const char *name, unsigned len)
{
const CapsuleDirHeader *dir = capsule_get_directory();
const CapsuleDesc *descs = capsule_get_descriptors();
const CapsuleNameEntry *names = capsule_get_names();
const uint8_t *arena = capsule_get_arena();
const CapsuleDesc *cap;
const uint8_t *payload;
char cname[V4_MANAGE_LINE + 1u];
unsigned i;
(void)self;
if (asker >= SK_V4_PLACES || len == 0 || len > V4_MANAGE_LINE) return 0;
for (i = 0; i < len; i++) cname[i] = name[i];
cname[len] = 0;
if (len < 4 || cname[len - 4] != '.' || cname[len - 3] != '4' || cname[len - 2] != 't' || cname[len - 1] != 'h') return 0;
cap = capsule_find_by_name(dir, descs, names, cname);
if (!cap || capsule_validate(cap, arena, dir->arena_size, 1) != CAPSULE_VALID) return 0;
if (capsule_verify_signature(descs, names, capsule_get_signatures(), arena, dir->desc_count, (int)(cap - descs))
== CAPSULE_SIG_INVALID) return 0;
payload = capsule_get_payload(cap, arena);
if (!payload) return 0;
sk_v4_reads[asker].p = payload;
sk_v4_reads[asker].end = payload + cap->length;
sk_v4_reads[asker].in_block = 0;
return 1;
}
static int sk_v4_cell(void *self, unsigned asker, v4_cell *w)
{
(void)self; (void)asker; (void)w;
return 0; /* no capsule of F18 code is open: none can be (sk_v4_open) */
}
/* The next line of the open capsule that is part of a block, at most V4_MANAGE_LINE characters:
* its length, or -1 at its end (or a line too long, which no capsule mkcapsule passes has). */
static int sk_v4_line(void *self, unsigned asker, char *text)
{
sk_v4_reader *r;
(void)self;
if (asker >= SK_V4_PLACES) return -1;
r = &sk_v4_reads[asker];
while (r->p && r->p < r->end) {
const uint8_t *after, *nl;
uint32_t num;
unsigned len, i;
if (capsule_block_header(r->p, r->end, &num, &after)) { r->in_block = 1; r->p = after; continue; }
for (nl = r->p; nl < r->end && *nl != '\n'; nl++) { }
len = (unsigned)(nl - r->p);
if (len && r->p[len - 1] == '\r') len--;
if (r->in_block && len > V4_MANAGE_LINE) { r->p = r->end; return -1; }
if (r->in_block) for (i = 0; i < len; i++) text[i] = (char)r->p[i];
r->p = nl < r->end ? nl + 1 : r->end;
if (r->in_block) return (int)len;
}
return -1;
}
/* A node the asker birthed has booted: its parity line, as the boot prints a capsule's. */
static void sk_v4_parity(void *self, unsigned place, v4_cell number, const v4_node *n)
{
static const char hex[] = "0123456789abcdef";
uint64_t h = v4_image_dict_hash(n, &sk_v4_image);
char line[64];
unsigned at = 0, k;
const char *s;
(void)self; (void)place;
for (s = "PARITY:V4_BIRTH node="; *s; s++) line[at++] = *s;
{ char d[20]; unsigned nd = 0; v4_ucell v = (v4_ucell)number; do { d[nd++] = (char)('0' + v % 10u); v /= 10u; } while (v && nd < 20);
while (nd) line[at++] = d[--nd]; }
for (s = " dict_hash=0x"; *s; s++) line[at++] = *s;
for (k = 16; k-- > 0; ) line[at++] = hex[(h >> (4 * k)) & 0xFu];
line[at] = 0;
console_println(line);
}
static int sk_v4_request(void *self, v4_node *n, v4_cell request)
{
(void)self; (void)n;
return v4_manage_serve(&sk_v4_manage, sk_v4_hera_place, request);
}
static const char *const sk_v4_capsules[] = { "v4:centre.4th", "v4:hera.4th" };
static char sk_v4_text[SK_V4_LINE + 1u]; static char sk_v4_text[SK_V4_LINE + 1u];
@@ -170,7 +285,25 @@ void sk_v4_boot(void (*chain)(void), const BootDisk *boot_disk)
{ {
console_println("StarForth v4: one host node, the F18-derived engine"); console_println("StarForth v4: one host node, the F18-derived engine");
sk_v4_image = v4_map_image; sk_v4_image = v4_map_image;
boot.n = &sk_v4_node; boot.es = &sk_v4_es; boot.h = &sk_v4_heat; boot.im = &sk_v4_image; /* the kernel's fabric, Hera at its one place, entered in its registry as node 10 (#189) */
v4_fabric_init(&sk_v4_fabric, sk_v4_places, SK_V4_PLACES);
v4_fabric_gone_error(&sk_v4_fabric, V4_ERROR_NO_ONE);
{
int place = v4_fabric_add(&sk_v4_fabric, &sk_v4_hera, sk_v4_image.port);
if (place < 0) { console_println("StarForth v4: no place for Hera in the fabric"); sk_v4_stop(); }
sk_v4_hera_place = (unsigned)place;
}
sk_v4_manage.f = &sk_v4_fabric; sk_v4_manage.reg = sk_v4_registry; sk_v4_manage.reg_cap = SK_V4_PLACES;
sk_v4_manage.hera = sk_v4_hera_place;
sk_v4_manage.born = sk_v4_born; sk_v4_manage.gone = sk_v4_gone; sk_v4_manage.open = sk_v4_open;
sk_v4_manage.cell = sk_v4_cell; sk_v4_manage.line = sk_v4_line; sk_v4_manage.parity = sk_v4_parity;
sk_v4_manage.self = 0;
if (!v4_manage_enter(&sk_v4_manage, (v4_cell)SK_V4_HERA_NODE, sk_v4_hera_place, 0)) {
console_println("StarForth v4: Hera not entered in the registry"); sk_v4_stop();
}
boot.n = &sk_v4_hera.n; boot.es = &sk_v4_hera.es; boot.h = &sk_v4_hera.h; boot.im = &sk_v4_image;
boot.request = sk_v4_request; boot.request_self = 0;
boot.capsules = sk_v4_capsules; boot.capsule_count = sizeof sk_v4_capsules / sizeof sk_v4_capsules[0];
boot.out = sk_v4_out; boot.key = sk_v4_key; boot.out = sk_v4_out; boot.key = sk_v4_key;
boot.words = sk_v4_words; boot.word_count = sizeof sk_v4_words / sizeof sk_v4_words[0]; boot.serve = sk_v4_serve; boot.words = sk_v4_words; boot.word_count = sizeof sk_v4_words / sizeof sk_v4_words[0]; boot.serve = sk_v4_serve;
blk_subsys_init(sk_v4_post_blk_ram, sizeof(sk_v4_post_blk_ram)); blk_subsys_init(sk_v4_post_blk_ram, sizeof(sk_v4_post_blk_ram));
@@ -178,6 +311,10 @@ void sk_v4_boot(void (*chain)(void), const BootDisk *boot_disk)
console_println("StarForth v4: not started"); console_println("StarForth v4: not started");
sk_v4_stop(); /* #177: halts, not a 100% spin */ sk_v4_stop(); /* #177: halts, not a 100% spin */
} }
/* She is not told her number ((ME) stays 0): the boot's console addresses its lines to node 0,
* and a node takes a message as its own only when its first word is its number (quit.f18). The
* registry knows her as 10; NODE-ME answers her place. (Told 10, she let the console's next line
* go -- the boot stopped at "did not let go of POST's blocks", 2026-11-13.) */
if (chain) { if (chain) {
/* the blocks are other blocks now: what the node has in its two /* the blocks are other blocks now: what the node has in its two
* buffers is POST's, and is let go */ * buffers is POST's, and is let go */
+1
View File
@@ -0,0 +1 @@
# #189 Phase 1 Task 5a: the registry check (qemu_session.py registry()) before Hera is in the kernel's fabric: NODE-ME is an unknown word.
Binary file not shown.
@@ -0,0 +1,53 @@
LOG: logs/20261113-132410/amd64/qemu-amd64-20261113-132410.log
PASS: POST: PASSED
PASS: Boot disk: not a StarshipOS stick (an ISO or ESP boot)
PASS: Zuse: genesis minted onto attached thumbdrive (a fresh data stick)
PASS: the Stadium: its cells allocated
PASS: the Stadium: conservation CONSERVED
PASS: the heartbeat: ticking
PASS: [zuse@Hera] ok>
PASS: 7 ok, and a prompt
FAIL: NODE-ME: Hera is at place 0 of the kernel's fabric
--- serial log, the log's last 40 lines (logs/20261113-132410/amd64/qemu-amd64-20261113-132410.log):
142 | PARITY:V4_F18 name=v4:sys:log.f18 capsule_hash=0xfc6540e6b824a52b
143 | V4: capsule v4:sys:acl.f18 signature: missing (unsigned)
144 | PARITY:V4_F18 name=v4:sys:acl.f18 capsule_hash=0x6845aa048db7eb22
145 | V4: capsule v4:sys:end.f18 signature: missing (unsigned)
146 | PARITY:V4_F18 name=v4:sys:end.f18 capsule_hash=0x5ef01312c07b79cc
147 | PARITY:V4_ROM words=2372 rom_hash=0x87b23d3ae2643c8d instructions=59537530
148 | V4: capsule v4:forth79.4th signature: missing (unsigned)
149 | PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x7d5965f7983485b9
150 | PARITY:V4_POST tests=539 pass=539 fail=0
151 | PARITY:V4_SYSTEM word_count=320 dict_hash=0x47570d4ced6ce255
152 | PARITY:OK
153 | POST: PASSED
154 | PCI: init...
155 | PCI: ECAM mapped (amd64)
156 | virtio-rng: no device on PCI bus 0
157 | rng: backend = rdrand
158 | entropy: ready
159 | virtio-input: no device on PCI bus 0
160 | Starting heartbeat...
161 | APIC Timer: started (LVT=0x00020020 ICR=10052969)
162 |
163 | I/O APIC: keyboard IRQ1 unmasked
164 | Heartbeat running.
165 | Stadium opcodes: 30 resident, hottest opcode 8
166 | Stadium words: promotions=0 evictions=0
167 | Stadium conservation: resident_sum=43673 reservoir=21863 sum=65536 (Q48_ONE=65536)
168 | Stadium conservation: CONSERVED
169 | Stadium quota grant self-test: OK
170 | Hera reservoir=21863
171 | test-vm reservoir=65536
172 | Heartbeat: 4 ticks, trust=0x00010000, variance=0x00000000
173 | blkio_usb: MSC device ready
174 | Artemis: the stick's partition attached
175 | \x1b[38;2;255;165;0m[Hera]\x1b[39m Zuse: genesis minted onto attached thumbdrive
176 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m \x1b[38;2;85;255;255mok> \x1b[39m3 4 + .
177 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m 7 ok
178 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m \x1b[38;2;85;255;255mok> \x1b[39mNODE-ME .
179 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m UNKNOWN WORD: 'NODE-ME'
180 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m ERROR
181 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m \x1b[38;2;85;255;255mok> \x1b[39m
---
RESULT: FAIL
+5
View File
@@ -0,0 +1,5 @@
# Stopped, not a result: #189 Task 5a's first boot, with Hera told '10 (ME) !' after the boot.
# The boot passed POST, then stopped at 'StarForth v4: the node did not let go of POST's blocks':
# the console addresses node 0, and told she was 10 she let its next line go. The boot outlasted
# the tool's 10 minutes and was moved to the background; QEMU was stopped with SIGTERM at once.
# The (ME) line was taken out; every boot command is now under 'timeout 570' (memory rule 6).
Binary file not shown.
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
# #189 Phase 1 Task 5a
../../20261113-132410/amd64: the registry check before -- NODE-ME is an unknown word.
../amd64: the amd64 boot after -- NODE-ME . gives 0 ok; POST 539/539; word_count 375.
test-test32.log: make -C v4 test test-32: 99 programs, 3724180 checks, 0 failures.
../../20261113-135049/v4-checks: scripts/nightly.sh v4-checks: 79 programs under the sanitizers, 0 failures; the hosted builds alike.
Binary file not shown.
+12
View File
@@ -487,6 +487,16 @@ def physics():
check("the heartbeat: ticking", wait_for(r"Heartbeat: [1-9]\d* ticks", 120)) check("the heartbeat: ticking", wait_for(r"Heartbeat: [1-9]\d* ticks", 120))
def registry():
"""Hera in the kernel's fabric and registry (#189, Phase 1 Task 5a): NODE-ME, made by centre.4th,
is answered by the kernel with her place, 0 -- as the host mesh answers it (test_host_unit.c)."""
n = count(PROMPT)
type_line("NODE-ME .")
check("NODE-ME: Hera is at place 0 of the kernel's fabric",
wait_for(PROMPT, 60, n) and re.search(r"(?:^|[^0-9])0\s+ok", text()[-200:]) is not None
and "UNKNOWN WORD" not in text()[-200:])
check("POST: PASSED", wait_for(r"POST: PASSED", 900)) check("POST: PASSED", wait_for(r"POST: PASSED", 900))
m = a.mode m = a.mode
@@ -503,6 +513,7 @@ elif m == "zuse":
n = count(PROMPT) n = count(PROMPT)
type_line("3 4 + .") type_line("3 4 + .")
check("7 ok, and a prompt", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()) is not None) check("7 ok, and a prompt", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()) is not None)
registry()
elif m in ("stick_boot", "two_sticks"): elif m in ("stick_boot", "two_sticks"):
def disks(): def disks():
@@ -575,6 +586,7 @@ elif m in ("genesis", "stick_genesis"):
n = count(PROMPT) n = count(PROMPT)
type_line("3 4 + .") type_line("3 4 + .")
check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None) check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None)
registry()
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0) check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
if m == "genesis": if m == "genesis":
shutdown() shutdown()
+16
View File
@@ -62,6 +62,22 @@ typedef struct {
unsigned word_count; unsigned word_count;
void (*serve)(v4_node *n, unsigned request); void (*serve)(v4_node *n, unsigned request);
/* THE KERNEL'S REGISTRY (MESH.md 9a; #189, ENGINE step 4 Phase 1). A
* request that is not a block's and not one of the kernel's words goes
* to `request`, if there is one: it returns 1 when it served the request
* -- or refused it, raising error 12 on the node itself (v4/manage.h) --
* and 0 when it is not one of its; then the request is error 12, as
* before. 0: none (the hosted product). */
int (*request)(void *self, v4_node *n, v4_cell request);
void *request_self;
/* The kernel's own capsules, by name, loaded after the boot's
* (forth79.4th) and before the system is sealed, so POST runs on them
* and COLD comes back to them: on bare metal, centre.4th and hera.4th
* (#189). 0 of them: the hosted product. */
const char *const *capsules;
unsigned capsule_count;
} v4_boot; } v4_boot;
/* What `key` returns when it has no character. */ /* What `key` returns when it has no character. */
+4
View File
@@ -268,6 +268,8 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep
} else if (b->serve && n->request >= 1 && n->request <= (v4_cell)b->word_count) { } else if (b->serve && n->request >= 1 && n->request <= (v4_cell)b->word_count) {
b->serve(n, (unsigned)n->request); b->serve(n, (unsigned)n->request);
v4_node_port_served(n); v4_node_port_served(n);
} else if (b->request && b->request(b->request_self, n, n->request)) {
v4_node_port_served(n); /* the registry's, served or refused (#189) */
} else { } else {
/* a request no one serves is an error, as any other (D-18): /* a request no one serves is an error, as any other (D-18):
* 12, Argument out of range */ * 12, Argument out of range */
@@ -596,6 +598,8 @@ int v4_boot_run(const v4_boot *b)
for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++) for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++)
if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; } if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
for (i = 0; i < b->capsule_count; i++) /* the kernel's own (#189) */
if (!load_capsule(b, b->capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
/* The kernel's words and the capsules are the system: COLD comes back /* The kernel's words and the capsules are the system: COLD comes back
* to here, and FORGET will not go below it. */ * to here, and FORGET will not go below it. */
v4_image_seal(b->n, b->im); v4_image_seal(b->n, b->im);
+2
View File
@@ -84,6 +84,8 @@ int main(void)
boot.n = &n; boot.es = &es; boot.h = &h; boot.im = &im; boot.n = &n; boot.es = &es; boot.h = &h; boot.im = &im;
boot.out = console_out; boot.key = console_key; boot.out = console_out; boot.key = console_key;
boot.words = kernel_words; boot.word_count = sizeof kernel_words / sizeof kernel_words[0]; boot.serve = serve; boot.words = kernel_words; boot.word_count = sizeof kernel_words / sizeof kernel_words[0]; boot.serve = serve;
boot.request = 0; boot.request_self = 0; /* no registry, and no capsules of its own: the hosted product */
boot.capsules = 0; boot.capsule_count = 0;
sf_time_init(); sf_time_init();
if (blk_subsys_init(block_ram, sizeof block_ram) != BLK_OK) { fputs("V4: no block storage\n", stderr); return 1; } if (blk_subsys_init(block_ram, sizeof block_ram) != BLK_OK) { fputs("V4: no block storage\n", stderr); return 1; }
if (!v4_boot_run(&boot)) { fflush(stdout); return 1; } if (!v4_boot_run(&boot)) { fflush(stdout); return 1; }
+22 -14
View File
@@ -14,8 +14,8 @@ with its stack notation, a description, and where it is defined.
v4/tools/mkdictionary.py md TSV OUT.md the dictionary as Markdown, alphabetical, for the book v4/tools/mkdictionary.py md TSV OUT.md the dictionary as Markdown, alphabetical, for the book
The live word list is the system's own: the hosted v4 (`make -C v4 hosted`) booted and asked The live word list is the system's own: the hosted v4 (`make -C v4 hosted`) booted and asked
`WORDS`, plus the kernel's words of the lone node (`sk_v4_words[]` in kernel/src/v4/sk_v4.c) -- `WORDS` with the kernel's own capsules loaded (`sk_v4_capsules[]`), plus the kernel's words
the words a booted StarshipOS has. The table (docs/reference/v4-dictionary.tsv) holds the stack (`sk_v4_words[]`), both in kernel/src/v4/sk_v4.c -- the words a booted StarshipOS has. The table (docs/reference/v4-dictionary.tsv) holds the stack
notation and the description, which only a person can write; the names are never typed by hand. notation and the description, which only a person can write; the names are never typed by hand.
""" """
import os, re, subprocess, sys import os, re, subprocess, sys
@@ -29,22 +29,30 @@ SOURCES = ([os.path.join("capsules/v4/sys", f) for f in sorted(os.listdir(os.pat
KERNEL_WORDS_SRC = "kernel/src/v4/sk_v4.c" KERNEL_WORDS_SRC = "kernel/src/v4/sk_v4.c"
def kernel_capsules():
"""The kernel's own capsules (sk_v4_capsules[] in kernel/src/v4/sk_v4.c), as files of capsules/v4."""
src = open(os.path.join(ROOT, KERNEL_WORDS_SRC)).read()
m = re.search(r"sk_v4_capsules\[\]\s*=\s*\{([^}]*)\}", src)
return [os.path.join("capsules/v4", n.split(":", 1)[1]) for n in re.findall(r'"([^"]+)"', m.group(1) if m else "")]
def live_words(): def live_words():
"""The hosted system's WORDS, then the kernel's own words not among them.""" """The hosted system's WORDS, with the kernel's own capsules loaded first, as the kernel loads
them (#189: centre.4th, hera.4th); then the kernel's own words not among them."""
if not os.path.exists(HOSTED): if not os.path.exists(HOSTED):
sys.exit(f"mkdictionary: {HOSTED} is missing: make -C v4 hosted") sys.exit(f"mkdictionary: {HOSTED} is missing: make -C v4 hosted")
out = subprocess.run([HOSTED], input="WORDS\n", capture_output=True, text=True, timeout=120).stdout feed = []
lines = out.split("\n") for path in kernel_capsules():
try: for l in open(os.path.join(ROOT, path)):
start = next(k for k, l in enumerate(lines) if l.startswith("ok> ") and l[4:].strip()) if not re.match(r"^Block \d+\s*$", l):
except StopIteration: feed.append(l.rstrip("\n"))
feed.append('." [[WORDS]]" CR WORDS')
out = subprocess.run([HOSTED], input="\n".join(feed) + "\n", capture_output=True, text=True, timeout=120).stdout
if "ERROR" in out.split("[[WORDS]]")[0]:
sys.exit("mkdictionary: a kernel capsule did not load in the hosted system")
if "[[WORDS]]" not in out:
sys.exit("mkdictionary: the hosted system printed no WORDS") sys.exit("mkdictionary: the hosted system printed no WORDS")
got = [] text = out.split("[[WORDS]]", 1)[1].replace("ok>", " ")
for l in lines[start:]:
if l.startswith("ok> ") and got:
break
got.append(l[4:] if l.startswith("ok> ") else l)
text = " ".join(got)
if " ok" in text: if " ok" in text:
text = text[:text.rfind(" ok")] text = text[:text.rfind(" ok")]
words = text.split() words = text.split()