diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index b10fefa1..7ce9c276 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -348,7 +348,7 @@ Four types, beside text, output and done (section 6): | Block read | the block number, one word | Block data, or Block done with an error | | Block write | the block number, one word; a Block data message follows it | Block done | | Block data | 1024 characters | | -| Block done | one word: it worked, there is no storage, or it was refused | | +| Block done | one word: 0 it worked, 1 there is no storage, 2 it was refused, 3 there is no such block | | A write is two messages because a block is exactly the most a message carries, so the number cannot go with the data. Whatever answers pairs the @@ -368,20 +368,28 @@ block. The engine (`v4/src`) knows nothing of it. - Blocks 0 to 2047 are the chain's fast RAM, as in v3; nodes that share the common store share those too. - The device holds a **view**: the common chain, or a private chain, or - both. A number at the top of the space (ruling 9) is the private - chain's; any other is the common chain's; a number in a chain the view - does not have is "no storage". + both. Private block *k* is the private chain's block 2048 + *k*, its + first on a device. A number is the private chain's when *k* is less than + the blocks that chain has on devices; any other is the common chain's, + and one past its end is "no such block". Numbers below 2048 are the + common chain's fast RAM, or the private chain's if the view has no + common chain. A number that belongs to no chain the view has is "no + storage". +- Whatever is on a storage port has a number, as a node has, and is sent + to and answers like one. Whoever wires a node tells it that number + (`STORAGE`) and the way to it (`ROUTE`). A node not told has no storage. +- A block number is not signed: on 32-bit cells the numbers from the top + down are the negative ones. Only 0 is no block. **A change to v3's C.** Its block subsystem is one global chain. For a second chain its state becomes something there can be two of. The change is mechanical and the one chain v3 uses behaves as it did. (Approved.) -Found 2026-10-06 and not yet ruled: `blk_subsys_init` must be given a v3 -`VM`, and refuses without one, though the subsystem stores it and never -uses it (`block_subsystem.c` line 651 is its only mention). The hosted v4 -system has no v3 `VM` to give. The same change would have to take that -argument away, which also changes the kernel's one call of it -(`kernel/src/capsule/capsule_loader.c`). +`blk_subsys_init` must be given a v3 `VM`, and refuses without one, though +the subsystem stores it and never uses it (`block_subsystem.c` line 651 is +its only mention). The hosted v4 system has no v3 `VM` to give. **Ruled +2026-10-06:** the argument is removed, in the same change; the kernel's +call of it (`kernel/src/capsule/capsule_loader.c`) changes with it. **On bare metal** the lone node's storage port fronts the kernel's real chain — RAM, the ramdrive and the virtio disk — in place of the RAM array diff --git a/docs/v4.0.0/plans/2026-10-06-step6-storage.md b/docs/v4.0.0/plans/2026-10-06-step6-storage.md new file mode 100644 index 00000000..0708fa60 --- /dev/null +++ b/docs/v4.0.0/plans/2026-10-06-step6-storage.md @@ -0,0 +1,359 @@ +# Step 6: Storage Implementation Plan + +> **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:** A v4 node reads and writes blocks by sending messages; v3's block subsystem answers them; one node can have a private drive and one can have no storage (MESH.md acceptance 3). + +**Architecture:** `BLOCK` and its family keep their two buffers; the one word under them sends a Block read or Block write message to "storage", which has a number like a node and is reached by the node's ordinary routes. On the storage port is a device (`v4/system/storage.c`) that speaks those messages and holds a view of one or two v3 block chains. A small glue file (`v4/system/store_v3.c`) is the only v4 file that includes v3 headers. + +**Tech Stack:** C99 (engine, strict flags), v3's `block_subsystem.c` and `blkio_*.c` (gnu99, v3's flags), the v4 nucleus dialect (`v4/capsule/*.v4`), `make -C v4`, `make -f kernel/Makefile`. + +**Spec:** `docs/v4.0.0/MESH.md` section 8 (rulings 8.1, design 8.2 to 8.4, exclusions 8.5) and step 6 in section 10. `docs/v4.0.0/V3-PARITY.md` section 1d. + +## 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 `. +- No stubs, no stand-ins, no simulated devices. If something cannot be built as intended, stop and report. +- Do not fix anything in v3 that this plan does not name. If the sanitizers or a test show a fault in v3's code, report it and stop. +- A block is 1024 characters, 256 cells, four characters to a cell, the first lowest. +- Message types: 4 Block read, 5 Block write, 6 Block data, 7 Block done. Block done's word: 0 worked, 1 no storage, 2 refused, 3 no such block. +- Private block *k* is logical number 2^32 - 1 - *k* and is the private chain's block 2048 + *k*. +- Node error codes: 13 "Block out of range" (existing), 17 "No storage", 18 "Storage refused". +- 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 under `logs/`. +- Access (claim, ACL, Stadium touch) is not checked in this step; do not add it. + +## Review Focus + +1. **An answer lost on the way back.** A passing node keeps waiting messages in 400 cells and a Block data message is 264; a third arriving while two wait is let go, and the node that asked waits for ever. Expected: no block answer is lost when two nodes use storage at once. Test in Task 5, step 4. +2. **Block numbers from the top down on 32-bit cells.** They are negative there, and `(FIND-BUF)` refuses negatives today. Expected: `-1 BLOCK` reaches private block 0 at 32 bits. Test in Task 3, step 1. +3. **Block data that nobody announced.** A Block data message with no Block write before it from that sender. Expected: Block done 2, nothing written. Test in Task 2, step 1. +4. **A write that is refused.** `UPDATE` then `SAVE-BUFFERS` on a block storage will not take. Expected: the error message, the prompt, and the buffer empty so the next `BLOCK` asks again. Test in Task 3, step 1. +5. **A number no 32 bits hold, on 64-bit cells.** `4294967296 BLOCK`. Expected: "Block out of range", not block 0. Test in Task 3, step 1. + +**Unruled, to report and not to decide:** a node waiting for an answer from storage that never comes (the node holding the device is asleep or killed) waits for ever, as Hera does for a birth that hangs. + +--- + +### Task 1: v3's block subsystem — two chains, no `VM` + +**Files:** +- Modify: `v3/include/block_subsystem.h:330` and add the chain declarations beside it +- Modify: `v3/src/block_subsystem.c:169-201` (the global state), `:647-652` (`blk_subsys_init`) +- Modify: `v3/src/main.c:319-321`, `kernel/src/capsule/capsule_loader.c:96-98`, `kernel/src/vm/bootstrap/sk_vm_bootstrap.c:306` + +**Interfaces:** +- Produces: + ```c + typedef struct blk_chain blk_chain_t; + blk_chain_t *blk_chain_default(void); /* the one chain v3 has always had */ + blk_chain_t *blk_chain_new(void); /* another; 0 if there is no memory */ + blk_chain_t *blk_chain_select(blk_chain_t *c); /* every blk_* call acts on c from now on; returns the one before */ + int blk_subsys_init(uint8_t *ram_base, size_t ram_size); /* the VM argument is gone */ + ``` + +- [ ] **Step 1: Record the baseline.** Run `make clean && make 2>&1 | tail -3` at the repo root (v3's hosted build) and note that it links. If it does not link before any change, stop and report. + +- [ ] **Step 2: Make the state a struct with a current pointer.** In `block_subsystem.c`, name the anonymous global struct and select it through a pointer, so that none of the 86 uses of `g.` change: + + ```c + struct blk_chain { + uint8_t *ram_base; + size_t ram_size; + /* ... every other member exactly as it is today, except `VM *vm`, which is removed ... */ + }; + static struct blk_chain g_default; + static struct blk_chain *g_current = &g_default; + #define g (*g_current) + + blk_chain_t *blk_chain_default(void) { return &g_default; } + blk_chain_t *blk_chain_new(void) { return (blk_chain_t *)calloc(1, sizeof(struct blk_chain)); } + blk_chain_t *blk_chain_select(blk_chain_t *c) + { + blk_chain_t *was = g_current; + if (c) g_current = c; + return was; + } + ``` + + `memset(&g, 0, sizeof(g))` in `blk_subsys_init` keeps working through the macro. Check every other file-scope `static` variable in the file (`grep -n '^static [^(]*;' v3/src/block_subsystem.c`): any that holds chain state moves into the struct; any that is a constant table stays. + +- [ ] **Step 3: Remove the `VM` argument.** `blk_subsys_init(uint8_t *ram_base, size_t ram_size)`; drop the `!vm` test and `g.vm = vm`. Update the declaration and the three callers and the `extern` in `v3/src/main.c:319`. `capsule_blk_init` keeps its own `void *vm` parameter (its callers are not in this plan) and stops passing it on. + +- [ ] **Step 4: Verify.** `make clean && make 2>&1 | tail -3` at the root links. `grep -rn 'blk_subsys_init' v3 kernel --include=*.c --include=*.h` shows no call with three arguments. + +- [ ] **Step 5: Kernel acceptance.** This changes code that only the kernel's v3 configuration runs (the kernel built without `STARFORTH_V4`). Boot all three ISAs in that configuration, per the Global Constraints, and keep the logs. First look at the newest logs under `logs/` made in that configuration: if there are none from this branch, or the newest did not reach its prompt, the configuration may not have booted here before this change. In that case report it to Captain Bob and ask how he wants this change accepted. Do not improvise an acceptance test, and do not stash or branch to find out. + +- [ ] **Step 6: Commit.** `git add v3/include/block_subsystem.h v3/src/block_subsystem.c v3/src/main.c kernel/src/capsule/capsule_loader.c kernel/src/vm/bootstrap/sk_vm_bootstrap.c logs/` then commit `refactor(v3): the block subsystem's state is a chain there can be two of; blk_subsys_init takes no VM` and push. + +--- + +### Task 2: The storage device and the messages + +**Files:** +- Modify: `v4/include/v4/message.h` (four types, four status values) +- Create: `v4/include/v4/storage.h`, `v4/system/storage.c`, `v4/system/store_v3.c` +- Modify: `v4/Makefile` (build v3's `block_subsystem.c`, `blkio_ram.c`, `blkio_file.c` and what they need with v3's flags; link them and `v4/system/storage.c`, `store_v3.c` into tests named `test_store*.c` and `test_host_*.c`) +- Test: `v4/tests/test_store.c` + +**Interfaces:** +- Consumes: Task 1's `blk_chain_*` and `blk_subsys_init`; v3's `blk_get_buffer`, `blk_update`, `blk_flush`, `blk_get_total_blocks`, `blk_is_valid`, `blk_subsys_add_raw_device`, `blk_subsys_attach_device`. +- Produces: + ```c + /* message.h */ + #define V4_MSG_BLOCK_READ 4 + #define V4_MSG_BLOCK_WRITE 5 + #define V4_MSG_BLOCK_DATA 6 + #define V4_MSG_BLOCK_DONE 7 + #define V4_STORE_OK 0 + #define V4_STORE_NONE 1 + #define V4_STORE_REFUSED 2 + #define V4_STORE_RANGE 3 + + /* storage.h */ + #define V4_STORE_TOP 0xffffffffUL /* private block k is V4_STORE_TOP - k */ + #define V4_STORE_FIRST 2048UL /* a chain's first block on a device */ + #define V4_STORE_PENDING 64u + typedef struct { + int (*read)(void *chain, unsigned long block, unsigned char *bytes); /* V4_STORE_* */ + int (*write)(void *chain, unsigned long block, const unsigned char *bytes); /* V4_STORE_* */ + unsigned long (*blocks)(void *chain); /* how many block numbers the chain answers to, fast RAM included */ + } v4_store_ops; + typedef struct v4_store v4_store; /* defined in storage.h; its owner supplies one */ + void v4_store_init(v4_store *s, const v4_store_ops *ops, void *common, void *private_chain, v4_cell number); + int v4_store_take(void *self, v4_cell value); /* a v4_device's take */ + int v4_store_give(void *self, v4_cell *value); /* a v4_device's give */ + + /* store_v3.c */ + extern const v4_store_ops v4_store_v3; /* chain is a blk_chain_t * */ + ``` + +- [ ] **Step 1: Write the failing test** `v4/tests/test_store.c`. It speaks to the device with `v4_message_text` and `v4_store_take`/`v4_store_give` directly; no node. It makes a common chain and a private chain, each `blk_subsys_init` with 2080 KiB of RAM and one `blk_subsys_add_raw_device` of 64 blocks. Checks, each one `CHECK(cond, "words")` in the style of `v4/tests/test_host_unit.c`: + - a Block write of block 2050 then its Block data is answered by Block done 0, addressed to the sender, from the storage's number; + - a Block read of 2050 is answered by Block data of 1024 characters equal to what was written; + - a Block read of `V4_STORE_TOP` on a view with a private chain returns private block 0, and what is written there is not seen at any common number; + - the same read on a view with only a common chain is Block done 3; + - a read of 2050 on a view with only a private chain is Block done 1; + - a read of block 0 is Block done 3; + - a Block data message with no Block write before it from that sender is Block done 2 and block 2050 is unchanged (Review Focus 3); + - two senders' writes interleaved (A's write, B's write, B's data, A's data) each land in their own block; + - the device takes a second request while its answer to the first has not been read; + - a chain is written through v3's own `blk_get_buffer`/`blk_update`/`blk_flush`, and the device reads the same bytes back: what v3 wrote reads through the port. + +- [ ] **Step 2: Run it and see it fail.** `make -C v4 run-64-test_store.c` fails to build: `v4/storage.h` does not exist. + +- [ ] **Step 3: Find what v3's block code needs to link.** `block_subsystem.c`, `blkio_ram.c` and `blkio_file.c` compile alone with `-std=gnu99 -Iv3/include` and need two symbols besides libc: `log_message` and `sf_time_backend`. Find the v3 files that define them (`grep -rn '^.*log_message(\|sf_time_backend' v3/src --include=*.c`), add those files, and repeat `nm -u` until only libc is undefined. If the chain of files reaches v3's VM (`vm.c`), stop and report with the list: that is a ruling for Captain Bob, not something to work around with definitions of your own. + +- [ ] **Step 4: Write `storage.c`.** It includes only `v4/storage.h` and `v4/message.h` and uses nothing from the C library, as `image.c`. + - `take` collects words into a `v4_message` with `v4_message_word`. On a whole message: + - Block read: queue `{to = from, kind = read, block}`. + - Block write: remember `{from, block}` in a table of `V4_STORE_PENDING`. + - Block data: find the sender in that table. If it is there, do the write now and queue `{to, kind = done, status}`; if not, queue done with `V4_STORE_REFUSED`. + - Anything else addressed to storage is let go. + - `take` returns 0 only when the queue of answers is full. + - `give` builds the answer at the head of the queue when asked for its first word (for a read, the read is done then) and hands it over a word at a time. + - Which chain a number means, as MESH.md 8.4: + ```c + static void *chain_for(const v4_store *s, unsigned long n, unsigned long *block) + { + if (n == 0 || n > V4_STORE_TOP) return 0; /* V4_STORE_RANGE */ + if (s->private_chain) { + unsigned long k = V4_STORE_TOP - n, have = s->ops->blocks(s->private_chain); + if (have > V4_STORE_FIRST && k < have - V4_STORE_FIRST) { *block = V4_STORE_FIRST + k; return s->private_chain; } + } + *block = n; + if (s->common) return s->common; + if (n < V4_STORE_FIRST) return s->private_chain; /* its fast RAM */ + return 0; /* V4_STORE_NONE */ + } + ``` + The caller tells `V4_STORE_RANGE` from `V4_STORE_NONE` by the first line. + +- [ ] **Step 5: Write `store_v3.c`.** Each function selects the chain, calls v3, and selects back: + ```c + static int v3_read(void *chain, unsigned long block, unsigned char *bytes) + { + blk_chain_t *was = blk_chain_select((blk_chain_t *)chain); + uint8_t *b = blk_is_valid((uint32_t)block) ? blk_get_buffer((uint32_t)block, 0) : 0; + int st = b ? V4_STORE_OK : (blk_is_valid((uint32_t)block) ? V4_STORE_REFUSED : V4_STORE_RANGE); + if (b) memcpy(bytes, b, 1024); + blk_chain_select(was); + return st; + } + ``` + `v3_write` is the same with `blk_get_buffer(block, 1)`, `memcpy` into it, `blk_update(block)` and `blk_flush(block)`; any of those failing is `V4_STORE_REFUSED`. `v3_blocks` is `blk_get_total_blocks()`. Read `blk_is_valid` and `blk_get_total_blocks` in `block_subsystem.c` first and confirm they count block numbers as v3's `BLOCK` does; if they count something else, use what `v3/src/word_source/block_words.c` uses to bound a block number. + +- [ ] **Step 6: Makefile.** Compile the v3 files and `store_v3.c` to objects with `$(CSTD:c99=gnu99) -Wall -Wextra $(OPT) -I$(ROOT)/v3/include`; `storage.c` with the engine's strict `$(CFLAGS)`. Add them to the link of every test whose name begins `test_store` or `test_host_`, in both the plain and the sanitizer rule. + +- [ ] **Step 7: Run.** `make -C v4 run-32-test_store.c run-64-test_store.c` and the sanitizer targets for the same file: all checks pass, 0 failures. A sanitizer report inside v3's code is reported, not fixed. + +- [ ] **Step 8: Commit** `feat(v4.0.0): storage speaks messages -- a device over v3's block chains, common and private` and push. + +--- + +### Task 3: The node asks by message + +**Files:** +- Modify: `v4/capsule/blocks.v4:7-9,18,33-42,49-66` (the device comment, `(DEVICE)`, `(FIND-BUF)`) +- Modify: `v4/capsule/core.v4:21-22` (the error list), `v4/capsule/quit.v4` (the messages for codes 17 and 18, beside the one for 16 in `(RAISED)`; `STORAGE`) +- Modify: `v4/tests/host_map.h:61,66,212-215`, `v4/tools/mkimage.c:91,118`, `v4/include/v4/image.h:45`, `v4/src/image.c` (`v4_image_born` loses `disk`, `blocks`), `v4/include/v4/node.h:119-122,336-362`, `v4/src/node.c:21-57,140-141` +- Modify: `v4/tests/test_node.c` (remove the storage-register tests), `v4/tests/test_host_quit.c:65-72,230-231,610-629,1136-` +- Regenerate: `capsules/v4/nucleus-64.f18` (the build writes it) + +**Interfaces:** +- Consumes: Task 2's device and message types. +- Produces: nucleus cells `(STORE)` (storage's number, 0: none) at `BVARS + 10` and `(B-TO)` at `BVARS + 11`; the word `STORAGE ( node -- )`; `v4_image_born(n, es, h, im)`. + +- [ ] **Step 1: Write the failing tests** in `test_host_quit.c`. Its node is driven by hand; put a `v4_store` on port 2 of it, served in the same loop that serves the console port, and tell the node `2 2 ROUTE 2 STORAGE` after the nucleus is in. `put_block` writes through `v4_store_v3.write`. The existing block checks (lines 610 to 629 and 1136 on) must pass unchanged. Add: + - before `STORAGE` is told: `20 BLOCK` prints `No storage` and the prompt returns; + - `0 BLOCK` prints `Block out of range`; + - on 64-bit cells `4294967296 BLOCK` prints `Block out of range` (Review Focus 5); + - `-1 BLOCK` at 32 bits, and `4294967295 BLOCK` at 64, reach private block 0 of a view that has a private chain: write a character, `SAVE-BUFFERS`, `EMPTY-BUFFERS`, read it back (Review Focus 2); + - on a view with a private chain whose device is still provisional (attach a blank `blkio_ram` device and do not confirm its format): `-1 BLOCK DROP UPDATE SAVE-BUFFERS` prints `Storage refused`, and a following `-1 BLOCK` sends a new Block read (count the messages the device took) (Review Focus 4). + +- [ ] **Step 2: Run and see them fail.** `make -C v4 run-64-test_host_quit.c`: the new checks fail and the build may fail on the removed attach call. + +- [ ] **Step 3: Replace `(DEVICE)` in `blocks.v4`.** The words, to be proven by Step 1's tests: + + ``` + \ ( n type -- flag ) send storage a message of one word, n. 0: there is + \ no storage, or no way to it. + : (B-ASK) + push (STORE) a! @ if NONE \ n store R: type + dup (PORT-FOR) if NOWAY \ n store port + (GATE) !b \ n to: storage + pop (HDR) 4 !b !b -1 ; \ from, type; four characters; n + NOWAY: drop + NONE: drop drop pop drop 0 ; + + \ ( addr -- ) send storage the 256 cells at addr as Block data + : (B-DATA) + (STORE) a! @ dup (PORT-FOR) (GATE) !b \ addr + 6 (HDR) 1024 !b + a! 255 FOR @+ !b UNEXT ; + + \ ( addr -- status ) wait for storage's answer. Block data goes into the + \ 256 cells at addr and the status is 0; Block done gives its word. Every + \ other message that comes is kept with the messages waiting. + : (B-AWAIT) + (B-TO) a! ! + L: (PORT)+8 b! @b (PORT)+9 b! @b (PORT) + dup b! SWAP (TAKE-HDR) + (MQ-HDR) a! @ (ME) a! @ xor if W1 drop jump KEEP + W1: drop (MQ-HDR)+1 a! @ (STORE) a! @ xor if W2 drop jump KEEP + W2: drop (MQ-HDR)+2 a! @ -6 + if DATA -1 + if DONE drop jump KEEP + DONE: drop @b ; + DATA: drop (B-TO) a! @ a! 255 FOR @b !+ UNEXT 0 ; + KEEP: (TAKE-KEEP) jump L + + \ ( i status -- ) 0: done. Otherwise nothing is in buffer i, and the + \ error is 17 no storage, 18 refused, 13 no such block. + : (B-END) + if FINE + push (B) + a! 0 !+ 0 ! pop + -1 + if E17 -1 + if E18 drop NODE-ERROR b! 13 !b ; + E17: drop NODE-ERROR b! 17 !b ; + E18: drop NODE-ERROR b! 18 !b ; + FINE: drop drop ; + + \ ( command i -- ) command 1: read buffer i's block; 2: write it + : (DEVICE) + SWAP -1 + if RD + drop dup push (B) + a! @ 5 (B-ASK) if NOWAY + drop pop dup push (BUF) (B-DATA) 0 (B-AWAIT) pop SWAP jump (B-END) + RD: drop dup push (B) + a! @ 4 (B-ASK) if NOWAY + drop pop dup push (BUF) (B-AWAIT) pop SWAP jump (B-END) + NOWAY: drop pop 1 jump (B-END) + ``` + + In `(FIND-BUF)` remove the sign test and keep the test for 0 (`if BAD`). Replace the header comment's "four registers" paragraph with one sentence saying storage is asked by message (MESH.md 8.2). In `quit.v4`, beside `ROUTE`: + + ``` + \ ( node -- ) the number of what is on a storage port, that this node asks + \ for its blocks; 0: it has no storage. Whoever wires the node tells it. + header STORAGE + : STORAGE (STORE) a! ! ; + ``` + +- [ ] **Step 4: Take the registers out.** Remove `storage_reg`, `storage`, `storage_blocks`, `storage_command`, `v4_node_storage_attach` and the BLOCK-COMMAND test in `node.c`; the four `BLOCK-*` constants and `STORAGE_REG` in `host_map.h` and `mkimage.c` (add `(STORE)` and `(B-TO)`; `mkimage.c:91` zeroes those two cells instead); `storage_reg` in `image.h`; the `disk`, `blocks` parameters of `v4_image_born`. Remove `test_node.c`'s tests of the registers. + +- [ ] **Step 5: Run.** `make -C v4 run-32-test_host_quit.c run-64-test_host_quit.c run-64-test_node.c`: 0 failures. Then the whole suite will not build yet (`boot.c`, `test_host_unit.c`, `hosted.c` still pass a disk): that is Tasks 4 and 5. Do not commit a tree that does not build: go straight on to Task 4 and commit the two together. + +--- + +### Task 4: The lone node's storage port + +**Files:** +- Modify: `v4/include/v4/boot.h` (a `storage` member; `v4_boot_run(const v4_boot *b)`), `v4/system/boot.c:115-166,338-351` +- Modify: `v4/tools/hosted.c:18,58`, `v4/Makefile` (the hosted binaries link Task 2's objects) + +**Interfaces:** +- Consumes: `v4_device`, `v4_store_take`, `v4_store_give`, `STORAGE`, `ROUTE`. +- Produces: `const v4_device *storage;` in `v4_boot` (0: the node has no storage); `#define STORAGE_PORT 2u` and `#define STORAGE_ID 2` in `boot.c`. + +- [ ] **Step 1: `boot.c`.** In the loop that serves the node's ports (lines 132 to 166): + - a write on `STORAGE_PORT` is offered to `b->storage->take`, and the node is served when it is taken; + - a read of `STORAGE_PORT`, or of any port when the console has nothing for it, is offered `b->storage->give`. + + Add `1u << STORAGE_PORT` to the readers mask at line 351 when `b->storage` is set. When the nucleus is in and before the capsules, hand the node the line `2 2 ROUTE 2 STORAGE` if `b->storage` is set; it must complete like any boot line. Confirm 2 is not `CONSOLE_ID`; if it is, use the next number free. + +- [ ] **Step 2: `hosted.c`.** Replace the `disk` array. Make the chain as v3's hosted program does (`v3/src/main.c:319-340`): `blk_subsys_init` on 2080 KiB of static RAM, then whatever device v3's hosted program attaches by default. Read those lines first and do the same; add no new command-line option. `v4_store_init(&store, &v4_store_v3, blk_chain_default(), 0, 2)`. + +- [ ] **Step 3: Run.** `make -C v4 hosted-check`: `POST: PASSED`, pass=538 fail=0 on the three ISAs, and the existing COLD and FORGET lines. If the cross-compilers cannot link v3's files for aarch64 or riscv64, report the error and stop. + +- [ ] **Step 4:** Leave the commit to the end of Task 5, when the whole suite builds. + +--- + +### Task 5: Acceptance 3 in the unit of five + +**Files:** +- Modify: `v4/tests/test_host_unit.c:20-21,39,45,96-113` and its checks +- Modify: `capsules/v4/hera.4th` only if a node must be told `STORAGE` and its `ROUTE` at birth by Hera; keep every block to 16 lines of 64 characters and run `build/tools/mkcapsule --lint capsules/` + +**Interfaces:** +- Consumes: everything above. + +The wiring, which is the test's and nobody's ruling (MESH.md ruling 4): storage is number 90. The common chain's device is on a free port of outer node 11. Node 13 has its own device on a free port, with a view of a private chain and the common chain, and is told 90 by that port. Nodes 10 (Hera) and 12 reach 90 through 11. Node 14 is told nothing. + +- [ ] **Step 1: Write the failing checks.** + - node 12: `2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS` completes; node 10: `2100 BLOCK C@ .` prints `65` (a block written by one node is read by another, through a node in between); + - node 13 reads `65` at 2100 too; + - node 13: `4294967295 BLOCK 1024 BLANK 66 4294967295 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 4294967295 BLOCK C@ .` prints `66`; node 12, asked for the same number, prints `Block out of range`; + - node 14: `20 BLOCK` prints `No storage` and the node answers the next line; + - POST is still 538 of 538 on every node and the four outer nodes' dictionary hashes are still identical. + +- [ ] **Step 2: Run and see them fail,** then remove `disks[]` and the attach in `host_born`, add the two devices and the wiring, and tell each node its storage (`90 ROUTE 90 STORAGE` by `tell`, or in `hera.4th` if the test's nodes are wired by Hera before the test can speak to them). + +- [ ] **Step 3: Run.** `make -C v4 run-64-test_host_unit.c`: 0 failures. + +- [ ] **Step 4: Two at once (Review Focus 1).** Have nodes 10 and 12 each be told, without waiting for the first to end, to write and read back ten blocks (`: W 10 0 DO I 2200 + BLOCK DROP UPDATE SAVE-BUFFERS LOOP ; W`), then check both ended completed and node 11's `(LOST)` is 0. If an answer is lost, do not enlarge the queue or change the passing rule: report it to Captain Bob with the numbers. + +- [ ] **Step 5: The whole suite.** `make -C v4 -k test` at both widths and the sanitizer run: `all v4 tests passed` and `all v4 tests passed under ASan+UBSan`. Then `make -C v4 hosted-check`. + +- [ ] **Step 6: Commit** Tasks 3, 4 and 5 together: `feat(v4.0.0): a node asks for its blocks by message -- shared, private, and none` and push. + +--- + +### Task 6: Bare metal + +**Files:** +- Modify: `kernel/src/kernel_main.c:518-523,620-668` (the chain is set up before the v4 node starts) +- Modify: `kernel/src/v4/sk_v4.c:24,30,83-89`, `kernel/Makefile:616-624` (link `v4/system/storage.c` and `store_v3.c`) + +- [ ] **Step 1: Read `kernel_main.c:600-700`** and list what the chain's setup needs that has not happened by line 523 (the heap, PCI, virtio). Move the v4 start below the setup, or lift the setup into a function called before it; whichever leaves the v3 path's order of events exactly as it is. If something the setup needs cannot be had before the v4 node starts, report it and stop. + +- [ ] **Step 2: `sk_v4.c`.** Remove `sk_v4_disk`. `v4_store_init(&sk_v4_store, &v4_store_v3, blk_chain_default(), 0, 2)`; `boot.storage = &sk_v4_storage_device`. + +- [ ] **Step 3: Acceptance.** The three bare-metal boots, per the Global Constraints. Each log must show `PARITY:V4_POST tests=538 pass=538 fail=0`, `POST: PASSED`, and the typed session of the previous step's logs; add to the typed session `2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ .` printing `65`. + +- [ ] **Step 4: Commit** with the three logs: `feat(v4.0.0): bare metal -- the node's storage port is the kernel's block chain` and push. + +--- + +### Task 7: Write it up + +**Files:** +- Modify: `docs/v4.0.0/MESH.md` (step 6: done, what was built, what is not as intended), `v4/README.md`, `docs/v4.0.0/V3-PARITY.md` section 1d (what now goes through v3's subsystem and what is still skipped: claim, ACL, Stadium) + +- [ ] **Step 1:** Write step 6 up in the manner of step 5: the date, the files, the test counts, the log directories, and under "not as intended yet" at least: access not checked; anything reported under Review Focus; the unruled case of an answer that never comes. +- [ ] **Step 2: Commit** `docs(v4.0.0): storage -- step 6 done` and push.