The boot runs POST with the runner (v4/system/post.c) after the capsules:
it feeds the 538 cases to the node and judges them from outside. A
case's printing no longer reaches the console. What the cases define
stays in the dictionary, as in v3; the system is sealed after POST and
the boot prints PARITY:V4_SYSTEM word_count=N dict_hash=..., as v3 prints
its parity after POST.
Gone: capsules/v4/post79.4th and its blocks 7000 up; the harness words;
(CATCH) and (EMIT-HOOK), with what EMIT and the prompt loop did for them.
The generator runs v3 on the lines as the kernel sends them, without the
capsule's "T| ". One expected result follows from that: >IN.initial
prints 6, not 9.
make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, word_count=411, dict_hash
0x6fb1d09418b189ee on all six: logs/20261007-105118, -105335, -105651.
T{ is unknown at the prompt; RS1 prints 42 42 before and after COLD. A
scratch build with one expectation changed names the case and ends
PARITY:FAIL, POST: FAILED.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v4/system/post.c: for each case it empties the node's data stack, sends
DECIMAL FORTH DEFINITIONS, sends the case's lines keeping what the node
prints, reads the stack, and judges: an error exactly if one is expected,
and otherwise the stack and every character printed. A failing case is
named with what it printed and left. Nothing of it is on the node.
Its tests are in test_host_quit.c, on that test's node: 29 checks of cases
that must pass and cases that must fail -- a wrong value, depth, order or
output, an error wanted or unwanted, a case of two lines, depth-only
cases, the starting state, more printing than is kept, and a line that
never comes back. Both widths and the sanitizers.
The boot still runs the capsule; that is the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mkpost.py writes v4/system/post_cases.c where it wrote the capsule: for
each case its name, its lines, and what it must do -- end in an error, or
leave v3's stack and print v3's output, which is now held in full where
the capsule held its length and a checksum.
The same 538 cases: checked against capsules/v4/post79.4th case by case --
names, lines, stacks, and the length and checksum of each output -- with
no difference. The capsule is still what the boot runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruled 2026-10-07. BLOCK, BUFFER, UPDATE, SAVE-BUFFERS and EMPTY-BUFFERS
are each one kernel request on port 0, by block number only. The node
has a window of four slots in its memory, as a v3 VM has; the kernel
copies blocks into it, keeps the record of which block is in which slot,
and decides when a block is written (v4/system/blocks.c). The node keeps
no record and gives the kernel no address, so a request cannot overwrite
the node's code. When a block is written stays FORTH-79's: UPDATE marks
it. When the chain of devices changes the slots are let go, as in v3.
The window is 512 cells more than the two buffers were; the dictionary
space ends that much lower, at 13824.
MESH.md 8.5 had said, from the review, that a v3 VM's BLOCK is the
kernel's buffer. It is not, and the section now says what was reported,
what v3 does, and what was ruled.
make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, the same hashes on all six:
logs/20261007-092835, -093112, -093456.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
A block is a kernel request, as ENGINE.md 3.3 has it: the node puts the
block's number and the address of 256 cells on its stack and writes the
request to port 0, and the kernel leaves the status there. The requests
are -1, read, and -2, write, the same for every node. v4/system/blocks.c
serves them from the kernel's block subsystem, which is v3's. The four
storage registers are gone from the engine.
The device that spoke block messages (4a505a15) is withdrawn with its
test and its message types: Captain Bob ruled on 2026-10-07 that it, a
node's own drive, and nodes with no storage had left the OS as designed
(docs/v4.0.0/MESH.md 8.5).
Hera no longer sends POST to the nodes she births: POST is the kernel's,
once. Every node has its kernel on port 0; it serves a node's blocks and,
for Hera alone, her requests for nodes and capsules.
Bare metal: the node boots and is POSTed against POST's own block RAM,
and the kernel's chain -- fast RAM, the ramdrive, the virtio disk -- is
set up after POST and before the prompt, as on the v3 path. The disk is
read and not written: nothing in v4 yet gives the owner's word that it
may be formatted. A hosted program has the chain's fast RAM, as hosted
v3 has with no disk. Error 17 is Storage refused.
make -C v4 test and sanitize pass at both widths; hosted-check passes on
three ISAs; amd64, aarch64 and riscv64 boot, POST 538 of 538, with the
typed session: logs/20261007-081603, -081839, -082226. The hashes are
the same on all six.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What is on a storage port (v4/system/storage.c): it takes Block read,
Block write and Block data and answers Block data or Block done. It
holds a view of a common chain, a private chain, or both; private block k
is number 2^32 - 1 - k. Behind it is v3's block subsystem, reached
through v4/system/store_v3.c, the one v4 file that includes v3's headers.
v3's block code links here with its two device back ends, its log and
its clock, and nothing else of v3.
v4/tests/test_store.c: 36 checks at 64 bits, 35 at 32, and under the
sanitizers. docs/v4.0.0/MESH.md 8.3, 8.4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MESH.md step 5, in the fabric under test; the products are still one
node each until steps 8 and 9.
manage.c: what Hera asks of whoever holds the fabric, eleven requests by
KERNEL-WORD -- NODE-ME -BORN -WIRE -UNWIRE -SLEEP -WAKE -KILL -PARITY and
CAPSULE-OPEN -CELL -LINE. Nucleus: PORT!, SEND-ON, AWAIT, (SEAL).
capsules/v4/hera.4th: BIRTH and UNIT, the unit rule in FORTH and nowhere
else. The dictionary hash moves to the engine (v4_image_dict_hash).
test_host_unit.c, 34 checks, 64-bit: Hera is born empty, takes the
nucleus through her port, FORTH-79, POST and her capsule; 10 UNIT; four
nodes are born, each takes the nucleus and FORTH-79 through its port from
Hera and passes POST 538/538; four parities, one dictionary hash; they
talk, and a message between two corners not wired goes by Hera.
All v4 tests at both widths and under ASan+UBSan. hosted-check on three
ISAs. Bare metal: logs/20261006-143934 (amd64), -144204 (aarch64),
-144601 (riscv64).
Open, recorded in MESH.md: not run at 32 bits; a node that never answers
leaves Hera waiting; the capsules are read from files in the test, not
from the baked directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MESH.md step 4, the fault found there, as ruled (section 7a): a node
writes only to a neighbour that is reading, keeps what it takes in
meanwhile, and loses and counts what it has no room for.
Engine: two more addresses after a node's ports -- which ports have a
neighbour waiting to write to it, which to read from it. Nucleus: (GATE)
before every message; the messages waiting, a ring of 400 cells, dealt
with when the node is idle. Of two neighbours the lower number may wait
to write (ruled after it was built); NEIGHBOUR tells a node who is on
each port.
test_host_mesh.c, 44 checks: the case that stopped the nodes passes; six
messages from each node to each at once all arrive; 800 at once, the
nodes come to rest and every message arrived or was counted (287 arrived,
714 of all kinds let go). test_fabric.c: the two looks. Both widths,
ASan+UBSan.
POST: twelve cases handed HERE, a cell address on v4, to words that take
a byte address, and so wrote into or read from the nucleus's code at cell
HERE/4. Ruled: left out, marked OPEN, until HERE and the byte words are
made to agree as its own step. POST is 538 cases.
hosted-check on three ISAs, 538/538. Bare metal: logs/20261006-134817
(amd64), -135044 (aarch64), -135440 (riscv64).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MESH.md step 3. The ports are the transport; the message is what is
transported: to, from, type, heat and TTL, ACL tag, sequence, length, then
text four characters to a word.
- quit.v4: a node with nothing to do is blocked reading "any port"; text
for it is interpreted; (FINISH) sends what it printed and then how the
text ended, and it waits again
- core.v4: EMIT keeps what is printed, (FLUSH-OUT) and (HDR) send it to the
sender on the port the message came on. EMIT still needs one free data
cell and no more; it works on the return stack and in A and B
- message.h/.c: the same format for whatever is on a port and is not a node
- boot.c: the boot is the node's console on port 1 and its kernel on port 0
- the prompt tests are a console that speaks messages
- gone: v4_line_begin, v4_line_done, v4_line_status; writing a node's input
buffer and setting its P from outside; any use of CONSOLE-TX
Verified: make -C v4 test (test_host_quit.c 1283 checks, the full-stack
figures unchanged) and make -C v4 sanitize pass; hosted-check passes on
three ISAs with POST 550 of 550; clean qemu with STARFORTH_V4=1 passes POST
and answers lines typed at each prompt on amd64, aarch64 and riscv64
(logs/20261006-110551, -111621, -111341). -110837 is an aarch64 run ended
by the test wrapper's limit while still in UEFI firmware; it shows nothing
about v4.
Not done: KEY, EXPECT and QUERY still read the console's input registers;
a message not for this node is let go (step 4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MESH.md step 2. A capsule of F18 code is the words a neighbour writes to a
node's port: for each stretch of memory, "@p a! @p push", the address and
count, "@p !+ unext" and the words; then a jump to the start. A node born
empty executes that from its port, so it needs nothing in it beforehand.
- capsule.h/.c: v4_capsule_write, any node's memory as such a capsule
- mkimage writes the nucleus so, to capsules/v4/nucleus-64.f18, and the
addresses a host needs as a C file; the memory image is no longer linked
into either product
- mkcapsule is unchanged: the nucleus capsule is a built file kept under
capsules/, as BLOCK_MAP.md is, and is baked, hashed and signed with the
rest
- boot: the node is born empty (v4_image_born); the nucleus capsule is
found, its hash and signature checked, and given to the node a word at a
time as it reads its port; PARITY:V4_NUCLEUS carries its name and hash
Verified: test_fabric.c (59 checks, both widths, and under ASan and UBSan):
a memory with a programme and scattered words arrives word for word in an
empty node and runs. The nucleus capsule rebuilds byte for byte.
hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64,
aarch64 and riscv64 takes the nucleus in, passes POST (550 of 550) and
answers lines typed at each prompt (logs/20261006-102421, -102706,
-103048).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ENGINE.md 3b, the node's side of ruling A (a word's code is the node's, its
accounts the kernel's).
- dict.v4, system.v4: (WORD-DEFINED) ( xt -- ) is run when an entry is
made, (WORD-FORGOTTEN) ( w -- ) when FORGET or COLD removes entries; with
0 there no one is told, as on the hosted product
- test_host_quit.c: a kernel that keeps the list of words and is checked to
hold exactly the node's dictionary after definitions, a vocabulary, an
abandoned definition, FORGET, a refused FORGET and COLD; KERNEL-WORD
called from the prompt and from a definition
Fixed, found while writing that test: since the capsules moved from build
time to boot time (294e6946), what COLD returns to and FORGET protects was
still the nucleus alone, so COLD lost U*, U/MOD and BYE and FORGET U* was
allowed. The boot now seals the system when it has loaded it
(v4_image_seal), and hosted-check checks COLD, the capsule word after it,
the refused FORGET and BYE.
Verified: make -C v4 test passes at both widths (1283 checks in
test_host_quit.c); hosted-check passes on three ISAs; clean qemu with
STARFORTH_V4=1 on amd64, aarch64 and riscv64 passes POST, and COLD, U*
after it, FORGET U* (refused), an unserved kernel word and BYE typed at
each prompt are answered correctly (logs/20261005-193045, -193307, -193636).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ENGINE.md step 2, the carrier. Ruled 2026-10-05 (V3-PARITY.md 1i), on
DECOMPOSITION.md section 6: a write to a port blocks until the neighbour
reads.
- node: v4_node_port_attach, v4_node_port_served; a store to the port keeps
the value as the request and blocks the node
- exec: a blocked node executes nothing; served, it goes on from the opcode
after the store, in the same instruction word; a fault meanwhile abandons
the rest of the word
- compile.v4: n KERNEL-WORD name makes a word whose body writes n to the
port; its arguments and results are on the data stack
- boot: the kernel's words are made by handing the node text, and requests
are served between the node's opcodes; one no one serves is error 12
- BYE, the first kernel word: hosted it leaves the program, as hosted v3;
on the lone node it is v3's cold restart
- ENGINE.md 3a: multiuser, multitasking, preemptive and cooperative, and
what that asks of the engine
Verified: make -C v4 test passes at both widths, with tests/test_port.c;
hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64,
aarch64 and riscv64 passes POST with the same hashes as hosted, and a
kernel word no one serves and BYE typed at each prompt are answered
(logs/20261005-185506, -185734, -190101; -185234 is an amd64 run in which
those two lines were not typed).
Not done: v3's own C functions serving a node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A v4 node no longer reads its own command line or prints a prompt. Its
host puts a line of text in the node's input buffer and starts it at
(LINE); the node interprets it and stops at (IDLE), leaving in
(LINE-STATUS) how it ended: completed, an error, or QUIT. The host says
" ok" or " ERROR" and prompts, as the kernel's REPL does for a v3 VM. A
line may be 1024 characters, a block, as v3's. Ruled 2026-10-05
(V3-PARITY.md 1b); design ENGINE.md 3.1.
- quit.v4: (REPL), the node's prompt loop, is gone; (LINE) (IDLE) (DONE)
- image.h/.c: v4_line_begin, v4_line_done, v4_line_status; the node is
idle at switch-on
- boot.c: v4_boot_line, the one loop the hosted binary, the kernel and the
capsule loader hand a line with; the code that took " ok" and the prompt
back out of the node's output is gone
- hosted.c, sk_v4.c: the prompt and the line editing are the host's
- test_host_quit.c: the tests are the node's host; two tests of the old
80-character prompt line now test a whole line, 1024 and 1025 characters
Verified: make -C v4 test passes at both widths; hosted-check passes on
three ISAs; clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64
passes POST (550 of 550) with the same hashes as hosted, and three lines
typed at each bare-metal prompt through the serial port are answered
correctly (logs/20261005-180922, -181152, -181541).
Still the lone node: kernel_main.c starts it before the fleet tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The boot is now nucleus, forth79.4th, POST, prompt, on both products.
- forth79.4th: U* and U/MOD, the capsule's first colon definitions. They
are in the FORTH-79 Required Word Set and neither v3 nor v4 had them.
- post79.4th: 550 cases, 126 of the 130 required words. 443 are v3's with
v3's result. The rest follow three rulings (2026-10-05): address-
dependent cases are checked for count, not value; where v3 departs from
FORTH-79 the standard's result is expected; words v3 has no case for get
cases written by hand. v4/tools/post79_rules.py holds each exception
with its reason and docs/v4.0.0/POST79.md lists them all.
- every case starts from an empty stack, DECIMAL and FORTH DEFINITIONS
- the boot requires POST's tally line with fail=0
Verified: tests=550 pass=550 fail=0 and identical PARITY lines on hosted
amd64, aarch64 and riscv64 (make -C v4 hosted-check) and on bare metal,
clean qemu with STARFORTH_V4=1, on the same three (logs/20261005-1619xx,
-1621xx, -1625xx). A U/MOD broken on purpose fails five cases and stops
the boot. make -C v4 test passes.
Not shown: all words but those two are still assembled, so POST has so far
tested the assembled words. Nothing was typed at a bare-metal prompt.
Open: PAD 42 OVER ! faults on v4 (D-1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
capsules/v4/post79.4th: 537 of v3's POST cases for the FORTH-79 Required
Word Set, each carrying what the hosted v3 binary did with the same line
(error or not, the stack, the length and checksum of what it printed).
Written by v4/tools/mkpost.py. The harness is FORTH-79 plus the two
nucleus hooks. docs/v4.0.0/NUCLEUS.md section 6.
- NODE-ERROR has a FORTH name: how a definition in FORTH raises an error
- the boot passes POST only on seeing its tally line with fail=0
- POST is not in the boot yet (V4_POST_AT_BOOT=0); make -C v4 post runs it
Result: tests=537 pass=439 fail=98. The 98 are not yet sorted into v4
defects and differences needing a ruling; v4/README.md has a first reading.
Verified: make -C v4 test passes; hosted-check passes on three ISAs; clean
qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64 reaches ok> with the
same hashes as hosted (logs/20261005-1601xx..1604xx). Nothing was typed at
a bare-metal prompt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One boot, v4/system/boot.c, for both products: it starts the nucleus image,
finds each capsule in the baked capsule directory, recomputes its hash,
checks its signature, gives its blocks to the node a line at a time, and
prints PARITY:V4_NUCLEUS, PARITY:V4_CAPSULE and PARITY:OK before the prompt.
A line the node does not accept ends the boot with the capsule, block and
line named. docs/v4.0.0/NUCLEUS.md.
- hosted Linux product for amd64, aarch64 and riscv64 (make -C v4 hosted);
make -C v4 hosted-check boots all three and requires identical output
- the kernel's v4 entry (STARFORTH_V4=1) calls the same boot
- capsules/v4/forth79.4th, block 6000: no definitions yet
- mkimage builds the nucleus only; no FORTH source is compiled at build time
- capsule_blocks.c: the Block-header parse, free of any VM, for every loader
Verified: make -C v4 test passes; hosted-check passes on the three ISAs with
the same hashes; the kernel compiles with STARFORTH_V4=1 on the three.
Not verified: no bare-metal boot of v4 has been run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>