fix(v4.0.0): storage -- what the review of step 6 found

A block request with fewer than two values on the stack is refused; it
had acted on whatever the stack ring held and stopped the node.

Bare metal: when the kernel's chain takes the place of POST's block RAM
the node's two buffers are emptied, so it no longer holds POST's copy of
a block; and the chain's fast RAM is cleared, so a node cannot read what
was in the kernel's heap.

blocks.c is built with each test under that test's own warnings and
sanitizers; it had been left out of both.  The hosted link cleans its
object directory first: it had linked the withdrawn store_v3.o left there
from the day before.

node.h and DECOMPOSITION.md D-19 no longer describe the message device or
the four registers as current.  MESH.md 8.5 records two findings for
ruling: a node's own copy of a block, and a block read over a node's code.

From a clean build: make -C v4 test, sanitize and hosted-check pass;
amd64, aarch64 and riscv64 boot, POST 538 of 538, same hashes, blocks 1
and 2047 clean at the prompt: logs/20261007-085017, -085254, -085636.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 08:58:52 -04:00
co-authored by Claude Opus 5.5
parent 6d90375da7
commit bcfd6556a8
15 changed files with 543 additions and 33 deletions
+2 -2
View File
@@ -180,7 +180,7 @@ definition below depends on one, it says so.
| **D-16** | Stack overflow and underflow; `DEPTH`, `PICK`, `ROLL` (ruled 2026-10-04; revises D-2). | **Guarded: a stack fault.** Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a `T` or `S` it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: `Stack overflow`, `Stack underflow`, `Return stack overflow`, `Return stack underflow`, then ` ERROR` and the prompt. **Every fault, D-14's included, empties both stacks**: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: `DROP: Stack underflow`.) The fault handler is a table of five words, one per kind, each a jump (`(FAULTS)` in `v4/capsule/quit.v4`). Two registers (§7) are all a programme sees of the stacks: `DSTACK-DEPTH` and `RSTACK-DEPTH` read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so `SP@` and `SP!` stay retired; `.S` waits for number output to reach the capsule. The sizes were unchanged by this ruling, 10 and 9 (D-17 then deepens the host node's): one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): `tests/test_exec.c` for every opcode at every depth of both stacks, `tests/test_host_quit.c` from the prompt. |
| **D-17** | Stack sizes on the host node (2026-10-04, following D-16). | **32 values and 32 return entries on the host node; a mesh node keeps the F18's 10 and 9.** Once the stacks are counted (D-16) their size is a parameter of the node, like its memory, and the host node is the one that runs the interpreter and the compiler underneath the user's programme: at 10 and 9 the prompt left a programme about six values, and `/` could be used only four words deep. The mechanism is the same at both sizes — top registers over a ring — and so is every word's definition. v3's stacks are deeper still. In the golden model the sizes are `V4_DATA_RING` and `V4_RET_RING` (`stack.h`), set for the host-node tests in `v4/Makefile`. |
| **D-18** | Every other error (ruled 2026-10-04: "guard all errors"). | **A word that finds an error raises it, and the line ends there.** The word takes its own arguments off the stack and stores an error code in `NODE-ERROR` (§7). On a node with a prompt that store is a trap, a sixth kind of fault beside D-14's and D-16's: nothing after it executes, the return stack is emptied, and the prompt prints the code's message, ends any definition that was open, prints ` ERROR` and waits for the next line. Unlike the other faults it leaves the data stack as the word left it. The codes: 1 `Negative count` (`CMOVE`, `TYPE`), 2 `Not a number` (`NUMBER`), 3 `Number too long` (`HOLD` into a full buffer), 4 `Not a character` (`HOLD`), 5 `Dictionary full` (`,` `C,` `ALLOT`, any defining word), 6 `Name missing` (a defining word with nothing after it), 7 `Control structure mismatch`, 8 `Control structures too deep`, 9 `Shift count out of range`, 10 `Protected word`, 11 `Division by zero` (`Q./`), 12 `Argument out of range`, 13 `Block out of range`, 14 `No block is being loaded`, 15 `Deferred word not set`, 16 `Not a word`; −1 means the word printed its own message (`UNKNOWN WORD: 'xxx'`, `xxx: compile-only`). Before this ruling these set the flag and the line ran on to its end. v3 stops at once too; its messages name the word (`LOOP: missing DO`) where v4's name the fault. With the trap not attached `NODE-ERROR` is plain memory and the word returns, which is how the words are tested below the prompt. D-11, D-12 and D-13 said "set `NODE-ERROR`"; on a node with a prompt that now means this. Executed on the golden model (2026-10-04): `tests/test_exec.c`, `tests/test_host_quit.c`. |
| **D-19** | Block storage on the golden model (2026-10-05). | **Four memory-mapped registers on the host node, until the mesh carries the storage service.** `BLOCK-NUMBER`, `BLOCK-ADDRESS` (the word address of 256 cells), `BLOCK-COMMAND` (a store of 1 reads the block into those cells, 2 writes it from them) and `BLOCK-STATUS` (0 when the command worked, −1 for a block the device has not got or cells not all in memory). A block is 1024 bytes: 256 cells, four bytes to a cell, the first byte lowest, at either cell width. This is the console's arrangement (§7) applied to storage; like it, it is the model's stand-in and not the mesh protocol, which §5.11 still leaves to the device node. |
| **D-19** | Block storage on the golden model (2026-10-05). **Replaced 2026-10-07:** the four registers are gone; a node asks its kernel for a block by a request on port 0 (`MESH.md` section 8, `v4/include/v4/blocks.h`). What follows is the decision as it was. | **Four memory-mapped registers on the host node, until the mesh carries the storage service.** `BLOCK-NUMBER`, `BLOCK-ADDRESS` (the word address of 256 cells), `BLOCK-COMMAND` (a store of 1 reads the block into those cells, 2 writes it from them) and `BLOCK-STATUS` (0 when the command worked, −1 for a block the device has not got or cells not all in memory). A block is 1024 bytes: 256 cells, four bytes to a cell, the first byte lowest, at either cell width. This is the console's arrangement (§7) applied to storage; like it, it is the model's stand-in and not the mesh protocol, which §5.11 still leaves to the device node. |
**Consequences of D-2 that every definition must respect.** The data stack holds 10 items and the
return stack 9, and every `call`, `FOR`, `DO` loop frame and `push` uses return-stack slots. Nesting
@@ -1105,4 +1105,4 @@ Addresses are assigned in the node memory map (D-4). Names only here.
| `CONSOLE-STATUS` | R | Console receive status: a fetch gives −1 when a character is pending and 0 when not, and changes nothing. `?TERMINAL` reads it, and `KEY` polls it. On the mesh it is the console port's bit of `PORT-STATUS`. |
| `DSTACK-DEPTH` | R/W | A fetch gives how many values the data stack holds, the fetch's own push not counted. A store empties the data stack; the value stored is taken off first and ignored. `DEPTH` reads it; `ABORT` stores to it (D-16). |
| `RSTACK-DEPTH` | R/W | The same for the return stack. `QUIT`, `ABORT` and the error exits store to it before they call anything. |
| `BLOCK-NUMBER` `BLOCK-ADDRESS` `BLOCK-COMMAND` `BLOCK-STATUS` | R/W | The block storage device on the golden model's host node (D-19). |
| ~~`BLOCK-NUMBER` `BLOCK-ADDRESS` `BLOCK-COMMAND` `BLOCK-STATUS`~~ | | Removed 2026-10-07 (D-19): blocks are a kernel request, not registers. |
+34 -5
View File
@@ -276,7 +276,7 @@ kernel-Hermes's work in v3 and is not decided for the mesh.
**Ruled 2026-10-06 and 2026-10-07** (Captain Bob). On 2026-10-07 he found
this section to have left the OS as designed, and brought it back: every
node asks the kernel directly, as every v3 VM does. What that withdrew is
in 8.5, so that it is not proposed again.
in 8.6, so that it is not proposed again.
### 8.1 The rulings that stand
@@ -376,7 +376,24 @@ block number not to exist, and on the real chain it does.
step 6a.
- **Cloud stores and real USB drives** wait for their drivers.
### 8.5 Withdrawn 2026-10-07
### 8.5 Open, for ruling
Found by review of step 6 on 2026-10-07; nothing is built for either.
- **A node keeps its own copy of a block.** `BLOCK` gives the address of
one of the node's two buffers, and the kernel is asked only when a block
is not in one. In v3 a VM's `BLOCK` gives the kernel's buffer, so every
VM sees one copy. Here, if node A has block *n* in a buffer and node B
writes it, A goes on reading what it had; and if A then does `UPDATE` and
`SAVE-BUFFERS`, all of B's block is overwritten. One node alone cannot
see this, and the unit test's readers had never held the block they
read.
- **A block read over the node's own code.** The kernel checks that the
256 cells are in the node's memory and nothing more, so a request made
by hand with an address in the nucleus overwrites it and the node stops.
`!` can do the same; whether the kernel should refuse it is not ruled.
### 8.6 Withdrawn 2026-10-07
Each of these was ruled on 2026-10-06 or stood in this document, and each
left v3's design. Captain Bob: "something is really off. it sounds like a
@@ -595,7 +612,7 @@ Each is tested, committed and pushed before the next.
above is step 5 as it was built.
6. **Storage (section 8): every node asks the kernel for its blocks.**
**Done 2026-10-07.** It was first built another way and brought back;
8.5 says what was withdrawn and why.
8.6 says what was withdrawn and why.
- *The requests* (`v4/include/v4/blocks.h`, `v4/system/blocks.c`). -1
reads a block and -2 writes one, `( n waddr -- status )`, for any
node, from the kernel's block subsystem. `v4/tests/test_blocks.c`: 22
@@ -624,8 +641,20 @@ Each is tested, committed and pushed before the next.
boots, POST 538 of 538, and typed at the prompt: block 2100 written
and read back, block 3072 read from the disk, a write to it refused,
a block that is not there. `logs/20261007-081603` (amd64), `-081839`
(aarch64), `-082226` (riscv64). The parity hashes are the same on the
(aarch64), `-082226` (riscv64); and again after the review's changes
below, with blocks 1 and 2047 read clean at the prompt:
`logs/20261007-085017`, `-085254`, `-085636`. The parity hashes are the same on the
three hosted and the three bare-metal systems.
- *Review, 2026-10-07.* One reading of the whole step by a fresh
reviewer; no critical defect. Changed after it: a block request with
fewer than two values on the stack is refused (it had stopped the
node); on bare metal the node's two buffers are emptied when the
chain takes the place of POST's block RAM (it had gone on holding
POST's block 1), and the chain's fast RAM is cleared in the v4 path;
`blocks.c` is built under each test's own warnings and sanitizers;
the hosted link no longer takes whatever objects lie in its
directory (it had linked the withdrawn `store_v3.o`). Two findings
are for ruling, section 8.5.
- **Not as intended yet.**
- Who may have which block is not checked (8.4).
- On bare metal the virtio disk is read and not written. v3's
@@ -637,7 +666,7 @@ Each is tested, committed and pushed before the next.
- The suite's unit test is still not run at 32 bits.
- **Seen and not changed.** On the v3 path the chain's fast RAM comes
from `kmalloc` and is not cleared (`kernel_main.c`, where the chain is
set up); only the ramdrive is. The v4 path does as v3 does.
set up); only the ramdrive is. The v4 path clears its own.
**6a. Storage that changes while running.** Rulings 6 and 8 of
section 8.1: chains of several devices, the device and chain
+1 -1
View File
@@ -194,7 +194,7 @@ space and the metadata are v3's. Still skipped: the owner and first-touch
claim, the ACL, and the Stadium touch, which need the node's identity. A
first build of that step had block requests passed from node to node and
nodes with no storage; Captain Bob withdrew it as a divergence from this
ruling (`MESH.md` 8.5).
ruling (`MESH.md` 8.6).
## 1e. Messaging (discussed and ruled 2026-10-05)
@@ -8,7 +8,7 @@
**Tech Stack:** C99 (engine, strict flags), v3's `block_subsystem.c` and `blkio_*.c` (gnu99), the v4 nucleus dialect (`v4/capsule/*.v4`), `make -C v4`, `make -f kernel/Makefile`.
**Spec:** `docs/v4.0.0/MESH.md` section 8 (8.1 to 8.4; 8.5 lists what was withdrawn), section 9's ruling of 2026-10-07, and step 6 in section 10. `docs/v4.0.0/ENGINE.md` 3.3. `docs/v4.0.0/V3-PARITY.md` 1d.
**Spec:** `docs/v4.0.0/MESH.md` section 8 (8.1 to 8.4; 8.6 lists what was withdrawn), section 9's ruling of 2026-10-07, and step 6 in section 10. `docs/v4.0.0/ENGINE.md` 3.3. `docs/v4.0.0/V3-PARITY.md` 1d.
**History:** the first version of this plan built storage as a device speaking messages, with private drives and nodes that had none. Tasks 1 and 2 of it were committed (`0e761cb1`, `4a505a15`) and Tasks 3 and 4 were working in the tree when Captain Bob ruled, on 2026-10-07, that it had left the OS as designed. This version keeps what still serves and removes the rest.