From 26a07484554baa2abe99ec8a454a67cef4bc378b Mon Sep 17 00:00:00 2001 From: rajames Date: Wed, 7 Oct 2026 07:21:10 -0400 Subject: [PATCH] docs(v4.0.0): storage brought back to the OS as designed -- every node asks the kernel Ruled 2026-10-07 (Captain Bob): POST on every node, nodes without storage, and block requests passed from node to node had left v3's design. MESH.md section 8 is rewritten: a block is a kernel request, as ENGINE.md 3.3 already had it; every node has blocks; there is one chain. Private drives and the message device are withdrawn, and listed in 8.5 so that they are not proposed again. Acceptance 1 and 3 change with it. A born node is not POSTed, and the kernel is to hold POST's cases. The plan for step 6 is revised to match. Co-Authored-By: Claude Opus 5.5 --- docs/v4.0.0/MESH.md | 212 +++++----- docs/v4.0.0/plans/2026-10-06-step6-storage.md | 379 ++++-------------- 2 files changed, 176 insertions(+), 415 deletions(-) diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index 7ce9c276..c8e47677 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -27,8 +27,9 @@ before making v4 equal v3 on bare metal (`ENGINE.md` step 4), which waits. until the neighbour writes (`DECOMPOSITION.md` section 6). What travels is v3's Hermes message with what it carries, so v3's messaging rules are not dropped: they go with the message. -2. **Some nodes have storage of their own**, in addition to or instead of - the common SSD. Some have none. +2. ~~Some nodes have storage of their own, in addition to or instead of + the common SSD. Some have none.~~ **Withdrawn 2026-10-07:** every node + has blocks, and asks the kernel for them (section 8). 3. **The first set of nodes is "2x2 + 1 central"**: five. 4. **The geometry is data, not design.** A node has a number of ports, and that number is a parameter. Which port connects to what is a table that @@ -53,16 +54,18 @@ before making v4 equal v3 on bare metal (`ENGINE.md` step 4), which waits. 1. **Five nodes come up from nothing.** Hera is born empty, takes in the nucleus capsule and the FORTH-79 capsule, and passes POST. She births - the four outer nodes while running; each is born empty, takes in the - same capsules through its port from Hera, and passes POST. Each prints a - parity line; the four outer nodes' dictionary hashes are identical. + the four outer nodes while running; each is born empty and takes in the + same capsules through its port from Hera. Each prints a parity line; + the four outer nodes' dictionary hashes are identical. (Changed + 2026-10-07: an outer node is not POSTed. POST is the kernel's, once, as + in v3.) 2. **They talk.** A line typed at the console reaches Hera as a message. A message from Hera runs on an outer node and its output comes back. A message between two outer nodes that are not wired to each other is forwarded by a node in between. -3. **They share the SSD.** A block written by one node is read by another. - One outer node has a drive of its own and uses it. One has no storage, - and `BLOCK` on it is an error with a message. +3. **They share the SSD.** Every node asks the kernel for its blocks. A + block written by one node is read by another. (Changed 2026-10-07: no + node has a drive of its own and none is without storage.) 4. **It scales while running.** A second unit of five is born and joined centre to centre. A message crosses from one unit to the other. The second unit is removed and the first carries on. @@ -270,22 +273,21 @@ kernel-Hermes's work in v3 and is not decided for the mesh. ## 8. Storage -**Ruled 2026-10-06** (Captain Bob), in the order the rulings were given. -What is marked *step 6* is built there; what is marked *step 6a* is ruled -and waits for that step. +**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. -### 8.1 The rulings +### 8.1 The rulings that stand -1. **A disk is a device on a port that speaks messages.** Asking for a - block is a message — read block *n*, or write block *n* with 1024 - characters — and the answer is a message back. -2. **A node only ever asks for a block by number, and knows nothing else.** - It has one way to storage, as it has routes: a port, or nothing. This - replaces the earlier proposal that a node be told ranges of block - numbers. Nobody chooses ranges. +1. **A node only ever asks for a block by number, and it asks the kernel.** + `V3-PARITY.md` 1d and `ENGINE.md` 3.3: a kernel request, written to the + port where the node's kernel is, served between the node's opcodes. No + node asks another node for a block, and none passes such a request on. +2. **Every node has blocks, always.** There is no node without storage. 3. **Two numbers.** A logical block number is what `BLOCK n` and capsules - use. A physical block number is a place on a real device. A mapper - stands between them. + use. A physical block number is a place on a real device. The kernel's + mapper stands between them. 4. **The view is static; reality shifts to keep it so.** A logical number always means the same block. Devices chain one after another in physical space, and that chain changes as devices come and go; the mapper moves @@ -301,121 +303,93 @@ and waits for that step. identity, and the identity of the chain it belongs to. A v3 disk reads zero there: not yet given one. With them a device is known when it returns, wherever it is plugged in. -7. **The mapper is the kernel's.** `V3-PARITY.md` section 1d stands: the - device chain, the metadata, first-touch claim, ACL, migration and the - Stadium touch stay in the kernel's block subsystem, which is v3's C - code. What a node finds on a storage port is that subsystem, speaking - messages in logical block numbers. (The mapper was first ruled to be a - capsule on the node that holds the devices; that contradicted 1d, and on - being shown the contradiction Captain Bob ruled that 1d stands.) -8. **A node's own drive is private.** It is a second chain in the kernel, - wired to that node's port, and its blocks are nobody else's. A node may - have it in addition to the common store or instead of it. -9. **Common from the bottom up, private from the top down.** A block of the - common store has the same logical number on every node that shares it, - from 2048 upward. A node's private blocks are numbered from the top of - the number space downward: private block *k* is logical number - 2^32 - 1 - *k*, v3's block numbers being 32 bits. The two meet only if - the whole space is used up. -10. **A device leaves by being asked for.** *(step 6a)* Asked to release a - device, the mapper moves its blocks onto the devices that remain and - then says it may be pulled; if there is no room, the release is refused - with a message. A device pulled without asking leaves holes: its - logical numbers stay claimed and answer "no storage" until it returns, - is known by its identity, and its blocks are at the same numbers again. +7. **The mapper is the kernel's**: the device chain, the metadata, + first-touch claim, ACL, migration and the Stadium touch stay in the + kernel's block subsystem, which is v3's C code. +8. **A device leaves by being asked for.** *(step 6a)* Asked to release a + device, the mapper moves its blocks onto the devices that remain and + then says it may be pulled; if there is no room, the release is refused + with a message. A device pulled without asking leaves holes: its + logical numbers stay claimed and answer "no storage" until it returns, + is known by its identity, and its blocks are at the same numbers again. ### 8.2 What a node does *(step 6)* `BLOCK`, `BUFFER`, `UPDATE` and `SAVE-BUFFERS` keep their two buffers and their FORTH-79 behaviour. The one word beneath them in `v4/capsule/blocks.v4` that read or wrote a block by storing to four -registers sends a message and waits for the answer instead. +registers makes a kernel request instead: it puts the block's number and +the word address of the buffer's 256 cells on the data stack and writes the +request's number to port 0, where its kernel is. When the write has been +served the status is on the stack in their place: 0 it worked, 2 it was +refused, 3 there is no such block. -- A node wired to storage writes the message to that port. -- A node that is not sends the same message toward the node that is, and - it is passed on like any other (section 7). -- A node with no way to storage has none: `BLOCK` is an error with a - message, and the node is back at its prompt. +There are two requests, read a block and write a block. Their numbers are +the same for every node and are below zero; the requests a host names for +its own words (`KERNEL-WORD`) count up from 1. The four storage registers and `v4_node_storage_attach` go from the engine. -### 8.3 The messages *(step 6)* +### 8.3 What the kernel does *(step 6)* -Four types, beside text, output and done (section 6): +Every node has its kernel on port 0, whatever else is there for it: Hera's +requests for nodes (section 9) are honoured only from Hera, and the two +block requests from every node. -| Type | Payload | Answered by | -|---|---|---| -| 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: 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 -two by who sent them. - -### 8.4 What is on a storage port *(step 6)* - -A device (4.3), in `v4/system/storage.c`, which the hosted and the -bare-metal builds share as they share `boot.c`. It speaks the messages of -8.3 and asks v3's block subsystem (`v3/src/block_subsystem.c`) for every -block. The engine (`v4/src`) knows nothing of it. +`v4/system/blocks.c`, which the hosted and the bare-metal builds and the +tests share, serves them: it takes the number and the address from the +node's data stack, checks that the 256 cells are in the node's memory, and +reads or writes the block through v3's block subsystem +(`v3/src/block_subsystem.c`), four characters to a cell, the first lowest. +The engine (`v4/src`) knows nothing of it. - v3's code does what it does: header, allocation map, block cards, relocation, three blocks and their cards to a 4 KiB device block. -- The devices beneath are v3's own back ends: RAM and a file when hosted, - the virtio disk on bare metal. -- 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. 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. +- There is one chain, as in v3: fast RAM at blocks 0 to 2047, then the + devices. Every node sees the same blocks at the same numbers. +- The devices are v3's own back ends: RAM and a file when hosted, the + virtio disk on bare metal. A hosted program started with no disk has the + fast RAM alone, as hosted v3 has. -**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.) +`blk_subsys_init` took a v3 `VM`, stored it and never used it; the hosted +v4 system has none to give. **Ruled 2026-10-06:** the argument is removed. -`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 +**On bare metal** the node's blocks are the kernel's real chain — RAM, the +ramdrive and the virtio disk — in place of the RAM array `kernel/src/v4/sk_v4.c` gives it today. (Approved.) Found 2026-10-06: `kernel_main.c` starts the v4 node (line 523) before it sets that chain up (lines 620 to 668), and the v4 node never returns, so under `STARFORTH_V4` the chain does not exist today. Step 6 has the kernel set it up before the node starts. -### 8.5 Not in step 6 +### 8.4 Not in step 6 - **Who may have which block is not checked.** First-touch claim, the block - card's ACL and the Stadium touch need the node's identity, which is the - message's ACL tag (`V3-PARITY.md` 1d). Blocks are read and written - through v3's subsystem with nobody's claim on them. Not as intended yet. + card's ACL and the Stadium touch need the node's identity + (`V3-PARITY.md` 1d). Blocks are read and written through v3's subsystem + with nobody's claim on them. Not as intended yet. - **Chains of more than one device, identities, release and holes** are step 6a. -- **Cloud stores and real USB drives** wait for their drivers. The format - and the messages already fit them. +- **Cloud stores and real USB drives** wait for their drivers. -### 8.6 Open +### 8.5 Withdrawn 2026-10-07 -A node that starts with only its own drive and later gains a way to the -common store. With no common store, its blocks from 2048 upward, a -capsule's among them, can only be on its own drive; when the common store -appears those numbers are claimed twice. Not ruled. +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 +divergence from the os as designed"; and of the three below, "all three, +every node asks the kernel directly". + +- **A disk as a device on a port that speaks messages**, and a block + request passed from node to node until it reaches the node wired to the + disk. Built and tested as far as one node (commit `4a505a15`), then + withdrawn. In v3 a VM calls the kernel. +- **Nodes with no storage** (ruling 2 of section 2, and acceptance 3 as it + was). In v3 every VM has blocks. +- **A drive of a node's own**, numbered from the top of the number space + downward. In v3 there is one chain. +- **POST on every node at its birth** (acceptance 1 as it was). In v3 the + kernel runs POST once, at boot, with block RAM it supplies, and a born VM + prints its parity. See section 9. ## 9. Birth, and Hera @@ -428,6 +402,12 @@ same kind. Only Hera's requests are honoured. The unit rule (ruling 5) is Hera's, in capsule code: it is what she does with those requests. The engine and the host do not know it. +**Ruled 2026-10-07:** a node Hera births is not POSTed. POST is the +kernel's, run once at boot on Hera, as v3's kernel runs it once on its +first VM; a born node prints its parity. And the kernel is to hold POST's +cases and feed them to Hera itself, with no POST words loaded into her +dictionary — today they are a capsule she loads (`NUCLEUS.md` section 6). + ## 10. Steps Each is tested, committed and pushed before the next. @@ -606,17 +586,15 @@ Each is tested, committed and pushed before the next. `BYE`, and nothing it asks is honoured, as ruled. - The suite now takes about four minutes, eight under the sanitizers: five POSTs. -6. **Storage (section 8): the disk speaks messages, the kernel's mapper - answers, private drives.** Sections 8.2 to 8.4. Tests: a node reads and - writes blocks through a port, and a disk image v3's code formatted - reads back; in the unit of five, one outer node writes a block and - another reads it, one uses a drive of its own at numbers from the top - down, and one has no way to storage and `BLOCK` on it is an error with - a message (acceptance 3); POST 538 of 538 with `blocks.v4` changed, at - both widths and under the sanitizers; `hosted-check`; the three - bare-metal boots. +6. **Storage (section 8): every node asks the kernel for its blocks.** + Sections 8.2 and 8.3. Tests: a node reads and writes blocks by kernel + request, and what v3's own calls wrote reads back; in the unit of five, + one node writes a block and another reads it (acceptance 3); Hera no + longer sends POST to the nodes she births; POST 538 of 538 on Hera with + `blocks.v4` changed, at both widths and under the sanitizers; + `hosted-check`; the three bare-metal boots. - **6a. Storage that changes while running.** Rulings 6 and 10 of + **6a. Storage that changes while running.** Rulings 6 and 8 of section 8.1: chains of several devices, the device and chain identities, a device joining at the end and known when it returns, release by asking, holes. Its acceptance is to be approved before it is 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 index 0708fa60..6622b32d 100644 --- a/docs/v4.0.0/plans/2026-10-06-step6-storage.md +++ b/docs/v4.0.0/plans/2026-10-06-step6-storage.md @@ -1,359 +1,142 @@ -# Step 6: Storage Implementation Plan +# 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:** 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). +**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 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. +**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, v3's flags), the v4 nucleus dialect (`v4/capsule/*.v4`), `make -C v4`, `make -f kernel/Makefile`. +**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 (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. +**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. + +**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 `. -- 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/`. +- 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. **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. +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 — two chains, no `VM` +### Task 1: v3's block subsystem — done (`0e761cb1`) -**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` +`blk_subsys_init` takes no `VM`; the state is a chain selected through a pointer. Accepted on three v3-configuration boots. -**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. +- [ ] **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 storage device and the messages +### Task 2: The kernel serves block requests **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` +- 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:** -- 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: +- Produces, in `blocks.h`: ```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 * */ + #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** `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. +- [ ] **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 by message +### Task 3: The node asks its kernel **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: +- 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)`:** ``` - \ ( 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 + \ ( 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) - 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) + 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 ; ``` - - 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. + 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's storage port +### Task 4: The lone node -**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) +**Files:** `v4/include/v4/boot.h`, `v4/system/boot.c`, `v4/tools/hosted.c`, `v4/Makefile` -**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. +- [ ] **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: Acceptance 3 in the unit of five +### Task 5: 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/` +**Files:** `v4/tests/test_host_unit.c`, `capsules/v4/hera.4th` -**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. +- [ ] **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:** -- 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`) +**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 (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. +- [ ] **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 -**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 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.