Skip to content

Nothing ships for tests alone: the five virt probes leave the shipped toybox, and two owner rulings enter reviewer.md - #636

Merged
Japabu merged 8 commits into
mainfrom
wt/toyos-rulesbatch
Oct 2, 2026
Merged

Japabu merged 8 commits into
mainfrom
wt/toyos-rulesbatch

Conversation

@Japabu

@Japabu Japabu commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

At ee6f425ba, on main at a31eec595. The branch was written on 2026-09-30 as code plus prompt rules. #678 has since rewritten all four prompts from the owner's later decisions, so every prompt rule this branch wrote is dropped: the merge at 10aed2d75 takes main's CLAUDE.md, implementer.md, orchestrator.md and reviewer.md whole. What remains is the code half and briefed prompt edits in three files.

What changed

The five virt probes leave the shipped toybox

The owner's ruling of 2026-09-30: nothing ships for tests alone; test-only programs live in test images, and the kernel's actuators stay, compiled only into test kernels. On main the shipped toybox answers debug_refused, first_entry, fp_isolation, preempt and unmap_touch, which only the AArch64 virt_ tests run.

  • userland/kernelprobe is a new userland workspace member that only tests/virtjobcase/system.toml and tests/virtsmpcase/system.toml name. The five probes moved from toybox with git mv, unchanged. main.rs dispatches on argv[0] and panics on any other name.
  • The two cases build [programs.kernelprobe], and their symlinks point at /system/bin/kernelprobe.
  • Toybox loses the five applets and its arch module.
  • The x86-64 half of that module is deleted, not moved. Its fp_isolation and first_entry were panics, and its read_unmap_read existed so the shipped x86-64 toybox compiled: no x86-64 test runs unmap_touch. kernelprobe is AArch64 only: its arch selector refuses any other architecture with a compile_error! (measured below). No x86-64 config names it.
  • tests/toyos.rs: two comments named toybox. The one over virt_timer_preempts is deleted, and its arm is one line like its four siblings; the other loses the word.

Issues

  • issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md is closed. Its exit is met: the x86-64 toybox answers neither name. git grep the-x86-toybox-ships finds no citation (exit 1).
  • issues/design-debt/the-shipped-image-carries-three-test-programs.md is filed for what stays in the shipped system.toml: proctest, input-test and toybox's spin. Its spin row is written for main after The guest suite keeps the 21 tests only a booted machine answers; the rest are metal, host or tracked #660: the two guest cases that ran spin are gone, and its one named use is a metal row the guest-suite track owes.

Prompt edits in three files, each briefed

  • reviewer.md, Fit: "Nothing ships for tests alone, and a diff that ships something for a test alone is a BLOCKER." The orchestrator's sentence, word for word. It ranks a diff, as the Forks clause does, so the three rows the filed issue records send no branch back. It ranks by what a thing is for and not by who runs it: mv and hexdump, which at this head only test binaries run, are not what it names.
  • reviewer.md, Forks: "LLVM and Rust are never changed to suit a ToyOS tool; a tool that cannot build them unchanged is the one that goes, and a diff that changes either for one is a BLOCKER."
  • implementer.md: the wait recipe said "under an explicit timeout". macOS has no timeout command (/bin/sh -c 'command -v timeout' exits 1 on this host, macOS 27.0.1). The recipe is now n=0; until <it is done> || [ $((n+=1)) -gt 50 ]; do sleep 2; done.
  • Root CLAUDE.md, Dependencies: "A third-party source ToyOS cannot build without changing it is carried as an unmodified-source packaging mirror with a byte-identity gate, not as a fork." becomes "Third-party source is used unmodified, pinned by hash, wherever only its packaging has to change; a fork is for source changes only." The file is 21 bytes shorter.

Against main, the four prompts differ by these edits and nothing else (git diff --numstat origin/main HEAD -- CLAUDE.md .claude: implementer.md 3/3, reviewer.md 3/0, CLAUDE.md 1/1).

The review of a098330c3

#636 (comment), answered in ee6f425ba, which is git diff --stat a098330c3 ee6f425ba: reviewer.md and userland/kernelprobe/src/arch/mod.rs, +2 −3.

  • BLOCKER, reviewer.md:53-54: the middle clause is deleted, and the sentence is the one quoted above. No other word of any prompt changes.
  • NOTE, the T14 boot: it has run, and Gates states the run, its verdict and its readback.
  • NOTE, the body's three descriptions of the Fit sentence: the Fit bullet above is the new sentence's, this section replaces the one that said "in the words the orchestrator briefed", and the Unsure bullet about the middle clause is gone.
  • REMOVE, userland/kernelprobe/src/arch/mod.rs:1: ", one module per architecture" is deleted.

Gates

Each on a clean tree. The host suite is at ee6f425ba; every other row is a reading of a098330c3 and was not run again, because the one commit since changes a prompt sentence and a source comment. Logs are under /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/: the host suite's in 636-r3/, the rest in 636-r2/.

