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
+142 -5
View File
@@ -32,7 +32,13 @@
#include "starkernel/arch.h"
#include "starkernel/timer.h"
#include "v4/boot.h"
#include "v4/fabric.h"
#include "v4/manage.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
#include "starkernel/vm/stadium.h"
#include "starkernel/vm/stadium_ops.h"
@@ -44,10 +50,119 @@
* console's own "[zuse@Hera] " line prefix. */
#define SK_V4_PROMPT "\x1b[38;2;85;255;255mok> \x1b[39m"
static v4_node sk_v4_node;
static v4_exec_state sk_v4_es;
static v4_heat sk_v4_heat;
static v4_image sk_v4_image;
/* THE KERNEL'S FABRIC (#189, ENGINE step 4 Phase 1, Task 5a; Captain Bob, 2026-11-13: "Hera as now,
* in a fabric"). Hera -- node 10, the one the kernel brings up -- is booted as before (v4_boot_run)
* and is the node at place 0 of the kernel's fabric, entered in its registry (v4/manage.h,
* 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];
@@ -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");
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.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));
@@ -178,6 +311,10 @@ void sk_v4_boot(void (*chain)(void), const BootDisk *boot_disk)
console_println("StarForth v4: not started");
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) {
/* the blocks are other blocks now: what the node has in its two
* buffers is POST's, and is let go */