feat(v4.0.0): every node asks the kernel for its blocks; a born node is not POSTed

A block is a kernel request, as ENGINE.md 3.3 has it: the node puts the
block's number and the address of 256 cells on its stack and writes the
request to port 0, and the kernel leaves the status there.  The requests
are -1, read, and -2, write, the same for every node.  v4/system/blocks.c
serves them from the kernel's block subsystem, which is v3's.  The four
storage registers are gone from the engine.

The device that spoke block messages (4a505a15) is withdrawn with its
test and its message types: Captain Bob ruled on 2026-10-07 that it, a
node's own drive, and nodes with no storage had left the OS as designed
(docs/v4.0.0/MESH.md 8.5).

Hera no longer sends POST to the nodes she births: POST is the kernel's,
once.  Every node has its kernel on port 0; it serves a node's blocks and,
for Hera alone, her requests for nodes and capsules.

Bare metal: the node boots and is POSTed against POST's own block RAM,
and the kernel's chain -- fast RAM, the ramdrive, the virtio disk -- is
set up after POST and before the prompt, as on the v3 path.  The disk is
read and not written: nothing in v4 yet gives the owner's word that it
may be formatted.  A hosted program has the chain's fast RAM, as hosted
v3 has with no disk.  Error 17 is Storage refused.

make -C v4 test and sanitize pass at both widths; hosted-check passes on
three ISAs; amd64, aarch64 and riscv64 boot, POST 538 of 538, with the
typed session: logs/20261007-081603, -081839, -082226.  The hashes are
the same on all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 08:24:47 -04:00
co-authored by Claude Opus 5.5
parent 26a0748455
commit d5b7235464
34 changed files with 953 additions and 725 deletions
-64
View File
@@ -195,69 +195,6 @@ static void test_out_of_range_word_address_touches_nothing(void)
free(n);
}
/* D-19: the block storage device. Four registers; a store to the third
* reads a block into 256 cells of memory or writes it from them. */
static void test_storage_device(void)
{
static unsigned char blocks[4 * V4_BLOCK_BYTES];
const v4_cell REG = 40, BUF = 100;
v4_node *n = malloc(sizeof *n);
unsigned i;
int ok;
if (!n) { CHECK(0, "malloc"); return; }
v4_node_reset(n);
CHECK(n->storage_reg == -1, "a fresh node has no storage");
for (i = 0; i < sizeof blocks; i++) blocks[i] = (unsigned char)(i * 7u + (i >> 10));
v4_node_storage_attach(n, REG, blocks, 4);
/* read block 2 */
n->mem[BUF - 1] = 111; n->mem[BUF + 256] = 222;
v4_node_store(n, REG, 2); v4_node_store(n, REG + 1, BUF);
n->mem[REG + 3] = 55;
v4_node_store(n, REG + 2, 1);
CHECK(n->mem[REG + 3] == 0, "a read that works leaves status 0");
for (ok = 1, i = 0; i < V4_BLOCK_CELLS; i++) {
const unsigned char *b = blocks + 2 * V4_BLOCK_BYTES + 4u * i;
v4_ucell want = (v4_ucell)b[0] | ((v4_ucell)b[1] << 8) | ((v4_ucell)b[2] << 16) | ((v4_ucell)b[3] << 24);
if ((v4_ucell)n->mem[BUF + (v4_cell)i] != want) ok = 0;
}
CHECK(ok, "the block is in memory, four bytes to a cell, the first lowest, the rest of the cell zero");
CHECK(n->mem[BUF - 1] == 111 && n->mem[BUF + 256] == 222, "and only 256 cells were written");
CHECK(n->mem[REG] == 2 && n->mem[REG + 1] == BUF && n->mem[REG + 2] == 1, "the registers are memory words");
/* write it to block 0, changed */
n->mem[BUF] = (v4_cell)0x44332211; n->mem[BUF + 255] = (v4_cell)(v4_ucell)0xAABBCCDDu;
v4_node_store(n, REG, 0);
v4_node_store(n, REG + 2, 2);
CHECK(n->mem[REG + 3] == 0 && blocks[0] == 0x11 && blocks[1] == 0x22 && blocks[2] == 0x33 && blocks[3] == 0x44
&& blocks[1020] == 0xDD && blocks[1023] == 0xAA, "a write puts the low four bytes of each cell on the device");
CHECK(memcmp(blocks + 4, blocks + 2 * V4_BLOCK_BYTES + 4, 1016) == 0, "the rest of the block as it was read");
CHECK(blocks[V4_BLOCK_BYTES] == (unsigned char)(1024u * 7u + 1u), "and the next block untouched");
/* what does not work: status -1, nothing moved */
v4_node_store(n, REG, 4); v4_node_store(n, REG + 2, 1);
CHECK(n->mem[REG + 3] == -1, "a block past the last");
v4_node_store(n, REG, -1); v4_node_store(n, REG + 2, 1);
CHECK(n->mem[REG + 3] == -1, "a negative block");
v4_node_store(n, REG, 1); v4_node_store(n, REG + 1, (v4_cell)V4_NODE_WORDS - 255); v4_node_store(n, REG + 2, 1);
CHECK(n->mem[REG + 3] == -1, "cells that run past the end of memory");
v4_node_store(n, REG + 1, -4); v4_node_store(n, REG + 2, 2);
CHECK(n->mem[REG + 3] == -1, "a negative address");
v4_node_store(n, REG + 1, BUF); v4_node_store(n, REG + 2, 3);
CHECK(n->mem[REG + 3] == -1, "an unknown command");
v4_node_store(n, REG + 1, (v4_cell)V4_NODE_WORDS - 256); v4_node_store(n, REG + 2, 1);
CHECK(n->mem[REG + 3] == 0 && v4_node_guards_intact(n), "the last 256 cells of memory are usable");
/* detached: plain memory */
v4_node_storage_attach(n, -1, 0, 0);
n->mem[REG + 3] = 9; v4_node_store(n, REG + 2, 1);
CHECK(n->mem[REG + 3] == 9 && n->mem[REG + 2] == 1, "detached, the registers are memory");
v4_node_storage_attach(n, REG, blocks, 4);
v4_node_reset(n);
CHECK(n->storage_reg == -1, "reset detaches the device");
free(n);
}
static void test_ends_are_distinguishable(void)
{
/* The two boundaries carry different patterns precisely so that a failure
@@ -317,7 +254,6 @@ int main(void)
test_linear_overrun_low_is_caught();
test_linear_overrun_high_is_caught();
test_out_of_range_word_address_touches_nothing();
test_storage_device();
test_ends_are_distinguishable();
test_workload_never_false_alarms();