gate head exit
cargo run -- --ci host ("Host: 64 step(s), all green", host.log:7070) ee6f425ba 0
cargo run -- --build-only a098330c3 0
cargo run -- --build-only --arch aarch64 a098330c3 0
cargo test --test toyos-build, the whole guest suite, 21 tests, 16 of them virt_ a098330c3 0
cargo build --target x86_64-unknown-toyos --profile toyos -p kernelprobe, which must fail a098330c3 101
the image check on both shipped images a098330c3 0
the image check with the whole change reverted a098330c3 1
cargo test --test toyos-build -- --metal --metal-readback <dir> toybox_file_tools, staging only a098330c3 2
the T14 boot of that staged image, cargo run --bin toyos-metal -- --image … --readback … --fat32-check, run by the orchestrator; verdict passed a098330c3 0
  • Guest. The five probes ran from kernelprobe and said their lines in virt_timer_preempts, virt_first_entry, virt_fp_isolation, virt_unmap_touch, virt_debug_refused, virt_smp, virt_el1_smp and virt_failed_ap_leaves_no_hole. No guest test is new and none changes behaviour: the same probes say the same lines from another binary.
  • kernelprobe for x86-64 fails with one error and nothing else: error: kernelprobe is built for AArch64 alone: its probes are the AArch64 guest's, at kernelprobe/src/arch/mod.rs:9:1. At e9701cdf4 the same command died at unresolved imports arch::first_entry, arch::fp_isolation.
  • The image check counts, in an image's bytes, the names debug_refused, first_entry, fp_isolation and unmap_touch and the line preempt: the counting thread. It exits 2 unless it also finds toybox's own refusal line there, so a green cannot be an image it could not read. At a098330c3 every count is 0 in target/bootable.img and target/bootable.aarch64.img, and in the two toybox binaries themselves.
  • Negative control. The whole change reverted onto a098330c3, which makes the working tree origin/main's (tree 41253219… both ways). Both images built there, exit 0 each, and the check exits 1: each image holds debug_refused 3, first_entry 2, fp_isolation 2, unmap_touch 8 and the preempt line 1. Restored and rebuilt, the check exits 0 again and git status is empty.
  • T14. The review of e9701cdf4 read that no guest runs a toybox applet, so the x86-64 toybox this diff edits ran nowhere. The shared boot with the one job test_rs_toybox_file_tools runs cp, mv and hexdump there and ends through reboot. It was staged at a098330c3 (exit 2 is the harness's Verdict::Staged) and then flashed and booted once by the orchestrator, on the LENOVO 20W0003AMZ: Nothing ships for tests alone: the five virt probes leave the shipped toybox, and two owner rulings enter reviewer.md #636 (comment).
    • The readback is 636-r2/metal/shared/. verdict.txt reads passed. kernel.log:696 is ===TEST_END test_rs_toybox_file_tools exit=0===. The kernel's spawn records in kernel.log count /system/bin/cp 5, /system/bin/mv 6, /system/bin/hexdump 11 and /system/bin/reboot 1, and its last line, 701, is init's power line.
    • The run's exit is the line t14-636-shared EXIT=0 (75s) in summary.txt, written by the orchestrator's loop. The run's own log, logs/t14-636-shared.log, carries no exit line: it ends at PASS: the machine booted ToyOS in 1165 ms.
    • It is the green arm only, and of a098330c3: the change targets no hardware, and nothing was staged or booted at ee6f425ba.

The check, the control's script and log, the revert patch, the x86-64 build's output and the staging run's log are in #636 (comment).

Not on root CLAUDE.md's high-risk list: test programs move between images, and no kernel, ABI or shipped behaviour changes but toybox answering five fewer names. The control is the brief's.

No new gate is in the tree. The image check is a measurement for this pull request. The rule it measured is now a sentence in reviewer.md, and what a shipped config or toybox's commands! names is something a reviewer reads.

Growth

git diff --shortstat origin/main...HEAD: 20 files, +91 −105.

  • Production, userland/ without its lockfile: +40 −69.
  • Tests, the two case configs and tests/toyos.rs: +11 −14.
  • userland/Cargo.lock: +7. Issues: +26 −18. Prompts: +7 −4.

Unsure

  • main is still untrue of "Nothing ships for tests alone" in three rows, which the filed issue records.
  • The image check sees a probe by its name or its line. A renamed probe would pass it, and another program's string reds it: on the staged metal image it exits 1 on one first_entry, which is cranelift's assertion failed: index > 0 || self.at_first_entry(). grep -a -c -F at_first_entry counts 1 in tests/toyos-rust-tests/tls-cranelift's built libtls_cranelift.so, 1 in the staged image and 0 in each shipped image, and the check counts first_entry 0 in each toybox binary. The other four counts in the staged image are 0.
  • The count of 50 keeps one wait under the 120 s a foreground command gets by default here; another harness may want another number.

🤖 Generated with Claude Code

https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm

Japabu and others added 2 commits September 30, 2026 15:13
CLAUDE.md's Principles gains a bullet after "Fail fast, trust nothing.":
nothing ships for tests alone. The orchestrator's brief gives it as the
owner's ruling of 2026-09-30, "i dont want to write kernel functionality
just for tests thats never used in production", confirmed with "yes".

Six accepted process rules go where the agent that applies them reads
them. Every addition is a pure insertion.

- reviewer.md, Tests: a check that a judge refuses something plants a
  sibling the judge must accept; a mutation counts once it is shown to
  build; a pull request body names a gate only under the head it ran at.
- reviewer.md, Growth: the pull request that meets a track milestone's
  exit deletes that milestone from its track.
- reviewer.md, Prose: a removal's sweep deletes the present-tense,
  forward-looking and instructive statements of what it removed and
  leaves dated, commit- or pull-request-anchored records of runs.
- orchestrator.md, Agents: a brief asks for the agent's own attribution
  lines, never pastes the orchestrator's.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
The owner's rulings of 2026-09-30, given to the orchestrator:

  "the agents should be able to talk to everyone and collaborate. move
  cpu heavy stuff as requests to you the orchestrator you can then queue
  them and bundle them and tell an agent maybe that it doesnt make sense.
  i dont like useless overtesting it just slows us down. we do one full
  test run if the pr is done anyways."

  On negative-control checks, to the orchestrator: "its not your job to
  verify or confirm stuff concerning point 3".

  CI already runs the host checks on every push, so agents do not repeat
  that gate locally.

The owner then asked for these edits to be retried ("try to let the agent
make the edits again") after the permission check refused them on the
previous round.

- implementer.md gains SendMessage in its tools: without it an
  implementer cannot message the orchestrator or an overlapping agent.
- implementer.md, "The brief is a fence": an agent may message any agent
  whose work overlaps its own.
- implementer.md, "Measure, build, test": CI runs the host checks; an
  agent runs only targeted checks, and minutes of CPU beyond that are a
  request to the orchestrator, who queues, bundles or declines it. Test to
  prove the change, not to cover the tree.
- orchestrator.md, Agents: every brief names the agents on overlapping
  code. Land: one full guest run of a finished pull request and its metal
  runs are the orchestrator's; negative controls are the implementer's
  and the reviewer's.

Deleted, because the rulings contradict them:

- implementer.md: the "Host tests only: `cargo run -- --ci host`, and
  `cargo run -- --build-only` at most ..." bullet.
- CLAUDE.md: "An agent verifies with host tests and builds the image at
  most;" ("Agents never run QEMU." and the orchestrator's clause stay).
- orchestrator.md: the mutation-loop sentences, "A mutation loop, guest
  or metal, starts on a clean worktree ... None runs while an agent edits
  that worktree."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu Japabu changed the title Nothing ships for tests alone; six review and briefing rules Nothing ships for tests alone; agents collaborate, CI owns the host gate, heavy runs are the orchestrator's Sep 30, 2026
@Japabu
Japabu marked this pull request as ready for review September 30, 2026 13:52
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review of 6c11159. CI host: success (run 36724881825). git diff --shortstat origin/main...HEAD: 4 files, +27 −9. All of it is prompt prose, with no production or test lines.

BLOCKER

  • CLAUDE.md:31: "A test build's fault injection is compiled out of what ships." No quoted owner word says this. The sentence permits kernel/Cargo.toml:116 test-actuators (SYS_DEBUG and every actuator behind it), which is exactly "kernel functionality just for tests thats never used in production". The prose decides against the ruling on the one mechanism the ruling covers. Delete the sentence. Whether actuators (and reviewer.md:98-101) survive the ruling is for the owner to decide, filed as kind: question. The one exception: the owner's "yes" was to this exact sentence, but neither the brief nor the PR body quotes it.
  • CLAUDE.md:31: "The shipped system carries no mechanism whose only user is a test" changes the ruling from writing kernel functionality to shipping anything:
    • On the kernel it is narrower: test-only code behind a cfg passes.
    • On userland it is wider. The shipped /system/bin/toybox (system.toml:151, userland/toybox/src/main.rs:39) compiles debug_refused, preempt, unmap_touch, first_entry and fp_isolation. Their only user is tests/virtjobcase/system.toml:21-25, so the tree violates the new principle the day it lands, and no issue is filed.
  • .claude/agents/implementer.md:23: "CI runs the host checks on every push" is false.
    • .github/workflows/ci.yml:21 runs host only when draft == false, and implementer.md:67 opens the PR as a draft.
    • This PR's own push run 36724833910 at 6c11159 skipped host. The green run 36724881825 came from the ready_for_review event.
    • Together with "never repeats CI's gate locally" and implementer.md:70 ("Mark it ready when your tests are green"), no host gate runs on any head before the PR is marked ready.
  • .claude/agents/orchestrator.md:63-64: "negative controls are … not the orchestrator's" goes further than the ruling, which was "not your job to verify or confirm".
    • Agents never run QEMU (CLAUDE.md:76) and the reviewer runs nothing (reviewer.md:10). So a guest negative control, which CLAUDE.md:97 and reviewer.md:83-84 still require, has nobody to run it.
    • The PR body's "Unsure" section says the orchestrator still runs mutated trees on request, and :71 still reverts them. Yet the deleted mutation-loop sentence was the procedure for that run, including "None runs while an agent edits that worktree".
    • The PR picks neither answer. Either the orchestrator runs requested mutations without judging them, and that procedure stays, or nobody runs them, and :71's clause and the two-check law go to the owner.
  • .claude/agents/implementer.md:27: "one targeted check per claim" is a limit the owner did not set. It contradicts:
    • CLAUDE.md:97: a high-risk change names two checks, a negative control and an independent oracle.
    • reviewer.md:31-34: mutations are hunted without limit on high-risk code.
    • implementer.md:78-80: every mutation a review names is run.

NOTE

  • .claude/agents/implementer.md:15-16: the ruling is "talk to everyone". The line narrows it to agents "whose work overlaps its own, to agree on shared code". reviewer.md:4 gets no SendMessage, so the reviewer can talk to nobody. The orchestrator decides whether the reviewer's isolation (reviewer.md:7-8) is kept. The implementer's new tools: entry does work: a local subagent transcript's SendMessage to: "main" returned "Message queued for the main conversation's next turn".
  • .claude/agents/implementer.md:39-40: one kind of request now has two channels. A guest or toolchain run goes by SendMessage (:25); a T14 run goes by request file plus a final line. One channel should go.
  • .claude/agents/implementer.md:25: nothing says what the implementer does between sending a request and the run happening. :32 says "Stay inside one turn while anything runs"; orchestrator.md:46-47 says "no agent idles in a poll loop".
  • .claude/agents/implementer.md:25: "a toolchain … build" does not say whether the sysroot rebuild counts. After an ABI edit, any build in a worktree triggers one (src/CLAUDE.md:21). If it counts, an ABI brief cannot compile anything without a round trip to the orchestrator.
  • .claude/agents/orchestrator.md:39-43: :39-40 says "a brief carries only the task"; :42-43 makes every brief carry the standing attribution rule. The two contradict each other four lines apart.
  • .claude/agents/reviewer.md:118-119 conflicts with The dependency rule: Rust, QEMU, and declared C or C++ tools ToyOS can one day build and run; removing machinery updates every prompt that names it #632 at e9262d7. git merge-tree --write-tree HEAD e9262d7a6 exits 0 (tree f92aec22f), so the text merges clean, but the rules clash:
  • .claude/agents/reviewer.md:118-119: "leaves dated … records of runs" conflicts with CLAUDE.md:94 ("a merge that deletes a document also deletes every citation to it"). A dated run record that names a deleted document is kept by one rule and deleted by the other.

REMOVE

  • CLAUDE.md:31: "tests exercise what ships, driven from outside by the harness". It goes beyond the ruling, and it is false of kernel-loom/ and toyos-sched/loom (tests/CLAUDE.md:25), which test models.
  • .claude/agents/orchestrator.md:43: ": a subagent's model differs from its caller's". False in general: a subagent can be spawned on its caller's model.
  • .claude/agents/orchestrator.md:63: "and its metal runs". It repeats :81, "You alone run the T14".
  • .claude/agents/reviewer.md:86-88: "on an architecture built with -D warnings, deleting a call can fail as dead code instead of reaching the test". An example beyond the accepted rule.
  • .claude/agents/reviewer.md:77-78, 85-86, 88-89, 118-119: the rationale tails ("so a met milestone is never briefed again …", "so a judge that refuses everything goes red", ": a merge of main brings code …", ", which stay true"). They go beyond the accepted rules.
  • PR body, "The owner's rulings": "CI already runs the host checks on every push, so agents do not repeat that gate locally." No owner word says it, it is false for drafts (ci.yml:21), and the body becomes main's record.
  • PR body, "Gates": "CI runs the host check on this push". The push's run 36724833910 skipped host.
  • PR body: the wc -w word-count line. It carries nothing the record needs.

SEND BACK

Japabu and others added 2 commits September 30, 2026 22:54
Main's edits to the four instruction files merged without conflict and
every hunk of both sides stands: implementer.md keeps main's "never run
`git submodule` in a linked worktree" beside this branch's bullets;
orchestrator.md keeps main's "a landing that changes how agents work"
beside this branch's Land line; reviewer.md keeps main's Instructions and
Arrivals bullets; CLAUDE.md keeps main's Dependencies rewrite and its
pointer to Arrivals beside this branch's principle and QEMU line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
The owner ruled that nothing ships for tests alone and that the five
toybox applets `tests/virtjobcase` and `tests/virtsmpcase` run move out
of the shipped image, so the rule is true when it lands.

Code:
- `userland/kernelprobe` is a new userland workspace member, named only
  by the two virt cases' `system.toml`: `debug_refused`, `first_entry`,
  `fp_isolation`, `preempt` and `unmap_touch`, moved from toybox by
  `git mv` unchanged, dispatched on argv[0], and an unknown name panics.
- Toybox loses the five and its `arch` module. The x86-64 half of that
  module is deleted rather than moved: it existed so the shipped x86-64
  toybox compiled, and no x86-64 config names kernelprobe, so nothing
  would compile it. kernelprobe built for x86-64 fails at its imports.
- `issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md`
  is closed: the x86-64 toybox answers neither name. No citation named it.
- `issues/design-debt/the-shipped-image-carries-three-test-programs.md`
  files what the principle still finds in the shipped `system.toml`:
  `proctest`, `input-test` and toybox's `spin`.

Rules (root `CLAUDE.md`, `.claude/agents/*.md`):
- The principle is the owner's sentence. Its "tests exercise what ships"
  clause and the fault-injection sentence the review found permissive
  are gone.
- Dependencies gains the owner's LLVM-and-Rust sentence; the firmware
  paragraph admits the CPU's own microcode, which the kernel loads.
- implementer.md: CI's host check runs on every push to a pull request
  marked ready, not on a draft (`.github/workflows/ci.yml`'s `host`
  condition). "One targeted check per claim" now sits beside the two
  checks for high-risk code and the review's mutations. Negative controls
  are the implementer's and the reviewer's to design and judge. Guest and
  T14 runs and compiler or LLVM builds go through one channel, the
  request file and a final `RUN REQUESTED:` line; SendMessage-to-main as
  a second channel is gone, and a sysroot build stays the implementer's
  so an ABI brief can build. An agent may message any agent. Attribution
  lines are the agent's own, a standing rule moved here from
  orchestrator.md, where it contradicted "a brief carries only the task".
- orchestrator.md: runs the guest and T14 runs requested and judges
  none; the mutation-loop procedure deleted in round one is back, since
  the orchestrator still runs those loops.
- reviewer.md: the rationale tails and the `-D warnings` example go; the
  removal-sweep sentence defers to Instructions and keeps no run record
  that cites a deleted document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu Japabu changed the title Nothing ships for tests alone; agents collaborate, CI owns the host gate, heavy runs are the orchestrator's Nothing ships for tests alone: the virt probes leave toybox; agents collaborate, and heavy runs are requests the orchestrator runs unjudged Sep 30, 2026
Japabu added a commit that referenced this pull request Sep 30, 2026
Review round 1 on 7eb4428.

- The microcode ruling orders the kernel to load what root CLAUDE.md's
  firmware rule still bars: it admits only device firmware that never
  executes on the CPU. PR #636 amends that sentence to admit the CPU's own
  microcode, which the kernel loads, and this branch lands after it. The
  defect regains the deleted question's pointer at that rule, as one line
  saying the rule admits the microcode once #636 lands.
- Step 6 of the small-kernel track loses its heading "The scheduler knows
  nothing about devices": step 5 now keeps the pass painting the panel, a
  device the pass reaches.
- The child-process track's stage headings lose " (ruled)", and stage 3
  loses "ruled; ": every stage carried it, so it distinguished nothing.
  The track is 35 bytes longer than on main (18608 -> 18643), at 260
  lines.
- doom.jpg's row loses "he keeps it, and rules that": the committed file
  shows it is kept, and "no licence question applies" carries the ruling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Japabu added a commit that referenced this pull request Oct 1, 2026
…table is tested, and Data Size 0 is refused

Review round 1 of #653 found a mutant (`is_none_or` -> `is_some_and` at the
extended-table size check) under which a 4..16-byte table that sums to 0
panicked at `table[EXT_HEADER..]`. The file is untrusted input, so the fix is
structural rather than one more test:

- The crate denies `clippy::indexing_slicing`, `arithmetic_side_effects`,
  `unwrap_used`, `expect_used`, `panic`, `panic_in_result_fn`, `unreachable`,
  `todo` and `unimplemented`, so `--clippy` refuses any future site that
  could panic on input. The tests allow them back: a test fails by panicking.
- Every length is taken by a checked split whose failure is a `Refusal`:
  the header by `split_first_chunk`, data and table by `split_at_checked`,
  the extended table's header by `split_first_chunk`, so its entries exist
  only as the remainder of a table that held a header. The size check
  compares `count.checked_mul(12)` with the entries' length.
- Header fields are read by destructuring a fixed `[u32; N]` out of a fixed
  `[u8; 4N]`; a mismatched N does not build. `Update` keeps the parsed
  revision, signature, flags, data and entries, so nothing re-reads bytes.

Intel's `intel-ucode/06-cc-02` at microcode-20260925 (bdc92abe), unmodified,
165,888 bytes, sha256 4e43bb4d..., is committed with its NOTICE and
COMMITTED_FILES rows: one update, revision 0x11c, flags 0x94, with four
extended signatures. A test selects its last, 0xe0652, to Load below 0x11c.
`build()` writes entry checksums with the formula the parser checks, so only
Intel's bytes test that formula.

Data Size 0 stands for 2000 bytes of data in updates for CPUs from long
before ToyOS's 2020 floor; the arm and `DEFAULT_DATA` go, and Data Size 0 is
refused as `ZeroDataSize`. An empty file is refused as truncated rather than
answering `NoMatch`: a file with no update is not an Intel file.

New tests kill the four survivors the review named: a table shorter than its
header, a count naming fewer entries than the table holds, the T14's entry
first in the extended table, and a Total Size of 2560. A valid 3072-byte update
kills a 2048-byte granule.

NOTICE: one section for `toyos-microcode/intel-ucode/*`; the copy of the
notice and disclaimer goes (licenses/ carries it), as does the paragraph that
restated the crate's doc. The issue loses the line about #636 and the sentence
that pointed at that copy; `src/licence.rs` loses the comment that restated
its row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu
Japabu marked this pull request as draft October 2, 2026 16:13
Japabu and others added 2 commits October 2, 2026 18:15
The four conflicts are the four prompts: CLAUDE.md, implementer.md,
orchestrator.md and reviewer.md. Each is taken from main whole. #678 rewrote
them from the owner's later decisions, which override every prompt rule this
branch wrote on 2026-09-30; none of this branch's prompt hunks survives.

Everything else merged without a conflict: the kernelprobe move, the two case
configs, the closed issue and the filed one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
…; one Dependencies sentence is said directly

reviewer.md, Fit: "Nothing ships for tests alone", the owner's ruling of
2026-09-30, as the sentence a reviewer applies to a program, applet or kernel
path a diff adds.

reviewer.md, Forks: the owner's "LLVM and Rust are never changed to suit a
ToyOS tool; a tool that cannot build them unchanged is the one that goes."

implementer.md: the wait recipe was "under an explicit `timeout`". macOS has
no `timeout` command (`command -v timeout` exits 1 on this host), and three
agents reported it on 2026-10-02. The recipe is now a counted `until` loop,
run to its bound and to its condition under /bin/sh, bash, zsh and dash here.

CLAUDE.md, Dependencies: the packaging-mirror sentence read as a
contradiction ("cannot build without changing it" beside "unmodified"). One
sentence replaces it with the same intent, as the owner approved: source is
used unmodified and pinned by hash wherever only packaging has to change, and
a fork is for source changes only.

The filed issue cites the ruling where it now lives, and its `spin` row is
made true of main after #660: the two guest cases that ran `spin` are gone,
and the one use the tree names is a metal row the guest-suite track owes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Evidence for the body at e9701cdf4: the image check, the negative control's script and its log, and the revert patch.

The check (shipped-check.sh, sha256 4b081e36970b7735f21a14460f6334d98a19b90b9a1db295653c371292c95ea1). It is not in the tree.

#!/bin/sh
# Exit 0: no image named carries a kernel probe. Exit 1: one does.
# Exit 2: an image holds no toybox, so the check read nothing.
status=0
for img in "$@"; do
    seen=$(LC_ALL=C grep -a -c -F -e 'toybox: unknown command' "$img")
    echo "$img: toybox's own refusal line: $seen"
    [ "$seen" -ge 1 ] || { echo "$img: no toybox in it, so the check read nothing"; exit 2; }
    for needle in debug_refused first_entry fp_isolation unmap_touch 'preempt: the counting thread'; do
        n=$(LC_ALL=C grep -a -c -F -e "$needle" "$img")
        echo "$img: $needle: $n"
        [ "$n" -eq 0 ] || status=1
    done
done
exit $status

Green arm, on the images cargo run -- --build-only and --build-only --arch aarch64 wrote at the head: EXIT=0.

e9701cdf4b4940672d893e6187b0abdcbfd401e9
target/bootable.img: toybox's own refusal line: 1
target/bootable.img: debug_refused: 0
target/bootable.img: first_entry: 0
target/bootable.img: fp_isolation: 0
target/bootable.img: unmap_touch: 0
target/bootable.img: preempt: the counting thread: 0
target/bootable.aarch64.img: toybox's own refusal line: 1
target/bootable.aarch64.img: debug_refused: 0
target/bootable.aarch64.img: first_entry: 0
target/bootable.aarch64.img: fp_isolation: 0
target/bootable.aarch64.img: unmap_touch: 0
target/bootable.aarch64.img: preempt: the counting thread: 0

Negative control (negative-control.sh): the whole change reverted, both images built, the check run, the tree restored, both images built again, the check run again.

#!/bin/sh
# The whole change reverted onto the head the green arm was measured on: the
# tree becomes origin/main's. Builds both shipped images there, runs the image
# check, restores, rebuilds and checks again.
S=/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-recut
cd /Users/jan/Dev/jan/toyos-rulesbatch || exit 9
echo "head: $(git rev-parse HEAD)"
echo "status before: [$(git status --porcelain --ignore-submodules=none)]"
git apply --check $S/revert-to-main.patch; echo "APPLY_CHECK=$?"
git apply $S/revert-to-main.patch; echo "APPLY=$?"
git diff --quiet origin/main; echo "TREE_DIFFERS_FROM_ORIGIN_MAIN=$?"
cargo run -- --build-only > $S/nc-build-x86.log 2>&1; echo "REVERTED_BUILD_X86=$?"
cargo run -- --build-only --arch aarch64 > $S/nc-build-aarch64.log 2>&1; echo "REVERTED_BUILD_AARCH64=$?"
sh $S/shipped-check.sh target/bootable.img target/bootable.aarch64.img; echo "REVERTED_CHECK=$?"
git apply -R $S/revert-to-main.patch; echo "RESTORE=$?"
echo "status after: [$(git status --porcelain --ignore-submodules=none)]"
cargo run -- --build-only > $S/nc-rebuild-x86.log 2>&1; echo "RESTORED_BUILD_X86=$?"
cargo run -- --build-only --arch aarch64 > $S/nc-rebuild-aarch64.log 2>&1; echo "RESTORED_BUILD_AARCH64=$?"
sh $S/shipped-check.sh target/bootable.img target/bootable.aarch64.img; echo "RESTORED_CHECK=$?"
echo "status at end: [$(git status --porcelain --ignore-submodules=none)]"

Its log. REVERTED_CHECK=1 is the red arm. TREE_DIFFERS_FROM_ORIGIN_MAIN=1 is that line's own defect, not a difference: git diff origin/main reads the files the patch re-creates as deleted, because the index does not track them. Measured the right way below.

head: e9701cdf4b4940672d893e6187b0abdcbfd401e9
status before: []
APPLY_CHECK=0
APPLY=0
TREE_DIFFERS_FROM_ORIGIN_MAIN=1
REVERTED_BUILD_X86=0
REVERTED_BUILD_AARCH64=0
target/bootable.img: toybox's own refusal line: 1
target/bootable.img: debug_refused: 3
target/bootable.img: first_entry: 2
target/bootable.img: fp_isolation: 2
target/bootable.img: unmap_touch: 8
target/bootable.img: preempt: the counting thread: 1
target/bootable.aarch64.img: toybox's own refusal line: 1
target/bootable.aarch64.img: debug_refused: 3
target/bootable.aarch64.img: first_entry: 2
target/bootable.aarch64.img: fp_isolation: 2
target/bootable.aarch64.img: unmap_touch: 8
target/bootable.aarch64.img: preempt: the counting thread: 1
REVERTED_CHECK=1
RESTORE=0
status after: []
RESTORED_BUILD_X86=0
RESTORED_BUILD_AARCH64=0
target/bootable.img: toybox's own refusal line: 1
target/bootable.img: debug_refused: 0
target/bootable.img: first_entry: 0
target/bootable.img: fp_isolation: 0
target/bootable.img: unmap_touch: 0
target/bootable.img: preempt: the counting thread: 0
target/bootable.aarch64.img: toybox's own refusal line: 1
target/bootable.aarch64.img: debug_refused: 0
target/bootable.aarch64.img: first_entry: 0
target/bootable.aarch64.img: fp_isolation: 0
target/bootable.aarch64.img: unmap_touch: 0
target/bootable.aarch64.img: preempt: the counting thread: 0
RESTORED_CHECK=0
status at end: []

The reverted working tree is origin/main's tree, by a scratch index (GIT_INDEX_FILE=<scratch> git add -A; git write-tree) against git rev-parse 'origin/main^{tree}':

reverted tree:    41253219982833789c4629410be1822f6c93f9c0
origin/main tree: 41253219982833789c4629410be1822f6c93f9c0

The revert patch is git diff --binary e9701cdf4 a31eec595, sha256 83523469fac972aba3da4dd3e1d2ff47301d33e025a9713bf0b4283bd4459fd8.

revert-to-main.patch
diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md
index 447fb5868..c88fb33d7 100644
--- a/.claude/agents/implementer.md
+++ b/.claude/agents/implementer.md
@@ -32,10 +32,9 @@ guess. Then build, then test on root `CLAUDE.md`'s tiers before anyone reviews:
 - A result is the command's own exit code: `<cmd> > <file> 2>&1; echo EXIT=$?`. A grepped
   `test result` line is not one, and a gate you did not run is a gate you do not claim.
 - Long commands run in the background with output to a file under the scratchpad the brief names.
-  Stay inside one turn while anything runs: block in the foreground on `n=0; until <it is done> ||
-  [ $((n+=1)) -gt 50 ]; do sleep 2; done`, which its count bounds, print a line, repeat; a long
-  `sleep` is refused. Ten minutes of silence kills you, and ending a turn to announce a wait strands
-  the work.
+  Stay inside one turn while anything runs: block in the foreground on `until <it is done>; do sleep
+  2; done` under an explicit `timeout`, print a line, repeat; a long `sleep` is refused. Ten minutes
+  of silence kills you, and ending a turn to announce a wait strands the work.
 - Nothing a pull request's evidence rests on, mutation patches and run logs included, lives only in
   a temporary directory: `/tmp` is wiped when the CLI restarts. Post mutation patches to the pull
   request as a comment.
diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md
index 40ea29dd4..6d1883b3d 100644
--- a/.claude/agents/reviewer.md
+++ b/.claude/agents/reviewer.md
@@ -50,8 +50,6 @@ if it meets the bar above; otherwise it is a NOTE.
   `arch::aarch64` in no generic kernel code; and none of them in a crate whose manifest
   `description` says pure.
   A `4096` in the kernel that means a page is a private copy of `mm::PAGE_SIZE`.
-  Nothing ships for tests alone: a program, an applet or a kernel path whose only user is a test
-  lives in a test image or a test kernel, and one a shipped image carries is a BLOCKER.
 - **Hosts.** A BLOCKER: a change that makes an app build on fewer of Linux under Wayland, macOS
   and Windows, or answers a host build failure by making the app ToyOS-only — by a split, a `cfg`
   that compiles what it does out of a host, or an `exempt` in its manifest — instead of fixing it
@@ -129,8 +127,6 @@ if it meets the bar above; otherwise it is a NOTE.
 - **Forks.** A broken rule of `implementer.md`'s "A fork" is a BLOCKER, and so is a lockfile or
   gitlink left naming a fork branch whose consumed commit the pull request changes. A search for
   callers that skipped the fork clones and `~/.cargo/git/checkouts/` searched part of the tree.
-  LLVM and Rust are never changed to suit a ToyOS tool; a tool that cannot build them unchanged is
-  the one that goes, and a diff that changes either for one is a BLOCKER.
 
 ## Prose
 
diff --git a/CLAUDE.md b/CLAUDE.md
index f2a665b4d..4c8de6657 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -55,7 +55,7 @@ There are no spec documents. A rule a reader can check lives in an agent's promp
 
 ## Dependencies
 
-**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates — one that does *our* job we write ourselves, and a driver crate never; third-party crates are used as published, and a fork carries a change written to upstream quality and goes when upstream has it. No upstream pull request is sent for now. Third-party source is used unmodified, pinned by hash, wherever only its packaging has to change; a fork is for source changes only. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS?
+**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates — one that does *our* job we write ourselves, and a driver crate never; third-party crates are used as published, and a fork carries a change written to upstream quality and goes when upstream has it. No upstream pull request is sent for now. A third-party source ToyOS cannot build without changing it is carried as an unmodified-source packaging mirror with a byte-identity gate, not as a fork. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS?
 
 Vendor firmware a device or CPU verifies by its maker's signature may be shipped: pinned by version and hash, redistributable unmodified, recorded in `NOTICE`. A device's is loaded only by its own driver through its IOMMU domain and never executes on the CPU; CPU microcode is loaded by the kernel. `NOTICE` names every committed third-party file with its hash, upstream and licence.
 
diff --git a/issues/design-debt/the-shipped-image-carries-three-test-programs.md b/issues/design-debt/the-shipped-image-carries-three-test-programs.md
deleted file mode 100644
index f43b8c0cc..000000000
--- a/issues/design-debt/the-shipped-image-carries-three-test-programs.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-status: open
-kind: defect
-opened: 2026-09-30
----
-
-# The shipped image carries three test programs
-
-"Nothing ships for tests alone" (`.claude/agents/reviewer.md`, Fit) is untrue
-of the shipped `system.toml` in three rows:
-
-- `proctest = {}`: `userland/proctest/README.md` calls it a test harness, and
-  nothing in the tree runs `/system/bin/proctest` but `proctest` itself.
-- `input-test = {}`: a test utility by its README, and nothing runs
-  `/system/bin/input-test`; `toyos-symbols/tests/real.rs` reads a frozen copy
-  under `toyos-symbols/tests/fixtures/`, not the shipped binary.
-- `"bin/spin" = "/system/bin/toybox"`: `userland/toybox/src/spin.rs` loops
-  forever, and nothing in the tree runs it; its one named use is a test, the
-  metal `sshd_exec` row
-  `issues/build/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md` owes.
-
-Found by `git grep -n -w -e proctest -e input-test` and
-`git grep -n -w -e spin -- system.toml tests userland issues/build`.
-
-**Exit condition**: the shipped `system.toml` names none of the three, and a
-test that needs one carries it in its own image alone.
diff --git a/issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md b/issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md
new file mode 100644
index 000000000..7dae8903a
--- /dev/null
+++ b/issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md
@@ -0,0 +1,18 @@
+---
+status: open
+kind: defect
+opened: 2026-09-29
+---
+
+# The x86-64 toybox ships two applets that only panic
+
+`userland/toybox/src/main.rs` puts `fp_isolation` and `first_entry` in one
+`commands!` list for every architecture, so the x86-64 toybox that ships in
+every image answers both names, and `userland/toybox/src/arch/x86_64.rs`'s
+body for each is a `panic!` naming where x86-64's probe lives or is owed
+(`test_rs_fpu_isolation`,
+`issues/isolation/a-new-x86-thread-enters-ring-3-holding-kernel-register-values.md`).
+A shipping binary carries two commands whose only behaviour is to die.
+
+**Exit condition**: the x86-64 toybox answers neither name with a panic —
+each is left out of its `commands!`, or runs a probe of x86-64's own.
diff --git a/tests/toyos.rs b/tests/toyos.rs
index 4011139c0..db3d987a6 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -1365,7 +1365,7 @@ fn judge_virt_job(mut qemu: QemuInstance, job: &str, said: &str) -> Result<Strin
     Ok(serial)
 }
 
