The final review of step 6e, by a fresh reviewer told to break it, and
what was ruled: fix them all, test first. Each has a test that failed
before the mend.
- Two texts from one node could be begun in the wrong order when room
for their answers came between the node's look at the first and at the
second. Texts whose answers go by the same port are begun in the order
they came: (D-MASK), quit.v4. 102 of 700 timings were wrong before.
- A word written with PORT! to a node that speaks whole messages, or to
a console's port, blocked its writer for ever and cut off every node
beyond it. It is error 18 now, whether that node is asleep, running
or stuck: fabric.c, node.c.
- A node asleep for room on one wire is woken when another of its wires
is cut.
- A line there was no room for on the lone node's wire was reported as a
fault and ended the hosted program. It is Message refused, and the
prompt comes back: V4_BOOT_LINE_REFUSED. hosted-check has the case;
no bare-metal boot has typed it.
- Ruled: text behind held output for the same port is refused though it
would fit. MESH.md 7d.4 said otherwise and is corrected.
- The removal test also cuts and re-makes a wire, and has an exchange in
which output is held at a full wire: 26,814 disturbances in all.
- MESH.md and v4/README.md no longer say that a stuck node holds up no
other node, that only printing waits, or that no node is left waiting
on one that is gone; 7d.6 lists the limits and what the products do
that they did not before.
make -C v4 test and sanitize at both widths, hosted-check, lint. Three
bare-metal boots: logs/20261013-194827 (amd64), -195131 (aarch64),
-195518 (riscv64). POST 538 of 538, word_count=317,
dict_hash=0xe5a57f3eb72ff94a on all six; the three sessions are the same
character for character. The boots were built with the working tree's
uncommitted change to kernel/Makefile that adds the ISA to the kernel's
version line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_host_mesh.c, 220 checks: three exchanges on the row of three -- text
from the console for node 12 with what it prints and how it ended; node 11
SENDing node 12 text and waiting for its answer; a message node 12 has no
way for and its NACK to node 10 -- are each played once for every engine
step they take with node 11 removed at that step, and again with node 12:
17,642 removals at 64 bits. Every node left comes to rest asleep, blocked
at no port and in no operation, and answers. What is typed is a word
defined beforehand, so that the steps are the exchange's and not the
dictionary being searched.
Removed from the engine, nothing using them: v4_fabric_interrupt_error, a
device's pending hook, v4_node_words_since_look, V4_ERROR_INTERRUPTED.
MESH.md: 4.1's table has the addresses after the ports; 7a and 7b say what
of them still holds; 7b.7's limit is closed; step 6e is written up.
v4/README.md likewise.
make -C v4 test and sanitize at both widths, hosted-check, lint. Three
bare-metal boots with the typed session and a loop printing 400 numbers:
logs/20261013-165124 (amd64), -165428 (aarch64), -165818 (riscv64). POST
538 of 538, word_count=317, dict_hash=0x0951d7e02e251554 on all six, and
the three sessions are the same character for character. The boots were
built with the working tree's uncommitted change to kernel/Makefile that
adds the ISA to the kernel's version line; nothing else differs from this
commit.
The plan's final review is not done yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Engine: a wire keeps whether the message at its mark is the one last
looked at; a node's take, move and drop of any other answer 3 and write
nothing (MESH.md 7d.3). A wire cut and made again between a look and a
take no longer has a NACK done as text, or 256 cells written where one
was looked for.
- Engine: v4_fabric_wire_kernel wires a kernel to a port with no queues;
a message for it is refused at once, error 18, on a mesh as on the
products.
- The result of a drop is looked at; a take that does not take has the
message dropped, or gone past: nothing goes round.
- An answer nobody waits for is let go uncounted; (LOST) counts what should
have been delivered or acted on and was not. The flood's account is
exact.
- Error 18 from under a word that was printing empties the data stack, as
a fault does: that word's own values are not left there.
- Tests: the take and drop windows cut at every step, with and without
another message put; a refusal owed to a stuck node whose wire is full.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The result of every take is looked at: a wire cut between the look and
the take leaves nothing acted on (the text before run again, a refusal
counted, a wait ended by an old answer, a way forgotten).
- A node with no room to pass on what another printed, or its word of how
text ended, leaves it on the wire it came by and moves it when there is
room; what is behind it for the same port waits too. Sleep for room (9)
is woken by room on any of the node's wires (engine; MESH.md 7d.3).
- The lone node's host keeps no queue for the kernel's port: a message to
it is error 18. The step limit is counted while the node waits for room.
- A text waiting for room to print is not interrupted by console text;
AWAIT is, as before. (BREAK?) is gone.
- A GONE is believed only by the centre's port; unawaited answers let go
are counted; nothing past a message's last character goes on a wire;
AWAIT sets (AWAIT-FROM) only once the stack has been tried.
- test_host_depth.c: ten paths, each run works or ends in one named stack
error; it fails with (ROOM) or (ROOM-SERVE) taken out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 6e, tasks 2 and 3 (docs/v4.0.0/MESH.md 7d). The nucleus puts, looks
at, takes, moves and drops whole messages through the six addresses after
its ports; its own ring of messages waiting, the refusals owed, (GATE) and
the looking words' use are gone. SEND, an answer, a NACK and a GONE go or
are refused at once; what a text prints waits for room; a text is not
begun until the wire its answer goes by has room for it, and that room is
kept. The lone node's host (v4/system/boot.c) keeps its two wires and
does its operations; the test hosts' consoles put and take whole messages
through the fabric. New: tests/test_host_depth.c, every message path of
a mesh node at every depth of both stacks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Queues of whole messages on the wires between nodes (v4/wire.h), the six
addresses and ten operations a node asks of the fabric (MESH.md 7d.3), and the
fabric's pool of queues. Engine only: the nucleus does not use any of it yet.
The ports in the host map move to BUF0_W - 140 so that the new addresses are
not among the nucleus variables, with a compile-time check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two root causes behind what the reviews of the wait found, both in
the code as it stands. Each with a check that failed first.
EMIT stored its character and only then looked whether the buffer was
exactly full. A flush that ended in a fault -- the stack too deep to
begin a message -- left the buffer full, and the fault's own message was
then put past its end, with (OUT^) never again at the end to be flushed.
The 28 cells after the buffer happen to be unused, so nothing showed;
the wait had put the ports there, and the node blocked writing to one.
EMIT now sends a buffer it finds full before it stores.
AWAIT, and (GATE) when a neighbour is writing, read a message's first
word and then used stack that was not known to be there. With 27 or 28
values on the stack, or 28 or 29 calls deep, a line typed during AWAIT
was taken and never answered. (ROOM) tries six cells and four return
entries first: if they are not there the text ends 'Stack overflow' with
nothing read. (GATE) tries the three cells its writing needs before a
first word goes; it was safe before only by the order things were done.
v4/tools/depthsweep.py runs every message path of the hosted product at
every depth of both stacks: each line typed must be answered and the
node must come back. hosted-check runs it.
make -C v4 test and sanitize at both widths, hosted-check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruled 2026-10-08. The engine, the nucleus and their tests are as they
were at dfabfa46, before step 6d; the code of the wait and its tests are
in history at 075385ab..2cf37aab. The limit of MESH.md 7b.7 stands: a
node that is stuck can hold up its neighbours until Hera kills it.
MESH.md 7c is kept as the record of the design as approved, and 7c.6 says
what was built, what the two reviews found, the cause the findings share
-- a node's message machinery runs on the stacks its text is using, and a
fault abandons whatever was in progress -- and what a second attempt
must settle before anything is built.
Kept from it, in hosted-check: a line that leaves 29 values on the stack,
and WORDS with 26 values on it. Both hung the products at some commit of
the wait.
make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots typing both. POST 538 of 538, word_count=317,
dict_hash=0xc0769523a47b7dc3 on all six: the hash of step 6c's code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MESH.md 4.1, 7a, 7c.2, 7c.3, 7c.4 and step 6d say what the code now does,
what the review found and how each was mended, and what is small and not
mended. The README no longer says the aim is unmet; it says the mended
code has not been reviewed again.
Three bare-metal boots on 2cf37aab, typing 29 values on a line: POST 538
of 538, word_count=317, dict_hash=0xd41a6ac9448fff60 on all six.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruled 2026-10-08: A, B, C, D and the guard for a half message. Each with
a scene in test_host_mesh.c that failed first.
A. The words of a message after its first are written from the passes
over the messages waiting by (!W): an offer and a wait for that offer
only (a new engine address). A reader removed in mid-message makes no
error; the rest is let go and counted. Before, the error left the
messages waiting in pieces and the node going round for ever.
B. A store to the wait withdraws every offer; (GATE1) and (PAY-SET) do it
first. Before, text begun after a node went round without sleeping
could go to the port of an older offer: text for one node was done by
another.
C. What a finished text printed is put with the messages waiting, as how
it ended is. With no room either is let go and counted; neither is
begun as a message from (FINISH) any more, where a stuck node on the
way held the whole node.
D. Only what a sender is owed from an earlier text -- its output, how it
ended -- holds that sender's next text back; a GONE does not; and what
cannot be noted holds nothing. A node's own messages keep the order
they were made in.
- A message half taken in when its writer is removed is let go
((MQ-MEND)), not kept as if whole.
- Smaller: a refused line no longer ends a waiting text 'Interrupted';
a node's own queued messages may be passed on by 16 nodes, not 15; no
value is left on the stack when a port goes in mid-refusal.
make -C v4 test and sanitize at both widths, hosted-check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review of step 6d found it not sound. Mended here, with a check
that failed first: a text that left 29 values on the stack hung the
node for ever, on the products too -- the passes over the messages
waiting that follow every text need four cells, and a stack fault in the
middle of one left the messages in pieces. Such a text ends 'Stack
overflow', as before step 6d. hosted-check types it.
The other findings are open and are written under step 6d in MESH.md
section 10; the README no longer says the aim is met.
make -C v4 test and sanitize at both widths, hosted-check. The bare
metal boots have not been made again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine's console release (v4_fabric_interrupt_error), a device's
pending, and the count of words since a look are removed: nothing uses
them. MESH.md 4.1, 7b and 7c say what was built, where it differs from
what was approved and why, and what is still a limit; step 6d is marked
built, with the defects found on the way.
make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots. POST 538 of 538, word_count=317,
dict_hash=0x6a39c0bb9d183418 on all six.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in the five-node test, each with a check that failed first:
- A node with answers it could not give to two stuck senders began no
text at all. It now holds back only text from a sender whose own
answer is still with it.
- KILL waited to tell a stuck node, and a typed line that broke it left
the nodes after it untold. GONE is put with the messages waiting, as
how text ended is, and goes when it can.
- So that 'tell, then wait' still works, AWAIT does what (IDLE) does but
for beginning text: it looks through the messages waiting, offers what
is to be passed on and what is owed, and sleeps in the wait. A waiting
node no longer keeps back what passes through it.
- A node in the wait with an offer on a port that turns out to have
nothing on it is woken and told so (which port: 2 * V4_PORTS + k); no
error is raised on it. (GATE) makes it error 18; (IDLE) and AWAIT let
the message go. Before, removing a node raised 'No one on that port'
on the unrelated text of a neighbour that had something queued for it.
make -C v4 test and sanitize at both widths, hosted-check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1c720ab2 was committed with hosted-check failing: with how text ended
queued, a line typed while the node waited was done before the earlier
line's answer had gone, and the pass that offers what is to be passed on
let go text that was for the node itself.
How text ended is put before the other messages waiting; a node does not
begin another text from a sender while its answer to that sender is
still with it; and what is for the node itself is not offered.
make -C v4 test and sanitize at both widths, hosted-check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found after the last commit: a message being passed on toward a node that
was stuck held the node passing it on in the wait, taking in what came
but serving none of it; text typed for a stuck node left Hera deaf. And
a node that had done text for a sender that then stuck waited on it to
give its answer.
A node with nothing to do now deals first with what is for itself; then
offers at once the first message for each port and each refusal owed,
and sleeps in the wait. What is taken is written and done with; the
rest stays with the messages waiting, in order. How text ended is put
with the messages waiting, not written from (FINISH). The stack is kept
as shallow between lines as it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(GATE) offers a message's first word and sleeps until it is taken, taking
in whatever is written to the node meanwhile; (PAY) offers every refusal
owed at once, one to a port; a node that owes one sleeps in the wait.
Writing without looking, looking again and again, and the lower-number
rule are gone. Text from the console for a node whose own text waits to
begin a message ends that text Interrupted. The lone node's hosts serve
an offer as a write. MESH.md 7c; plan step 6d task 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node stores a word to offer on any of its ports and fetches the wait: it
is blocked until a word comes for it or one of its offers is taken, and
the fabric does the handing over in one step. The port block moves in
the nucleus's map to make room for the new addresses. Nothing uses it
yet. MESH.md 7c; plan step 6d task 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review of step 6c found it did not keep its rule. Ruled: fix them
all; 16 hops.
- Paying a NACK no longer waits on a lower-numbered neighbour; a node
that owes one does not sleep, and goes on taking in what it is sent.
- AWAIT looks first through the messages already waiting.
- A node waiting to write to a removed neighbour gets error 18: the
word that says who is reading also says which ports have anything on
them.
- A console line lets Hera go when she is blocked on the first word of
a message (v4_fabric_interrupt_error, a device's `pending`).
- A refused answer is told to the node that waits, not the one that
answered; one owed to the node itself goes to its own queue.
- A message is passed on by at most 16 nodes, then refused.
- A GONE is believed only when it says it is from the node's centre.
- Tests: the check that tested nothing is replaced; no room for a
waiting sender; the limit that remains is tested as a limit.
- MESH.md 7b's rule reworded to what holds, 7b.7 says where it falls
short; README likewise.
make -C v4 test, sanitize, hosted-check; three bare-metal boots. POST
538 of 538, word_count=317, dict_hash=0xc0769523a47b7dc3 on all six.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bare-metal host hands a waiting node the next line typed, as the
hosted one does: 5 AWAIT, then 7 8 * . -- Interrupted, then 56.
amd64, aarch64 and riscv64 boot, POST 538 of 538, word_count=317,
dict_hash 0x54520ade672566ad, the same as the three hosted programs:
logs/20261007-202737, -203230, -203616.
MESH.md step 6c: what was built, how it was verified, and two things
found on the way that are not mended and are for ruling: Hera blocked
writing to a stuck node, and a message that can go round for ever between
two nodes whose ways disagree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hera keeps a table of the nodes she has had born, number and place.
KILL ( n -- ): she asks the kernel to remove node n, forgets her own way
to it, and sends GONE about it to every other node in the table. A node
she never had born, herself, or one she has killed already is an error
that says so, and nothing is removed. v3's word and meaning; v3's takes
a name, and a mesh node has a number.
test_host_unit.c, 74 checks: Hera kills node 14 while node 12 is blocked
writing to it, and what 12 then sends there is refused; node 12 waits on
node 13, stuck in a loop and not its neighbour, and the wait ends Node
gone when Hera kills 13; Hera waits on node 12, stuck, and a line typed at
the console ends her wait Interrupted and then kills 12.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text for a node from its console, arriving while the node waits for an
answer (AWAIT), is kept as any message is; the wait ends in error 21,
Interrupted; and the text is then done as usual. Console text for
another node, passing through a node that is waiting, does not end its
wait.
On the hosted program 5 AWAIT ended the program with "the node stopped".
The boot now reports that the line is waiting, the host reads the next
line and hands it over, which breaks the wait, and then has the kept line
done. The bare-metal host does the same; it is booted in the commit that
follows.
test_host_mesh.c: 76 checks, both widths and the sanitizers. hosted-check
types 5 AWAIT and two more lines: Interrupted, then 3 and 56.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node that has no room for a message, or no way to pass it on, lets it
go and counts it as before, and now notes that it owes its sender a NACK
(type 4: the node the message was for). It sends what it owes when it
next has nothing else to do. 36 cells of the messages waiting are kept
for NACK and GONE, and nothing is owed for either.
A node sent a NACK counts it in (REFUSED); one waiting for an answer from
that node (AWAIT) stops, with error 19, Message refused. A GONE (type 5)
from a node's centre makes it forget the way to the node named (NO-ROUTE,
new), and ends a wait on it with error 20, Node gone. From anyone else
it is ignored. GONE ( n node -- ) sends one.
test_host_mesh.c, three nodes: text for a node nobody has a way to comes
back to the console, and to a node two away, as a NACK; a waiting sender's
line ends Message refused; a node that is waiting keeps what it has room
for, owes eight refusals, counts the rest, and sends them when its wait is
ended by a GONE from its centre. 70 checks, both widths, sanitizers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A block write the device refuses after the kernel has taken the node's
copy: the node was told "Storage refused" while the kernel's cache kept
the new data, to be read back and perhaps written later. The kernel now
puts its own copy back as it was. Tested with a device whose writes can
be made to fail; it failed first.
POST's runner: a case whose starting state could not be set says so, with
what the node said, where it showed nothing; and a case that ends with
QUIT fails, as it did when the cases were a capsule, where the runner had
taken it for a completed line. Both tests failed first.
The v4 boot says "Artemis: virtio-blk attached" only when the attach
worked.
make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, dict_hash 0x5f0a949a6fc8ef2b on all six:
logs/20261007-135741, -140004, -140329.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node writing to a node that was stuck, and was then killed, stayed
blocked for ever: killing a stuck node could cost its neighbours. And on
the hosted and bare-metal products a write to an empty port, 5 7 PORT!,
ended the program with "the node stopped".
Now a node that can take an error, blocked writing to or reading from one
port that nothing is wired to -- nothing ever was, or what was there has
been killed or the wire cut -- has error 18, No one on that port, raised
on it and goes on. A bare node waits as on the fabric; so does a node
whose neighbour is asleep, and one reading "any port".
test_host_unit.c: Hera kills node 14 while node 12 is blocked writing to
it. It failed first: 12 stayed blocked. test_fabric.c: a bare node still
waits. hosted-check types 5 7 PORT! and goes on; it failed first too.
make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, dict_hash 0x5f0a949a6fc8ef2b on all six:
logs/20261007-121743, -122008, -122337.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Captain Bob was told that v3 leaves the words POST's cases define in the
dictionary, and ruled that v4 should. That was false: v3's run_test_suite
puts the dictionary back after each word's cases (test_common.c:333,
:365). Shown that, he ruled that POST leaves nothing. The boot now
seals the system, runs POST, and has the node do COLD, whose printing is
not shown; PARITY:V4_SYSTEM is the system as sealed.
A case the node does not come back from ends POST there, named, with how
many were not run: it would have stalled the boot for hours, where the
capsule had ended it. The runner's test of it now uses a word that
really never ends.
hosted-check also requires that RS1 and T{ are unknown after boot, and
boots a program whose POST has failing cases (tests/post_cases_fail.c):
PARITY:FAIL, POST: FAILED, no prompt, none of a case's printing shown.
Comments and documents that still named the POST capsule or its two
hooks are brought up to date; NUCLEUS.md 6.3 says what v3 does, with the
lines, and how the wrong ruling came about.
make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, word_count=314, dict_hash
0x220ab283a504a3b3 on all six: logs/20261007-112638, -112901, -113220.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
MESH.md step 6 as built, what is not as intended yet, and step 6b for
POST becoming the kernel's. README and V3-PARITY.md brought up to date.
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 4. Each node has a table of destinations and the port toward
each, and a port for everything else (ROUTE, DEFAULT-ROUTE, NO-ROUTES). A
message not for this node is passed on whole; one with nowhere to go is
dropped and counted. What text prints and how it ended go back to the node
it came from by the same table. SEND sends text to another node.
test_host_mesh.c: three StarForth nodes in a row behind a console, 28
checks at both widths and under ASan+UBSan. hosted-check on three ISAs.
Bare metal: logs/20261006-115225 (amd64), -115501 (aarch64), -115849
(riscv64).
NOT DONE. Two neighbours that write to each other at once wait for ever:
a write blocks until the neighbour reads, and a node that is writing is
not reading. The last check in test_host_mesh.c shows it (KNOWN FAULT).
MESH.md section 7a sets out the ways out; none is chosen.
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>
MESH.md step 1, in the engine, which knows nothing of StarForth or of any
kernel.
- node: V4_PORTS ports (8), a build parameter; "any port" and the port the
last such read came from; a read blocks until the neighbour writes, as a
write blocks until the neighbour reads; v4_node_born: empty, P at "any
port"
- exec: a fetch from a port -- @ @b @+ @p, or of an instruction word when P
is a port -- waits for a word; a node executes what arrives at a port
without advancing P; a blocked node goes on from the slot it stopped at
- fabric: the nodes there are and the table of how their ports are wired,
both changed while the nodes run; devices on a port; asleep and awake; a
step is every unblocked node executing one instruction word, then every
write with a reader waiting being handed over
- DECOMPOSITION.md section 6: four named ports withdrawn for V4_PORTS
numbered ones and wiring as data, as ruled
Verified: tests/test_fabric.c, 53 checks at both widths: two nodes exchange
words; an empty node is filled through its port by a device, and by another
node, and runs what it was sent; a word is passed on by a node in between;
a waiting node executes nothing; the wiring is changed while they run; a
node is put to sleep, woken and removed while looping; a node is born while
others run; the fabric is given more room. make -C v4 test and make -C v4
sanitize pass. The single-node products are unchanged: hosted-check on
three ISAs, and clean qemu with STARFORTH_V4=1 on amd64, aarch64 and
riscv64 with lines typed at each prompt (logs/20261006-074907, -075150,
-075532).
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>
(CATCH): while it is not zero, a line that ends in an error sets it to -1
and ends " ok" instead of " ERROR". (EMIT-HOOK): the xt of a word that is
given each character EMIT would send to the console. The prompt loop sets
(EMIT-HOOK) to 0 at the end of every line. docs/v4.0.0/NUCLEUS.md 6.3,
amended: it said one nucleus word would do.
Verified: make -C v4 test passes at both widths; by hand at the hosted
prompt, an unknown word and a division by zero are caught, the output hook
receives every character, and an uncaught error still says ERROR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64, one at a
time. Each prints the same PARITY:V4_NUCLEUS and PARITY:V4_CAPSULE lines as
the three hosted binaries (image_hash 0x60b74e4f87adb0ac, dict_hash
0x0baed67626b4fac4), then PARITY:OK and ok>.
Each run was ended once the prompt was in the log. Nothing was typed at a
bare-metal prompt. The capsules were unsigned: no signing key on this
machine. The v3 boot (STARFORTH_V4=0) was not re-run.
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>
- Each entry's flags cell also holds v3's four fields: denied, pinned,
mode and a 16-bit TTL.
- compile.v4: INTERPRET checks every word it is about to execute or
compile -- recheck at TTL 0, else count down; a denied word is refused
with v3's line, the stack emptied and the line ended.
- capsule/acl.v4: ACL-MODE@ ACL-MODE! ACL-TTL@ ACL-TTL! ACL-ALLOW@
ACL-ALLOW! ACL-PINNED? ACL-PIN ACL-INHERIT ACL-INIT-PRIMITIVES ACL-HEAT@
ACL-WORD-ID, and ACL-HOOK.
- capsule/ACL.fth: v3's ACL.4th as FORTH source the node compiles; loading
it switches access control on.
- v3 checks every execution, inside definitions too. v4's code is native,
so it checks a word when it is compiled as well as when interpreted; a
call compiled while the word was allowed is not checked again.
DECOMPOSITION.md 5.20 says so.
- ACL-HEAT@ is 0 until heat is readable (D-6).
- tests/test_host_quit.c: seven transcripts of the v3 binary; the policy
file loaded and exercised; COLD.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- capsule/log.v4: the level constants LOG-ERROR .. LOG-DEBUG, LOG-LEVEL!
and LOG-LEVEL@, LOG-ERROR" .. LOG-DEBUG" and LOG-ERROR-STR ..
LOG-DEBUG-STR. A message is printed if its level is at or below
LOG-LEVEL, as v3's line -- colour, level, text -- without the time of
day, which a node has not got.
- LOG-xxx" compiles like ." : the level, a call to (LOG"), the text. SEE
shows it as text. COLD puts LOG-LEVEL back to LOG-INFO.
- tests/test_host_quit.c: ten transcripts of the v3 binary, every level
at every setting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>