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>
12 KiB
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.0in 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_READis -1 andV4_REQ_BLOCK_WRITEis -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 noteacceptance-test-rules.md. One QEMU at a time, in the foreground,cleanbeforeqemu, all three ISAs, logs kept. - Access (claim, ACL, Stadium touch) is not checked in this step; do not add it.
Review Focus
- 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.
- A write that is refused.
UPDATEthenSAVE-BUFFERSon a disk the subsystem will not write. Expected: "Storage refused", the prompt, and the buffer empty so the nextBLOCKasks again. Test in Task 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. - Two nodes writing at once. Expected: every block of both reaches storage. Test in Task 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_newandblk_chain_selectserve nothing. Either they are taken out again, leaving only theVMargument'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 fourV4_MSG_BLOCK_*types andV4_STORE_*inv4/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 andblocks.c, built with v3's flags, linked into tests namedtest_blocks*.candtest_host_*.c)
Interfaces:
-
Produces, in
blocks.h:#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 barev4_node. Push a number and an address, callv4_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 pastV4_NODE_WORDS, and a negative address, are status 2 with memory unchanged (Review Focus 1); a block written by v3's ownblk_get_buffer/blk_update/blk_flushreads 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.hdoes not exist. -
Step 3: Write
blocks.c. It includesv4/blocks.h,v4/node.hand v3'sblock_subsystem.h. For a read: pop the address and the number; refuse a bad address withv4_node_addr_okon the first and last cell;blk_is_valid, thenblk_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 isV4_BLOCK_REFUSED. A number below 1 or above 0xFFFFFFFF isV4_BLOCK_RANGEwithout 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.cand 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 withdrawnand push.
Task 3: The node asks its kernel
Files:
-
Modify:
v4/capsule/blocks.v4(header comment,(DEVICE);(FIND-BUF)andLOADget their sign test back),v4/capsule/core.v4(error list),v4/capsule/quit.v4(message 17;STORAGEand 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(itskernel_servecallsv4_blocks_servefirst; 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.ckeepdiskas 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 BLOCKis "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)andSTORAGE. -
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.ca request on the kernel port is offered tov4_blocks_servebefore the host's named words. Remove the storage port,STORAGE_TOLDand thestoragemember ofv4_boot.v4_boot_run(const v4_boot *b)keeps its one argument. - Step 2:
hosted.ccallssf_time_initandblk_subsys_initon 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
BIRTHno longer sends POST: after10 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 doNODE-BORNis 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 whosetakeservesv4_blocks_servefor 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 andSTORAGElines. Inhera.4th,BIRTHsendsv4:forth79.4thand notv4:post79.4th; keep every block to 16 lines of 64 characters and runbuild/tools/mkcapsule --lint capsules/. - Step 3: Run
make -C v4 run-64-test_host_unit.c: 0 failures. Thenmake -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 POSTedand 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-700and 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.closessk_v4_disk;v4/system/blocks.cis 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, and2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ .printing65. - Step 4: Commit with the logs:
feat(v4.0.0): bare metal -- the node's blocks are the kernel's block chainand 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.md1d (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 doneand push.