-/// What `unmap_touch` says once every read of a page just unmapped,
+/// What toybox's `unmap_touch` says once every read of a page just unmapped,
 /// on the unmapping thread and on another, ended its process.
 const UNMAP_TOUCH_SAID: &str =
     "unmap_touch: 4 reads of a page just unmapped on the unmapping thread, and 4 on another";
@@ -1732,7 +1732,7 @@ fn run_screen_test(name: &str, profile: qemu::Profile, test_config: &Path) -> Re
             Ok(())
         }
         "virt_timer_preempts" => {
-            // Spelled in `userland/kernelprobe/src/preempt.rs`.
+            // Spelled in `userland/toybox/src/preempt.rs`.
             virt_job(profile, "preempt", "preempt: the counting thread was preempted twice")
         }
         "virt_fp_isolation" => virt_job(profile, "fp_isolation", "fp_isolation: v0-v31, FPCR and FPSR survived"),
diff --git a/tests/virtjobcase/system.toml b/tests/virtjobcase/system.toml
index 838f3dda1..190d1b33a 100644
--- a/tests/virtjobcase/system.toml
+++ b/tests/virtjobcase/system.toml
@@ -1,4 +1,4 @@
-# One CPU and the AArch64 guest's jobs, each a kernelprobe applet or, last because
+# One CPU and the AArch64 guest's jobs, each a toybox applet or, last because
 # a kernel it fails on rewrites the clock page every reader asserts on, the
 # suite's `test_rs_abuse_readonly_copyout`, which the harness puts on ROOT;
 # each job's line comes only if the kernel kept what that job asks about.
