Files
LithosAnanake/docs/v4.0.0/plans/2026-10-06-step6-storage.md
T
rajamesandClaude Opus 5.5 bcfd6556a8 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>
2026-10-07 08:58:52 -04:00

143 lines
12 KiB
Markdown

# Step 6: Storage Implementation Plan (revised 2026-10-07)
> **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:** Every v4 node reads and writes blocks by asking the kernel directly, and the kernel answers from v3's block subsystem (MESH.md acceptance 3 as changed 2026-10-07).
**Architecture:** `BLOCK` and its family keep their two buffers; the one word under them makes a kernel request on port 0 (`ENGINE.md` 3.3): block number and buffer address on the data stack, status left in their place. `v4/system/blocks.c` serves the two requests for any node from v3's one block chain. Nothing is routed and no node is told where storage is.
**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.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.
## Global Constraints
- Work on branch `StarForth-v4.0.0` in the main checkout. No new branch, no stash, no worktree.
- Commit and push after every task. End commit messages with `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
- v4 follows the OS as designed: before building any mechanism, read how v3 does it, and report a departure instead of building it.
- No stubs, no stand-ins. If something cannot be built as intended, stop and report.
- Do not fix anything in v3 that this plan does not name.
- A block is 1024 characters, 256 cells, four characters to a cell, the first lowest; a read leaves the rest of each cell zero and a write takes the low 32 bits.
- The requests: `V4_REQ_BLOCK_READ` is -1 and `V4_REQ_BLOCK_WRITE` is -2, each `( n waddr -- status )`. Status: 0 worked, 2 refused, 3 no such block.
- Node error codes: 13 "Block out of range" (existing), 17 "Storage refused". A block number below 1 is error 13, as it was.
- Before any QEMU run read `.claude/CLAUDE.md` "Running / Acceptance" and the memory note `acceptance-test-rules.md`. One QEMU at a time, in the foreground, `clean` before `qemu`, all three ISAs, logs kept.
- Access (claim, ACL, Stadium touch) is not checked in this step; do not add it.
## Review Focus
1. **A buffer address that is not in the node's memory.** A request whose 256 cells run off the end of memory, or begin below 0. Expected: status 2, no memory touched, the node not harmed. Test in Task 2.
2. **A write that is refused.** `UPDATE` then `SAVE-BUFFERS` on a disk the subsystem will not write. Expected: "Storage refused", the prompt, and the buffer empty so the next `BLOCK` asks again. Test in Task 3.
3. **A request from a node that is not Hera.** An outer node's block request is served; its request for a node (`NODE-BORN`) still is not. Test in Task 5.
4. **Two nodes writing at once.** Expected: every block of both reaches storage. Test in Task 5.
5. **A request number that is neither a block request nor a named word.** Expected: error 12 on the node, as today. Existing test; must still pass.
---
### Task 1: v3's block subsystem — done (`0e761cb1`)
`blk_subsys_init` takes no `VM`; the state is a chain selected through a pointer. Accepted on three v3-configuration boots.
- [ ] **Step 1 (awaits Captain Bob):** with no second chain wanted any more, `blk_chain_new` and `blk_chain_select` serve nothing. Either they are taken out again, leaving only the `VM` argument's removal, with three v3-configuration boots to accept it; or they stay, unused. Do what he rules.
---
### Task 2: The kernel serves block requests
**Files:**
- Delete: `v4/include/v4/storage.h`, `v4/system/storage.c`, `v4/system/store_v3.c`, `v4/tests/test_store.c`; the four `V4_MSG_BLOCK_*` types and `V4_STORE_*` in `v4/include/v4/message.h`
- Create: `v4/include/v4/blocks.h`, `v4/system/blocks.c`, `v4/tests/test_blocks.c`
- Modify: `v4/Makefile` (the storage rules become: v3's block objects and `blocks.c`, built with v3's flags, linked into tests named `test_blocks*.c` and `test_host_*.c`)
**Interfaces:**
- Produces, in `blocks.h`:
```c
#define V4_REQ_BLOCK_READ (-1)
#define V4_REQ_BLOCK_WRITE (-2)
#define V4_BLOCK_OK 0
#define V4_BLOCK_REFUSED 2
#define V4_BLOCK_RANGE 3
/* If `request` is a block request, serve it -- ( n waddr -- status ) on the
* node's data stack -- and return 1; otherwise touch nothing and return 0. */
int v4_blocks_serve(v4_node *n, v4_cell request);
```
- [ ] **Step 1: Write the failing test** `test_blocks.c`: one chain (2080 KiB of RAM and a raw device of 64 blocks) and a bare `v4_node`. Push a number and an address, call `v4_blocks_serve`, pop the status. Checks: a write then a read of block 2050 gives the same 256 cells back with the high bits of each cell zero; block 20 is the fast RAM; block 0, a negative number, 2112 and (at 64 bits) 4294967296 are status 3; an address whose 256 cells run past `V4_NODE_WORDS`, and a negative address, are status 2 with memory unchanged (Review Focus 1); a block written by v3's own `blk_get_buffer`/`blk_update`/`blk_flush` reads back; a request of 5 returns 0 and leaves the stack as it was.
- [ ] **Step 2: Run** `make -C v4 run-64-test_blocks.c`: fails to build, `v4/blocks.h` does not exist.
- [ ] **Step 3: Write `blocks.c`.** It includes `v4/blocks.h`, `v4/node.h` and v3's `block_subsystem.h`. For a read: pop the address and the number; refuse a bad address with `v4_node_addr_ok` on the first and last cell; `blk_is_valid`, then `blk_get_buffer(n, 0)`, and each cell is its four bytes, first lowest. For a write: `blk_get_buffer(n, 1)`, copy the low four bytes of each cell, `blk_update`, `blk_flush`; any of those failing is `V4_BLOCK_REFUSED`. A number below 1 or above 0xFFFFFFFF is `V4_BLOCK_RANGE` without calling v3.
- [ ] **Step 4: Remove the message device** and its test, types and Makefile rules.
- [ ] **Step 5: Run** `make -C v4 run-32-test_blocks.c run-64-test_blocks.c` and the two sanitizer targets: 0 failures.
- [ ] **Step 6: Commit** `feat(v4.0.0): the kernel serves block requests from v3's block chain; the message device is withdrawn` and push.
---
### Task 3: The node asks its kernel
**Files:**
- Modify: `v4/capsule/blocks.v4` (header comment, `(DEVICE)`; `(FIND-BUF)` and `LOAD` get their sign test back), `v4/capsule/core.v4` (error list), `v4/capsule/quit.v4` (message 17; `STORAGE` and message 18 removed)
- Modify: `v4/tests/host_map.h` (`(STORE)` and `(B-TO)` go; the two request numbers come in as constants), `v4/tools/mkimage.c`
- Modify: `v4/tests/test_host_quit.c` (its `kernel_serve` calls `v4_blocks_serve` first; the storage port, its routes and the private-drive cases go)
- Already done in the tree and kept: the four registers removed from `node.h`, `node.c`, `image.h`, `image.c`, `mkimage.c`, `test_node.c`
- [ ] **Step 1: Change the tests first.** In `test_host_quit.c` keep `disk` as the chain's fast RAM, the range checks at 2112 and 3000, and the refused-write case, now on a blank RAM-backed disk of 2048 blocks attached to the one chain after the raw device and never confirmed (its first block is 2112). Remove the cases for no storage, a private drive and a number 32 bits do not hold being reached; `-1 BLOCK` is "Block out of range" at both widths again.
- [ ] **Step 2: Run** `make -C v4 run-64-test_host_quit.c`: fails, the nucleus still asks by message.
- [ ] **Step 3: `(DEVICE)`:**
```
\ ( command i -- ) read buffer i's block from storage, command 1, or
\ write it, command 2: a request of this node's kernel ( n waddr -- status ).
: (DEVICE)
dup push SWAP push \ i R: i command
dup (B) + a! @ SWAP (BUF) \ n waddr
pop -1 + if RD drop BLOCK-WRITE# jump ASK
RD: drop BLOCK-READ#
ASK: (PORT) b! !b \ status
pop SWAP \ i status
if FINE
push (B) + a! 0 !+ 0 ! pop \ nothing is in the buffer
-2 + if E17 drop NODE-ERROR b! 13 !b ;
E17: drop NODE-ERROR b! 17 !b ;
FINE: drop drop ;
```
to be proven by Step 1's tests. Remove `(B-ASK)`, `(B-DATA)`, `(B-AWAIT)`, `(B-END)` and `STORAGE`.
- [ ] **Step 4: Run** `make -C v4 run-32-test_host_quit.c run-64-test_host_quit.c run-64-test_node.c`: 0 failures. Go straight on; Tasks 3, 4 and 5 commit together.
---
### Task 4: The lone node
**Files:** `v4/include/v4/boot.h`, `v4/system/boot.c`, `v4/tools/hosted.c`, `v4/Makefile`
- [ ] **Step 1:** In `boot.c` a request on the kernel port is offered to `v4_blocks_serve` before the host's named words. Remove the storage port, `STORAGE_TOLD` and the `storage` member of `v4_boot`. `v4_boot_run(const v4_boot *b)` keeps its one argument.
- [ ] **Step 2:** `hosted.c` calls `sf_time_init` and `blk_subsys_init` on 2080 KiB of static RAM, as hosted v3 does with no disk, and nothing else.
- [ ] **Step 3: Run** `make -C v4 hosted-check`: `POST: PASSED`, 538 of 538, on the three ISAs.
---
### Task 5: The unit of five
**Files:** `v4/tests/test_host_unit.c`, `capsules/v4/hera.4th`
- [ ] **Step 1: Write the failing checks.** Hera's `BIRTH` no longer sends POST: after `10 UNIT`, exactly one POST tally has been seen, Hera's, and the four parities and their one dictionary hash are as before. Node 12 writes block 2100 and Hera, 11, 13 and 14 each read it. An outer node asked to do `NODE-BORN` is refused, as before (Review Focus 3). Hera sets node 12 writing ten blocks and writes ten of her own meanwhile, and all twenty reach the chain (Review Focus 4).
- [ ] **Step 2: Run** and see them fail. Then: in `host_born`, every node born gets the kernel on its port 0 — a device whose `take` serves `v4_blocks_serve` for that node and refuses everything else; Hera's existing kernel device does the same before it serves her own requests. Remove the two message devices, the storage routes and `STORAGE` lines. In `hera.4th`, `BIRTH` sends `v4:forth79.4th` and not `v4:post79.4th`; keep every block to 16 lines of 64 characters and run `build/tools/mkcapsule --lint capsules/`.
- [ ] **Step 3: Run** `make -C v4 run-64-test_host_unit.c`: 0 failures. Then `make -C v4 -k test`, `make -C v4 sanitize`, `make -C v4 hosted-check`.
- [ ] **Step 4: Commit** Tasks 3, 4 and 5: `feat(v4.0.0): every node asks the kernel for its blocks; a born node is not POSTed` and push.
---
### Task 6: Bare metal
**Files:** `kernel/src/kernel_main.c:518-523,620-668`, `kernel/src/v4/sk_v4.c`, `kernel/Makefile:616-624`
- [ ] **Step 1:** Read `kernel_main.c:600-700` and list what the chain's setup needs that has not happened by line 523. Have the chain set up before the v4 node starts, leaving the v3 path's order of events as it is. If something it needs cannot be had by then, report and stop.
- [ ] **Step 2:** `sk_v4.c` loses `sk_v4_disk`; `v4/system/blocks.c` is linked.
- [ ] **Step 3: Acceptance.** The three bare-metal boots. Each log: `PARITY:V4_POST tests=538 pass=538 fail=0`, `POST: PASSED`, the typed session of the previous step, and `2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ .` printing `65`.
- [ ] **Step 4: Commit** with the logs: `feat(v4.0.0): bare metal -- the node's blocks are the kernel's block chain` and push.
---
### Task 7: Write it up
- [ ] **Step 1:** MESH.md step 6 as done, in the manner of step 5; `v4/README.md`; `V3-PARITY.md` 1d (what goes through v3's subsystem now, what is still skipped). Under "not as intended yet": access not checked; POST is still a capsule Hera loads until the kernel holds its cases.
- [ ] **Step 2: Commit** `docs(v4.0.0): storage -- step 6 done` and push.