From 634ff402994666786e12b198d3176ffed2e0ded3 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 30 Sep 2026 15:13:57 +0200 Subject: [PATCH 1/6] Nothing ships for tests alone; six review and briefing rules 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 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- .claude/agents/orchestrator.md | 2 ++ .claude/agents/reviewer.md | 9 +++++++++ CLAUDE.md | 1 + 3 files changed, 12 insertions(+) diff --git a/.claude/agents/orchestrator.md b/.claude/agents/orchestrator.md index bf08cc0f1ad..f1f2a13a56b 100644 --- a/.claude/agents/orchestrator.md +++ b/.claude/agents/orchestrator.md @@ -38,6 +38,8 @@ fix round sent. When the permission check refuses an agent, ask the owner and ne A brief is the fence: what to build, where it may touch, the worktree and branch, the scratchpad for its logs, and the two checks expected of high-risk code. The role files carry the standing rules, so a brief carries only the task. +A brief asks for the agent's own attribution lines, never pastes the orchestrator's: a subagent's +model differs from its caller's. The cost is Claude tokens and the owner's time; CI minutes are free. An agent's tokens grow with how long it runs, far more than with what it writes, so a brief is sized to finish and no agent idles in diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md index cdc50feddcb..99a882ad600 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -74,12 +74,19 @@ above; otherwise it is a NOTE. abstraction with one caller, a parameter with one value, dead code. Size is never bought with a weaker check: tests are cut only when they test nothing. A compromise the branch found is removed or recorded in `issues/` with an owner, evidence and an exit condition. + The pull request that meets a track milestone's exit deletes that milestone from its track, so a + met milestone is never briefed again as open work. Code is liability: code that does not earn its keep is deleted or simplified, and code kept "just in case", or because nobody knows whether it is needed, is an instant delete. Doubt is not a reason to keep; confidence decides. - **Tests.** The refusals and the boundary, not the happy path. Write down the partial fix or one-field mutation that would still pass, as a patch the implementer can apply. High-risk code names a negative control, the whole change reverted onto a named commit and red there, and one oracle independent of the author. + A check that a judge refuses something also plants a sibling the judge must accept, so a judge + that refuses everything goes red. A mutation counts only once it is shown to build: on an + architecture built with `-D warnings`, deleting a call can fail as dead code instead of reaching + the test. A pull request body names a gate only under the head it ran at: a merge of main brings + code an earlier head's gate never compiled. - **Edges.** Untrusted input never panics the kernel; it is refused. Check-then-act races. A lock held across a user copy or a device wait. Arithmetic on a value the caller chooses. A short read, an exit status nobody reads. An `at_most(::MAX)` or `index(usize::MAX)` on an @@ -108,6 +115,8 @@ REMOVE, one line, and the implementer deletes it. Nobody rewrites prose. Reducing prose is part of the review. Every comment, doc line, issue line and PR-body line a branch adds or rewrites must be load-bearing — the code or the record needs it — or it is REMOVE. Prose rewritten or "corrected" instead of deleted is REMOVE. +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, which stay true. ## Output diff --git a/CLAUDE.md b/CLAUDE.md index 117bca0825d..d413d93c2c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,6 +28,7 @@ A subdirectory `CLAUDE.md` loads when a file in that subtree is `Read`, and not - **Zero legacy.** No backwards compatibility, no fallbacks, no workarounds, no BIOS, no 32-bit. Research state-of-the-art OS design instead of replicating older OSes. - **Zero silent debt.** Dead code is deleted; every abstraction earns its place. A discovered compromise has exactly two legal outcomes: remove it, or record it with ownership, evidence and an exit condition — and it stays a present-state weakness until removed. - **Fail fast, trust nothing.** Panics over silent degradation; exhaustive matches; the unimplemented dies loudly. Input that crossed a trust boundary is never trusted and never panics the kernel — it is refused. +- **Nothing ships for tests alone.** The shipped system carries no mechanism whose only user is a test; tests exercise what ships, driven from outside by the harness. A test build's fault injection is compiled out of what ships. - **The kernel never crashes from userland.** A kernel bug crashes loudly; a userland bug never reaches it. - **No kernel threads.** The kernel creates no thread but the per-CPU idle loop: kernel work runs, bounded, on the thread or interrupt that caused it and is charged to it; long-running work with no owner is a userland server's. - **Rust is first class.** Not POSIX, not C. Unrepresentable is best: prefer compile-time safety over runtime checks over tests. From 6c11159c1dc9872e46f791bb651284170d993db3 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 30 Sep 2026 15:52:11 +0200 Subject: [PATCH 2/6] Agents collaborate, and CPU-heavy work is the orchestrator's to queue 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 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- .claude/agents/implementer.md | 13 +++++++++---- .claude/agents/orchestrator.md | 9 +++++---- CLAUDE.md | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index 4b1b82fc040..66785de13aa 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -1,7 +1,7 @@ --- name: implementer description: Builds or fixes one branch from one brief, tests it, and hands it over through its pull request. -tools: Bash, Read, Write, Edit, Grep, Glob +tools: Bash, Read, Write, Edit, Grep, Glob, SendMessage --- You build one branch from the brief the orchestrator gave you. Root `CLAUDE.md` is the law and the @@ -12,15 +12,20 @@ rest. One brief, one worktree, one branch. What the brief does not name you do not touch. A defect you find off your path is filed in `issues/`, never fixed. If something blocks you, stop and say so in -one clause; do not work around it. +one clause; do not work around it. An agent may message any other agent whose work overlaps its +own, to agree on shared code. ## Measure, build, test Where hardware or anything uncertain is involved, take the cheap measurement before you build on a guess. Then build, then test before anyone reviews: -- Host tests only: `cargo run -- --ci host`, and `cargo run -- --build-only` at most for the - image. Never a guest test or any other `cargo run`: the orchestrator runs every guest test. +- CI runs the host checks on every push; an agent runs only quick, targeted checks of what it + changed and never repeats CI's gate locally. Work that costs minutes of CPU beyond that — a + toolchain or LLVM build, a guest run — is a request to the orchestrator (SendMessage to `main`: + what, where, why), who queues, bundles or declines it. +- Test to prove the change, not to cover the tree: one targeted check per claim; the one full run + when the pull request is done is the orchestrator's. - A result is the command's own exit code: ` > 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 job scratchpad the brief diff --git a/.claude/agents/orchestrator.md b/.claude/agents/orchestrator.md index f1f2a13a56b..cd722112d60 100644 --- a/.claude/agents/orchestrator.md +++ b/.claude/agents/orchestrator.md @@ -38,6 +38,7 @@ fix round sent. When the permission check refuses an agent, ask the owner and ne A brief is the fence: what to build, where it may touch, the worktree and branch, the scratchpad for its logs, and the two checks expected of high-risk code. The role files carry the standing rules, so a brief carries only the task. +Every brief names the agents working on overlapping code, so they can talk to each other. A brief asks for the agent's own attribution lines, never pastes the orchestrator's: a subagent's model differs from its caller's. @@ -59,6 +60,8 @@ After a landing, sync the primary checkout. A red that is not about the diff is owner, never re-run away, and nothing but a defect may turn `main` red. A fix for a red lands ahead of feature work. A nightly name red three nights running gets an owner issue that day, and is deleted if nobody owns it a week later. +The orchestrator runs one full guest run of a finished pull request and its metal runs; a pull +request's negative controls are the implementer's and the reviewer's, not the orchestrator's. ## Runs @@ -69,10 +72,8 @@ its script path under the job directory, and revert any mutation a killed run le left mid-flash or mid-boot is power-cycled by the owner and comes back to Ubuntu: BootNext is one-shot. -A mutation loop, guest or metal, starts on a clean worktree at the head under review and leaves it -clean: `git apply --check`, `git apply`, the tests by name, `git apply -R`. None runs while an -agent edits that worktree. A queue script passes only flags `src/testargs.rs` declares: any other -word becomes the run's filter, and a one-test run reports as a pass. +A queue script passes only flags `src/testargs.rs` declares: any other word becomes the run's +filter, and a one-test run reports as a pass. A run is the whole suite, or a positional filter that reaches any enabled test. ## The bench diff --git a/CLAUDE.md b/CLAUDE.md index d413d93c2c6..7135fcd0825 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,7 @@ The bar is not yet the tree. The standing failures are declared rather than remo The testing rules live where they are enforced: known reds in `src/redlist.rs`, the PR gate and the nightly in `.github/workflows/`. Operationally: - `cargo run` builds everything (toolchain, kernel, bootloader, userland, image) and launches QEMU; `--build-only` skips the launch. `cargo test` runs the QEMU harness; `cargo run -- --ci host` runs every host suite, as the PR gate's required `host` check does. -- **Agents never run QEMU.** An agent verifies with host tests and builds the image at most; the orchestrator runs every guest test, one suite at a time. +- **Agents never run QEMU.** The orchestrator runs every guest test, one suite at a time. - **Both produce large output**: run them in the background and read the output file — `[N characters truncated]` means data was lost. A full boot is under a second; incremental builds finish in seconds. - **Leave the machine as you found it.** The development machine is shared: every agent stops what it started, removes the worktrees and scratch build output it no longer needs, and never leaves an emulator, a build or a watcher running. From 8f142ba9eb70e62d06198397885f2d9b181a275f Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 30 Sep 2026 23:01:57 +0200 Subject: [PATCH 3/6] Test-only probes leave the shipped toybox; answer the review of 6c11159c1 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 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- .claude/agents/implementer.md | 23 +++++---- .claude/agents/orchestrator.md | 14 ++--- .claude/agents/reviewer.md | 16 +++--- CLAUDE.md | 6 +-- ...ipped-image-carries-three-test-programs.md | 25 +++++++++ ...oybox-ships-two-applets-that-only-panic.md | 18 ------- tests/toyos.rs | 4 +- tests/virtjobcase/system.toml | 14 ++--- tests/virtsmpcase/system.toml | 4 +- userland/Cargo.lock | 7 +++ userland/Cargo.toml | 1 + userland/kernelprobe/Cargo.toml | 9 ++++ .../src/arch/aarch64.rs | 0 userland/kernelprobe/src/arch/mod.rs | 6 +++ .../src/debug_refused.rs | 0 userland/kernelprobe/src/main.rs | 20 ++++++++ .../{toybox => kernelprobe}/src/preempt.rs | 0 .../src/unmap_touch.rs | 0 userland/toybox/src/arch/mod.rs | 11 ---- userland/toybox/src/arch/x86_64.rs | 51 ------------------- userland/toybox/src/main.rs | 8 +-- 21 files changed, 109 insertions(+), 128 deletions(-) create mode 100644 issues/design-debt/the-shipped-image-carries-three-test-programs.md delete mode 100644 issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md create mode 100644 userland/kernelprobe/Cargo.toml rename userland/{toybox => kernelprobe}/src/arch/aarch64.rs (100%) create mode 100644 userland/kernelprobe/src/arch/mod.rs rename userland/{toybox => kernelprobe}/src/debug_refused.rs (100%) create mode 100644 userland/kernelprobe/src/main.rs rename userland/{toybox => kernelprobe}/src/preempt.rs (100%) rename userland/{toybox => kernelprobe}/src/unmap_touch.rs (100%) delete mode 100644 userland/toybox/src/arch/mod.rs delete mode 100644 userland/toybox/src/arch/x86_64.rs diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index 28a457ddeb2..704789717df 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -12,20 +12,19 @@ rest. One brief, one worktree, one branch. What the brief does not name you do not touch. A defect you find off your path is filed in `issues/`, never fixed. If something blocks you, stop and say so in -one clause; do not work around it. An agent may message any other agent whose work overlaps its -own, to agree on shared code. +one clause; do not work around it. You may message any agent and work with it. ## Measure, build, test Where hardware or anything uncertain is involved, take the cheap measurement before you build on a guess. Then build, then test before anyone reviews: -- CI runs the host checks on every push; an agent runs only quick, targeted checks of what it - changed and never repeats CI's gate locally. Work that costs minutes of CPU beyond that — a - toolchain or LLVM build, a guest run — is a request to the orchestrator (SendMessage to `main`: - what, where, why), who queues, bundles or declines it. -- Test to prove the change, not to cover the tree: one targeted check per claim; the one full run - when the pull request is done is the orchestrator's. +- CI runs the host checks on every push to a pull request marked ready, not on a draft; you run + only quick, targeted checks of what you changed and never repeat CI's gate locally. +- Test to prove the change, not to cover the tree: a targeted check per claim, beside the two + checks root `CLAUDE.md` asks of high-risk code and the mutations a review names. You and the + reviewer design and judge the negative controls; the one full run when the pull request is done + is the orchestrator's. - A result is the command's own exit code: ` > 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 job scratchpad the brief @@ -36,8 +35,9 @@ guess. Then build, then test before anyone reviews: - Never a flat wait, in code or in a test: wait on the event, bounded by a timeout that fails loudly. A fixed delay only where a hardware document mandates it and offers no notification, cited at the site. No defensive code: fail fast, never degrade silently. -- The T14 is the orchestrator's. Write the request file the brief names and end with - `T14 RUN REQUESTED: `. +- A guest or T14 run, and a compiler or LLVM build, are the orchestrator's, who queues, bundles or + declines them and runs them without judging them; a sysroot build is yours. Write the request + file the brief names and end with `RUN REQUESTED: `. ## A fork @@ -62,7 +62,8 @@ Fork sources live outside this repository: a search for callers must also cover `git commit -F `, never `-m`. No `--amend`, no rebase, no force: merge `origin/main`, never rebase onto it. Never run `git submodule` in a linked worktree: it writes `core.worktree` into the fork's shared config and breaks git in the primary checkout's `rust/`. Never touch `toyos-abi/src`, `toyos/src` or `userland/libc/src` unless the brief is -an ABI brief. No new dependency. +an ABI brief. No new dependency. Commits and the pull request end with your own attribution lines, +never a brief's. Push from your branch, never `main`, with `git status --porcelain` empty: `git push -u origin `, and `gh pr create --draft` at the first push. The pull request body is the handoff the reviewer reads, diff --git a/.claude/agents/orchestrator.md b/.claude/agents/orchestrator.md index e8939fb81df..0ee23293a22 100644 --- a/.claude/agents/orchestrator.md +++ b/.claude/agents/orchestrator.md @@ -38,9 +38,7 @@ fix round sent. When the permission check refuses an agent, ask the owner and ne A brief is the fence: what to build, where it may touch, the worktree and branch, the scratchpad for its logs, and the two checks expected of high-risk code. The role files carry the standing rules, so a brief carries only the task. -Every brief names the agents working on overlapping code, so they can talk to each other. -A brief asks for the agent's own attribution lines, never pastes the orchestrator's: a subagent's -model differs from its caller's. +Every brief names the agents working on overlapping code. The cost is Claude tokens and the owner's time; CI minutes are free. An agent's tokens grow with how long it runs, far more than with what it writes, so a brief is sized to finish and no agent idles in @@ -62,8 +60,8 @@ A red that is not about the diff is fixed at its owner, never re-run away, and n may turn `main` red. A fix for a red lands ahead of feature work. A nightly name red three nights running gets an owner issue that day, and is deleted if nobody owns it a week later. -The orchestrator runs one full guest run of a finished pull request and its metal runs; a pull -request's negative controls are the implementer's and the reviewer's, not the orchestrator's. +You run one full guest run of a finished pull request. Its negative controls are designed and +judged by its implementer and reviewer: you run the guest and T14 runs they request and judge none. ## Runs @@ -74,8 +72,10 @@ its script path under the job directory, and revert any mutation a killed run le left mid-flash or mid-boot is power-cycled by the owner and comes back to Ubuntu: BootNext is one-shot. -A queue script passes only flags `src/testargs.rs` declares: any other word becomes the run's -filter, and a one-test run reports as a pass. +A mutation loop, guest or metal, starts on a clean worktree at the head under review and leaves it +clean: `git apply --check`, `git apply`, the tests by name, `git apply -R`. None runs while an +agent edits that worktree. A queue script passes only flags `src/testargs.rs` declares: any other +word becomes the run's filter, and a one-test run reports as a pass. A run is the whole suite, or a positional filter that reaches any enabled test. ## The bench diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md index 9e48157111b..c7da39c5336 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -82,19 +82,16 @@ above; otherwise it is a NOTE. abstraction with one caller, a parameter with one value, dead code. Size is never bought with a weaker check: tests are cut only when they test nothing. A compromise the branch found is removed or recorded in `issues/` with an owner, evidence and an exit condition. - The pull request that meets a track milestone's exit deletes that milestone from its track, so a - met milestone is never briefed again as open work. + The pull request that meets a track milestone's exit deletes that milestone from its track. Code is liability: code that does not earn its keep is deleted or simplified, and code kept "just in case", or because nobody knows whether it is needed, is an instant delete. Doubt is not a reason to keep; confidence decides. - **Tests.** The refusals and the boundary, not the happy path. Write down the partial fix or one-field mutation that would still pass, as a patch the implementer can apply. High-risk code names a negative control, the whole change reverted onto a named commit and red there, and one oracle independent of the author. - A check that a judge refuses something also plants a sibling the judge must accept, so a judge - that refuses everything goes red. A mutation counts only once it is shown to build: on an - architecture built with `-D warnings`, deleting a call can fail as dead code instead of reaching - the test. A pull request body names a gate only under the head it ran at: a merge of main brings - code an earlier head's gate never compiled. + A check that a judge refuses something also plants a sibling the judge must accept. A mutation + counts only once it is shown to build. A pull request body names a gate only under the head it + ran at. - **Edges.** Untrusted input never panics the kernel; it is refused. Check-then-act races. A lock held across a user copy or a device wait. Arithmetic on a value the caller chooses. A short read, an exit status nobody reads. An `at_most(::MAX)` or `index(usize::MAX)` on an @@ -123,8 +120,9 @@ REMOVE, one line, and the implementer deletes it. Nobody rewrites prose. Reducing prose is part of the review. Every comment, doc line, issue line and PR-body line a branch adds or rewrites must be load-bearing — the code or the record needs it — or it is REMOVE. Prose rewritten or "corrected" instead of deleted is REMOVE. -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, which stay true. +A removal's sweep deletes the present-tense and forward-looking statements of what it removed, but +for the instructions **Instructions** updates, and keeps dated, commit- or pull-request-anchored +records of runs that cite no deleted document. ## Output diff --git a/CLAUDE.md b/CLAUDE.md index 8af08776d56..d989d9f6537 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,7 @@ A subdirectory `CLAUDE.md` loads when a file in that subtree is `Read`, and not - **Zero legacy.** No backwards compatibility, no fallbacks, no workarounds, no BIOS, no 32-bit. Research state-of-the-art OS design instead of replicating older OSes. - **Zero silent debt.** Dead code is deleted; every abstraction earns its place. A discovered compromise has exactly two legal outcomes: remove it, or record it with ownership, evidence and an exit condition — and it stays a present-state weakness until removed. - **Fail fast, trust nothing.** Panics over silent degradation; exhaustive matches; the unimplemented dies loudly. Input that crossed a trust boundary is never trusted and never panics the kernel — it is refused. -- **Nothing ships for tests alone.** The shipped system carries no mechanism whose only user is a test; tests exercise what ships, driven from outside by the harness. A test build's fault injection is compiled out of what ships. +- **Nothing ships for tests alone.** The shipped system carries nothing whose only user is a test; test-only programs and fault injection live in test builds only. - **The kernel never crashes from userland.** A kernel bug crashes loudly; a userland bug never reaches it. - **No kernel threads.** The kernel creates no thread but the per-CPU idle loop: kernel work runs, bounded, on the thread or interrupt that caused it and is charged to it; long-running work with no owner is a userland server's. - **Rust is first class.** Not POSIX, not C. Unrepresentable is best: prefer compile-time safety over runtime checks over tests. @@ -59,9 +59,9 @@ A subdirectory `CLAUDE.md` loads when a file in that subtree is `Read`, and not ## 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, Ninja, make), each declared. 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 requests are sent for now: ToyOS needs more attention and more contributors before upstream projects take it seriously, and upstreams tend to refuse AI-first projects and their contributions. 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**: nothing — build, test, or verification — rests on a host binary. Ask of anything new: could this ever run inside ToyOS? Self-hosting means ToyOS rebuilds itself on ToyOS and reproduces the host's bytes; a bootstrap from source with no binary seed is out of scope. +**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, Ninja, make), each declared. 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. LLVM and Rust are never changed to suit a ToyOS tool; a tool that cannot build them unchanged is the one that goes. No upstream pull requests are sent for now: ToyOS needs more attention and more contributors before upstream projects take it seriously, and upstreams tend to refuse AI-first projects and their contributions. 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**: nothing — build, test, or verification — rests on a host binary. Ask of anything new: could this ever run inside ToyOS? Self-hosting means ToyOS rebuilds itself on ToyOS and reproduces the host's bytes; a bootstrap from source with no binary seed is out of scope. -Vendor firmware a device verifies by its maker's signature may be shipped: pinned by version and hash, redistributable unmodified, recorded in `NOTICE`, and loaded only by that device's own driver through its IOMMU domain; it never executes on the CPU. +Vendor firmware a device verifies by its maker's signature may be shipped: pinned by version and hash, redistributable unmodified, recorded in `NOTICE`, and loaded only by that device's own driver through its IOMMU domain; it never executes on the CPU, save the CPU's own microcode, which the kernel loads. The bar is not yet the tree: `.claude/agents/reviewer.md`, "Arrivals", says where every host tool and every standing failure is declared. `NOTICE` names every committed third-party file with its hash, upstream and licence; an image carrying `DOOM1.WAD` may not be sold. 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 new file mode 100644 index 00000000000..b20e1cb6e4f --- /dev/null +++ b/issues/design-debt/the-shipped-image-carries-three-test-programs.md @@ -0,0 +1,25 @@ +--- +status: open +kind: defect +opened: 2026-09-30 +--- + +# The shipped image carries three test programs + +Root `CLAUDE.md`'s "Nothing ships for tests alone" 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 every caller is a test: `tests/jobdeadlinecase`'s job list and + `tests/common/ssh.rs` on `tests/sshdcase`. + +Found by `git grep -n -w -e proctest -e input-test` and +`git grep -n -e 'bin/spin' -e '"spin"'`. + +**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 deleted file mode 100644 index 7dae8903a8c..00000000000 --- a/issues/design-debt/the-x86-toybox-ships-two-applets-that-only-panic.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -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 f031b536ad4..39ed59f6db5 100644 --- a/tests/toyos.rs +++ b/tests/toyos.rs @@ -3597,7 +3597,7 @@ fn judge_virt_job(mut qemu: QemuInstance, job: &str, said: &str) -> Result { - // Spelled in `userland/toybox/src/preempt.rs`. + // Spelled in `userland/kernelprobe/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 190d1b33acd..838f3dda18a 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 toybox applet or, last because +# One CPU and the AArch64 guest's jobs, each a kernelprobe 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.toybox] +[programs.kernelprobe] [symlinks] -"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" +"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" diff --git a/tests/virtsmpcase/system.toml b/tests/virtsmpcase/system.toml index e7dfb0bc46d..bd0e9c302b0 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.toybox] +[programs.kernelprobe] [symlinks] -"bin/unmap_touch" = "/system/bin/toybox" +"bin/unmap_touch" = "/system/bin/kernelprobe" diff --git a/userland/Cargo.lock b/userland/Cargo.lock index 5489d455e17..b0ff3f7063c 100644 --- a/userland/Cargo.lock +++ b/userland/Cargo.lock @@ -1849,6 +1849,13 @@ 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 55b8b482f91..201041e210f 100644 --- a/userland/Cargo.toml +++ b/userland/Cargo.toml @@ -15,6 +15,7 @@ members = [ "init", "input-test", "inspect", + "kernelprobe", "logd", "metalprobe", "netd", diff --git a/userland/kernelprobe/Cargo.toml b/userland/kernelprobe/Cargo.toml new file mode 100644 index 00000000000..09c5a5ae823 --- /dev/null +++ b/userland/kernelprobe/Cargo.toml @@ -0,0 +1,9 @@ +[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/toybox/src/arch/aarch64.rs b/userland/kernelprobe/src/arch/aarch64.rs similarity index 100% rename from userland/toybox/src/arch/aarch64.rs rename to userland/kernelprobe/src/arch/aarch64.rs diff --git a/userland/kernelprobe/src/arch/mod.rs b/userland/kernelprobe/src/arch/mod.rs new file mode 100644 index 00000000000..0af20cbff7e --- /dev/null +++ b/userland/kernelprobe/src/arch/mod.rs @@ -0,0 +1,6 @@ +//! 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/toybox/src/debug_refused.rs b/userland/kernelprobe/src/debug_refused.rs similarity index 100% rename from userland/toybox/src/debug_refused.rs rename to userland/kernelprobe/src/debug_refused.rs diff --git a/userland/kernelprobe/src/main.rs b/userland/kernelprobe/src/main.rs new file mode 100644 index 00000000000..4728869ff9d --- /dev/null +++ b/userland/kernelprobe/src/main.rs @@ -0,0 +1,20 @@ +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 = 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/toybox/src/preempt.rs b/userland/kernelprobe/src/preempt.rs similarity index 100% rename from userland/toybox/src/preempt.rs rename to userland/kernelprobe/src/preempt.rs diff --git a/userland/toybox/src/unmap_touch.rs b/userland/kernelprobe/src/unmap_touch.rs similarity index 100% rename from userland/toybox/src/unmap_touch.rs rename to userland/kernelprobe/src/unmap_touch.rs diff --git a/userland/toybox/src/arch/mod.rs b/userland/toybox/src/arch/mod.rs deleted file mode 100644 index 95e0e9d154f..00000000000 --- a/userland/toybox/src/arch/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! 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 deleted file mode 100644 index c08523a994b..00000000000 --- a/userland/toybox/src/arch/x86_64.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! 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) { - 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) { - 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/toybox/src/main.rs b/userland/toybox/src/main.rs index 449ce6e89d4..9e3df687707 100644 --- a/userland/toybox/src/main.rs +++ b/userland/toybox/src/main.rs @@ -1,7 +1,5 @@ -mod arch; mod cat; mod cp; -mod debug_refused; mod echo; mod free; mod grep; @@ -11,7 +9,6 @@ mod ls; mod mkdir; mod mv; mod net; -mod preempt; mod ps; mod pwd; mod reboot; @@ -21,7 +18,6 @@ mod shutdown; mod spin; mod stats; mod tone; -mod unmap_touch; macro_rules! commands { ($($name:ident),*) => { @@ -34,9 +30,7 @@ macro_rules! commands { }; } -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); +commands!(cat, cp, echo, free, grep, hexdump, locale, ls, mkdir, mv, net, ps, pwd, reboot, rm, screen, shutdown, spin, stats, tone); fn main() { let args: Vec = std::env::args().collect(); From e9701cdf4b4940672d893e6187b0abdcbfd401e9 Mon Sep 17 00:00:00 2001 From: japabu Date: Fri, 2 Oct 2026 18:18:33 +0200 Subject: [PATCH 4/6] Two owner rulings enter reviewer.md; the wait recipe runs on any host; 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 Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm --- .claude/agents/implementer.md | 7 ++++--- .claude/agents/reviewer.md | 4 ++++ CLAUDE.md | 2 +- .../the-shipped-image-carries-three-test-programs.md | 11 ++++++----- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index c88fb33d7bf..447fb5868c5 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -32,9 +32,10 @@ guess. Then build, then test on root `CLAUDE.md`'s tiers before anyone reviews: - A result is the command's own exit code: ` > 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 `until ; 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. + Stay inside one turn while anything runs: block in the foreground on `n=0; until || + [ $((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. - 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 6d1883b3d70..40ea29dd440 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -50,6 +50,8 @@ 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 @@ -127,6 +129,8 @@ 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 4c8de665767..f2a665b4d2e 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. 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? +**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? 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 index b20e1cb6e4f..f43b8c0cc7d 100644 --- a/issues/design-debt/the-shipped-image-carries-three-test-programs.md +++ b/issues/design-debt/the-shipped-image-carries-three-test-programs.md @@ -6,8 +6,8 @@ opened: 2026-09-30 # The shipped image carries three test programs -Root `CLAUDE.md`'s "Nothing ships for tests alone" is untrue of the shipped -`system.toml` in three rows: +"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. @@ -15,11 +15,12 @@ Root `CLAUDE.md`'s "Nothing ships for tests alone" is untrue of the shipped `/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 every caller is a test: `tests/jobdeadlinecase`'s job list and - `tests/common/ssh.rs` on `tests/sshdcase`. + 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 -e 'bin/spin' -e '"spin"'`. +`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. From a098330c3529c2624372e23eff3efb1159ff4dc4 Mon Sep 17 00:00:00 2001 From: japabu Date: Fri, 2 Oct 2026 18:49:51 +0200 Subject: [PATCH 5/6] Review round on #636: the Fit sentence ranks a diff, and kernelprobe refuses another architecture by name Answers https://github.com/ToyOSOrg/ToyOS/pull/636#issuecomment-5957000088. 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 Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm --- .claude/agents/implementer.md | 5 ++--- .claude/agents/reviewer.md | 2 +- tests/toyos.rs | 5 +---- userland/kernelprobe/src/arch/mod.rs | 3 +++ 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index 447fb5868c5..76e4b54a29f 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -33,9 +33,8 @@ guess. Then build, then test on root `CLAUDE.md`'s tiers before anyone reviews: `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 || - [ $((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. + [ $((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. - 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 40ea29dd440..63dcca51f04 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -51,7 +51,7 @@ if it meets the bar above; otherwise it is a NOTE. `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. + 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 diff --git a/tests/toyos.rs b/tests/toyos.rs index 4011139c085..f3da8dfc11d 100644 --- a/tests/toyos.rs +++ b/tests/toyos.rs @@ -1731,10 +1731,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`. - virt_job(profile, "preempt", "preempt: the counting thread was preempted twice") - } + "virt_timer_preempts" => 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/userland/kernelprobe/src/arch/mod.rs b/userland/kernelprobe/src/arch/mod.rs index 0af20cbff7e..d1bc784fe7e 100644 --- a/userland/kernelprobe/src/arch/mod.rs +++ b/userland/kernelprobe/src/arch/mod.rs @@ -4,3 +4,6 @@ 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"); From ee6f425baf951910beb32b33a861245bac1c32c2 Mon Sep 17 00:00:00 2001 From: japabu Date: Fri, 2 Oct 2026 19:33:58 +0200 Subject: [PATCH 6/6] Review round on #636: the Fit sentence is the briefed one, and kernelprobe's selector header drops a clause The review of a098330c3 (https://github.com/ToyOSOrg/ToyOS/pull/636#issuecomment-5957787194) 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 a098330c3, 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 Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm --- .claude/agents/reviewer.md | 3 +-- userland/kernelprobe/src/arch/mod.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md index 63dcca51f04..78dda63b29a 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -50,8 +50,7 @@ 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. + Nothing ships for tests alone, and a diff that ships something for a test alone 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 diff --git a/userland/kernelprobe/src/arch/mod.rs b/userland/kernelprobe/src/arch/mod.rs index d1bc784fe7e..814151dcf80 100644 --- a/userland/kernelprobe/src/arch/mod.rs +++ b/userland/kernelprobe/src/arch/mod.rs @@ -1,4 +1,4 @@ -//! What the probes must say in assembly, one module per architecture. +//! What the probes must say in assembly. #[cfg(target_arch = "aarch64")] mod aarch64;