@@ -15,11 +15,11 @@ receives = ["power"]
 # CPU, and a job past the bound reboots the guest with the job named.
 args = ["--bound-ms=240000", "preempt", "fp_isolation", "first_entry", "unmap_touch", "debug_refused", "test_rs_abuse_readonly_copyout"]
 
-[programs.kernelprobe]
+[programs.toybox]
 
 [symlinks]
-"bin/preempt" = "/system/bin/kernelprobe"
-"bin/fp_isolation" = "/system/bin/kernelprobe"
-"bin/first_entry" = "/system/bin/kernelprobe"
-"bin/unmap_touch" = "/system/bin/kernelprobe"
-"bin/debug_refused" = "/system/bin/kernelprobe"
+"bin/preempt" = "/system/bin/toybox"
+"bin/fp_isolation" = "/system/bin/toybox"
+"bin/first_entry" = "/system/bin/toybox"
+"bin/unmap_touch" = "/system/bin/toybox"
+"bin/debug_refused" = "/system/bin/toybox"
diff --git a/tests/virtsmpcase/system.toml b/tests/virtsmpcase/system.toml
index bd0e9c302..e7dfb0bc4 100644
--- a/tests/virtsmpcase/system.toml
+++ b/tests/virtsmpcase/system.toml
@@ -13,7 +13,7 @@ receives = ["power"]
 # the bound reboots the guest with the job named.
 args = ["--bound-ms=240000", "unmap_touch"]
 
-[programs.kernelprobe]
+[programs.toybox]
 
 [symlinks]
-"bin/unmap_touch" = "/system/bin/kernelprobe"
+"bin/unmap_touch" = "/system/bin/toybox"
diff --git a/userland/Cargo.lock b/userland/Cargo.lock
index 5aa43186b..b21a2ec8e 100644
--- a/userland/Cargo.lock
+++ b/userland/Cargo.lock
@@ -1886,13 +1886,6 @@ dependencies = [
  "rand_core 0.10.0",
 ]
 
-[[package]]
-name = "kernelprobe"
-version = "0.1.0"
-dependencies = [
- "toyos-abi",
-]
-
 [[package]]
 name = "kurbo"
 version = "0.13.0"
