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

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.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:

    #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.