diff --git a/userland/Cargo.toml b/userland/Cargo.toml
index 5533dec5f..c2aa2e8f8 100644
--- a/userland/Cargo.toml
+++ b/userland/Cargo.toml
@@ -15,7 +15,6 @@ members = [
     "init",
     "input-test",
     "inspect",
-    "kernelprobe",
     "logd",
     "metalprobe",
     "netd",
diff --git a/userland/kernelprobe/Cargo.toml b/userland/kernelprobe/Cargo.toml
deleted file mode 100644
index 09c5a5ae8..000000000
--- a/userland/kernelprobe/Cargo.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[package]
-name = "kernelprobe"
-version = "0.1.0"
-edition = "2021"
-license = "MIT OR Apache-2.0"
-description = "Test-only multi-call binary: the kernel probes the AArch64 guest's virt jobs run, one per name it is invoked by."
-
-[dependencies]
-toyos-abi = { path = "../../toyos-abi" }
diff --git a/userland/kernelprobe/src/arch/mod.rs b/userland/kernelprobe/src/arch/mod.rs
deleted file mode 100644
index 0af20cbff..000000000
--- a/userland/kernelprobe/src/arch/mod.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-//! What the probes must say in assembly, one module per architecture.
-
-#[cfg(target_arch = "aarch64")]
-mod aarch64;
-#[cfg(target_arch = "aarch64")]
-pub use aarch64::*;
diff --git a/userland/kernelprobe/src/main.rs b/userland/kernelprobe/src/main.rs
deleted file mode 100644
index 4728869ff..000000000
--- a/userland/kernelprobe/src/main.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-mod arch;
-mod debug_refused;
-mod preempt;
-mod unmap_touch;
-
-use arch::{first_entry, fp_isolation};
-
-fn main() {
-    let mut args = std::env::args();
-    let invoked_as = args.next().expect("argv[0] names the probe");
-    let args: Vec<String> = args.collect();
-    match std::path::Path::new(&invoked_as).file_name().and_then(|n| n.to_str()) {
-        Some("debug_refused") => debug_refused::main(args),
-        Some("first_entry") => first_entry::main(args),
-        Some("fp_isolation") => fp_isolation::main(args),
-        Some("preempt") => preempt::main(args),
-        Some("unmap_touch") => unmap_touch::main(args),
-        other => panic!("kernelprobe: no probe is called {other:?}"),
-    }
-}
diff --git a/userland/kernelprobe/src/arch/aarch64.rs b/userland/toybox/src/arch/aarch64.rs
similarity index 100%
rename from userland/kernelprobe/src/arch/aarch64.rs
rename to userland/toybox/src/arch/aarch64.rs
diff --git a/userland/toybox/src/arch/mod.rs b/userland/toybox/src/arch/mod.rs
new file mode 100644
index 000000000..95e0e9d15
--- /dev/null
+++ b/userland/toybox/src/arch/mod.rs
@@ -0,0 +1,11 @@
+//! What toybox's applets must say in assembly, one module per architecture.
+
+#[cfg(target_arch = "aarch64")]
+mod aarch64;
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::*;
+
+#[cfg(target_arch = "x86_64")]
+mod x86_64;
+#[cfg(target_arch = "x86_64")]
+pub use x86_64::*;
diff --git a/userland/toybox/src/arch/x86_64.rs b/userland/toybox/src/arch/x86_64.rs
new file mode 100644
index 000000000..c08523a99
--- /dev/null
+++ b/userland/toybox/src/arch/x86_64.rs
@@ -0,0 +1,51 @@
+//! x86-64's half of `unmap_touch`; its FP and first-entry probes are
+//! elsewhere, or owed.
+
+pub mod fp_isolation {
+    pub fn main(_args: Vec<String>) {
+        panic!("fp_isolation probes AArch64's FP/SIMD switch; x86-64's is test_rs_fpu_isolation");
+    }
+}
+
+pub mod first_entry {
+    pub fn main(_args: Vec<String>) {
+        panic!(
+            "first_entry probes AArch64's first entry to EL0; x86-64's is owed by \
+             issues/isolation/a-new-x86-thread-enters-ring-3-holding-kernel-register-values.md"
+        );
+    }
+}
+
+/// Reads the word at `page`, unmaps the `len` bytes there with `SYS_MUNMAP`
+/// and reads the word again, in one block, so nothing but the unmap itself
+/// can switch the CPU between the reads. Answers the first read, the unmap's
+/// answer and the second read.
+///
+/// # Safety
+/// `page` starts a mapping of `len` bytes that nothing else names. The second
+/// read ends the process unless the unmap was refused or left its
+/// translation standing.
+pub unsafe fn read_unmap_read(page: *const u64, len: usize) -> (u64, u64, u64) {
+    let (first, answer, second): (u64, u64, u64);
+    // SAFETY: the caller's; the two loads name `page` and the `syscall` is
+    // the ABI's (`toyos_abi::syscall`), which clobbers rax, rcx and r11.
+    unsafe {
+        core::arch::asm!(
+            "mov {first}, qword ptr [{page}]",
+            "syscall",
+            "mov {second}, qword ptr [{page}]",
+            page = in(reg) page,
+            first = out(reg) first,
+            second = lateout(reg) second,
+            in("rdi") toyos_abi::syscall::SYS_MUNMAP,
+            in("rsi") page,
+            in("rdx") len,
+            in("r8") 0u64,
+            in("r9") 0u64,
+            out("rax") answer,
+            out("rcx") _,
+            out("r11") _,
+        );
+    }
+    (first, answer, second)
+}
diff --git a/userland/kernelprobe/src/debug_refused.rs b/userland/toybox/src/debug_refused.rs
similarity index 100%
rename from userland/kernelprobe/src/debug_refused.rs
rename to userland/toybox/src/debug_refused.rs
diff --git a/userland/toybox/src/main.rs b/userland/toybox/src/main.rs
index 9e3df6877..449ce6e89 100644
--- a/userland/toybox/src/main.rs
+++ b/userland/toybox/src/main.rs
@@ -1,5 +1,7 @@
+mod arch;
 mod cat;
 mod cp;
+mod debug_refused;
 mod echo;
 mod free;
 mod grep;
@@ -9,6 +11,7 @@ mod ls;
 mod mkdir;
 mod mv;
 mod net;
+mod preempt;
 mod ps;
 mod pwd;
 mod reboot;
@@ -18,6 +21,7 @@ mod shutdown;
 mod spin;
 mod stats;
 mod tone;
+mod unmap_touch;
 
 macro_rules! commands {
     ($($name:ident),*) => {
@@ -30,7 +34,9 @@ macro_rules! commands {
     };
 }
 
-commands!(cat, cp, echo, free, grep, hexdump, locale, ls, mkdir, mv, net, ps, pwd, reboot, rm, screen, shutdown, spin, stats, tone);
+use arch::{first_entry, fp_isolation};
+
+commands!(cat, cp, debug_refused, echo, first_entry, fp_isolation, free, grep, hexdump, locale, ls, mkdir, mv, net, preempt, ps, pwd, reboot, rm, screen, shutdown, spin, stats, tone, unmap_touch);
 
 fn main() {
     let args: Vec<String> = std::env::args().collect();
diff --git a/userland/kernelprobe/src/preempt.rs b/userland/toybox/src/preempt.rs
similarity index 100%
rename from userland/kernelprobe/src/preempt.rs
rename to userland/toybox/src/preempt.rs
diff --git a/userland/kernelprobe/src/unmap_touch.rs b/userland/toybox/src/unmap_touch.rs
similarity index 100%
rename from userland/kernelprobe/src/unmap_touch.rs
rename to userland/toybox/src/unmap_touch.rs

kernelprobe for x86-64: cargo build --target x86_64-unknown-toyos --profile toyos -p kernelprobe in userland/, with RUSTUP_TOOLCHAIN naming this worktree's sysroot (c54f9128630496ae): EXIT=101.

   Compiling kernelprobe v0.1.0 (/Users/jan/Dev/jan/toyos-rulesbatch/userland/kernelprobe)
error[E0432]: unresolved imports `arch::first_entry`, `arch::fp_isolation`
 --> kernelprobe/src/main.rs:6:12
  |
6 | use arch::{first_entry, fp_isolation};
  |            ^^^^^^^^^^^  ^^^^^^^^^^^^ no `fp_isolation` in `arch`
  |            |
  |            no `first_entry` in `arch`

error[E0425]: cannot find function `read_unmap_read` in module `crate::arch`
  --> kernelprobe/src/unmap_touch.rs:75:57
   |
75 |     let (first, answer, second) = unsafe { crate::arch::read_unmap_read(page.cast(), PAGE) };
   |                                                         ^^^^^^^^^^^^^^^ not found in `crate::arch`

Some errors have detailed explanations: E0425, E0432.
For more information about an error, try `rustc --explain E0425`.
error: could not compile `kernelprobe` (bin "kernelprobe") due to 2 previous errors

@Japabu Japabu changed the title Nothing ships for tests alone: the virt probes leave toybox; agents collaborate, and heavy runs are requests the orchestrator runs unjudged Nothing ships for tests alone: the five virt probes leave the shipped toybox, and two owner rulings enter reviewer.md Oct 2, 2026
@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Review of e9701cdf4 against origin/main a31eec595. Second review of this pull request; the first read 6c11159c1.

Earlier BLOCKERs (the review of 6c11159c1)

  1. CLAUDE.md:31, "A test build's fault injection is compiled out of what ships": CLOSED. git grep of the sentence in CLAUDE.md and .claude at e9701cdf4 exits 1, and git diff origin/main e9701cdf4 -- CLAUDE.md .claude is three files, +9 −4.
  2. CLAUDE.md:31, "The shipped system carries no mechanism whose only user is a test", untrue of the tree with no issue filed: CLOSED. The sentence is gone by the same grep; shipped-check.sh exits 0 on both shipped images at the head and 1 with the whole change reverted (reverted tree 41253219…, origin/main's); the three rows left are filed. What replaces the sentence is the BLOCKER below.
  3. implementer.md:23, "CI runs the host checks on every push": CLOSED, same grep.
  4. orchestrator.md:63-64, negative controls "not the orchestrator's": CLOSED. git diff --quiet origin/main e9701cdf4 -- .claude/agents/orchestrator.md exits 0.
  5. implementer.md:27, "one targeted check per claim": CLOSED, same grep.

Growth. git diff --shortstat origin/main...e9701cdf4: 20 files, +90 −102. Production (userland/ less its lockfile) +37 −69. Tests +11 −11. Lockfile +7, issues +26 −18, prompts +9 −4. Production shrinks, and nothing more is deletable inside the fence.

The three briefed edits, against the brief

  • reviewer.md:132-133, Forks: the clause says no more than the ruling. "A diff that changes either for one" is "changed to suit a ToyOS tool" as a review meets it, and BLOCKER is the rank every other Forks rule carries.
  • reviewer.md:53-54, Fit: the clause says more than the ruling. BLOCKER below.
  • implementer.md:35-36: the fix and no sentence beyond it; one clause under REMOVE.
  • CLAUDE.md:58: the briefed sentence word for word, one for one, the file 14758 bytes to 14737.
  • Nothing else differs from main in any CLAUDE.md or .claude/agents/*.md.

BLOCKER

  • .claude/agents/reviewer.md:54 — "and one a shipped image carries is a BLOCKER" ranks the image's state, where the ruling places test-only things and the Forks clause of the same commit ranks "a diff" — the head's own system.toml:39,40,175 ships three (issues/design-debt/the-shipped-image-carries-three-test-programs.md), so this sentence read against this head sends it back, and every branch after it until that issue closes. Scope it as Forks is ("and a diff that ships one is a BLOCKER"), or take the three rows out here; the words are the orchestrator's to brief.

NOTE

  • T14 — one boot is owed before landing: the shared boot filtered to toybox_file_tools. No guest at this head runs a toybox applet on either architecture (the five x86-64 guests run none; tests/virtjobcase and tests/virtsmpcase no longer build it), so the x86-64 toybox whose commands! this diff edits has been built and grepped and never run, and src/metalimage.rs:88,112 ends every metal boot through its bin/reboot. That boot runs cp, mv and hexdump and ends through reboot. No second boot: endowment_denied and process_tree run the same binary, and process_tree rides proctreecase, not the shared boot the body's Unsure line names. Not a BLOCKER: the change targets no hardware, and commands! at the head is main's list less the five, which a reader checks.
  • PR body, the implementer.md bullet — "It ran to its bound and to its condition under /bin/sh, bash, zsh and dash here, and one full loop took 101 s" has no command, exit code or log on the pull request or in 636-recut/, which holds only loop.t0 — a claim about behaviour stands on its measurement: post it or delete the line.
  • userland/kernelprobe/src/arch/mod.rs:3-6 — the crate refuses x86_64-unknown-toyos, the target userland/.cargo/config.toml builds its workspace for, with unresolved imports arch::first_entry, arch::fp_isolation (the round's exit 101): a refusal, but not by name — say it in the selector, where target_arch may stand (#[cfg(not(target_arch = "aarch64"))] compile_error!).

REMOVE

  • tests/toyos.rs:1735 — // Spelled in userland/kernelprobe/src/preempt.rs. — a stale comment corrected instead of deleted, none of the three kinds, and its four sibling arms carry none.
  • .claude/agents/implementer.md:36 — ", which its count bounds" — it says what the recipe's own -gt 50 says.

Read for this review, under /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/: 636-recut/host.log (exit 0, "Host: 64 step(s), all green"), 636-recut/guest-all.log (exit 0, 21 of 21), 636-recut/negative-control.log, 636-recut/kernelprobe-x86.log; this review's own greps are in 636-review/.

SEND BACK

…refuses another architecture by name

Answers #636 (comment).

reviewer.md, Fit: "and one a shipped image carries is a BLOCKER" ranked the
image's state, so the sentence sent back every branch while system.toml
ships the three programs
issues/design-debt/the-shipped-image-carries-three-test-programs.md records.
It now ranks a diff, as the Forks clause beside it does: "and a diff that
ships one is a BLOCKER". The orchestrator briefed the words.

userland/kernelprobe/src/arch/mod.rs: the selector refuses any architecture
but AArch64 with a compile_error!. Before, a build for x86_64-unknown-toyos
died at `unresolved imports arch::first_entry, arch::fp_isolation`, which
names what is missing and not why. With the compile_error! rustc stops at it
and prints that one error.

tests/toyos.rs: the comment over `virt_timer_preempts` named the file its
line is spelled in. It is deleted, and the arm is one line like its four
siblings.

implementer.md: ", which its count bounds" said what the recipe's `-gt 50`
says, and is deleted; the paragraph is rewrapped and no other word changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Evidence for the body at a098330c3: the image check, the negative control's script and log, the revert patch, the x86-64 build of kernelprobe, and the staging run for the T14.

The check (shipped-check.sh, sha256 4b081e36970b7735f21a14460f6334d98a19b90b9a1db295653c371292c95ea1, the one #636 (comment) carries). It is not in the tree.

#!/bin/sh
# Exit 0: no image named carries a kernel probe. Exit 1: one does.
# Exit 2: an image holds no toybox, so the check read nothing.
status=0
for img in "$@"; do
    seen=$(LC_ALL=C grep -a -c -F -e 'toybox: unknown command' "$img")
    echo "$img: toybox's own refusal line: $seen"
    [ "$seen" -ge 1 ] || { echo "$img: no toybox in it, so the check read nothing"; exit 2; }
    for needle in debug_refused first_entry fp_isolation unmap_touch 'preempt: the counting thread'; do
        n=$(LC_ALL=C grep -a -c -F -e "$needle" "$img")
        echo "$img: $needle: $n"
        [ "$n" -eq 0 ] || status=1
    done
done
exit $status

Green arm, on the images cargo run -- --build-only and --build-only --arch aarch64 wrote at the head: EXIT=0.

a098330c3529c2624372e23eff3efb1159ff4dc4
target/bootable.img: toybox's own refusal line: 1
target/bootable.img: debug_refused: 0
target/bootable.img: first_entry: 0
target/bootable.img: fp_isolation: 0
target/bootable.img: unmap_touch: 0
target/bootable.img: preempt: the counting thread: 0
target/bootable.aarch64.img: toybox's own refusal line: 1
target/bootable.aarch64.img: debug_refused: 0
target/bootable.aarch64.img: first_entry: 0
target/bootable.aarch64.img: fp_isolation: 0
target/bootable.aarch64.img: unmap_touch: 0
target/bootable.aarch64.img: preempt: the counting thread: 0

On the two toybox binaries themselves, userland/target/{x86_64,aarch64}-unknown-toyos/toyos/toybox: EXIT=0.

a098330c3529c2624372e23eff3efb1159ff4dc4
userland/target/x86_64-unknown-toyos/toyos/toybox: toybox's own refusal line: 1
userland/target/x86_64-unknown-toyos/toyos/toybox: debug_refused: 0
userland/target/x86_64-unknown-toyos/toyos/toybox: first_entry: 0
userland/target/x86_64-unknown-toyos/toyos/toybox: fp_isolation: 0
userland/target/x86_64-unknown-toyos/toyos/toybox: unmap_touch: 0
userland/target/x86_64-unknown-toyos/toyos/toybox: preempt: the counting thread: 0
userland/target/aarch64-unknown-toyos/toyos/toybox: toybox's own refusal line: 1
userland/target/aarch64-unknown-toyos/toyos/toybox: debug_refused: 0
userland/target/aarch64-unknown-toyos/toyos/toybox: first_entry: 0
userland/target/aarch64-unknown-toyos/toyos/toybox: fp_isolation: 0
userland/target/aarch64-unknown-toyos/toyos/toybox: unmap_touch: 0
userland/target/aarch64-unknown-toyos/toyos/toybox: preempt: the counting thread: 0

Negative control (negative-control.sh): the whole change reverted, the reverted tree compared with origin/main's, both images built, the check run, the tree restored, both images built again, the check run again. The script's exit: EXIT=0.

#!/bin/sh
# The whole change reverted onto the head the green arm was measured on: the
# tree becomes origin/main's. Builds both shipped images there, runs the image
# check, restores, rebuilds and checks again.
S=/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2
cd /Users/jan/Dev/jan/toyos-rulesbatch || exit 9
echo "head: $(git rev-parse HEAD)"
echo "origin/main: $(git rev-parse origin/main)"
echo "status before: [$(git status --porcelain --ignore-submodules=none)]"
git diff --binary HEAD origin/main > $S/revert-to-main.patch; echo "PATCH=$?"
git apply --check $S/revert-to-main.patch; echo "APPLY_CHECK=$?"
git apply $S/revert-to-main.patch; echo "APPLY=$?"
rm -f $S/nc.index
GIT_INDEX_FILE=$S/nc.index git add -A; echo "SCRATCH_ADD=$?"
echo "reverted tree:    $(GIT_INDEX_FILE=$S/nc.index git write-tree)"
echo "origin/main tree: $(git rev-parse 'origin/main^{tree}')"
rm -f $S/nc.index
cargo run -- --build-only > $S/nc-build-x86.log 2>&1; echo "REVERTED_BUILD_X86=$?"
cargo run -- --build-only --arch aarch64 > $S/nc-build-aarch64.log 2>&1; echo "REVERTED_BUILD_AARCH64=$?"
sh $S/shipped-check.sh target/bootable.img target/bootable.aarch64.img; echo "REVERTED_CHECK=$?"
git apply -R $S/revert-to-main.patch; echo "RESTORE=$?"
echo "status after: [$(git status --porcelain --ignore-submodules=none)]"
cargo run -- --build-only > $S/nc-rebuild-x86.log 2>&1; echo "RESTORED_BUILD_X86=$?"
cargo run -- --build-only --arch aarch64 > $S/nc-rebuild-aarch64.log 2>&1; echo "RESTORED_BUILD_AARCH64=$?"
sh $S/shipped-check.sh target/bootable.img target/bootable.aarch64.img; echo "RESTORED_CHECK=$?"
echo "status at end: [$(git status --porcelain --ignore-submodules=none)]"
echo "head at end: $(git rev-parse HEAD)"

Its log. REVERTED_CHECK=1 is the red arm. The warning is git's, for the scratch index meeting the rust/ checkout.

head: a098330c3529c2624372e23eff3efb1159ff4dc4
origin/main: a31eec5951035691f296986008b7adb3fa5e3733
status before: []
PATCH=0
APPLY_CHECK=0
APPLY=0
warning: adding embedded git repository: rust
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint: 	git submodule add <url> rust
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint: 	git rm --cached rust
hint:
hint: See "git help submodule" for more information.
hint: Disable this message with "git config set advice.addEmbeddedRepo false"
SCRATCH_ADD=0
reverted tree:    41253219982833789c4629410be1822f6c93f9c0
origin/main tree: 41253219982833789c4629410be1822f6c93f9c0
REVERTED_BUILD_X86=0
REVERTED_BUILD_AARCH64=0
target/bootable.img: toybox's own refusal line: 1
target/bootable.img: debug_refused: 3
target/bootable.img: first_entry: 2
target/bootable.img: fp_isolation: 2
target/bootable.img: unmap_touch: 8
target/bootable.img: preempt: the counting thread: 1
target/bootable.aarch64.img: toybox's own refusal line: 1
target/bootable.aarch64.img: debug_refused: 3
target/bootable.aarch64.img: first_entry: 2
target/bootable.aarch64.img: fp_isolation: 2
target/bootable.aarch64.img: unmap_touch: 8
target/bootable.aarch64.img: preempt: the counting thread: 1
REVERTED_CHECK=1
RESTORE=0
status after: []
RESTORED_BUILD_X86=0
RESTORED_BUILD_AARCH64=0
target/bootable.img: toybox's own refusal line: 1
target/bootable.img: debug_refused: 0
target/bootable.img: first_entry: 0
target/bootable.img: fp_isolation: 0
target/bootable.img: unmap_touch: 0
target/bootable.img: preempt: the counting thread: 0
target/bootable.aarch64.img: toybox's own refusal line: 1
target/bootable.aarch64.img: debug_refused: 0
target/bootable.aarch64.img: first_entry: 0
target/bootable.aarch64.img: fp_isolation: 0
target/bootable.aarch64.img: unmap_touch: 0
target/bootable.aarch64.img: preempt: the counting thread: 0
RESTORED_CHECK=0
status at end: []
head at end: a098330c3529c2624372e23eff3efb1159ff4dc4

The revert patch is git diff --binary a098330c3 a31eec595, sha256 e7c1585dc49f76cf241f9bd916a7d98c4ccdad55ece0e7ad61fdb71dc65eeba6.

revert-to-main.patch
diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md
index 76e4b54a2..c88fb33d7 100644
--- a/.claude/agents/implementer.md
+++ b/.claude/agents/implementer.md
@@ -32,9 +32,9 @@ guess. Then build, then test on root `CLAUDE.md`'s tiers before anyone reviews:
 - A result is the command's own exit code: `<cmd> > <file> 2>&1; echo EXIT=$?`. A grepped
   `test result` line is not one, and a gate you did not run is a gate you do not claim.
 - Long commands run in the background with output to a file under the scratchpad the brief names.
-  Stay inside one turn while anything runs: block in the foreground on `n=0; until <it is done> ||
-  [ $((n+=1)) -gt 50 ]; do sleep 2; done`, print a line, repeat; a long `sleep` is refused. Ten
-  minutes of silence kills you, and ending a turn to announce a wait strands the work.
+  Stay inside one turn while anything runs: block in the foreground on `until <it is done>; do sleep
+  2; done` under an explicit `timeout`, print a line, repeat; a long `sleep` is refused. Ten minutes
+  of silence kills you, and ending a turn to announce a wait strands the work.
 - Nothing a pull request's evidence rests on, mutation patches and run logs included, lives only in
   a temporary directory: `/tmp` is wiped when the CLI restarts. Post mutation patches to the pull
   request as a comment.
diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md
index 63dcca51f..6d1883b3d 100644
--- a/.claude/agents/reviewer.md
+++ b/.claude/agents/reviewer.md
@@ -50,8 +50,6 @@ if it meets the bar above; otherwise it is a NOTE.
   `arch::aarch64` in no generic kernel code; and none of them in a crate whose manifest
   `description` says pure.
   A `4096` in the kernel that means a page is a private copy of `mm::PAGE_SIZE`.
-  Nothing ships for tests alone: a program, an applet or a kernel path whose only user is a test
-  lives in a test image or a test kernel, and a diff that ships one is a BLOCKER.
 - **Hosts.** A BLOCKER: a change that makes an app build on fewer of Linux under Wayland, macOS
   and Windows, or answers a host build failure by making the app ToyOS-only — by a split, a `cfg`
   that compiles what it does out of a host, or an `exempt` in its manifest — instead of fixing it
@@ -129,8 +127,6 @@ if it meets the bar above; otherwise it is a NOTE.
 - **Forks.** A broken rule of `implementer.md`'s "A fork" is a BLOCKER, and so is a lockfile or
   gitlink left naming a fork branch whose consumed commit the pull request changes. A search for
   callers that skipped the fork clones and `~/.cargo/git/checkouts/` searched part of the tree.
-  LLVM and Rust are never changed to suit a ToyOS tool; a tool that cannot build them unchanged is
-  the one that goes, and a diff that changes either for one is a BLOCKER.
 
 ## Prose
 
diff --git a/CLAUDE.md b/CLAUDE.md
index f2a665b4d..4c8de6657 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -55,7 +55,7 @@ There are no spec documents. A rule a reader can check lives in an agent's promp
 
 ## Dependencies
 
-**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates — one that does *our* job we write ourselves, and a driver crate never; third-party crates are used as published, and a fork carries a change written to upstream quality and goes when upstream has it. No upstream pull request is sent for now. Third-party source is used unmodified, pinned by hash, wherever only its packaging has to change; a fork is for source changes only. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS?
+**Rust** and **QEMU** for development, on any host OS and architecture — the development machine is nothing special. Beside them, where no Rust tool does the job, only C or C++ tools ToyOS can one day build and run (Python, Perl, CMake, make), each declared where `reviewer.md`'s Arrivals says. No binary for one host OS alone: a macOS binary is a hard no, and "only for tests" does not soften it. ToyOS's own code is Rust; it writes no Python, Perl or shell of its own. Only general and widely used crates — one that does *our* job we write ourselves, and a driver crate never; third-party crates are used as published, and a fork carries a change written to upstream quality and goes when upstream has it. No upstream pull request is sent for now. A third-party source ToyOS cannot build without changing it is carried as an unmodified-source packaging mirror with a byte-identity gate, not as a fork. The north star is **self-hosting**: ToyOS rebuilds itself on ToyOS and reproduces the host's bytes, and nothing — build, test, or verification — rests on a host binary; a bootstrap from source with no binary seed is out of scope. Ask of anything new: could this ever run inside ToyOS?
 
 Vendor firmware a device or CPU verifies by its maker's signature may be shipped: pinned by version and hash, redistributable unmodified, recorded in `NOTICE`. A device's is loaded only by its own driver through its IOMMU domain and never executes on the CPU; CPU microcode is loaded by the kernel. `NOTICE` names every committed third-party file with its hash, upstream and licence.
 
diff --git a/issues/design-debt/the-shipped-image-carries-three-test-programs.md b/issues/design-debt/the-shipped-image-carries-three-test-programs.md
deleted file mode 100644
index f43b8c0cc..000000000
--- a/issues/design-debt/the-shipped-image-carries-three-test-programs.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-status: open
-kind: defect
-opened: 2026-09-30
----
-
-# The shipped image carries three test programs
-
-"Nothing ships for tests alone" (`.claude/agents/reviewer.md`, Fit) is untrue
-of the shipped `system.toml` in three rows:
-
-- `proctest = {}`: `userland/proctest/README.md` calls it a test harness, and
-  nothing in the tree runs `/system/bin/proctest` but `proctest` itself.
-- `input-test = {}`: a test utility by its README, and nothing runs
-  `/system/bin/input-test`; `toyos-symbols/tests/real.rs` reads a frozen copy
-  under `toyos-symbols/tests/fixtures/`, not the shipped binary.
-- `"bin/spin" = "/system/bin/toybox"`: `userland/toybox/src/spin.rs` loops
-  forever, and nothing in the tree runs it; its one named use is a test, the
-  metal `sshd_exec` row
-  `issues/build/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md` owes.
-
-Found by `git grep -n -w -e proctest -e input-test` and
-`git grep -n -w -e spin -- system.toml tests userland issues/build`.
-
-**Exit condition**: the shipped `system.toml` names none of the three, and a
-test that needs one carries it in its own image alone.
diff --git a/issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md b/issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md
new file mode 100644
index 000000000..7dae8903a
--- /dev/null
+++ b/issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md
@@ -0,0 +1,18 @@
+---
+status: open
+kind: defect
+opened: 2026-09-29
+---
+
+# The x86-64 toybox ships two applets that only panic
+
+`userland/toybox/src/main.rs` puts `fp_isolation` and `first_entry` in one
+`commands!` list for every architecture, so the x86-64 toybox that ships in
+every image answers both names, and `userland/toybox/src/arch/x86_64.rs`'s
+body for each is a `panic!` naming where x86-64's probe lives or is owed
+(`test_rs_fpu_isolation`,
+`issues/isolation/a-new-x86-thread-enters-ring-3-holding-kernel-register-values.md`).
+A shipping binary carries two commands whose only behaviour is to die.
+
+**Exit condition**: the x86-64 toybox answers neither name with a panic —
+each is left out of its `commands!`, or runs a probe of x86-64's own.
diff --git a/tests/toyos.rs b/tests/toyos.rs
index f3da8dfc1..db3d987a6 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -1365,7 +1365,7 @@ fn judge_virt_job(mut qemu: QemuInstance, job: &str, said: &str) -> Result<Strin
     Ok(serial)
 }
 
-/// What `unmap_touch` says once every read of a page just unmapped,
+/// What toybox's `unmap_touch` says once every read of a page just unmapped,
 /// on the unmapping thread and on another, ended its process.
 const UNMAP_TOUCH_SAID: &str =
     "unmap_touch: 4 reads of a page just unmapped on the unmapping thread, and 4 on another";
@@ -1731,7 +1731,10 @@ fn run_screen_test(name: &str, profile: qemu::Profile, test_config: &Path) -> Re
             }
             Ok(())
         }
-        "virt_timer_preempts" => virt_job(profile, "preempt", "preempt: the counting thread was preempted twice"),
+        "virt_timer_preempts" => {
+            // Spelled in `userland/toybox/src/preempt.rs`.
+            virt_job(profile, "preempt", "preempt: the counting thread was preempted twice")
+        }
         "virt_fp_isolation" => virt_job(profile, "fp_isolation", "fp_isolation: v0-v31, FPCR and FPSR survived"),
         "virt_first_entry" => virt_job(profile, "first_entry", "first_entry: x1-x30 were zero"),
         "virt_unmap_touch" => virt_job(profile, "unmap_touch", UNMAP_TOUCH_SAID),
diff --git a/tests/virtjobcase/system.toml b/tests/virtjobcase/system.toml
index 838f3dda1..190d1b33a 100644
--- a/tests/virtjobcase/system.toml
+++ b/tests/virtjobcase/system.toml
@@ -1,4 +1,4 @@
-# One CPU and the AArch64 guest's jobs, each a kernelprobe applet or, last because
+# One CPU and the AArch64 guest's jobs, each a toybox applet or, last because
 # a kernel it fails on rewrites the clock page every reader asserts on, the
 # suite's `test_rs_abuse_readonly_copyout`, which the harness puts on ROOT;
 # each job's line comes only if the kernel kept what that job asks about.
@@ -15,11 +15,11 @@ receives = ["power"]
 # CPU, and a job past the bound reboots the guest with the job named.
 args = ["--bound-ms=240000", "preempt", "fp_isolation", "first_entry", "unmap_touch", "debug_refused", "test_rs_abuse_readonly_copyout"]
 
-[programs.kernelprobe]
+[programs.toybox]
 
 [symlinks]
-"bin/preempt" = "/system/bin/kernelprobe"
-"bin/fp_isolation" = "/system/bin/kernelprobe"
-"bin/first_entry" = "/system/bin/kernelprobe"
-"bin/unmap_touch" = "/system/bin/kernelprobe"
-"bin/debug_refused" = "/system/bin/kernelprobe"
+"bin/preempt" = "/system/bin/toybox"
+"bin/fp_isolation" = "/system/bin/toybox"
+"bin/first_entry" = "/system/bin/toybox"
+"bin/unmap_touch" = "/system/bin/toybox"
+"bin/debug_refused" = "/system/bin/toybox"
diff --git a/tests/virtsmpcase/system.toml b/tests/virtsmpcase/system.toml
index bd0e9c302..e7dfb0bc4 100644
--- a/tests/virtsmpcase/system.toml
+++ b/tests/virtsmpcase/system.toml
@@ -13,7 +13,7 @@ receives = ["power"]
 # the bound reboots the guest with the job named.
 args = ["--bound-ms=240000", "unmap_touch"]
 
-[programs.kernelprobe]
+[programs.toybox]
 
 [symlinks]
-"bin/unmap_touch" = "/system/bin/kernelprobe"
+"bin/unmap_touch" = "/system/bin/toybox"
diff --git a/userland/Cargo.lock b/userland/Cargo.lock
index 5aa43186b..b21a2ec8e 100644
--- a/userland/Cargo.lock
+++ b/userland/Cargo.lock
@@ -1886,13 +1886,6 @@ dependencies = [
  "rand_core 0.10.0",
 ]
 
-[[package]]
-name = "kernelprobe"
-version = "0.1.0"
-dependencies = [
- "toyos-abi",
-]
-
 [[package]]
 name = "kurbo"
 version = "0.13.0"
diff --git a/userland/Cargo.toml b/userland/Cargo.toml
index 5533dec5f..c2aa2e8f8 100644
--- a/userland/Cargo.toml
+++ b/userland/Cargo.toml
@@ -15,7 +15,6 @@ members = [
     "init",
     "input-test",
     "inspect",
-    "kernelprobe",
     "logd",
     "metalprobe",
     "netd",
diff --git a/userland/kernelprobe/Cargo.toml b/userland/kernelprobe/Cargo.toml
deleted file mode 100644
index 09c5a5ae8..000000000
--- a/userland/kernelprobe/Cargo.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[package]
-name = "kernelprobe"
-version = "0.1.0"
-edition = "2021"
-license = "MIT OR Apache-2.0"
-description = "Test-only multi-call binary: the kernel probes the AArch64 guest's virt jobs run, one per name it is invoked by."
-
-[dependencies]
-toyos-abi = { path = "../../toyos-abi" }
diff --git a/userland/kernelprobe/src/arch/mod.rs b/userland/kernelprobe/src/arch/mod.rs
deleted file mode 100644
index d1bc784fe..000000000
--- a/userland/kernelprobe/src/arch/mod.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-//! What the probes must say in assembly, one module per architecture.
-
-#[cfg(target_arch = "aarch64")]
-mod aarch64;
-#[cfg(target_arch = "aarch64")]
-pub use aarch64::*;
-
-#[cfg(not(target_arch = "aarch64"))]
-compile_error!("kernelprobe is built for AArch64 alone: its probes are the AArch64 guest's");
diff --git a/userland/kernelprobe/src/main.rs b/userland/kernelprobe/src/main.rs
deleted file mode 100644
index 4728869ff..000000000
--- a/userland/kernelprobe/src/main.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-mod arch;
-mod debug_refused;
-mod preempt;
-mod unmap_touch;
-
-use arch::{first_entry, fp_isolation};
-
-fn main() {
-    let mut args = std::env::args();
-    let invoked_as = args.next().expect("argv[0] names the probe");
-    let args: Vec<String> = args.collect();
-    match std::path::Path::new(&invoked_as).file_name().and_then(|n| n.to_str()) {
-        Some("debug_refused") => debug_refused::main(args),
-        Some("first_entry") => first_entry::main(args),
-        Some("fp_isolation") => fp_isolation::main(args),
-        Some("preempt") => preempt::main(args),
-        Some("unmap_touch") => unmap_touch::main(args),
-        other => panic!("kernelprobe: no probe is called {other:?}"),
-    }
-}
diff --git a/userland/kernelprobe/src/arch/aarch64.rs b/userland/toybox/src/arch/aarch64.rs
similarity index 100%
rename from userland/kernelprobe/src/arch/aarch64.rs
rename to userland/toybox/src/arch/aarch64.rs
diff --git a/userland/toybox/src/arch/mod.rs b/userland/toybox/src/arch/mod.rs
new file mode 100644
index 000000000..95e0e9d15
--- /dev/null
+++ b/userland/toybox/src/arch/mod.rs
@@ -0,0 +1,11 @@
+//! What toybox's applets must say in assembly, one module per architecture.
+
+#[cfg(target_arch = "aarch64")]
+mod aarch64;
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::*;
+
+#[cfg(target_arch = "x86_64")]
+mod x86_64;
+#[cfg(target_arch = "x86_64")]
+pub use x86_64::*;
diff --git a/userland/toybox/src/arch/x86_64.rs b/userland/toybox/src/arch/x86_64.rs
new file mode 100644
index 000000000..c08523a99
--- /dev/null
+++ b/userland/toybox/src/arch/x86_64.rs
@@ -0,0 +1,51 @@
+//! x86-64's half of `unmap_touch`; its FP and first-entry probes are
+//! elsewhere, or owed.
+
+pub mod fp_isolation {
+    pub fn main(_args: Vec<String>) {
+        panic!("fp_isolation probes AArch64's FP/SIMD switch; x86-64's is test_rs_fpu_isolation");
+    }
+}
+
+pub mod first_entry {
+    pub fn main(_args: Vec<String>) {
+        panic!(
+            "first_entry probes AArch64's first entry to EL0; x86-64's is owed by \
+             issues/isolation/a-new-x86-thread-enters-ring-3-holding-kernel-register-values.md"
+        );
+    }
+}
+
+/// Reads the word at `page`, unmaps the `len` bytes there with `SYS_MUNMAP`
+/// and reads the word again, in one block, so nothing but the unmap itself
+/// can switch the CPU between the reads. Answers the first read, the unmap's
+/// answer and the second read.
+///
+/// # Safety
+/// `page` starts a mapping of `len` bytes that nothing else names. The second
+/// read ends the process unless the unmap was refused or left its
+/// translation standing.
+pub unsafe fn read_unmap_read(page: *const u64, len: usize) -> (u64, u64, u64) {
+    let (first, answer, second): (u64, u64, u64);
+    // SAFETY: the caller's; the two loads name `page` and the `syscall` is
+    // the ABI's (`toyos_abi::syscall`), which clobbers rax, rcx and r11.
+    unsafe {
+        core::arch::asm!(
+            "mov {first}, qword ptr [{page}]",
+            "syscall",
+            "mov {second}, qword ptr [{page}]",
+            page = in(reg) page,
+            first = out(reg) first,
+            second = lateout(reg) second,
+            in("rdi") toyos_abi::syscall::SYS_MUNMAP,
+            in("rsi") page,
+            in("rdx") len,
+            in("r8") 0u64,
+            in("r9") 0u64,
+            out("rax") answer,
+            out("rcx") _,
+            out("r11") _,
+        );
+    }
+    (first, answer, second)
+}
diff --git a/userland/kernelprobe/src/debug_refused.rs b/userland/toybox/src/debug_refused.rs
similarity index 100%
rename from userland/kernelprobe/src/debug_refused.rs
rename to userland/toybox/src/debug_refused.rs
diff --git a/userland/toybox/src/main.rs b/userland/toybox/src/main.rs
index 9e3df6877..449ce6e89 100644
--- a/userland/toybox/src/main.rs
+++ b/userland/toybox/src/main.rs
@@ -1,5 +1,7 @@
+mod arch;
 mod cat;
 mod cp;
+mod debug_refused;
 mod echo;
 mod free;
 mod grep;
@@ -9,6 +11,7 @@ mod ls;
 mod mkdir;
 mod mv;
 mod net;
+mod preempt;
 mod ps;
 mod pwd;
 mod reboot;
@@ -18,6 +21,7 @@ mod shutdown;
 mod spin;
 mod stats;
 mod tone;
+mod unmap_touch;
 
 macro_rules! commands {
     ($($name:ident),*) => {
@@ -30,7 +34,9 @@ macro_rules! commands {
     };
 }
 
-commands!(cat, cp, echo, free, grep, hexdump, locale, ls, mkdir, mv, net, ps, pwd, reboot, rm, screen, shutdown, spin, stats, tone);
+use arch::{first_entry, fp_isolation};
+
+commands!(cat, cp, debug_refused, echo, first_entry, fp_isolation, free, grep, hexdump, locale, ls, mkdir, mv, net, preempt, ps, pwd, reboot, rm, screen, shutdown, spin, stats, tone, unmap_touch);
 
 fn main() {
     let args: Vec<String> = std::env::args().collect();
diff --git a/userland/kernelprobe/src/preempt.rs b/userland/toybox/src/preempt.rs
similarity index 100%
rename from userland/kernelprobe/src/preempt.rs
rename to userland/toybox/src/preempt.rs
diff --git a/userland/kernelprobe/src/unmap_touch.rs b/userland/toybox/src/unmap_touch.rs
similarity index 100%
rename from userland/kernelprobe/src/unmap_touch.rs
rename to userland/toybox/src/unmap_touch.rs

kernelprobe for x86-64: cargo build --target x86_64-unknown-toyos --profile toyos -p kernelprobe in userland/, with RUSTUP_TOOLCHAIN naming this worktree's sysroot (c54f9128630496ae): EXIT=101.

   Compiling kernelprobe v0.1.0 (/Users/jan/Dev/jan/toyos-rulesbatch/userland/kernelprobe)
error: kernelprobe is built for AArch64 alone: its probes are the AArch64 guest's
 --> kernelprobe/src/arch/mod.rs:9:1
  |
9 | compile_error!("kernelprobe is built for AArch64 alone: its probes are the AArch64 guest's");
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: could not compile `kernelprobe` (bin "kernelprobe") due to 1 previous error

The T14 staging run: cargo test --test toyos-build -- --metal --metal-readback <dir> toybox_file_tools at a098330c3529c2624372e23eff3efb1159ff4dc4, git status --porcelain --ignore-submodules=none empty: EXIT=2, which is the harness's Verdict::Staged (tests/toyos.rs:3742). <dir> is /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal.

    Blocking waiting for file lock on package cache
    Finished `test` profile [optimized + debuginfo] target(s) in 0.10s
     Running tests/toyos.rs (target/debug/deps/toyos_build-8802c76dc0791521)
[toyos] Building Rust tests...
[toyos] Compiling 136 C tests, and attempting 24 declared ones...
[toyos] no metal registration matches filter Some("toybox_file_tools"); the shared boots' members are not filtered by name
[metal] 0 registration(s) and 1 shared member(s) over 1 boot(s)
[metal] shared: 1 job(s), armed with [] — /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img
# One boot per image. Each invocation is `cargo <words>` from this worktree.

shared
  image: /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img
  cargo run --bin toyos-metal -- --image /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img --readback /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared --fat32-check
[metal] staged 1 image(s); /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/request.txt lists them. The machine was not touched, so this run establishes nothing about it.
error: test failed, to rerun pass `--test toyos-build`

Caused by:
  process didn't exit successfully: `/Users/jan/Dev/jan/toyos-rulesbatch/target/debug/deps/toyos_build-8802c76dc0791521 --metal --metal-readback /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal toybox_file_tools` (exit status: 2)

The check on the staged image, metal/shared/image.img (sha256 65e2c04ab5e9015236599f2eea43dbcf8159fa636c22a4c870bf5fdc2862922a): EXIT=1.

/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img: toybox's own refusal line: 1
/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img: debug_refused: 0
/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img: first_entry: 1
/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img: fp_isolation: 0
/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img: unmap_touch: 0
/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/636-r2/metal/shared/image.img: preempt: the counting thread: 0

Its one first_entry is at byte 120094873, inside assertion failed: index > 0 || self.at_first_entry(), next to assertion failed: index < (::cranelift_entity::__core::u32::MAX as usize). grep -a -c -F at_first_entry counts 1 in the staged image and 0 in each shipped image.

@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

T14 evidence at a098330c3, run by the orchestrator: the one boot the round staged, shared filtered to toybox_file_tools, flashed and booted once (cargo run --bin toyos-metal -- --image … --readback … --fat32-check from the worktree, clean at the head before and after). Machine LENOVO 20W0003AMZ, BIOS N34ET71W (1.71).

  • image sha256 65e2c04ab5e9015236599f2eea43dbcf8159fa636c22a4c870bf5fdc2862922a, armed with boot-deadline=120000
  • toyos-metal EXIT=0, verdict passed, booted in 1165 ms; toyos-fat32-check: the log partition's 35651584 bytes check out
  • tests, by name: test_rs_toybox_file_tools=0
  • The boot ended through /system/bin/reboot (kernel.log:697-701: the reboot job, its spawn, and init's power line) and the machine answered ssh again after 38 s; this is the head's green arm only.

@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Review of a098330c3 against origin/main a31eec595. Third review of this pull request; the last read e9701cdf4.

Earlier BLOCKERs

  1. Review of e9701cdf4, .claude/agents/reviewer.md:54, "and one a shipped image carries is a BLOCKER": CLOSED. The line now ends "and a diff that ships one is a BLOCKER"; git diff --stat origin/main a098330c3 -- system.toml is empty and toybox's commands! is main's list less the five, so the sentence read against this head sends nothing back. What the sentence still says beyond its brief is the BLOCKER below.
  2. Review of 6c11159c1, the five prompt BLOCKERs: CLOSED, as at e9701cdf4. git diff --numstat origin/main a098330c3 -- '*CLAUDE.md' .claude is three files, +8 −4: implementer.md 3/3, reviewer.md 4/0, CLAUDE.md 1/1.

Earlier NOTEs and REMOVEs, all answered: the four-shells sentence is gone from the body; 636-r2/kernelprobe-x86.log is one error, the compile_error!'s, EXIT=101; tests/toyos.rs:1735's comment and ", which its count bounds" are gone in git diff e9701cdf4 a098330c3; the T14 boot has run (below).

Growth. git diff --shortstat origin/main...a098330c3: 20 files, +92 −105. The shipped toybox +1 −69; the test-only kernelprobe with its workspace row +39; tests +11 −14; lockfile +7; issues +26 −18; prompts +8 −4. What ships shrinks. The five probes have no existing home to merge into: tests/toyos-rust-tests/src/bin and userland/metalprobe both build for x86-64 (src/build.rs:2096 builds --bins), and would need back the x86-64 halves this diff deletes.

What the verdict rests on

  • Host: 636-r2/host.exit EXIT=0, host.log:7085 "Host: 64 step(s), all green", on this macOS host. GitHub's host is skipped on the draft (run 37037717466).
  • Guest: 636-r2/guest-all.exit EXIT=0, 21 of 21, each probe's line said from kernelprobe.
  • T14: Nothing ships for tests alone: the five virt probes leave the shipped toybox, and two owner rulings enter reviewer.md #636 (comment) and its readback in 636-r2/metal/shared/. verdict.txt is passed; kernel.log:696 is ===TEST_END test_rs_toybox_file_tools exit=0===; the kernel's spawn records count /system/bin/cp 5, mv 6, hexdump 11 and reboot 1 (:700), and :701 is init's power line. The image flashed is sha256 65e2c04a…, the one staged at this head. The green arm only, which is what the last review asked for: the change targets no hardware.

The image check as a negative control: sound as posted, and its pattern stays as it is.

  • Its two arms are the two shipped images at the head (every needle 0, EXIT=0) and with the whole change reverted (tree 41253219…, origin/main's; every needle nonzero on both; EXIT=1). The needle that misfires on the staged image reads 0 on both shipped images at the head, so the false red touches neither arm.
  • A control is unsound when it cannot go red. A needle that is too wide reds too often; narrowing first_entry to its line would weaken the green instead, because the bare name also catches the dispatch arm's stringify! name and the line catches only the probe's body.
  • On the staged image the 1 is accounted for without the check: one occurrence, at byte 120094873 (metal-first-entry.offsets); the built libtls_cranelift.so holds exactly one first_entry, at byte 958617 inside self.at_first_entry() (636-review-r2/so-first-entry.txt); the built kernelprobe holds two and carries the other four needles (3, 2, 8 and 1), which read 0 there. The staged image holds no probe by those readings, not by the check's exit, and the check's exit on that image is evidence of nothing either way. Where a 0 is wanted on a test image, the check is pointed at the toybox that image carries (check-toybox.log, EXIT=0).

The prompt edits against the brief. Forks (reviewer.md:132-133), the wait recipe (implementer.md:35-36) and Dependencies (CLAUDE.md:58, 14758 to 14737 bytes) are the briefed words and nothing beside them. Fit is the BLOCKER.

BLOCKER

  • .claude/agents/reviewer.md:53-54 — "a program, an applet or a kernel path whose only user is a test lives in a test image or a test kernel" is not in the brief, and it is what "one" resolves to, so the BLOCKER's criterion is this clause's and not "for tests alone" — it ranks by who uses a thing where the ruling ranks by what a thing is for (production value stays; code that exists only for a test goes). Read as this branch's own issue reads it ("nothing in the tree runs it", by git grep), it is true at this head of hexdump and mv: git grep -e '"mv"' -e '/bin/mv' -e '"hexdump"' -e '/bin/hexdump' a098330c3 finds tests/toyos-rust-tests/src/bin/toybox_file_tools.rs (17) and endowment_denied.rs (2) and no other file, so a diff that adds an applet like either is a BLOCKER by this sentence and lands by the ruling. Its list of three is also narrower than "Nothing", and "whose only user is a test" is the phrase the first review sent back at CLAUDE.md:31. Delete the clause: what is left is the brief's sentence, and if "one" then wants an antecedent, that word is the orchestrator's to give and carries no second criterion.

NOTE

  • PR body, the Gates table's last row, the T14 bullet under it and "NOTE, T14" under the review — they say the boot is staged and not run and that the x86-64 toybox "has been built and read and never run", which the head's own T14 boot has since made untrue — the body is main's record: state the run, its verdict and its readback. The comment's toyos-metal EXIT=0 has no line in logs/t14-636-shared.log, which ends at PASS: the machine booted ToyOS in 1165 ms; cite verdict.txt or post the exit with its log.
  • PR body, the Fit bullet under "Prompt edits", "in the words the orchestrator briefed" under the review, and the first Unsure bullet — each describes the sentence with its middle clause — they change with the BLOCKER's fix.

REMOVE

  • userland/kernelprobe/src/arch/mod.rs:1 — ", one module per architecture" — the selector holds one module and refuses every other architecture eight lines below.

Read for this review, under /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/: 636-r2/ (the round's logs and exits), 636-r2/metal/shared/ and logs/t14-636-shared.log (the T14 readback), and this review's own greps in 636-review-r2/. Nothing was built and the worktree is as found: git status --porcelain empty at a098330c3.

SEND BACK

…probe's selector header drops a clause

The review of a098330
(#636 (comment)) sent
the branch back on one BLOCKER and one REMOVE in the tree.

reviewer.md, Fit: the middle clause, "a program, an applet or a kernel path
whose only user is a test lives in a test image or a test kernel", was not
briefed, and it was what "one" resolved to: the BLOCKER ranked by who uses a
thing where the owner's ruling ranks by what a thing is for. Read that way it
was true of `mv` and `hexdump` at a098330, which only test binaries run and
which have production value. The sentence is now the orchestrator's, as
briefed: "Nothing ships for tests alone, and a diff that ships something for
a test alone is a BLOCKER." No other word of any prompt changes.

userland/kernelprobe/src/arch/mod.rs: ", one module per architecture" is
deleted. The selector holds one module and refuses every other architecture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Review of ee6f425ba against origin/main a31eec595. Fourth review of this pull request; the last read a098330c3.

Earlier BLOCKERs

  1. Review of a098330c3, .claude/agents/reviewer.md:53-54, the middle clause "a program, an applet or a kernel path whose only user is a test lives in a test image or a test kernel": CLOSED. reviewer.md:53 at this head is byte for byte the orchestrator's sentence, two spaces of bullet indent before it: git show ee6f425ba:.claude/agents/reviewer.md | awk 'NR==53' | cmp - <the briefed sentence> exits 0.
  2. Review of e9701cdf4, "and one a shipped image carries is a BLOCKER": CLOSED, as at a098330c3; the sentence still ranks a diff.
  3. Review of 6c11159c1, the five prompt BLOCKERs: CLOSED. git diff --numstat origin/main ee6f425ba -- '*CLAUDE.md' .claude is three files, +7 −4: implementer.md 3/3, reviewer.md 3/0, CLAUDE.md 1/1; orchestrator.md is main's. git diff --word-diff origin/main ee6f425ba over the same paths shows the wait recipe, the Fit sentence, the Forks sentence and the Dependencies sentence, and no other word.

Earlier NOTEs and the REMOVE, all answered. git diff a098330c3 ee6f425ba is two files, +2 −3: the Fit sentence and userland/kernelprobe/src/arch/mod.rs:1, which lost ", one module per architecture". The body states the T14 run, its verdict and its readback, and each holds in 636-r2/metal/shared/: verdict.txt is passed; kernel.log:696 is ===TEST_END test_rs_toybox_file_tools exit=0===; spawn records count /system/bin/cp 5, mv 6, hexdump 11, reboot 1; :701 is init's power line; summary.txt:700 is t14-636-shared EXIT=0 (75s); logs/t14-636-shared.log ends at :806, the PASS line. The body's three descriptions of the Fit sentence are the new sentence's.

Growth. git diff --shortstat origin/main...ee6f425ba: 20 files, +91 −105. Production, userland/ without its lockfile, +40 −69: the shipped toybox +1 −69, the test-only kernelprobe with its workspace row +39. Tests +11 −14. Lockfile +7. Issues +26 −18. Prompts +7 −4. What ships shrinks, and nothing at this head could be deleted that is not.

What the verdict rests on

  • Host, at this head: 636-r3/host.exit is EXIT=0 and host.log:7070 is "Host: 64 step(s), all green"; gates.head and gates.head-after are both ee6f425ba, and gates.status-before and -after are empty. Every FAILED in that log is at :5243-:6671, inside the control steps, which pass by failing. GitHub's host is skipped on the draft (run 37041862640).
  • Guest and T14, at a098330c3: 636-r2/guest-all.exit is EXIT=0, 21 of 21, each probe's line said from kernelprobe; the T14 readback is as above. Neither was run again. I accept that on the diff since, which a reader checks whole: one prompt line, and one //! line that stays one line, so no token and no line number a compiler sees has moved.
  • The image check and its negative control are as the last review read them (636-r2/negative-control.log: reverted tree 41253219… is origin/main's, REVERTED_CHECK=1, RESTORED_CHECK=0).

BLOCKER

None.

NOTE

  • PR body, line 1, "the merge at e9701cdf4 takes main's CLAUDE.md, implementer.md, orchestrator.md and reviewer.md whole" — e9701cdf4 has one parent and is the commit that adds the three briefed edits; the merge is 10aed2d75 (parents 8f142ba9e and a31eec595), and git diff --stat origin/main 10aed2d75 -- CLAUDE.md .claude is empty — the body becomes main's merge commit, and it sends its reader to the one commit where the prompts differ from main's for the place where they do not. Name 10aed2d75.

REMOVE

None.

Read for this review, under /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/: 636-r3/ (the round's host log, exit and head files), 636-r2/ (the guest, build, control and T14 readback), and this review's own copies in 636-review-r3/. Nothing was built or run, and the worktree is as found: git status --porcelain --ignore-submodules=none empty at ee6f425ba, which is also origin/wt/toyos-rulesbatch; origin/main is still a31eec595.

LAND AFTER NAMED CHANGES

@Japabu
Japabu marked this pull request as ready for review October 2, 2026 17:46
@Japabu
Japabu enabled auto-merge October 2, 2026 17:46
@Japabu
Japabu added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit b331934 Oct 2, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-rulesbatch branch October 2, 2026 18:08
Japabu added a commit that referenced this pull request Oct 2, 2026
#636 moved the five `virt_` probes out of the shipped toybox into
`userland/kernelprobe`, and put two rulings in `reviewer.md`.

One conflict, `tests/virtsmpcase/system.toml`, every hunk of both sides kept:
- main's: `[programs.toybox]` became `[programs.kernelprobe]`, and
  `bin/unmap_touch` points at `/system/bin/kernelprobe`. Both taken.
- this branch's: the header's second sentence, `shutdown` after `unmap_touch`
  in the job list, and `bin/shutdown` pointing at `/system/bin/toybox`. All
  three kept, and `[programs.toybox]` stays beside `[programs.kernelprobe]`,
  because `shutdown` is a shipped toybox applet and no probe.

`tests/toyos.rs` merged without conflict: main's two hunks are a doc line
and `virt_timer_preempts`'s arm, neither in a function this branch touches.
`tests/virtrebootcase/system.toml` names toybox alone, for `reboot`, and is
untouched.

`cargo test --test toyos-build -- virt_` on the merged tree before this
commit: exit 0, 19 of 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Japabu added a commit that referenced this pull request Oct 2, 2026
No file is changed on both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Japabu added a commit that referenced this pull request Oct 2, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Japabu added a commit that referenced this pull request Oct 2, 2026
#647, #642 and #636's follow-up landed since the last merge. One conflict,
in tests/toyos.rs: #647 made `boot_virt_smp` take a `BootOptions` and
`judge_virt_job` borrow its guest, and this branch had added
`virt_mask_windows` on the old shape. Main's shape is kept whole;
`virt_mask_windows` names its kernel build in the options and lends its
guest like the others. `tests/virtsmpcase` now ends in `shutdown`, which
this test does not wait for: it judges once `unmap_touch` has ended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant