From 75888835464fc956dc5755b7afdf57e0839c1820 Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 29 Sep 2026 09:31:02 +0200 Subject: [PATCH 01/24] Track: a crate has two consumers or says why it is alone Almost every single-consumer `toyos-*` crate says, in its manifest or module doc, that it is a crate so that its tests run on the host. Measured today: - The gated userland programs already host-test their own modules (`src/userlandhost.rs`), and blockd, calc, pkg and terminal carry a lib beside their bin. - A package with a `no_std`/`no_main` bin under a bare-target `.cargo/config.toml` and a `cfg_attr(not(test), no_std)` lib builds both for `x86_64-unknown-none`. Its lib's tests run under `cargo test --lib --target aarch64-apple-darwin`, and a host package that depends on it by path builds the lib alone. - The kernel binary itself runs a `#[cfg(test)]` test on aarch64-apple-darwin. That takes `cfg_attr(not(test), no_main)`, the panic handler, the global allocator and three aarch64 entry assembly items out under test, and allows dead code in the test build. Its x86_64 Linux test object links as a PIE under `-z text`, with only libc, unwinder and allocator-shim symbols left undefined. - `#![forbid(unsafe_code)]` is enforced at module scope, including against a local `allow`. The track folds 16 packages away, 95 to 79, and ends in a `src/hostws.rs` gate. That gate reds on an rlib-only package with fewer than two consumers unless it declares one of four exception kinds. Two defects found on the way are filed rather than fixed: - `toyos-pcid`'s `counting-allocator` control is run by nothing. By hand it still reds. - No userland crate is linted, though the gated ones build for the host. soundd shows 8 clippy findings and netd 10. Co-Authored-By: Claude Opus 5.5 --- ...s-two-consumers-or-says-why-it-is-alone.md | 111 ++++++++++++++++++ .../the-pcid-negative-control-runs-nowhere.md | 19 +++ .../userland-programs-are-never-linted.md | 19 +++ 3 files changed, 149 insertions(+) create mode 100644 issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md create mode 100644 issues/build/the-pcid-negative-control-runs-nowhere.md create mode 100644 issues/build/userland-programs-are-never-linted.md diff --git a/issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md b/issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md new file mode 100644 index 0000000000..a945622a77 --- /dev/null +++ b/issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md @@ -0,0 +1,111 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# A crate has two consumers or says why it is alone + +The tree has 95 packages. Its 44 `toyos-*` directories hold 47 of them. Of +those 47, 13 are a normal dependency of exactly one package, and 7 are a +dependency of none. Almost every single-consumer crate gives the same reason in +its manifest or module doc: as a crate, its tests run on the host. Measured on +2026-09-29, that reason does not hold: + +- **A userland program host-tests its own modules.** `--ci host` already runs + `cargo test --target ` in calc, logd, netd, pkg, soundd and sshd + (`src/userlandhost.rs`). blockd, calc, pkg and terminal each carry a `lib` + beside their `bin`. +- **A bare-target package can carry a host-tested library.** Take a package + with a `src/lib.rs` (`cfg_attr(not(test), no_std)`, `forbid(unsafe_code)`) + and a `no_std`/`no_main` bin, under a `.cargo/config.toml` naming + `x86_64-unknown-none`. It builds both targets for the bare target, and + `cargo test --lib --target aarch64-apple-darwin` runs the library's tests. + With `[[bin]] test = false`, a plain `cargo test --target ` builds no + binary. A host package that depends on it by path builds only the library. +- **The kernel binary itself runs a `#[cfg(test)]` test on the host** after + four edits: `cfg_attr(not(test), no_main)`, the panic handler and global + allocator taken out under test, and the three aarch64 entry assembly items + taken out under test. Mach-O refuses their ELF local label and ADRP fixups. + The test build also allows `dead_code` and `unused_imports`, and it builds + clean in 3.4 s. On `x86_64-unknown-linux-gnu`, the test target compiles, and + its object links as a PIE under `-z text`. Only libc, unwinder and + allocator-shim symbols stay undefined. The full link there is unmeasured, + because the CI host job runs on `macos-latest`. +- **`#![forbid(unsafe_code)]` holds at module scope.** In a module file, the + inner attribute refuses an `unsafe` block and a local `allow` (E0453). A + sibling module keeps its own `unsafe`. + +So a crate with one consumer earns its boundary only for a reason other than +host tests. It may be built into the toolchain's sysroot. It may compile +another package's sources under another `cfg` (loom, a simulator, a host +differential). The crate boundary may itself be under test. Or an open track +may name its first consumer. The `cfg` case has a second reason: under one +`cargo test --workspace`, cargo unifies features, so a feature one package +turns on would leak into every other consumer's build. + +**What a fold costs.** + +- A library target shares its package's dependency list with the binary. +- The gated userland programs are not clippy-clean + (`issues/build/userland-programs-are-never-linted.md`), so a crate folded + into one leaves clippy's reach until that entry closes. +- `src/sourcegate.rs`'s `PURE_CRATES` rule is by directory, so it has to + follow the modules. +- A clean bare kernel build takes 5.4 s, 4.3 s of it in the kernel crate. Each + of the five kernel-only crates takes 0.09 to 0.32 s. + +Stages. Each lands green on `--ci host` and `--build-only`: + +1. Delete `toyos-userpin`. Nothing depends on it, and it names nothing the + kernel defines. `munmap_reissues_read_window` is the gate. Check: + `git grep toyos-userpin` is empty. +2. Close `issues/build/userland-programs-are-never-linted.md`. Every stage + that folds into a userland program waits on it. +3. **The kernel's library.** `toyos-dma`, `-pci`, `-pcid`, `-proclife` and + `-ps2` become modules of a `lib` target of `kernel/`, with crate-level + `forbid(unsafe_code)`. It is a library, not tests inside the binary: the + library cannot name the binary's items, which is the wall the five crates + provided, and the binary needs no `cfg(test)`. `--ci host` tests it from + `kernel/`, so that directory's `-Dwarnings` applies, and a clippy shape + lints it on the host. + proclife's six controls and pcid's `counting-allocator` + (`issues/build/the-pcid-negative-control-runs-nowhere.md`) become kernel + features in `CONTROLS`. Check: `-- --list` shows at least the five crates' + 156 tests. Every moved control reds. An `unsafe {}` planted in a moved + module does not compile. +4. `toyos-symbols` splits. `locate` and `file_range` go into `toyos-elf`, and + the name budget goes into the kernel's library. Check: `cargo tree` in + `bootloader/` gains no package. +5. `toyos-acpi`, `-bootmap`, `-rootimage` and `-blackbox` become one + `toyos-boot`. The four have the same consumers (the loader, the kernel, and + the host reading the black box) and the same policy, so the merge adds no + edge. Check: 136 tests and the doc-test are listed, and every loader and + kernel clippy shape is green. +6. After stage 2, `toyos-mixer` folds into soundd, `toyos-desktop` into the + compositor, and `toyos-mdns` into netd. Each becomes a module with + `#![forbid(unsafe_code)]` at its root. Check: each program's listed tests + grow by the folded crate's own count (55, 95 and 12), and + `the_corpus_is_reproduced_bit_for_bit` is listed under `userland/soundd`. +7. `toyos-transport` folds into `toyos-blockring`, `toyos-net-wire` and + `-tcp` merge into one `toyos-net`, and `toyos-sched/loom` folds into + `kernel-loom`. Check: every moved control reds, and the listed counts sum + (13+19, 273+358, 23+58, plus doc-tests). +8. **The gate.** `src/hostws.rs` reads every manifest in the tree and reds on + a package whose only artifact is an `rlib` when it has fewer than two + consumers and declares no exception under `[package.metadata.toyos]`. A + binary or a `cdylib` is an artifact of its own and is not held to the rule. + A consumer is a normal, build or dev path dependency, or a `#[path]` + inclusion. The exceptions are a closed set: `sysroot`, `model`, `fixture` + and `track`. A `track` exception names an existing `issues/` file. Check: a + fixture with one consumer and no declaration reds. + +The end state is 79 packages and 29 `toyos-*` directories. Six exceptions are +left: + +- `model`: `kernel-loom`, `toyos-xhci/sim` and `toyos-libc-copies`. +- `fixture`: `tests/toyos-rust-tests/tls-multi-crate/dep`, whose crate boundary + is what its test crosses. +- `sysroot`: `userland/libc`. +- `track`: `toyos-net`, with 17,318 lines, 631 tests and no consumer + (`issues/design-debt/toyos-has-its-own-network-stack.md`). diff --git a/issues/build/the-pcid-negative-control-runs-nowhere.md b/issues/build/the-pcid-negative-control-runs-nowhere.md new file mode 100644 index 0000000000..0cd7cfc69c --- /dev/null +++ b/issues/build/the-pcid-negative-control-runs-nowhere.md @@ -0,0 +1,19 @@ +--- +status: open +kind: tooling +opened: 2026-09-29 +--- + +# `toyos-pcid`'s negative control runs nowhere + +`toyos-pcid/Cargo.toml` declares `counting-allocator`, the control that reverts +`PcidPool` to the counter that reissued a live tag. Nothing runs it: +`src/ci.rs`'s `CONTROLS` has no row for it, and `src/build.rs`'s +`declared_model_controls` does not read `toyos-pcid/Cargo.toml`, so +`every_model_control_is_run` cannot notice. The control still has teeth, run by +hand: `cargo test -p toyos-pcid --features counting-allocator` exits 101 with +`tests::two_live_address_spaces_never_share_a_pcid ... FAILED`. + +**Exit:** the control is a `CONTROLS` row demanding that `FAILED` line, and +`declared_model_controls` reads every manifest in the host workspace rather than +a list, so a control declared in a crate the list forgot reds. diff --git a/issues/build/userland-programs-are-never-linted.md b/issues/build/userland-programs-are-never-linted.md new file mode 100644 index 0000000000..617be2114c --- /dev/null +++ b/issues/build/userland-programs-are-never-linted.md @@ -0,0 +1,19 @@ +--- +status: open +kind: tooling +opened: 2026-09-29 +--- + +# No userland program is linted, though the gated ones build for the host + +`src/clippy.rs` lints no userland crate because the `toyos` toolchain ships no +clippy. The crates `src/userlandhost.rs` gates already build for the host with +the stable toolchain, and stable's clippy lints them there. They are not clean: +`cargo clippy --manifest-path userland//Cargo.toml --target +aarch64-apple-darwin --all-targets -- -D warnings` exits 101 with 8 findings +in soundd and 10 in netd. The compositor stops on 1 finding in its dependency +`userland/toyos-window`. + +**Exit:** `--ci host` lints every crate the survey gates, on the host target, +with warnings denied, and those findings are fixed. A clippy finding planted in +a gated userland crate reds the step. From b64b39ea6d6e6344a9f9efeb68853ccdb1c559a6 Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 29 Sep 2026 10:56:38 +0200 Subject: [PATCH 02/24] Track: code used by one program lives in that program The owner's direction on the first round: no gate, and push the cleanup as far as the measured facts allow. The track is renamed from a-crate-has-two-consumers-or-says-why-it-is-alone, because it no longer makes that claim, and it is rewritten as five steps, each with an exit and a check. - The kernel-only crates become a lib target of the kernel package, in kernel/pure/. The loom models become kernel/loom/, and the simulators kernel/sim/. - Single-program userland crates become modules of their program, or its library where another program reads them. - Shared crates on one subject merge: toyos-boot, toyos-log, toyos-block, and swap into toyos-manifest. - toyos-userpin is deleted. That takes the tree from 95 packages to 66, and the root's toyos-* directories from 44 to 16. Trial folds in scratch back the plan: - Seven kernel crates in kernel/pure/: the kernel builds for both bare targets, and the library lists 276 tests, the seven crates' sum. The simulator and loom counts are unchanged, four controls still red, the harness builds against the library, and both clippy shapes exit 0. - The mixer in soundd lists 58 tests, and the desktop in the compositor 95. soundd at opt-level 0 takes 34.31 s, and 3.07 s at 2. Co-Authored-By: Claude Opus 5.5 --- ...s-two-consumers-or-says-why-it-is-alone.md | 111 ------------------ ...ed-by-one-program-lives-in-that-program.md | 64 ++++++++++ 2 files changed, 64 insertions(+), 111 deletions(-) delete mode 100644 issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md create mode 100644 issues/build/code-used-by-one-program-lives-in-that-program.md diff --git a/issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md b/issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md deleted file mode 100644 index a945622a77..0000000000 --- a/issues/build/a-crate-has-two-consumers-or-says-why-it-is-alone.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -status: open -kind: track -opened: 2026-09-29 ---- - -# A crate has two consumers or says why it is alone - -The tree has 95 packages. Its 44 `toyos-*` directories hold 47 of them. Of -those 47, 13 are a normal dependency of exactly one package, and 7 are a -dependency of none. Almost every single-consumer crate gives the same reason in -its manifest or module doc: as a crate, its tests run on the host. Measured on -2026-09-29, that reason does not hold: - -- **A userland program host-tests its own modules.** `--ci host` already runs - `cargo test --target ` in calc, logd, netd, pkg, soundd and sshd - (`src/userlandhost.rs`). blockd, calc, pkg and terminal each carry a `lib` - beside their `bin`. -- **A bare-target package can carry a host-tested library.** Take a package - with a `src/lib.rs` (`cfg_attr(not(test), no_std)`, `forbid(unsafe_code)`) - and a `no_std`/`no_main` bin, under a `.cargo/config.toml` naming - `x86_64-unknown-none`. It builds both targets for the bare target, and - `cargo test --lib --target aarch64-apple-darwin` runs the library's tests. - With `[[bin]] test = false`, a plain `cargo test --target ` builds no - binary. A host package that depends on it by path builds only the library. -- **The kernel binary itself runs a `#[cfg(test)]` test on the host** after - four edits: `cfg_attr(not(test), no_main)`, the panic handler and global - allocator taken out under test, and the three aarch64 entry assembly items - taken out under test. Mach-O refuses their ELF local label and ADRP fixups. - The test build also allows `dead_code` and `unused_imports`, and it builds - clean in 3.4 s. On `x86_64-unknown-linux-gnu`, the test target compiles, and - its object links as a PIE under `-z text`. Only libc, unwinder and - allocator-shim symbols stay undefined. The full link there is unmeasured, - because the CI host job runs on `macos-latest`. -- **`#![forbid(unsafe_code)]` holds at module scope.** In a module file, the - inner attribute refuses an `unsafe` block and a local `allow` (E0453). A - sibling module keeps its own `unsafe`. - -So a crate with one consumer earns its boundary only for a reason other than -host tests. It may be built into the toolchain's sysroot. It may compile -another package's sources under another `cfg` (loom, a simulator, a host -differential). The crate boundary may itself be under test. Or an open track -may name its first consumer. The `cfg` case has a second reason: under one -`cargo test --workspace`, cargo unifies features, so a feature one package -turns on would leak into every other consumer's build. - -**What a fold costs.** - -- A library target shares its package's dependency list with the binary. -- The gated userland programs are not clippy-clean - (`issues/build/userland-programs-are-never-linted.md`), so a crate folded - into one leaves clippy's reach until that entry closes. -- `src/sourcegate.rs`'s `PURE_CRATES` rule is by directory, so it has to - follow the modules. -- A clean bare kernel build takes 5.4 s, 4.3 s of it in the kernel crate. Each - of the five kernel-only crates takes 0.09 to 0.32 s. - -Stages. Each lands green on `--ci host` and `--build-only`: - -1. Delete `toyos-userpin`. Nothing depends on it, and it names nothing the - kernel defines. `munmap_reissues_read_window` is the gate. Check: - `git grep toyos-userpin` is empty. -2. Close `issues/build/userland-programs-are-never-linted.md`. Every stage - that folds into a userland program waits on it. -3. **The kernel's library.** `toyos-dma`, `-pci`, `-pcid`, `-proclife` and - `-ps2` become modules of a `lib` target of `kernel/`, with crate-level - `forbid(unsafe_code)`. It is a library, not tests inside the binary: the - library cannot name the binary's items, which is the wall the five crates - provided, and the binary needs no `cfg(test)`. `--ci host` tests it from - `kernel/`, so that directory's `-Dwarnings` applies, and a clippy shape - lints it on the host. - proclife's six controls and pcid's `counting-allocator` - (`issues/build/the-pcid-negative-control-runs-nowhere.md`) become kernel - features in `CONTROLS`. Check: `-- --list` shows at least the five crates' - 156 tests. Every moved control reds. An `unsafe {}` planted in a moved - module does not compile. -4. `toyos-symbols` splits. `locate` and `file_range` go into `toyos-elf`, and - the name budget goes into the kernel's library. Check: `cargo tree` in - `bootloader/` gains no package. -5. `toyos-acpi`, `-bootmap`, `-rootimage` and `-blackbox` become one - `toyos-boot`. The four have the same consumers (the loader, the kernel, and - the host reading the black box) and the same policy, so the merge adds no - edge. Check: 136 tests and the doc-test are listed, and every loader and - kernel clippy shape is green. -6. After stage 2, `toyos-mixer` folds into soundd, `toyos-desktop` into the - compositor, and `toyos-mdns` into netd. Each becomes a module with - `#![forbid(unsafe_code)]` at its root. Check: each program's listed tests - grow by the folded crate's own count (55, 95 and 12), and - `the_corpus_is_reproduced_bit_for_bit` is listed under `userland/soundd`. -7. `toyos-transport` folds into `toyos-blockring`, `toyos-net-wire` and - `-tcp` merge into one `toyos-net`, and `toyos-sched/loom` folds into - `kernel-loom`. Check: every moved control reds, and the listed counts sum - (13+19, 273+358, 23+58, plus doc-tests). -8. **The gate.** `src/hostws.rs` reads every manifest in the tree and reds on - a package whose only artifact is an `rlib` when it has fewer than two - consumers and declares no exception under `[package.metadata.toyos]`. A - binary or a `cdylib` is an artifact of its own and is not held to the rule. - A consumer is a normal, build or dev path dependency, or a `#[path]` - inclusion. The exceptions are a closed set: `sysroot`, `model`, `fixture` - and `track`. A `track` exception names an existing `issues/` file. Check: a - fixture with one consumer and no declaration reds. - -The end state is 79 packages and 29 `toyos-*` directories. Six exceptions are -left: - -- `model`: `kernel-loom`, `toyos-xhci/sim` and `toyos-libc-copies`. -- `fixture`: `tests/toyos-rust-tests/tls-multi-crate/dep`, whose crate boundary - is what its test crosses. -- `sysroot`: `userland/libc`. -- `track`: `toyos-net`, with 17,318 lines, 631 tests and no consumer - (`issues/design-debt/toyos-has-its-own-network-stack.md`). diff --git a/issues/build/code-used-by-one-program-lives-in-that-program.md b/issues/build/code-used-by-one-program-lives-in-that-program.md new file mode 100644 index 0000000000..cf6d4fa186 --- /dev/null +++ b/issues/build/code-used-by-one-program-lives-in-that-program.md @@ -0,0 +1,64 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# Code used by one program lives in that program + +A crate exists because two programs share it. What only the kernel uses is the +kernel package's, what only one userland program uses is that program's, and +shared crates with one subject are one crate. No gate holds the layout; it is +kept by review (owner, 2026-09-29). + +Two open tracks plan single-program crates this one folds back: +`toyos-supervisor` (`issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md`) +and the stack's `toyos-net-*` (`issues/design-debt/toyos-has-its-own-network-stack.md`). +They are reconciled before step 5. + +Every step lands green on `--ci host` and `--build-only`. Test counts are what +`cargo test -p -- --list` lists today. + +1. **Delete `toyos-userpin`.** It models the pin invariant and names nothing + the kernel defines; `munmap_reissues_read_window` holds the kernel to it. + Check: `git grep toyos-userpin` is empty and that test has no + `src/redlist.rs` row. +2. **Lint userland first.** Close + `issues/build/userland-programs-are-never-linted.md`: steps 3 to 5 move code + out of the host workspace's clippy. Check: that issue's planted finding reds. +3. **The kernel's library.** `kernel/pure/` is the `kernel` package's lib, and + its bin is `test = false`. `toyos-dma`, `-pci`, `-pcid`, `-proclife`, + `-ps2`, `-sched`, `-xhci`, `-userbound` and `toyos-symbols`' name budget move + in; `locate` and `file_range` go to `toyos-elf`. `kernel-loom` and + `toyos-sched/loom` become `kernel/loom/`, and the two simulators + `kernel/sim/`. The harness dev-depends on the kernel, and the build system + does not depend on it. The library has no `tests/`, since an integration + test builds the binary for the host. `--ci host` tests it with + `sched-check,sched-protocol-port`, the features six scheduler tests need. + Closes `issues/build/the-pcid-negative-control-runs-nowhere.md`. + Check: the library lists at least 391 tests, and it and `toyos-elf` gain + `toyos-symbols`' 9 between them. `kernel/loom` lists 81 and `kernel/sim` 99. + Every moved control, and pcid's `counting-allocator`, reds with its verdict. + An `unsafe {}` planted in a module that was `forbid(unsafe_code)` does not + compile. `cargo tree -e normal -p toyos-build` names no `kernel`. +4. **Shared crates merge by subject.** `toyos-boot` is `toyos-acpi`, + `-bootmap`, `-rootimage`, `-blackbox`, `-tco` and `-quiesce`. `toyos-log` + is `toyos-elide` and `-logstream`. `toyos-block` is `toyos-blockhold`, + `-blockring` and `-transport`. `toyos-manifest` takes in `toyos-swap`, and + `toyos-fat32-check` moves to `toyos-fat32/check/`. + Check: the merged crates list at least 157, 26, 45, 25 and 67 tests. The + blockring and transport controls red, and every loader and kernel clippy + shape is green. `cargo tree` in `bootloader/` names no package it did not + name before, except `toyos-boot`. +5. **Userland code goes home.** `toyos-mixer` moves into soundd and + `toyos-desktop` into the compositor. `toyos-dns`, `-mdns`, `-net-wire` and + `-net-tcp` become netd's library, `filepicker-api` filepicker's library, and + `toyos-inspect` inspect's library. `toyos-libc-copies` moves to + `tests/libc-arch/`. Userland's host tests are built at opt-level 2, + since soundd's tests with the mixer take 34.31 s at 0 and 3.07 s at 2. + Check: soundd lists 55 more tests, the compositor 95, netd 709 and inspect + 21, and `the_corpus_is_reproduced_bit_for_bit` is listed under soundd. + `--build-only` builds the guest tests against netd's and inspect's + libraries. + +**Exit:** no directory this file names as moved or merged still exists. From 2b2aafc44ad9666847a8e7dda75815122b8369a8 Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 29 Sep 2026 13:11:54 +0200 Subject: [PATCH 03/24] Place operating lessons in the agent role files and the ARM64 ruling in CLAUDE.md Agents kill what they started before reporting, give rg an explicit path in scripts, and keep PR evidence out of /tmp; the orchestrator acts on a finished report before dispatching. CLAUDE.md states the general-purpose scope and that ARM64 is a QEMU target only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs --- .claude/agents/implementer.md | 7 +++++++ .claude/agents/orchestrator.md | 7 +++++-- .claude/agents/reviewer.md | 3 ++- CLAUDE.md | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index f4537fffd6..5561c3a6d8 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -26,6 +26,13 @@ guess. Then build, then test before anyone reviews: - Long commands run in the background with output to a file under the job scratchpad the brief names. Stay inside one turn while anything runs: sleep at most two minutes, print a line, check again. Ten minutes of silence kills you, and ending a turn to announce a wait strands the work. + Before reporting, list your processes and kill by PID anything you started that still runs: no + wait loop, watcher or build outlives its report. +- In a script or non-interactive shell `rg` is always given an explicit path: without one it reads + stdin and waits for ever. +- 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. - A mutation is a measurement only once the mutated tree is shown to build. Apply it as a checked patch, restore it in the same script, and leave the tree clean. - Never a flat wait, in code or in a test: wait on the event, bounded by a timeout that fails diff --git a/.claude/agents/orchestrator.md b/.claude/agents/orchestrator.md index d265a6ca59..06fa604027 100644 --- a/.claude/agents/orchestrator.md +++ b/.claude/agents/orchestrator.md @@ -35,11 +35,14 @@ for drivers, security boundaries and reviews of them, and a mid tier for mechani exact list. A resumed agent only ever finishes its own interrupted task. 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. +rules, so a brief carries only the task. A finished agent's report is acted on before any new work +is dispatched. 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 -a poll loop. Every agent's transcript records its usage: a claim about cost is read from those. +a poll loop. Every agent's transcript records its usage: a claim about cost is read from those. Nothing a pull +request's evidence rests on lives only in a temporary directory: `/tmp` is wiped when the CLI +restarts, and mutation patches are posted to the pull request as a comment. ## Judge diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md index 19d9269a83..09ba065c86 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -8,7 +8,8 @@ You review one branch against `origin/main`, at the head your brief names. The b request body and the tree are your whole context. You look for reasons to send the branch back: never agree by default, never soften, never praise, and take as many rounds as the code needs. You read; you run no test and no build. A claim about behaviour stands only on a measurement in the pull request body: -its command, its exit code and its log. You change nothing in the tree. The orchestrator judges. +its command, its exit code and its log. You change nothing in the tree. The orchestrator judges. Before reporting, list your processes and +kill by PID anything you started that still runs: no wait loop, watcher or build outlives its report. ## Test, gather, then review diff --git a/CLAUDE.md b/CLAUDE.md index 668e00682e..9a21d719bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # ToyOS -An operating system built from scratch in Rust, held to a production-grade engineering bar — the bar is the changes, not yet the product. Modern x86-64 hardware (2020+), UEFI only; ARM64 planned — keep the architecture portable. The quality bar is shipping software: correct, efficient, minimal, zero silent debt. A tracked weakness is still a weakness: the honest answer about current state is "known, tracked, still true" — never "we have an issue for that." +A general-purpose operating system built from scratch in Rust for modern hardware, held to a production-grade engineering bar — the bar is the changes, not yet the product. The proving machines decide the feature set, never the design. x86-64 (2020+), UEFI only; ARM64 runs under QEMU on the development machine, for quick runs and to keep the kernel's architecture clean — no ARM hardware is a target. The quality bar is shipping software: correct, efficient, minimal, zero silent debt. A tracked weakness is still a weakness: the honest answer about current state is "known, tracked, still true" — never "we have an issue for that." ## Where the rest of this lives From ced37a606757f1aa021fe3b93f82054c7edfd4f2 Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 29 Sep 2026 13:28:33 +0200 Subject: [PATCH 04/24] Answer review of #613 Restore "keep the architecture portable" beside the ARM64 sentence and drop "proving machines"; fold kill-by-PID into "Leave the machine as you found it"; keep the rg and /tmp rules in implementer.md only; drop the ARM-hardware clause from the process-memory issue. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs --- .claude/agents/orchestrator.md | 7 ++----- .claude/agents/reviewer.md | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.claude/agents/orchestrator.md b/.claude/agents/orchestrator.md index 06fa604027..d265a6ca59 100644 --- a/.claude/agents/orchestrator.md +++ b/.claude/agents/orchestrator.md @@ -35,14 +35,11 @@ for drivers, security boundaries and reviews of them, and a mid tier for mechani exact list. A resumed agent only ever finishes its own interrupted task. 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 finished agent's report is acted on before any new work -is dispatched. +rules, so a brief carries only the task. 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 -a poll loop. Every agent's transcript records its usage: a claim about cost is read from those. Nothing a pull -request's evidence rests on lives only in a temporary directory: `/tmp` is wiped when the CLI -restarts, and mutation patches are posted to the pull request as a comment. +a poll loop. Every agent's transcript records its usage: a claim about cost is read from those. ## Judge diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md index 09ba065c86..19d9269a83 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -8,8 +8,7 @@ You review one branch against `origin/main`, at the head your brief names. The b request body and the tree are your whole context. You look for reasons to send the branch back: never agree by default, never soften, never praise, and take as many rounds as the code needs. You read; you run no test and no build. A claim about behaviour stands only on a measurement in the pull request body: -its command, its exit code and its log. You change nothing in the tree. The orchestrator judges. Before reporting, list your processes and -kill by PID anything you started that still runs: no wait loop, watcher or build outlives its report. +its command, its exit code and its log. You change nothing in the tree. The orchestrator judges. ## Test, gather, then review From 88dbfedc9ecc2ad9601656a200e8e8fa1a29d670 Mon Sep 17 00:00:00 2001 From: japabu Date: Tue, 29 Sep 2026 13:31:05 +0200 Subject: [PATCH 05/24] Fold the kill-by-PID clause into CLAUDE.md and tighten the opening paragraph The kill-by-PID rule moves from implementer.md into the CLAUDE.md "Leave the machine as you found it" bullet, "for ever" becomes "forever", and the process-memory issue drops its ARM64 clause. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs --- .claude/agents/implementer.md | 4 +--- CLAUDE.md | 4 ++-- ...s-memory-is-2-mib-pages-and-that-caps-the-process-count.md | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index 5561c3a6d8..37aac74da6 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -26,10 +26,8 @@ guess. Then build, then test before anyone reviews: - Long commands run in the background with output to a file under the job scratchpad the brief names. Stay inside one turn while anything runs: sleep at most two minutes, print a line, check again. Ten minutes of silence kills you, and ending a turn to announce a wait strands the work. - Before reporting, list your processes and kill by PID anything you started that still runs: no - wait loop, watcher or build outlives its report. - In a script or non-interactive shell `rg` is always given an explicit path: without one it reads - stdin and waits for ever. + stdin and waits forever. - 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.md b/CLAUDE.md index 9a21d719bd..061a3df685 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # ToyOS -A general-purpose operating system built from scratch in Rust for modern hardware, held to a production-grade engineering bar — the bar is the changes, not yet the product. The proving machines decide the feature set, never the design. x86-64 (2020+), UEFI only; ARM64 runs under QEMU on the development machine, for quick runs and to keep the kernel's architecture clean — no ARM hardware is a target. The quality bar is shipping software: correct, efficient, minimal, zero silent debt. A tracked weakness is still a weakness: the honest answer about current state is "known, tracked, still true" — never "we have an issue for that." +A general-purpose operating system built from scratch in Rust for modern hardware, held to a production-grade engineering bar — the bar is the changes, not yet the product. The machines ToyOS is tested on decide its feature set, never its design. x86-64 (2020+), UEFI only; keep the architecture portable: ARM64 runs under QEMU on the development machine, for quick runs and to keep the kernel clean, and no ARM hardware is a target. The quality bar is shipping software: correct, efficient, minimal, zero silent debt. A tracked weakness is still a weakness: the honest answer about current state is "known, tracked, still true" — never "we have an issue for that." ## Where the rest of this lives @@ -74,7 +74,7 @@ The testing rules live where they are enforced: known reds in `src/redlist.rs`, - `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. - **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. +- **Leave the machine as you found it.** The development machine is shared: every agent stops what it started — before reporting it kills by PID anything of its own that still runs — removes the worktrees and scratch build output it no longer needs, and never leaves an emulator, a build or a watcher running. ## Repository layout diff --git a/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md b/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md index 4dea86b1b3..ea196086e6 100644 --- a/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md +++ b/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md @@ -14,9 +14,7 @@ can do is a restriction, and 2 MiB-only paging caps the process count. **What is to be built:** process memory — stacks, thread-local blocks, heaps, small data and code segments, and device windows handed to a process — moves to 4 KiB pages. 2 MiB stays where it pays: large mappings, DMA grants, the -IOMMU's superpages, the kernel's own mappings. One paging design serves both, -on x86-64 and on the ARM64 the tree is kept portable for -(`issues/kernel/toyos-runs-on-arm64.md`). +IOMMU's superpages, the kernel's own mappings. One paging design serves both. **The constraint that makes this a track, measured on the T14** (run 29 readback, `PMM: 100/16038MB used` at 11 s with four user processes and three From 7eb4428ddd4d4eb6aeb29da5ab307d214de00d51 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 30 Sep 2026 23:30:45 +0200 Subject: [PATCH 06/24] issues: write the owner's rulings of 2026-09-30 where they govern The child-process track's fourteen questions are ruled as recommended, each written as one line at the stage it governs, and issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md goes: - Q2, Q6d at stage 2: an end reads as an exit, a kill or a fault kind alike on every architecture, a bare code reads the last two as failures, and no end reads as a quit's reason. - Q3a, Q3b, Q3c at stage 3: libc imitates SIGCHLD; a handler runs at once, beside the program; a C child starts with descriptors 0-2 and what its file actions name, a stated departure from POSIX. - Q5a, Q5b, Q5c at stage 5: a login's session is a program that only parents what its user starts; init hands the compositor or sshd the right to start programs in it; sshd's right also quits and kills it. - Q6a, Q6b, Q6c, Q6e, Q6f at stage 6, whose text already said each as recommended: a quit carries interrupt, hang-up or terminate, reaches the subtree, kills a process that never listens, reaches a Rust program through std and ctrlc, and a C program as SIGINT, SIGHUP and SIGTERM. - Q7 at stage 7: a second Ctrl+C kills only a program that has not yet taken the first. Cut as moot: the pointer to the question file, the question labels on the stage headings, the Ruled block's session clause (now stage 5's line), stage 6's reason for the shell relaying nothing (now stage 6's reach line), and stage 7's "Stop and continue need a suspend primitive and are not proposed", which scoped the proposal the rulings closed. The track stays at 260 lines. The supervisor track's open item on the ask's ABI cited Q6a; Q6a is ruled and its stage 3 already asks by stage 6's quit, so the item goes. The rest: - The kernel is to load CPU microcode signed by the CPU's maker and pinned by version and hash, as vendor device firmware is. The question becomes the defect issues/kernel/the-kernel-loads-no-cpu-microcode.md, exit: current microcode loaded early on every CPU, at least as current as Linux's. The security track's list follows the rename. - std::os::toyos::io::{AsRawFd, FromRawFd} are renamed the next time the trait is touched in the fork: an open defect with that exit. - A boot start's device refusal is fatal only for a device the [programs] row marks as required; otherwise init logs it loudly and starts the program without it: an open defect whose exit is the row's mark and the two outcomes. - doomgeneric's fetch stays; its question file goes. - doom.jpg is the owner's own screenshot, which he keeps, and no licence question applies; its question file goes and its COMMITTED_FILES row says so in the column that names where its terms are recorded. The terms column stays NOASSERTION. - The blocked-task dump keeps painting its report on the panel and holding it there: one line at the small-kernel track's step 5 replaces that step's open question and stage 6's hold on it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- ...g-shows-ids-art-under-no-recorded-terms.md | 17 -- ...neric-becomes-a-submodule-is-the-owners.md | 20 -- ...efused-a-present-device-runs-without-it.md | 33 +- .../os-toyos-io-traits-keep-a-posix-name.md | 43 +-- ...rvisor-is-host-tested-and-owns-the-stop.md | 6 +- ...nt-and-a-parent-takes-its-children-down.md | 126 ++++---- ...ocess-track-waits-on-the-owners-rulings.md | 283 ------------------ ...s-linux-on-every-machine-toyos-supports.md | 2 +- ...-small-interrupts-post-and-threads-wait.md | 9 +- .../the-kernel-loads-no-cpu-microcode.md | 15 + ...ernel-loads-cpu-microcode-is-the-owners.md | 14 - src/licence.rs | 4 +- 12 files changed, 113 insertions(+), 459 deletions(-) delete mode 100644 issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md delete mode 100644 issues/build/whether-doomgeneric-becomes-a-submodule-is-the-owners.md delete mode 100644 issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md create mode 100644 issues/kernel/the-kernel-loads-no-cpu-microcode.md delete mode 100644 issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md diff --git a/issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md b/issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md deleted file mode 100644 index 518f966df5..0000000000 --- a/issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-26 ---- - -# `doom.jpg` shows id's art, and nothing records the terms it is under - -`doom.jpg`, the screenshot `README.md` shows, is a picture of this system -running doom, so most of its pixels are `assets/DOOM1.WAD`'s graphics. -Its row in `COMMITTED_FILES` (`src/licence.rs`) declares `NOASSERTION`, -because the tree's `MIT OR Apache-2.0` does not reach id's art, and the -shareware terms in `NOTICE` do not name screenshots. No image -ships the file, so the licence gate does not judge it. - -**Exit**: the owner rules on the terms, and the row says them. Or the -screenshot is replaced by one that shows only this tree's own work. diff --git a/issues/build/whether-doomgeneric-becomes-a-submodule-is-the-owners.md b/issues/build/whether-doomgeneric-becomes-a-submodule-is-the-owners.md deleted file mode 100644 index 82fa7f7337..0000000000 --- a/issues/build/whether-doomgeneric-becomes-a-submodule-is-the-owners.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-29 ---- - -# Whether doomgeneric becomes a submodule is the owner's - -`userland/doom/build.rs` fetches doomgeneric's commit `fc601639` as a GitHub -archive into the gitignored `userland/doom/doomgeneric/` and compiles it. The -fetch costs five build-dependencies in `userland/doom/Cargo.toml` — `ureq`, -`rustls-rustcrypto`, `webpki-roots`, `flate2`, `tar` — and leaves a ToyOS -change to the C nowhere to live but that untracked directory, which the build -replaces whenever its stamp disagrees with the pin. A fork repository of -doomgeneric as a submodule at that path deletes the fetch and the five. Neither -`ToyOSOrg/doomgeneric` nor `Japabu/doomgeneric` exists (`gh repo view`), and -creating one is the owner's. - -**Exit**: the owner rules — the fetch stays, or a submodule on a repository he -creates replaces it. diff --git a/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md b/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md index 14ee6cc58f..d252767e3e 100644 --- a/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md +++ b/issues/design-debt/a-boot-start-refused-a-present-device-runs-without-it.md @@ -1,26 +1,21 @@ --- -status: owner -kind: question +status: open +kind: defect opened: 2026-09-24 --- # A boot start refused a device the machine has runs without it -`/system/bin/init`'s `start` mints every device a `[programs]` row names. At a -swap or the restart that rolls one back, a device the process being replaced -held and the new one is refused fails the start (`Served::Restart`'s `owed`). -At boot there is no process before it, and a refusal is said in the kernel's -own word (`init: netd: pci:8086:10c9 is on this machine and could not be -handed over`) and the program is started without that device. +A boot start's device refusal is fatal only for a device its `[programs]` row +marks as required; for any other, init logs the refusal loudly and starts the +program without that device (owner, 2026-09-30). No row can mark a device +required, so every boot start refused a device the machine has — +`NotSupported`, `AlreadyExists`, `ResourceExhausted` — runs without it: +`/system/bin/init`'s `start` says the refusal in the kernel's own word +(`init: netd: pci:8086:10c9 is on this machine and could not be handed over`) +and starts the program. `refused_claim` (`tests/common/faults.rs`) and +`pci_function_is_exclusive` assert that behaviour today. -That is not the swap's defect — init claims nothing about a boot start beyond -`started`, and a row names every device its program can drive, so a machine -with a subset is a configuration, not a fault — but a refusal other than -`NotFound` is a fault, and the program then runs on less than the machine -has. `tests/common/faults.rs`'s `refused_claim` and `pci_function_is_exclusive` -assert exactly this behaviour today. - -The question for the owner: should a boot start refused a device the machine -has — `NotSupported`, `AlreadyExists`, `ResourceExhausted` — be fatal? A failed -boot start is an init panic, so the machine would not boot over one device; -and a row cannot yet say which of its devices its program needs. +**Exit**: a `[programs]` row can mark a device required, and guest tests show +a boot start refused a required device is fatal, and one refused a device its +row does not mark logs it loudly and starts the program without it. diff --git a/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md b/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md index d76b3aeaa2..df9885c522 100644 --- a/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md +++ b/issues/design-debt/os-toyos-io-traits-keep-a-posix-name.md @@ -1,37 +1,18 @@ --- -status: owner -kind: question +status: open +kind: defect opened: 2026-08-24 --- -# Does `std::os::toyos::io` get to keep `AsRawFd`/`FromRawFd`? +# `std::os::toyos::io` names its raw-handle traits `AsRawFd` and `FromRawFd` -The owner ruled on 2026-08-19 that **"fds belong only in libc jargon"**: the -kernel has no file descriptors, a process holds typed handles -(`toyos-abi/src/handle.rs`), and `userland/libc` is the one layer whose -interface the word describes. The sweep that followed took the kernel, the ABI, -the SDK, every forced call site and both fork pins. One name it did not settle -is left, and it is the owner's because it is a fork-repo name (`Japabu/rust`) -and not this tree's to rename alone. +`os::toyos::*` is ToyOS's own extension API and speaks ToyOS, and "fds belong +only in libc jargon" (owner, 2026-08-19). The `rust/` fork's +`library/std/src/os/toyos/io.rs` re-exports `std::os::fd`, so +`std::os::toyos::io::{AsRawFd, FromRawFd}` still speak POSIX. +`tests/toyos-rust-tests/src/bin/std_fs.rs` is their one caller in this +repository. -`std::os::toyos::io::{AsRawFd, FromRawFd}`. The naming table ruled that -`os::toyos::*` is ToyOS's own extension API and must speak ToyOS, and it named -every `process` row — but not this one. std's *POSIX* surface keeps the word by -charter; whether an `os::toyos` trait carrying a POSIX name is that surface or -the extension API is what nobody has decided. - -The two answers, both defensible: - -- **It stays**, as a deliberate mirror of std's own - `std::os::unix::io::{AsRawFd, FromRawFd}` convention — the trait names an - `os::toyos` caller reaches for by muscle memory, and diverging costs every - such caller a lookup. -- **It is renamed the next time the trait is touched in the fork**, to whatever - `os::toyos` decides its own non-POSIX vocabulary for a raw handle is, because - the extension API speaking POSIX is precisely what the ruling forbade - everywhere else. - -`tests/toyos-rust-tests/src/bin/std_fs.rs:4` is the only caller in this -repository (`use std::os::toyos::io::{AsRawFd, FromRawFd};`, used at `:59`). -Nothing is blocked on the answer; the word is legal in the two places it is -still spoken here, so this is a naming decision and not a defect. +**Exit**: the next time the trait is touched in the fork, `AsRawFd` and +`FromRawFd` in `std::os::toyos::io` are renamed to `os::toyos`'s own word for +a raw handle, and `std_fs.rs` uses the new names (owner, 2026-09-30). diff --git a/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md b/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md index a68d6923f8..9225b95b0c 100644 --- a/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md +++ b/issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md @@ -84,7 +84,5 @@ A deleted or disabled test covers nothing. ## Open with the owner -- Before stage 3: the ask's ABI, which is Q6a of - `issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md`; whether a program - started through `launcher` is asked or only stopped; whether - `SYS_SHUTDOWN`/`SYS_REBOOT` change at all. +- Before stage 3: whether a program started through `launcher` is asked or + only stopped; whether `SYS_SHUTDOWN`/`SYS_REBOOT` change at all. diff --git a/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md b/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md index 8a725701db..96d7ca05c2 100644 --- a/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md +++ b/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md @@ -10,8 +10,7 @@ libc cannot start a child: `fork` and `execvp` answer `ENOSYS`, `waitpid` `ECHILD` and `system` `-1` (`userland/libc/src/misc.rs`, `userland/libc/src/stdio.rs`), and there is no `posix_spawn`. M2 and M4 of `issues/build/toyos-builds-itself.md` and the exit of -`issues/kernel/toyos-runs-on-arm64.md` need it. Stages 2, 3, 5, 6 and 7 wait -on `issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md`. +`issues/kernel/toyos-runs-on-arm64.md` need it. **Constraints.** libc is built on ToyOS and never ToyOS on libc (owner, 2026-09-30: "toyos moderness and anti legacy may never be compromised because @@ -30,13 +29,11 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md — exit, crash or kill — kills its whole subtree at once; quitting gracefully is the parent's job before it exits, never the kernel's. The kernel starts init and init starts everything else, with no rule for services: a server - is init's child and lives as long as init. Each login, desktop or SSH, is a - session process under init that parents everything its user starts; an app - uses a server through handles and is never its child; logging out ends the - session. "Keep running after I close this" asks init, through `launcher`, to - be the parent, and nothing else reparents. A kill is cleanup without - cooperation, run on the causing path in bounded steps. init never dies and - stays tiny. + is init's child and lives as long as init. An app uses a server through + handles and is never its child. "Keep running after I close this" asks init, + through `launcher`, to be the parent, and nothing else reparents. A kill is + cleanup without cooperation, run on the causing path in bounded steps. init + never dies and stays tiny. ## Stages @@ -64,33 +61,36 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md handles to a held child closes, a non-blocking submit finds nothing. Negative control: the stage reverted whole (`NotSupported`); mutation: `close_ends_polls` at `true` reds the last arm. Oracle: pidfd_open(2). -2. **An end says how** (Q2, Q6d). *Exit*: exited 137, killed, and each fault +2. **An end says how** (ruled). An end reads as an exit, a kill or a fault kind + alike on every architecture, and a bare code reads the last two as failures. + No end reads as a quit's reason. *Exit*: exited 137, killed, and each fault kind the architecture raises, each read from a child that did it. Negative control: the stage reverted whole, where the first two read alike. Oracle: POSIX ``'s classes. -3. **libc starts and waits for children** (Q3a–Q3c; blocked on +3. **libc starts and waits for children** (ruled; blocked on `issues/isolation/a-childs-stdio-handle-is-not-the-one-command-named.md`). - `posix_spawn` and `posix_spawnp` with file actions and attributes, routed by - std's launch-or-spawn rule moved into `toyos`; a descriptor table with - close-on-exec, a child getting descriptors 0–2 and exactly what its file - actions name; `waitpid`, `wait`, `wait4`; `ppoll`, `sigpending` and the + libc imitates `SIGCHLD`. A C child starts with descriptors 0–2 and exactly + what its file actions name, a stated departure from POSIX. `posix_spawn` and + `posix_spawnp` with file actions and attributes, routed by std's + launch-or-spawn rule moved into `toyos`; a descriptor table with + close-on-exec; `waitpid`, `wait`, `wait4`; `ppoll`, `sigpending` and the signal-set calls; `environ`, `setenv`, `unsetenv`; `kill` with `SIGKILL` for a child or a `-pgid` of its children, 0 as a probe, `EINVAL` for other signals until stage 6; `system`, `popen` and `pclose` through `/system/bin/shell -c`. No `select`: a descriptor is a handle and passes - `FD_SETSIZE`. A handler for any signal libc imitates runs inside a blocking - call of a thread that leaves the signal unblocked (`ppoll` and `sigsuspend` - under their mask); after it `read`, `recv`, `accept`, `wait`, `waitpid` and - `wait4` restart if it was installed with `SA_RESTART` and answer `EINTR` if - not, and `poll`, `ppoll`, `pause`, `sigsuspend`, `nanosleep` and `usleep` - answer `EINTR`, `sleep` the time left. While no thread is in one, it runs at - once on libc's own thread; while every thread blocks the signal, it stays - pending, as `sigpending` reports, until one unblocks it. libc's own thread - watches at most 255 unwaited children on its own inbox, the 256th watch - being stage 6's notice, and wakes a thread in a blocking call through one - pipe, so `poll` takes at most 255 descriptors and children never count - against them. *Exit*, C corpus files, the mutation that reds an arm after - it: a piped stdout read to EOF through `poll`, then `waitpid`; + `FD_SETSIZE`. A handler for any signal libc imitates runs at once, beside the + program: inside a blocking call of a thread that leaves the signal unblocked + (`ppoll` and `sigsuspend` under their mask), and, while no thread is in one, + on libc's own thread. After it `read`, `recv`, `accept`, `wait`, `waitpid` + and `wait4` restart if it was installed with `SA_RESTART` and answer `EINTR` + if not, and `poll`, `ppoll`, `pause`, `sigsuspend`, `nanosleep` and `usleep` + answer `EINTR`, `sleep` the time left; while every thread blocks the signal, + it stays pending, as `sigpending` reports, until one unblocks it. libc's own + thread watches at most 255 unwaited children on its own inbox, the 256th + watch being stage 6's notice, and wakes a thread in a blocking call through + one pipe, so `poll` takes at most 255 descriptors and children never count + against them. *Exit*, C corpus files, the mutation that reds an arm after it: + a piped stdout read to EOF through `poll`, then `waitpid`; `waitpid(-1)` answering three children once each in the order they end, then `ECHILD` (waiting on the first alone), and `wait` and `wait4` alike (either answering `ECHILD` as `waitpid` does today); a killed child `WIFSIGNALED` @@ -168,20 +168,21 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md place spawned under init (it starts); std's `NotDeclared` fallback, or its early return for an endowment or extra slot, kept for init (it starts). Oracle: cgroup v2's `cgroup.kill` and `cgroup.max.depth`. -5. **A login is a session under init** (ruled; Q5a–Q5c). init starts a session - process per login — the desktop's at boot, one per SSH connection at sshd's - request — and the compositor and sshd start a login's programs under it. Its - namespace is `issues/filesystem/a-user-is-a-home-tree-and-a-login-row.md`'s - login row. *Exit*: killing the desktop session ends every program the - compositor started and leaves the compositor running; killing the compositor - ends none of them; a dropped SSH connection ends its session and all under - it. Negative control: the stage reverted whole, where the compositor's - programs die with it. Oracle: systemd-logind, whose session scope ends every - process of a login. -6. **A program is asked to quit** (Q3b, Q6a–Q6f). `SYS_PROCESS_QUIT` (124, - never assigned), `(process, reason)`, needs `MANAGE` as the kill does and - reaches the process's subtree by stage 4's walk; the reason is interrupt, - hang-up or terminate. Each process is started holding its notice, a new +5. **A login is a session under init** (ruled). Per login init starts a session + program that logout ends and that only parents what its user starts — the + desktop's at boot, one per SSH connection at sshd's request. As it starts + one, init hands the compositor or sshd a right to start programs in that + session alone. sshd's right also quits and kills it. Its namespace is + `issues/filesystem/a-user-is-a-home-tree-and-a-login-row.md`'s login row. + *Exit*: killing the desktop session ends every program the compositor started + and leaves the compositor running; killing the compositor ends none of them; + a dropped SSH connection ends its session and all under it. Negative control: + the stage reverted whole, where the compositor's programs die with it. + Oracle: systemd-logind, whose session scope ends every process of a login. +6. **A program is asked to quit** (ruled). `SYS_PROCESS_QUIT` (124, never + assigned), `(process, reason)`, needs `MANAGE` as the kill does, and the + reason is interrupt, hang-up or terminate. A quit reaches the process's + subtree by stage 4's walk. Each process is started holding its notice, a new object kind under the label `quit`, `READABLE` while a reason is asked and not yet read, whose read takes it. A quit kills a process that has never watched its notice or whose notice still holds a reason, whoever asks, in @@ -192,8 +193,8 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md Unix arm maps; rustc stops skipping its handler on ToyOS (`rust/compiler/rustc_driver_impl/src/lib.rs`, `install_ctrlc_handler`). Stage 5's session, `/system/bin/terminal` and `/system/bin/shell` listen and - relay nothing, the walk having asked what they run: each ends once that has, - `-c` with its status, but an interactive shell does not end on an interrupt. + relay nothing, each ending once what it runs has, `-c` with its status, but + an interactive shell does not end on an interrupt. libc takes the three reasons as `SIGINT`, `SIGHUP` and `SIGTERM`, watching the notice once one is handled, ignored or blocked: an ignored reason is dropped and an unhandled one ends the process with exit code 128 plus the @@ -236,25 +237,24 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md watching only from a handler's install (the ignoring child is killed); a spawn carrying no ignore, no mask or no `SETSIGDEF` (each spawned child in turn). Oracle: POSIX's signal actions (XSH 2.4.3) and `posix_spawn`. -7. **Job control** (Q6a–Q6c, Q7). The shell hands its terminal a `MANAGE`-only - duplicate of each process of the foreground line over its `surface` port. - The terminal turns each Ctrl+C into their interrupt, and before it closes it - asks its shell's tree to hang up and kills what is left at its deadline. - sshd does that to a dropped connection's session, and init at shutdown and - test-runner at a test's deadline ask with terminate. `&` is a line the shell - watches. sshd awaits exits through a ToyOS `tokio/src/process` arm in the - tokio fork, shaped as its Linux pidfd arm (`process/unix/pidfd_reaper.rs`). - Stop and continue need a suspend primitive and are not proposed. *Exit*: +7. **Job control** (ruled). The shell hands its terminal a `MANAGE`-only + duplicate of each process of the foreground line over its `surface` port. The + terminal turns each Ctrl+C into their interrupt, and a second kills only a + program that has not yet taken the first. Before it closes, the terminal asks + its shell's tree to hang up and kills what is left at its deadline; sshd does + that to a dropped connection's session, and init at shutdown and test-runner + at a test's deadline ask with terminate. `&` is a line the shell watches. + sshd awaits exits through a ToyOS `tokio/src/process` arm in the tokio fork, + shaped as its Linux pidfd arm (`process/unix/pidfd_reaper.rs`). *Exit*: Ctrl+C ends a pipeline whose stage started a child, none of it left and the prompt back, and ends no shell nested on the foreground line; a child that reads each interrupt survives three Ctrl+C, each pressed once it reports its - watch armed or the one before read, and one that reports its watch armed - and never reads dies on the second; a dropped SSH connection whose session - runs a watching child sees it write its file before it ends; tokio's - `Child::wait` returns with no loop polling `try_wait`. Negative control: - today's terminal and shell, where the pipeline runs on. Mutations: a quit - that kills on a reason already read (the reading child dies); a session - that never listens (the file unwritten); a shell that ends on an interrupt - (the nested shell). Oracle: POSIX's INTR, a `SIGINT` "sent to all processes - in the foreground process group" (XBD 11.1.9), and tokio's - `tests/process_smoke.rs`. + watch armed or the one before read, and one that reports its watch armed and + never reads dies on the second; a dropped SSH connection whose session runs a + watching child sees it write its file before it ends; tokio's `Child::wait` + returns with no loop polling `try_wait`. Negative control: today's terminal + and shell, where the pipeline runs on. Mutations: a quit that kills on a + reason already read (the reading child dies); a session that never listens + (the file unwritten); a shell that ends on an interrupt (the nested shell). + Oracle: POSIX's INTR, a `SIGINT` "sent to all processes in the foreground + process group" (XBD 11.1.9), and tokio's `tests/process_smoke.rs`. diff --git a/issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md b/issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md deleted file mode 100644 index a6d2f66ca9..0000000000 --- a/issues/kernel/the-child-process-track-waits-on-the-owners-rulings.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-30 ---- - -# The child-process track waits on the owner's rulings - -Stages 2, 3, 5, 6 and 7 of -`issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md` -wait on these, and are written there as recommended here. Each question is one -decision. Its ruling goes into the track as one line, and its entry here is -deleted. - -## Q2. Can a parent tell a failure from a kill or a crash? (stage 2) - -When a child ends, its parent reads one number. Today a child that exits with -137 and one that is killed both read 137, and a crash reads 139 or -1, numbers -a program can exit with too. - -- **Yes.** The number says whether the program ended itself or the kernel - ended it, and if the kernel did, why: killed, or which kind of crash. A crash - reads the same on x86-64 and on ARM64. A program that reads the number the - old way still sees a kill or a crash as a failure, never as a success. -- **No.** A tool that retries a crashed job cannot tell it from one that - failed, and a program that exits with 137 on purpose reads as killed. - -*Recommended: yes.* - -## Q3a. How does a C program learn that its child ended? (stage 3) - -C programs written for Unix learn it from a Unix signal, `SIGCHLD`: Ninja -waits for one (v1.13.1 `src/subprocess-posix.cc`), and so does libuv where it -has no kqueue (v1.53.0 `src/unix/process.c`). ToyOS has no signals, and its -kernel never will. - -- **libc imitates the signal.** libc, the C library, learns of the end from - ToyOS's own event and runs the program's handler for it. Ninja runs - unchanged, and the kernel gains nothing. -- **Each such program is changed for ToyOS.** Ninja, libuv and every other - program that waits for its children carries a ToyOS change of its own, as a - fork, for what one part of libc would serve. - -*Recommended: libc imitates it.* - -## Q3b. When does a C program's signal handler run? (stages 3 and 6) - -On Unix a signal interrupts the program wherever it is, and the handler runs -in its place. ToyOS never does that; the owner ruled it out as legacy. libc can -run a handler only on a thread it controls. - -- **At once, beside the program.** While one of the program's threads is - waiting inside libc — for input, for a child, for time to pass or for a - signal — the handler runs on that thread and cuts the wait short, as on - Unix: a C prompt waiting for a line comes back on Ctrl+C. As on Unix, a - program can ask that a wait for input or for a child carry on after its - handler instead, and a wait for time or for a signal is always cut short. - While no thread is waiting, libc runs the handler on a thread of its own, and - the program's own code goes on running at the same time. Ctrl+C stops clang - at once, and clang deletes the file it was writing. The cost: the handler and - the program's own code can then work on the same thing at once and collide. - GNU make's Ctrl+C handler collects the compiles that have ended, which make's - own code also does; on Windows, the one system where make's handler already - runs beside its code, make stops its own code first for exactly that reason - (make 4.4.1 `src/commands.c:508-510`). On ToyOS it collides only when Ctrl+C - comes while make is working rather than waiting. A handler that jumps back - into the program's main loop, as some C programs' do, lands on the wrong - thread and breaks the program; and clang can delete that file while its main - code is still writing into it. POSIX lets a handler run on any of a program's - threads that does not block the signal; this one is a thread the program - never made. -- **Only when the program next waits inside libc.** Nothing runs beside the - program. A compiler that is computing does not wait, so Ctrl+C does not stop - clang until it is killed, and its half-written file stays behind. - -*Recommended: at once, beside the program.* - -## Q3c. What does a C child start with? (stage 3) - -On Unix a child starts holding every file its parent has open, unless the -parent marked the file not to be passed on. - -- **Only what the parent names**, a stated departure from POSIX. A C child - starts with its input, output and error, and exactly what its parent names - when it starts it. A program started from C then holds what it holds when - started from Rust: what it is declared to hold. The cost: a C program that - hands its child a file by leaving it open, without naming it, loses it. GNU - make does that to share its limit on how many jobs run at once, with a pipe - it leaves open to a make it starts; LLVM's tools, told to share the limit, - take the same pipe (`llvm/lib/Support/Unix/Jobserver.inc`). Without it, a - make that make starts warns and runs one job at a time (make 4.4.1 - `src/main.c:1849`), and an LLVM tool warns and runs as many as it would - alone. make shares the limit by a named pipe instead where the system has - one, and ToyOS has none. -- **Unix's rule.** A C parent with any other file open passes it on, and every - program it starts runs with what that parent holds, where the same program - started from Rust runs with what it is declared to hold. - -*Recommended: only what the parent names.* - -## Q5a. What program is a login? (stage 5) - -Each login, on the desktop or over SSH, has one program that is the parent of -everything its user starts, so that logging out ends all of it. - -- **A program of its own that does nothing else.** init starts a small session - program for each login, and everything the user starts runs under it. It - listens: when the login is asked to end, its programs are asked too, and it - ends once they have. A crash of any program the user started leaves the - login and the rest of its programs running. -- **The login's first program.** An SSH login's session is the shell sshd - starts for it, and the desktop's is the first program the desktop starts for - the user. When that program ends or crashes, everything the user started - ends with it, as on Unix when the login shell exits. One program fewer runs - per login. - -*Recommended: a program of its own.* - -## Q5b. Who lets the compositor and sshd start programs in a login? (stage 5) - -A login's programs are started under its session, never under the compositor -or sshd, so each of them needs a right to start programs there. - -- **init hands it over when it starts the session.** At boot init starts the - desktop's session and gives the compositor the right to start programs in - it; for SSH, sshd asks init for a session per connection and gets that right - back. Each right reaches one session. -- **The session program hands it over itself**, over a connection init sets up - between them. The session program then has a second job besides being a - parent, and each login costs one connection more. - -*Recommended: init hands it over.* - -## Q5c. Can sshd end a login it asked for? (stage 5) - -When an SSH connection drops, its login ends: its programs are asked to hang -up, which reaches them because the session program listens (Q5a), and what is -left is killed. - -- **Yes, only its own.** The right sshd gets for a connection's session also - lets it ask that session to quit and kill it, so a dropped connection ends - its login with nobody else involved. sshd can end no other login. -- **No, it asks init.** Only init can end a login: sshd tells init that the - connection dropped, and init ends it. init does one more job, and stays the - only program that can end a login. - -*Recommended: yes, only its own.* - -## Q6a. Can one program ask another to quit? (stage 6) - -Today the only way one program can end another is a kill, which leaves it no -chance to save anything. The same answer decides how init asks each service to -finish when the machine shuts down -(`issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md`). - -- **Yes, with a reason.** A program allowed to end another — its parent, or - the terminal it runs in — can ask it to quit and say why: interrupt - (Ctrl+C), hang-up (a window closed, a connection dropped) or terminate - (shutdown, a deadline). The program hears it among its other events and - decides what to do: an editor saves, a compiler deletes its half-written - output, Ctrl+C in a shell or an editor stops what it is doing and ends - nothing, and a server may take a hang-up as the signal to reload its - settings. The shell, the terminal and a login's session program listen, so - the programs under them are asked too. Whoever asked still kills if the end - it wants does not come. The kernel gains one call and one kind of object. -- **Yes, without a reason.** A server that reloads on a hang-up and exits on - terminate would exit when asked to reload, and no program could tell Ctrl+C - from shutdown. -- **No: a kill only, as today.** Nothing saves its work when its window closes - or the machine shuts down. - -*Recommended: yes, with a reason.* Rejected: a quit channel std and libc would -make at every start, which a program started any other way would not have; and -a handler that interrupts the program's own code wherever it is, the legacy -the owner ruled out. - -## Q6b. What does a quit do to a program that never listens for one? (stage 6) - -Most programs never listen: `ls`, `cat`, a Rust program that does not ask. - -- **It is killed at once, with everything it started**, as a Unix program - that handles nothing dies of Ctrl+C. Ctrl+C stops such a program on the - first press. The cost: a program that listens still dies at once, without - cleaning up, when the program that started it does not listen, because it - dies with that program; on Unix only the one that does not listen would die. - ToyOS's own shell and terminal and each login's session program listen, so - a compiler run from a login cleans up. -- **Nothing, until whoever asked gives up and kills it.** Ctrl+C does nothing - to `cat` until a second press, and shutdown waits out its whole deadline for - every program that never listens. - -*Recommended: killed at once.* - -## Q6c. How far does a quit reach? (stages 6 and 7) - -- **The program and everything it started**, as a kill does. Ctrl+C reaches - `make` and every compile it is running at once, as on Unix, where Ctrl+C - reaches every program of the job in front. A closing terminal asks its shell - and everything the shell started to hang up. The cost: a program cannot keep - a helper it started out of a Ctrl+C meant for itself, as a Unix program can - by putting the helper in a job of its own. Ninja does that with every - compile and passes the Ctrl+C on to them itself (v1.13.1 - `src/subprocess-posix.cc:102`, `:451-457`), so here each compile is asked - twice, and one that has not yet taken the first when Ninja's comes is - killed (Q7), leaving its half-written file. The shell and the session - program pass nothing on, since the quit already reached what they run. -- **The program alone.** Ctrl+C reaches `make` and not its compiles, and make, - which expects its compiles to have been interrupted with it, waits for every - running one to finish before it stops (GNU make's `fatal_error_signal`). The - shell and the session program must then pass each quit on to what they run, - or a closing terminal reaches its shell and not what the shell started. - -*Recommended: the program and everything it started.* - -## Q6d. Does a program that Ctrl+C ended read as interrupted? (stages 2 and 6) - -Under the answers recommended here, no child that Ctrl+C ended ever reads to -its parent as interrupted. One that never listened reads as killed; a C -program that lets the interrupt end it once it has cleaned up, as clang does, -reads as having exited with 130, which is a failure. - -- **No.** The number keeps meaning only whether a program ended itself or the - kernel ended it. A build tool that tells an interrupt from a failure, as - Ninja does, may report a compile the user interrupted as failed; Ninja still - stops the build, because the Ctrl+C reached it too. -- **Yes.** Q2's reasons gain three — interrupted, hung up, terminated — so a - program a quit ended reads as ended for that reason, and a C program that - started it sees what it would on Unix. For clang to read that way, a program - must be able to end itself for one of those reasons, as a Unix program can - by sending the signal to itself, so the number no longer says only what the - kernel did. - -*Recommended: no.* - -## Q6e. Does an unchanged Rust program's Ctrl+C handler run? (stage 6) - -Rust programs that handle Ctrl+C mostly do it through one widely used library, -`ctrlc`; rustc is one of them. - -- **Yes.** Rust's standard library hands a program its quit notice, and the - `ctrlc` fork ToyOS already carries listens to it, so a Rust program's handler - runs on a quit as it does on Unix, with no change to the program. rustc - stops leaving its handler out on ToyOS. -- **No.** A Rust program hears a quit only through a call written for ToyOS; - rustc and every other `ctrlc` user is treated as a program that never - listens (Q6b). - -*Recommended: yes.* - -## Q6f. Does a C program hear a quit as the Unix signal for it? (stage 6) - -- **Yes.** libc turns interrupt into `SIGINT`, hang-up into `SIGHUP` and - terminate into `SIGTERM`, so a C program written for Unix acts as it does - there: clang deletes the file it was writing and stops, a server reloads on - a hang-up, and a program that says only to ignore Ctrl+C runs on through - it. A C program's `kill` with one of those signals asks for a quit. -- **No.** A C program never hears a quit, and is treated as a program that - never listens (Q6b): clang leaves its half-written file behind, and a server - asked to reload ends. - -*Recommended: yes.* - -## Q7. What does a second Ctrl+C do? (stage 7) - -A program that listens may stop what it is doing on Ctrl+C and carry on: a -Python prompt goes back to its prompt, an editor cancels a command. - -- **It kills only a program that has not yet taken the first.** A program - that takes each Ctrl+C and carries on survives any number of them; one stuck - so that it never takes the first dies on the second. The same holds whoever - asks: a program asked to quit again before it took the last ask is killed. - Two quick presses: a C program, or a Rust program using `ctrlc`, takes each - on a thread of its own as it comes, so it survives them unless the machine - is too busy to run that thread between the two; a program that takes its - Ctrl+C in its own loop and is busy across both presses dies, where on Unix - its handler would have run twice. A program that takes a Ctrl+C and then - hangs is not ended by Ctrl+C: closing its window or a kill ends it, as on - Unix. -- **It always kills.** A hung program always dies on the second press, but a - Python prompt or an editor dies on the second Ctrl+C of its life, and loses - what was not saved. - -*Recommended: only a program that has not yet taken the first.* diff --git a/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md b/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md index 6d7aa2f67a..72bb264b81 100644 --- a/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md +++ b/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md @@ -23,7 +23,7 @@ probe is a `boot-actuators` arm or a `test-actuators` `SYS_DEBUG` action. - `issues/kernel/user-pointer-checks-have-no-spectre-v1-fence-and-smap-is-optional.md` - `issues/kernel/indirect-branches-and-returns-run-without-thunks.md` - `issues/kernel/tsx-stays-as-firmware-left-it.md` -- `issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md` +- `issues/kernel/the-kernel-loads-no-cpu-microcode.md` - `issues/kernel/no-user-address-is-drawn-per-spawn.md` - `issues/kernel/the-kernel-has-no-stack-protector.md` - `issues/kernel/no-kernel-address-is-drawn-per-boot.md` diff --git a/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md b/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md index e7065240b7..3f91fb57ad 100644 --- a/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md +++ b/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md @@ -151,8 +151,7 @@ times: Each step measures the kernel's lines, and from step 2 the longest interrupts-off and preemption-off windows, against stage 6's first commit. Steps 3 and 4 do not land alone: they land with #592's i8042 stage and - with usbd. Stage 6 stays open past step 5 until the owner rules on the - panel the dump paints. + with usbd. 1. **Interrupts post.** A post is legal in a handler: the watches a handler posts, and the completions of a ring they complete into, sit behind interrupts-off locks nothing allocates or frees under, and every other @@ -182,9 +181,9 @@ times: them. **Exit**: step 10's. 5. **The pass is the scheduler's.** `drain_irqs` goes: the blocked-task dump and the heartbeat become `pass`'s own, and the TCO feed stays, - since what it proves is that passes run. The dump still paints its - report on the panel and holds it there, a device the pass reaches; - whether that stays is the owner's ruling. **Exit**: `drain_irqs` and the + since what it proves is that passes run. The dump keeps painting its + report on the panel and holding it there, a device the pass reaches + (owner, 2026-09-30). **Exit**: `drain_irqs` and the idle loop's device checks are gone, both windows are measured against stage 6's start, and the exits of `issues/kernel/an-irq-watchs-freeing-cancel-compiles-in-a-handler.md` diff --git a/issues/kernel/the-kernel-loads-no-cpu-microcode.md b/issues/kernel/the-kernel-loads-no-cpu-microcode.md new file mode 100644 index 0000000000..914bfc6c6f --- /dev/null +++ b/issues/kernel/the-kernel-loads-no-cpu-microcode.md @@ -0,0 +1,15 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# The kernel loads no CPU microcode + +The kernel is to load CPU microcode signed by the CPU's maker, pinned by +version and hash the way vendor device firmware is (owner, 2026-09-30). It +loads none, so a CPU whose BIOS ships stale microcode stays below Linux at +`Ubuntu-6.8.0-142.142` on every line that rests on microcode. + +**Exit**: the kernel loads current microcode early on every CPU, at least as +current as Linux's. diff --git a/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md b/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md deleted file mode 100644 index 1e6b8e4fef..0000000000 --- a/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-09-29 ---- - -# Whether the kernel loads CPU microcode is the owner's - -Microcode is firmware that runs on the CPU, which root `CLAUDE.md` does not -carve out: it admits only device firmware that never executes on the CPU. Until -the kernel loads it, a CPU whose BIOS ships stale microcode stays below Linux -at `Ubuntu-6.8.0-142.142` on every line that rests on microcode. - -**Exit**: the owner rules. diff --git a/src/licence.rs b/src/licence.rs index 437c9ac192..0e07eb3132 100644 --- a/src/licence.rs +++ b/src/licence.rs @@ -396,8 +396,8 @@ pub const COMMITTED_FILES: &[(&str, &str, &str, Terms)] = &[ ( "doom.jpg", "ae22f71dc732580bd4f789937c9fe564969029413fc2092f27bdae8d1ceaf8e3", - "a screenshot of this system running doom, in README.md; \ - issues/build/doom-jpg-shows-ids-art-under-no-recorded-terms.md", + "the owner's own screenshot of this system running doom, in README.md; he keeps \ + it, and rules that no licence question applies", Terms::Spdx("NOASSERTION"), ), ( From 90e2b17a5e06fc4c759ece1aa3834cdbadece802 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 30 Sep 2026 23:31:24 +0200 Subject: [PATCH 07/24] File: two builds of one LLVM key differ in their bytes Found while scouting whether rustc's bootstrap builds LLVM under n2 in place of Ninja. `llvm::tests::keyed_and_built` built key 64453b64c91c17c2 (LLVM, clang and LLD) from a fork checkout at c4c65e3e87a whose src/llvm-project was a git worktree of the primary's at a79bc52c1d5e, with only an n2 link named `ninja` reachable; exit 0 in 1693 s. The store holds a Ninja-built LLVM at the same key, made in toyos-mtime on 2026-09-29, before n2 was first fetched on this host. Both hold the same 3841 paths; 3660 are byte-identical. Every one of the other 181 is explained by something the key does not name: the LLVM checkout's origin URL (LLVM_REPOSITORY), the build directory (lld's LC_RPATH, llvm-config's roots), or the dates in 161 archives whose members are byte-identical. None traces to the build tool. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- ...s-of-one-llvm-key-differ-in-their-bytes.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md diff --git a/issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md b/issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md new file mode 100644 index 0000000000..5ee8859d21 --- /dev/null +++ b/issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md @@ -0,0 +1,33 @@ +--- +status: open +kind: tooling +opened: 2026-09-30 +--- + +# Two builds of one LLVM key differ in their bytes + +`src/llvm.rs` holds that an LLVM is a function of its key. Key +`64453b64c91c17c2` built twice by `build_in_fork` — the stored one, made in the +linked worktree `toyos-mtime`, and one made under n2 in place of Ninja from a +fork checkout whose `src/llvm-project` shares the primary's repository — gave +the same 3841 paths, 181 of them different, each for a reason the key does not +name and none of them the build tool: + +- **The LLVM checkout's `origin`.** LLVM's CMake writes it into + `VCSRevision.h` as `LLVM_REPOSITORY`, and every `--version` prints it: the + stored `lld` names `ToyOSOrg/llvm-project`, the other + `rust-lang/llvm-project`. The primary's `rust/src/llvm-project` has origin + `rust-lang`; the fork's `.gitmodules` names `ToyOSOrg`. 19 files: the header, + `libclangBasic.a` (its `Version.cpp.o`), `libclang.dylib`, + `libclang-cpp.dylib`, and fifteen executables, `clang-22` and `lld` among + them. +- **The build directory.** `lld`'s `LC_RPATH` and `llvm-config`'s object and + source roots name the `build/toyos-llvm` of the worktree that built it, which + `place` removes: the stored ones name `toyos-mtime`, which no longer exists. +- **Archive dates.** 161 of the 162 static archives differ only in their + members' dates (`ar tv`); every member is byte-identical. + +Every other file, `llvm-ar`, `llc`, `opt` and `llvm-tblgen` among them, is +byte-identical. + +**Exit**: two builds of one key, made in two worktrees, are byte-identical. From 71b3a57e1b7dc5f669ee266ac5d207d3ac908987 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 30 Sep 2026 23:55:10 +0200 Subject: [PATCH 08/24] issues: the microcode defect points at #636, and the review's four cuts Review round 1 on 7eb4428dd. - 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 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- ...event-and-a-parent-takes-its-children-down.md | 16 ++++++++-------- ...-is-small-interrupts-post-and-threads-wait.md | 2 +- .../kernel/the-kernel-loads-no-cpu-microcode.md | 2 ++ src/licence.rs | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md b/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md index 96d7ca05c2..f22a4a9252 100644 --- a/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md +++ b/issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md @@ -37,7 +37,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md ## Stages -0. **`SYS_PROCESS_OPEN` goes** (ruled). Deleted: `sys_process_open` and every +0. **`SYS_PROCESS_OPEN` goes**. Deleted: `sys_process_open` and every name only it reaches — `process_open`, `SysCap::open_process`, `MANAGE` in init's `SysCap`, the `reopenable` column, `process::process_object`, `reopen_selftest` and `sched::kthread::open_selftest` with their actuator, @@ -51,7 +51,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md whose symbols are test data. Negative control: the stage reverted whole, where `sys_process_open` answers 110. Oracle: rustc's name resolution, which fails the build of any caller left. -1. **An end is an event** (ruled). `read_watch` and `has_data` answer for a +1. **An end is an event**. `read_watch` and `has_data` answer for a `Process`, whose watch becomes an `Arc` as an `Acceptor`'s is, and `close_ends_polls` answers `false` for one; init's waiter threads go. *Exit*: children held on their stdin (`process_lifecycle`'s `held` role), @@ -61,13 +61,13 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md handles to a held child closes, a non-blocking submit finds nothing. Negative control: the stage reverted whole (`NotSupported`); mutation: `close_ends_polls` at `true` reds the last arm. Oracle: pidfd_open(2). -2. **An end says how** (ruled). An end reads as an exit, a kill or a fault kind +2. **An end says how**. An end reads as an exit, a kill or a fault kind alike on every architecture, and a bare code reads the last two as failures. No end reads as a quit's reason. *Exit*: exited 137, killed, and each fault kind the architecture raises, each read from a child that did it. Negative control: the stage reverted whole, where the first two read alike. Oracle: POSIX ``'s classes. -3. **libc starts and waits for children** (ruled; blocked on +3. **libc starts and waits for children** (blocked on `issues/isolation/a-childs-stdio-handle-is-not-the-one-command-named.md`). libc imitates `SIGCHLD`. A C child starts with descriptors 0–2 and exactly what its file actions name, a stated departure from POSIX. `posix_spawn` and @@ -117,7 +117,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md the pipe answering (passed to the kernel, which ends the caller). Negative control: today's libc, against which the corpus does not link. Oracle: POSIX's `posix_spawn`, `waitpid`, `poll` and `SA_RESTART`, and signal(7). -4. **A parent takes its children down** (ruled). A spawn's parent is its +4. **A parent takes its children down**. A spawn's parent is its spawner. A launch names its parent in its request, by one of two words: the caller, whose place is a copy of the handle to itself every process starts holding under the label `self` (`WRITE`, `DUP`, `TRANSFER`), carried as one @@ -168,7 +168,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md place spawned under init (it starts); std's `NotDeclared` fallback, or its early return for an endowment or extra slot, kept for init (it starts). Oracle: cgroup v2's `cgroup.kill` and `cgroup.max.depth`. -5. **A login is a session under init** (ruled). Per login init starts a session +5. **A login is a session under init**. Per login init starts a session program that logout ends and that only parents what its user starts — the desktop's at boot, one per SSH connection at sshd's request. As it starts one, init hands the compositor or sshd a right to start programs in that @@ -179,7 +179,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md a dropped SSH connection ends its session and all under it. Negative control: the stage reverted whole, where the compositor's programs die with it. Oracle: systemd-logind, whose session scope ends every process of a login. -6. **A program is asked to quit** (ruled). `SYS_PROCESS_QUIT` (124, never +6. **A program is asked to quit**. `SYS_PROCESS_QUIT` (124, never assigned), `(process, reason)`, needs `MANAGE` as the kill does, and the reason is interrupt, hang-up or terminate. A quit reaches the process's subtree by stage 4's walk. Each process is started holding its notice, a new @@ -237,7 +237,7 @@ directory is `issues/isolation/every-program-sees-only-the-files-it-was-given.md watching only from a handler's install (the ignoring child is killed); a spawn carrying no ignore, no mask or no `SETSIGDEF` (each spawned child in turn). Oracle: POSIX's signal actions (XSH 2.4.3) and `posix_spawn`. -7. **Job control** (ruled). The shell hands its terminal a `MANAGE`-only +7. **Job control**. The shell hands its terminal a `MANAGE`-only duplicate of each process of the foreground line over its `surface` port. The terminal turns each Ctrl+C into their interrupt, and a second kills only a program that has not yet taken the first. Before it closes, the terminal asks diff --git a/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md b/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md index 3f91fb57ad..5e90f87e89 100644 --- a/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md +++ b/issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md @@ -144,7 +144,7 @@ times: written once as straight-line code. **Exit**: no interrupts-off window longer than a register access, and keyboard input keeps flowing while a stick misbehaves. -6. **The scheduler knows nothing about devices.** A handler posts its +6. A handler posts its device's `Watch` and ends its interrupt; the thread waiting on that watch does the work, and no step creates a kernel thread. `irq_ring`, the driver list in `drain_irqs` and the idle loop's device checks are gone by step 5. diff --git a/issues/kernel/the-kernel-loads-no-cpu-microcode.md b/issues/kernel/the-kernel-loads-no-cpu-microcode.md index 914bfc6c6f..5b2513c213 100644 --- a/issues/kernel/the-kernel-loads-no-cpu-microcode.md +++ b/issues/kernel/the-kernel-loads-no-cpu-microcode.md @@ -11,5 +11,7 @@ version and hash the way vendor device firmware is (owner, 2026-09-30). It loads none, so a CPU whose BIOS ships stale microcode stays below Linux at `Ubuntu-6.8.0-142.142` on every line that rests on microcode. +Root `CLAUDE.md`'s firmware rule admits this microcode once PR #636 lands. + **Exit**: the kernel loads current microcode early on every CPU, at least as current as Linux's. diff --git a/src/licence.rs b/src/licence.rs index 0e07eb3132..d12eea314a 100644 --- a/src/licence.rs +++ b/src/licence.rs @@ -396,8 +396,8 @@ pub const COMMITTED_FILES: &[(&str, &str, &str, Terms)] = &[ ( "doom.jpg", "ae22f71dc732580bd4f789937c9fe564969029413fc2092f27bdae8d1ceaf8e3", - "the owner's own screenshot of this system running doom, in README.md; he keeps \ - it, and rules that no licence question applies", + "the owner's own screenshot of this system running doom, in README.md; \ + no licence question applies", Terms::Spdx("NOASSERTION"), ), ( From 865239cd6cd8d9e0bedba4306b82cda53ab00697 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 05:53:32 +0200 Subject: [PATCH 09/24] issues: two builds of one LLVM key are a defect M4 waits on, closed by a check that can fail Review of 90e2b17a5 on PR #646. The exit named two worktrees, while the origin that differs is the primary's against a worktree's, and it named no check. Which LLVM_REPOSITORY each checkout's build writes, from LLVM's own GenerateVersionFromVCS.cmake run as llvm/include/llvm/Support/CMakeLists.txt runs it, exit 0 each: - the primary's rust/src/llvm-project, gitlink a79bc52c1d5e, checkout 52ed14fcd56a: https://github.com/rust-lang/llvm-project.git - toyos-libcllvm's, gitlink and checkout 849da7d62fbc: https://github.com/ToyOSOrg/llvm-project.git The fork's .gitmodules and its shared config both name ToyOSOrg. Bootstrap's update_submodule, its git commands run on scratch repositories in the primary's state (cloned from A, .gitmodules and config naming B): with the checkout behind its gitlink, `submodule sync` and `update` exit 0 and the script then writes B; with the checkout at its gitlink, bootstrap runs nothing and it writes A. The exit now names the check: one key built in two fork checkouts at different paths whose origins name different repositories, every file of the two installs byte-identical. Path and origin together cover every pair: the primary's and a worktree's, two worktrees', a checkout made by hand. kind: defect. The origin reaches guest bytes: `llvm-readelf -p .comment` on sysroot d8a0215fc9eae3c5's libstd-1d0a603a43d0da8a.so gives "Linker: LLD 22.1.8 (https://github.com/ToyOSOrg/llvm-project.git 849da7d6...)", and 75 of the 77 members of its c/lib/libc++.a carry clang's version with the same URL; the other two are UnwindRegistersSave.S.o and UnwindRegistersRestore.S.o, which have no .comment. issues/build/toyos-builds-itself.md points M4 at it. The Ninja row of issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md takes the scout's result: bootstrap built LLVM, clang and LLD under n2, exit 0. The counts that rested on the n2 tree (181 different, 3660 identical, 161 archives differing only in dates) went with it, and the issue carries none of the comparison's counts. The stored side, rust/build/llvm/64453b64c91c17c2, reproduces: 3841 paths, 162 archives, 19 files naming ToyOSOrg/llvm-project, llvm-config and lld naming toyos-mtime, and every member of all 162 archives dated 2026 by `ar tv`. Removed as the review asked: "none of them the build tool", "every --version prints it", the key and toyos-mtime, and the examples of byte-identical tools. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- ...d-runs-host-tools-outside-rust-and-qemu.md | 2 +- issues/build/toyos-builds-itself.md | 4 ++ ...s-of-one-llvm-key-differ-in-their-bytes.md | 46 ++++++++++--------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md b/issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md index 9d993d7758..85e63d6888 100644 --- a/issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md +++ b/issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md @@ -16,7 +16,7 @@ arrives and is not one. M4 and M5 are stages of `issues/build/toyos-builds-itsel |---|---|---|---| | Python | LLVM's CMake, whenever this host builds an LLVM (`src/llvm.rs`); the C++ runtime's CMake, in every sysroot build (`src/libcxx.rs`), and its build, which runs `libcxx/utils/generate_iwyu_mapping.py` for a header it installs | admitted: no Rust tool does the job, LLVM's CMake requires one (`find_package(Python3 … REQUIRED)` in `rust/src/llvm-project/llvm/CMakeLists.txt`), and so does the runtimes', unconditionally (`runtimes/CMakeLists.txt`): configured as `src/libcxx.rs` does with no Python on `PATH` or in CMake's search, it found none and stopped, exit 1, and with only `python3` added it configured, exit 0 | M5 runs it in the guest | | CMake | rustc's bootstrap, for LLVM and clang; `src/llvm.rs`, for the LLVM's key; `src/libcxx.rs`, for the C++ runtime in every sysroot build | admitted: no Rust tool does the job, LLVM, clang and LLD are described in CMake, and upstream's only other descriptions are a GN overlay it does not support and a Bazel one | M5 runs it in the guest | -| Ninja | runs the build CMake generates for LLVM | refused: a Rust tool does it, n2 (`github.com/evmar/n2` at `b1fead5`), named `ninja` as its README directs for CMake: CMake's Ninja generator configured `rust/src/llvm-project/llvm` for it and it built `llvm-tblgen`, exit 0 each; named `n2`, CMake refuses its version, exit 1 | rustc's bootstrap builds LLVM under n2 | +| Ninja | runs the build CMake generates for LLVM | refused: a Rust tool does it, n2 (`github.com/evmar/n2` at `b1fead5`), named `ninja` as its README directs for CMake: CMake's Ninja generator configured `rust/src/llvm-project/llvm` for it and it built `llvm-tblgen`, exit 0 each, and rustc's bootstrap built LLVM, clang and LLD under it with no other `ninja` on `PATH`, exit 0; named `n2`, CMake refuses its version, exit 1 | rustc's bootstrap builds LLVM under n2 | | `sh` running LLVM's `config.guess`, and the POSIX tools and `cc` it runs | LLVM's CMake, whenever this host builds an LLVM, and the C++ runtime's, in every sysroot build, ask it the host's triple, unconditionally (`get_host_triple` in `rust/src/llvm-project/llvm/cmake/modules/GetHostTriple.cmake`, which runs `sh` by name) | refused: a Rust tool does the shell's part, brush 0.4.0: on the development host (macOS, arm64) `config.guess` printed `/bin/sh`'s triple under it, `arm64-apple-darwin27.0.0`, exit 0 each. The script runs `sed`, `uname`, `mktemp`, `grep`, `rm`, `rmdir` and `cc` there under either shell, and that `cc` is the `cc` rows'. Five of the other six are refused, uutils' doing each: under brush with sed 0.2.0, grep 0.2.0 and coreutils 0.12.0's `mktemp`, `rm` and `rmdir`, and nothing else on `PATH` but the host's `uname` and `cc`, it printed that triple, exit 0. `uname` is admitted: coreutils 0.12.0's answers `-p` with `unknown` where macOS's answers `arm`, and `config.guess` reads that as PowerPC, `powerpc-apple-darwin27.0.0`, exit 0 | CMake finds brush as its `sh`, uutils' `sed`, `grep`, `mktemp`, `rm` and `rmdir`, and a Rust `uname` that answers `-p` as the host's does; or M5 runs it in the guest | | `git` for worktrees, submodules, checkouts, fixtures and rustc's bootstrap | adds, removes and prunes worktrees (`src/worktree.rs`, `src/sysroot.rs`); updates submodules (`src/lib.rs`, `src/sysroot.rs`, `src/licence.rs`, `src/release.rs`); fetches the fork from the primary's and checks it out (`src/sysroot.rs`); fast-forwards the primary (`src/sync.rs`); makes the tests' fixture repositories; runs inside rustc's bootstrap | admitted: no Rust tool does the job, gitoxide 0.85 adds, removes and prunes no worktree, updates no submodule, stages, resets and pushes nothing, checks out only a fresh clone and fetches a local path by spawning `git`; a fixture must be what `git` makes, and bootstrap runs `git` itself | M4 runs it in the guest | | `git` for reads, a config write, a commit's paths written out, and clones and fetches over HTTPS | `rev-parse`, `show-ref`, `for-each-ref`, `rev-list`, `log`, `branch --contains`, `merge-base`, `ls-tree`, `ls-files`, `cat-file`, `config --get-regexp`, `worktree list`, `status`, `diff`, `ls-remote` and `grep`, in the build system and its tests; `config --global --add safe.directory` in the nightly's containers; `checkout -- ` through an index of its own, which writes the C++ runtime's sources out of the LLVM commit into the stored LLVM (`src/llvm.rs`); `src/sync.rs`'s fetch of `origin`; every workflow's checkout | refused: a Rust tool does it, gitoxide 0.85, which reads refs, objects, the index, config, worktrees and status, adds a value to a config file and writes it (gix-config 0.58's `File::section_mut_or_create_new`, `SectionMut::push`, `File::write_to`), walks history, diffs, and lists, fetches and clones a remote over HTTPS; `grep` is a search of the files its index names; and gitoxide's CLI 0.59 (gix 0.88) wrote the runtimes' sources of LLVM `849da7d6` into an empty directory, each path's tree through `gix rev parse`, `gix index from-tree` and `gix free index checkout-exclusive`, exit 0 each: the 18759 files `git` writes there, byte for byte and mode for mode | those are gitoxide's | diff --git a/issues/build/toyos-builds-itself.md b/issues/build/toyos-builds-itself.md index 4947b541ca..5ac605bcbc 100644 --- a/issues/build/toyos-builds-itself.md +++ b/issues/build/toyos-builds-itself.md @@ -79,3 +79,7 @@ M3's exit then waits on a linker in the guest (`issues/build/the-hosted-rustc-names-a-linker-toyos-does-not-have.md`), which toyos-ld is not: it refuses every executable with thread-local storage, so every std program. + +**M4 also waits on the host building one LLVM key to one set of bytes**, since +the guest's `libstd` and C++ runtime carry the origin of the checkout the LLVM +was built from: `issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md`. diff --git a/issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md b/issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md index 5ee8859d21..2e37312b81 100644 --- a/issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md +++ b/issues/build/two-builds-of-one-llvm-key-differ-in-their-bytes.md @@ -1,33 +1,35 @@ --- status: open -kind: tooling +kind: defect opened: 2026-09-30 --- # Two builds of one LLVM key differ in their bytes -`src/llvm.rs` holds that an LLVM is a function of its key. Key -`64453b64c91c17c2` built twice by `build_in_fork` — the stored one, made in the -linked worktree `toyos-mtime`, and one made under n2 in place of Ninja from a -fork checkout whose `src/llvm-project` shares the primary's repository — gave -the same 3841 paths, 181 of them different, each for a reason the key does not -name and none of them the build tool: +`src/llvm.rs` holds that an LLVM is a function of its key. Two builds of one +key by `build_in_fork`, one in a worktree and one under n2 in place of Ninja +from a fork checkout whose `src/llvm-project` shares the primary's repository, +differed in three things the key does not name: - **The LLVM checkout's `origin`.** LLVM's CMake writes it into - `VCSRevision.h` as `LLVM_REPOSITORY`, and every `--version` prints it: the - stored `lld` names `ToyOSOrg/llvm-project`, the other - `rust-lang/llvm-project`. The primary's `rust/src/llvm-project` has origin - `rust-lang`; the fork's `.gitmodules` names `ToyOSOrg`. 19 files: the header, - `libclangBasic.a` (its `Version.cpp.o`), `libclang.dylib`, - `libclang-cpp.dylib`, and fifteen executables, `clang-22` and `lld` among - them. + `VCSRevision.h` as `LLVM_REPOSITORY` (`get_source_info` in + `llvm/cmake/modules/VersionFromVCS.cmake`), and clang and LLD put it in their + version (`clang/lib/Basic/Version.cpp`, `lld/Common/Version.cpp`), so in the + guest's bytes: LLD writes it into the `.comment` of the `libstd` a sysroot + links, and clang into that of each C and C++ object of its `c/lib/libc++.a`. + Bootstrap's `update_submodule` sets a checkout's origin from the fork's + `.gitmodules`, `ToyOSOrg`, when it moves the checkout to the gitlink's commit, + and leaves it when the checkout holds that commit already. A worktree's + checkout is cloned from the URL the fork's shared config names, `ToyOSOrg`. + The primary's `rust/src/llvm-project` names `rust-lang`, and so does a git + worktree of its repository. - **The build directory.** `lld`'s `LC_RPATH` and `llvm-config`'s object and - source roots name the `build/toyos-llvm` of the worktree that built it, which - `place` removes: the stored ones name `toyos-mtime`, which no longer exists. -- **Archive dates.** 161 of the 162 static archives differ only in their - members' dates (`ar tv`); every member is byte-identical. + source roots name the `build/toyos-llvm` of the checkout that built it. +- **Archive dates.** The members of every static archive carry the time they + were built (`ar tv`). -Every other file, `llvm-ar`, `llc`, `opt` and `llvm-tblgen` among them, is -byte-identical. - -**Exit**: two builds of one key, made in two worktrees, are byte-identical. +**Exit**: a nightly check builds one key in two fork checkouts at different +paths whose `src/llvm-project` origins name different repositories, and every +file of the two installs is byte-identical. Until it is, every pair of +checkouts builds one key to different bytes: the primary's and a worktree's, +two worktrees', and one made by hand and any other. From 6b2eef15db34c310b868a07bbe7520971f06ae07 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 09:08:58 +0200 Subject: [PATCH 10/24] issues: a panic is never an accident, decided and enforced in seven crates The owner decided a rule graded by what a panic costs: no panic at all at an input boundary, no implicit panic in the kernel or the system services, normal Rust for apps, ports, tests and tooling. He asked that it be recorded, not done now. The track carries the rule, what holds today and the five stages with their exits. Measured for it: - `rg -o '\.unwrap\(\)|\.expect\(' kernel/src | wc -l` is 157 (154 lines by `rg -c`). - `cargo clippy --target x86_64-unknown-none -- -W clippy::indexing_slicing -W clippy::arithmetic_side_effects -W clippy::unwrap_used -W clippy::expect_used -W clippy::panic -W clippy::unreachable -W clippy::cast_possible_truncation --message-format=json`, in kernel/, counted per lint code with jq: 2,008 findings in the kernel crate. - `rustc -Z unstable-options --print target-spec-json` gives `"panic-strategy": "abort"` for x86_64-unknown-none and aarch64-unknown-none-softfloat. The no-panic README calls its attribute "useless in code built with panic = abort", so a link proof has to come from a host build. - A scratch crate run under the seven lints drew no warning for `assert!`, `copy_from_slice` or `split_at`. It drew one each for `a[3]` and `panic!`. The brief expected `issues/` to hold the two crafted-ELF panics an overflow check found, and it holds neither. The closed entry left the tracker at fa2799ded, yet seven comments still cite `issues/` for it, and the tree's rule is to file that rather than fix it here. The second file records it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- ...-cite-crafted-elf-panics-no-issue-holds.md | 17 +++++ issues/kernel/a-panic-is-never-an-accident.md | 69 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md create mode 100644 issues/kernel/a-panic-is-never-an-accident.md diff --git a/issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md b/issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md new file mode 100644 index 0000000000..570dc89bd1 --- /dev/null +++ b/issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md @@ -0,0 +1,17 @@ +--- +status: open +kind: tooling +opened: 2026-10-01 +--- + +# Seven comments cite crafted-ELF panics in `issues/`, and no issue holds them + +`git grep -n 'crafted-ELF' HEAD -- '*.toml' src/` finds the citation at +`Cargo.toml:211,231`, `bootloader/Cargo.toml:45`, `kernel/Cargo.toml:376`, +`userland/Cargo.toml:69` and `src/build.rs:410,635`. No issue names either +panic: `git grep -i -e phnum -e 'gnu\.hash' -e bloom_shift 1e4d3e0ec -- issues/` +exits 1. The closed entry left the tracker at `fa2799ded`, and `da3f56573` +then rewrote the citations' path to `issues/`. + +**Exit**: the clause is deleted at all seven sites, and the `git grep` above +finds no `issues/` citation. diff --git a/issues/kernel/a-panic-is-never-an-accident.md b/issues/kernel/a-panic-is-never-an-accident.md new file mode 100644 index 0000000000..460a2d6243 --- /dev/null +++ b/issues/kernel/a-panic-is-never-an-accident.md @@ -0,0 +1,69 @@ +--- +status: open +kind: track +opened: 2026-10-01 +--- + +# A panic is never an accident: the rule is decided, and seven crates enforce it + +The owner has decided the rule below. Outside seven crates nothing enforces it +yet, and that is the present-state weakness. + +| Tier | Rule | +|---|---| +| 1. Input boundaries: everything that reads bytes from outside the code's own trust (disk and partition formats, filesystems, ELF loading, microcode, network packets, USB descriptors, ACPI and PCI tables, system-call arguments, IPC messages, device registers and DMA data) | No panic at all. Clippy denies `indexing_slicing`, `arithmetic_side_effects`, `unwrap_used`, `expect_used`, `panic`, `unreachable` and truncating casts. Each parser's entry point also carries a link-time no-panic proof. | +| 2. The whole kernel | No implicit panic: the same lints are denied. A deliberate stop for a broken internal invariant stays (fail fast), but it is spelled out with its reason where a reviewer sees it, as an `#[expect(…, reason = …)]` or a named invariant. A type that makes the broken state unrepresentable is preferred over any stop. | +| 3. System services: init, logd, blockd, fsd, netd, compositor, soundd, sshd | The same rule as the kernel. | +| 4. Apps, ports, test code and build tooling | Normal Rust. | + +**Today.** +- Seven crates carry the lints (`rg -l 'clippy::indexing_slicing' --glob '*.rs'`). + `toyos-net-wire`, `-ip`, `-tcp`, `-udp`, `toyos-dhcp` and `toyos-gpt` forbid + five of them outside tests, plus `as_conversions` in place of truncating + casts. `toyos-transport` forbids four and denies `indexing_slicing` and + `as_conversions`, with one `#[allow]` at `toyos-transport/src/queue.rs:21`. + No crate denies `unreachable`. +- The kernel has 157 `.unwrap()`/`.expect(` calls on 154 lines + (`rg -o '\.unwrap\(\)|\.expect\(' kernel/src | wc -l`; `rg -c` counts + lines). Running `cargo clippy --target x86_64-unknown-none` in `kernel/` + with the seven lints as warnings reports 2,008 findings at default features: 992 + `arithmetic_side_effects`, 408 `indexing_slicing`, 394 + `cast_possible_truncation`, 109 `expect_used`, 60 `panic`, 29 `unwrap_used` + and 16 `unreachable`. +- `overflow-checks = true` is set in `[profile.toyos]` (`Cargo.toml:254`, + `kernel/Cargo.toml:387`, `userland/Cargo.toml:85`), so an overflow stops the + program. Overflow checks found the two crafted-ELF kernel panics, from + `e_phoff = u64::MAX` and from a `.gnu.hash` `bloom_shift` of 32 or more. + `issues/` holds neither. They are closed at `56aea566a:specs/known-issues.md:440`, + and their tests are `toyos-elf/tests/crafted.rs:113` and + `toyos-elf/tests/tables.rs:615`. +- The review of PR #653 at `38318d18b` found a length check at + `toyos-microcode/src/lib.rs:203` that no test covers. It guards the panicking + slice `table[EXT_HEADER..]` at `:213`. + +**Constraints.** +- The lints do not see `assert!`, `copy_from_slice`, `split_at` or a panic + inside a callee: on a crate holding the first three beside `a[3]` and + `panic!`, they warned on those two alone. +- The no-panic proof needs unwinding: its README says "The attribute is useless + in code built with `panic = "abort"`". Both kernel targets print + `"panic-strategy": "abort"` from `rustc -Z unstable-options --print + target-spec-json`, so the proof has to come from a host build. +- `--clippy` lints no userland program, because the `toyos` toolchain ships no + clippy (`src/clippy.rs:7-8`). + +**Stages, in order.** +1. Tier 1, one area at a time. An area's exit is its crate or kernel module + denying the lints under `cargo run -- --clippy`. `toyos-elf` is + `issues/design-debt/elf-domain-lint-line-not-yet-added.md`. +2. The link-time proof on the parser entry points: works or not, measured. The + exit is a host build that refuses to link an entry point with a panicking + path, or a `rejected` issue with the measurement. +3. The kernel, one module at a time, under `--clippy`. The stage ends when + `kernel/src/main.rs` denies the lints for the whole crate. +4. The system services. A service's exit is its crate root denying the lints + under a clippy run that reaches userland. +5. The rule goes into `.claude/agents/reviewer.md` beside "Edges". + +**Mutation**: adding one `buf[i]` to a module that denies the lints turns +`cargo run -- --clippy` red. From 066e1a0280d1fa05f9c90f940fd4ffccc8a10438 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 09:41:33 +0200 Subject: [PATCH 11/24] The ABI is free to change, and the kernel keeps only what only a kernel can do MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two owner rulings of 2026-10-01, written into the prompts that govern agents. The ABI. "We can change system calls. We can remove them, add them, change them. I want to have the cleanest, most sustainable ABI." Three lines said the opposite and steered agents away from the ABI: - Root CLAUDE.md, "Syscall ABI": "Never add or change a syscall without discussion; a deleted syscall's number is retired, never reused." It now says the cleanest, most sustainable ABI beats convenience and a removed number is free. "read the code" goes with it: the Architecture section's header already says it. The line is shorter than the one it replaces. Workflow's "An ABI change lands with the work that needs it" stays. - implementer.md: "Never touch toyos-abi/src, toyos/src or userland/libc/src unless the brief is an ABI brief." Deleted. The brief's fence already bounds what an implementer touches. - reviewer.md, "What no gate reads": a BLOCKER for reusing a retired syscall, SYS_DEBUG action or inbox op number, or declaring a retired ABI name. Deleted. Connect-by-name and pid-as-authority stay banned by root CLAUDE.md's Capabilities paragraph, which bans the design whatever it is called. Kernel or server. In the symlink incident, libc's symlink needed "refuse an existing name". The implementer avoided the kernel change and returned ENOSYS, and the reviewer asked for the kernel to refuse the name. Neither asked whether the kernel should own symlinks at all; fsd already resolves them. The owner: "one less thing for the kernel to do… the reviewer and implementer should know we try to use userland programs instead of the kernel." - implementer.md: before adding or keeping kernel behaviour, ask whether a userland server can own it. When the clean design changes the ABI, change the ABI. - reviewer.md, "Fit": a BLOCKER each for a kernel addition or a kept kernel path a userland server could own, and for a design made worse to spare the ABI. The code still keeps retired numbers: retired_syscalls!, the "formerly …" entries in toyos-abi, four test sites that use syscall 26 as their logged refusal, and seven issue files that plan by retirement. That is outside this branch's fence, so it is filed as issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- .claude/agents/implementer.md | 8 +++-- .claude/agents/reviewer.md | 13 ++++--- CLAUDE.md | 2 +- ...abi-still-keeps-retired-syscall-numbers.md | 36 +++++++++++++++++++ 4 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index a4d1bb91c6..b4680b297c 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -14,6 +14,11 @@ One brief, one worktree, one branch. What the brief does not name you do not tou 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. +Before adding or keeping kernel behaviour, ask whether a userland server can own it: the kernel +does only what only a kernel can (address spaces, scheduling, handles and capabilities, interrupts, +device arbitration). When the clean design changes the ABI, change the ABI; never pick a lesser +design to avoid that. + ## Measure, build, test Where hardware or anything uncertain is involved, take the cheap measurement before you build on a @@ -56,8 +61,7 @@ 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. +fork's shared config and breaks git in the primary checkout's `rust/`. No new dependency. 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/reviewer.md b/.claude/agents/reviewer.md index c780b0dee3..164d54f64a 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -42,7 +42,9 @@ above; otherwise it is a NOTE. - **Fit.** Does the tree already do this? Is each new thing where it belongs: a pure decision in a pure crate, the user/kernel boundary in `toyos-userbound`, a device claim in a userland server? One declaration read by every reader, refusal by name, authority moved in by the parent. Zero - legacy: no shim, no workaround, no silent default. No new + legacy: no shim, no workaround, no silent default. A BLOCKER each: a kernel addition, or a + kernel path the branch keeps, that a userland server could own; a design made worse to spare + the ABI. No new dependency or fetch. Nothing outside the brief's fence. Assembly, a naked function and a `core::arch` or `std::arch` path live only in an architecture's own module; `target_arch` only there, in its selector, in `src/arch.rs` and in @@ -68,12 +70,9 @@ above; otherwise it is a NOTE. A file added to or deleted from `tests/testcases/tinycc/` moves the count `tests/testcases/LICENSE` states in the same diff, and `46_grep.c` never comes back. Nothing else is tracked under `tests/testcases/` but that `LICENSE`, `system.toml` and `hello.c`. -- **What no gate reads.** A BLOCKER each: a diff that declares a retired ABI name or reuses a - retired syscall, `SYS_DEBUG` action or inbox op number (the retired numbers are - `kernel/src/syscall/dispatch.rs`'s `retired_syscalls!` and the "formerly …" and "retired and - unused" entries in `toyos-abi/src/syscall.rs` and `toyos-abi/src/inbox.rs`; the retired names - include `SharedToken` and `services::connect`); a workspace member's `Cargo.toml` declaring `[profile]` or `[patch]`, which - cargo ignores with only a warning; a new package without a `description` saying what it is. +- **What no gate reads.** A BLOCKER each: a workspace member's `Cargo.toml` declaring `[profile]` + or `[patch]`, which cargo ignores with only a warning; a new package without a `description` + saying what it is. A new cargo feature or `cfg` arm of one, and every arm a changed `src/clippy.rs` shape stops building, is shown linted in the pull request body: a `mem::forget` planted in that arm turns `cargo run -- --clippy` red. - **Growth.** Every line is a responsibility, not an asset. State the branch's net lines (`git diff --shortstat origin/main...HEAD`), production and tests apart. Production code that grows diff --git a/CLAUDE.md b/CLAUDE.md index 0b910305b3..fa9f283de9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,7 +44,7 @@ A subdirectory `CLAUDE.md` loads when a file in that subtree is `Read`, and not **The log is a userland file.** `/system/bin/logd` reads records on a cursor and owns `/log`; the kernel keeps the record ring, the console and the panel, and writes no file. `SYS_FSYNC` reaches the device's cache flush because logd's durability claim rests on it. -**Syscall ABI** — `toyos-abi/`: struct layouts, syscall numbers, typed wrappers; completely unstable, read the code. Never add or change a syscall without discussion; a deleted syscall's number is retired, never reused. `toyos/` builds on it with typed handles, IPC framing, ports, namespaces and `surface` — userland uses `toyos`, the kernel uses `toyos-abi` only. +**Syscall ABI** — `toyos-abi/`: struct layouts, syscall numbers, typed wrappers; completely unstable. The cleanest, most sustainable ABI beats convenience: add, change or remove syscalls; a removed number is free. `toyos/` builds on it with typed handles, IPC framing, ports, namespaces and `surface` — userland uses `toyos`, the kernel uses `toyos-abi` only. **Capabilities** — a process holds exactly what its parent moved into it, and among kernel objects there is nothing it can name to get more. No registry, no connect-by-name, no pid-as-authority: `/system/bin/init` builds every program's namespace and device claims from `system.toml` before spawning it, and a handle a process does not hold is a bug in that process — the kernel ends it rather than answering a word it can ignore. **Isolation is non-negotiable, and the filesystem is inside it**: a process names only the paths in the view its parent built for it, the unit of isolation is the program, and a user is the part of the tree a session was handed. Not yet true of files: the kernel still resolves every path against one machine-wide tree until the storage track's per-program views land. diff --git a/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md b/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md new file mode 100644 index 0000000000..3b0c3a2d7b --- /dev/null +++ b/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md @@ -0,0 +1,36 @@ +--- +status: open +kind: defect +opened: 2026-10-01 +--- + +# The ABI still keeps retired syscall numbers + +The ABI is completely unstable and a removed syscall's number is free (owner, +2026-10-01): there are no retired numbers and no compatibility shims. The tree +still keeps them: + +- `kernel/src/syscall/dispatch.rs`'s `retired_syscalls!` names deleted calls so + that "an old binary is told which call it was", logging each call of one; + every other unassigned number answers `InvalidArgument` silently. +- `toyos-abi/src/syscall.rs` and `toyos-abi/src/inbox.rs` carry a "formerly …" + or "retired and unused" entry per deleted syscall, `SYS_DEBUG` action and + inbox op, `SYS_INBOX_SETUP`'s doc states the retirement rule, and + `kernel/src/inbox/mod.rs` names op 2 retired. +- `tests/toyos-rust-tests/src/bin/log_hold.rs`, + `tests/toyos-rust-tests/src/bin/panic_halts_first.rs`, + `tests/common/origin.rs` and `tests/toyos.rs` take syscall 26 as their logged + refusal and read `syscall 26 is retired`. +- Plans still follow the old rule: + `issues/kernel/sys-clock-realtime-is-now-a-format-of-sys-clock-epoch.md`, + `issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md`, + `issues/kernel/the-kernel-still-parses-what-userland-writes.md`, + `issues/kernel/the-capability-end-state-is-twelve-answers.md`, + `issues/diagnostics/the-kernel-keeps-nothing-it-enumerates.md`, + `issues/diagnostics/no-cyclictest.md`, and + `issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md`, whose + "ABI brief" names a gate `.claude/agents/implementer.md` no longer has. + +**Exit**: `retired_syscalls!` and every retirement entry are gone, a deleted +number answers as an unassigned one does, the four test sites take their logged +refusal from something live, and no issue plans by retirement. From f469e9022fd03fd0e7212412ad68d303de40b6ee Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 09:49:24 +0200 Subject: [PATCH 12/24] issues: the no-panic track names the owner's set and gates a bare #[allow]; seven crafted-ELF clauses go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answers the review of 6b2eef15d on PR #665. The set covers every operation that can panic on input. Each lint name was checked against clippy 0.1.98 (48a229ceae) on a scratch crate under `#![deny(unknown_lints)]`. All fifteen drew a finding on their own form. A bogus `clippy::no_such_lint_control` was refused, and it was the only error (exit 101). The set adds these to PR #653's nine: - `string_slice`, because `indexing_slicing` drew nothing on `&s[1..]` and `string_slice` did; - `cast_possible_truncation`, which the owner names; - `disallowed_methods` naming `slice::split_at` and `slice::copy_from_slice`; - `disallowed_macros` naming `core::assert`, `assert_eq` and `assert_ne`. Each of those configured names fired. What the set does not see, measured: `a << b`, `a >> b`, `1u64 << b`, `a.pow(b)` and `a.abs()` drew no finding. Built with `-C overflow-checks=on`, the shift, the `pow` and the `abs` each exit 101: "attempt to shift left with overflow", "attempt to exponentiate with overflow", "attempt to negate with overflow". The gate: - `#![forbid(clippy::indexing_slicing)]` turns an inner `#[expect(…, reason)]` into E0453, and an inner `#![allow(…, reason)]` too. - With `allow_attributes` and `allow_attributes_without_reason` on, an outer `#[allow]` draws the first lint, with or without a reason. - A bare `#[allow]`, `#![allow]` or `#[expect]` draws the second. - An inner `#![allow(…, reason = …)]` draws neither. Stage 3's exit closes that hole with a `--ci host` step. clippy.toml: with a parent file listing `copy_from_slice` and a child listing `split_at`, the child crate drew only `split_at`. With the child's file removed, it drew only `copy_from_slice`. Only the nearest file is read. The count. `cargo clippy --target x86_64-unknown-none --message-format=json` ran in kernel/ with the set as `-W`. The two methods and three macros were added to a copy of the root clippy.toml, passed through CLIPPY_CONF_DIR. It exits 101 under the kernel's `-Dwarnings`. Counted by code with jq: - 992 arithmetic_side_effects - 408 indexing_slicing - 394 cast_possible_truncation - 208 disallowed_macros (200 assert, 8 assert_eq) - 109 expect_used - 60 panic - 39 disallowed_methods (all copy_from_slice) - 29 unwrap_used - 16 unreachable - 14 panic_in_result_fn - 5 string_slice That is 2,274. Beside them are 26 allow_attributes and 28 allow_attributes_without_reason. Seven clauses cited `issues/` for crafted-ELF panics that the tracker closed at fa2799ded. They sat at Cargo.toml (two), bootloader/, kernel/ and userland/Cargo.toml, and src/build.rs (two). All are deleted, with the file that recorded them. None of the five files is a sysroot input. Also deleted, as REMOVEs: - the "seven crates" claim; - the boundary and service lists; - "where a reviewer sees it"; - the per-crate inventory; - the rg count; - the crafted-ELF history; - the microcode citation; - the constraint's provenance; - stage 5; - the mutation line. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- Cargo.toml | 8 +- bootloader/Cargo.toml | 5 +- ...-cite-crafted-elf-panics-no-issue-holds.md | 17 ---- issues/kernel/a-panic-is-never-an-accident.md | 93 +++++++++---------- kernel/Cargo.toml | 5 +- src/build.rs | 9 +- userland/Cargo.toml | 5 +- 7 files changed, 57 insertions(+), 85 deletions(-) delete mode 100644 issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md diff --git a/Cargo.toml b/Cargo.toml index c65155c62b..751b41e5b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -207,8 +207,7 @@ opt-level = 2 # `opt-level = 0`. Nothing fail-fast is traded for it — `opt-level` does not # touch `debug-assertions`, which cargo still passes as `on` (checked with # `cargo build -p toyos-fat32 -v`), and rustc derives `overflow-checks` from -# that. The pure crates' hostile-input tests keep both knobs that *found* the -# two crafted-ELF kernel panics in `issues/`. +# that. # # **One member depends on this line for correctness rather than for speed, and # its own manifest says so at length — this is the other half of that @@ -227,9 +226,8 @@ opt-level = 3 # The one profile every guest binary is built with. Optimised, because an # unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. # # It is declared here rather than in `toyos-ld/Cargo.toml` because cargo diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index 7e57aadd82..d2073464a7 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -41,9 +41,8 @@ uefi-services = { version = "0.23.0", features = ["panic_handler", "logger"] } # The one profile every guest binary is built with. Optimised, because an # unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. [profile.toyos] inherits = "dev" diff --git a/issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md b/issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md deleted file mode 100644 index 570dc89bd1..0000000000 --- a/issues/build/seven-comments-cite-crafted-elf-panics-no-issue-holds.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -status: open -kind: tooling -opened: 2026-10-01 ---- - -# Seven comments cite crafted-ELF panics in `issues/`, and no issue holds them - -`git grep -n 'crafted-ELF' HEAD -- '*.toml' src/` finds the citation at -`Cargo.toml:211,231`, `bootloader/Cargo.toml:45`, `kernel/Cargo.toml:376`, -`userland/Cargo.toml:69` and `src/build.rs:410,635`. No issue names either -panic: `git grep -i -e phnum -e 'gnu\.hash' -e bloom_shift 1e4d3e0ec -- issues/` -exits 1. The closed entry left the tracker at `fa2799ded`, and `da3f56573` -then rewrote the citations' path to `issues/`. - -**Exit**: the clause is deleted at all seven sites, and the `git grep` above -finds no `issues/` citation. diff --git a/issues/kernel/a-panic-is-never-an-accident.md b/issues/kernel/a-panic-is-never-an-accident.md index 460a2d6243..2276a72fb2 100644 --- a/issues/kernel/a-panic-is-never-an-accident.md +++ b/issues/kernel/a-panic-is-never-an-accident.md @@ -4,47 +4,44 @@ kind: track opened: 2026-10-01 --- -# A panic is never an accident: the rule is decided, and seven crates enforce it - -The owner has decided the rule below. Outside seven crates nothing enforces it -yet, and that is the present-state weakness. +# A panic is never an accident: the owner's rule, not yet enforced | Tier | Rule | |---|---| -| 1. Input boundaries: everything that reads bytes from outside the code's own trust (disk and partition formats, filesystems, ELF loading, microcode, network packets, USB descriptors, ACPI and PCI tables, system-call arguments, IPC messages, device registers and DMA data) | No panic at all. Clippy denies `indexing_slicing`, `arithmetic_side_effects`, `unwrap_used`, `expect_used`, `panic`, `unreachable` and truncating casts. Each parser's entry point also carries a link-time no-panic proof. | -| 2. The whole kernel | No implicit panic: the same lints are denied. A deliberate stop for a broken internal invariant stays (fail fast), but it is spelled out with its reason where a reviewer sees it, as an `#[expect(…, reason = …)]` or a named invariant. A type that makes the broken state unrepresentable is preferred over any stop. | -| 3. System services: init, logd, blockd, fsd, netd, compositor, soundd, sshd | The same rule as the kernel. | +| 1. Input boundaries | No panic at all: the set below is forbidden. Where the build allows, each parser's entry point also carries a link-time no-panic proof. | +| 2. The kernel | No implicit panic: the set is denied. A deliberate stop for a broken internal invariant stays (fail fast), as an `#[expect(…, reason = "…")]` whose reason names the invariant. A type that makes the broken state unrepresentable is preferred over any stop. | +| 3. System services | The same rule as the kernel. | | 4. Apps, ports, test code and build tooling | Normal Rust. | -**Today.** -- Seven crates carry the lints (`rg -l 'clippy::indexing_slicing' --glob '*.rs'`). - `toyos-net-wire`, `-ip`, `-tcp`, `-udp`, `toyos-dhcp` and `toyos-gpt` forbid - five of them outside tests, plus `as_conversions` in place of truncating - casts. `toyos-transport` forbids four and denies `indexing_slicing` and - `as_conversions`, with one `#[allow]` at `toyos-transport/src/queue.rs:21`. - No crate denies `unreachable`. -- The kernel has 157 `.unwrap()`/`.expect(` calls on 154 lines - (`rg -o '\.unwrap\(\)|\.expect\(' kernel/src | wc -l`; `rg -c` counts - lines). Running `cargo clippy --target x86_64-unknown-none` in `kernel/` - with the seven lints as warnings reports 2,008 findings at default features: 992 - `arithmetic_side_effects`, 408 `indexing_slicing`, 394 - `cast_possible_truncation`, 109 `expect_used`, 60 `panic`, 29 `unwrap_used` - and 16 `unreachable`. -- `overflow-checks = true` is set in `[profile.toyos]` (`Cargo.toml:254`, - `kernel/Cargo.toml:387`, `userland/Cargo.toml:85`), so an overflow stops the - program. Overflow checks found the two crafted-ELF kernel panics, from - `e_phoff = u64::MAX` and from a `.gnu.hash` `bloom_shift` of 32 or more. - `issues/` holds neither. They are closed at `56aea566a:specs/known-issues.md:440`, - and their tests are `toyos-elf/tests/crafted.rs:113` and - `toyos-elf/tests/tables.rs:615`. -- The review of PR #653 at `38318d18b` found a length check at - `toyos-microcode/src/lib.rs:203` that no test covers. It guards the panicking - slice `table[EXT_HEADER..]` at `:213`. +**The set**, every name checked against clippy 0.1.98 (`48a229ceae`): +- indexing and slicing: `clippy::indexing_slicing`, and `clippy::string_slice`, + because the first does not fire on slicing a `str`; +- arithmetic: `clippy::arithmetic_side_effects`. `[profile.toyos]` sets + `overflow-checks`, so arithmetic is a panicking form; +- `clippy::unwrap_used`, `clippy::expect_used`, `clippy::panic`, + `clippy::unreachable`, `clippy::todo`, `clippy::unimplemented`, + `clippy::panic_in_result_fn`; +- truncating casts: `clippy::cast_possible_truncation`. A truncating `as` does + not panic; the owner names it, and for tiers 1 to 3 that overrides its + rejection in `issues/build/clippy-stage-two-is-lints-one-at-a-time.md`; +- standard functions: `clippy::disallowed_methods` naming `slice::split_at` and + `slice::copy_from_slice`, and `clippy::disallowed_macros` naming + `core::assert`, `core::assert_eq` and `core::assert_ne`. + +**Today.** No crate carries the set: none names `string_slice`, +`panic_in_result_fn`, `todo`, `unimplemented` or `cast_possible_truncation`. +In `kernel/`, `cargo clippy --target x86_64-unknown-none` with the set as +warnings reports 2,274 findings, 992 of them `arithmetic_side_effects`, 408 +`indexing_slicing`, 394 `cast_possible_truncation` and 208 `disallowed_macros`. **Constraints.** -- The lints do not see `assert!`, `copy_from_slice`, `split_at` or a panic - inside a callee: on a crate holding the first three beside `a[3]` and - `panic!`, they warned on those two alone. +- The set does not see a shift by a variable amount, `pow` or `abs`, though + each panics under `overflow-checks`; nor a standard function it does not + name, nor a panic inside a callee. +- `clippy::allow_attributes` checks outer attributes alone: an inner + `#![allow(…, reason = "…")]` passes it and `allow_attributes_without_reason`. +- `disallowed_methods` and `disallowed_macros` read the nearest `clippy.toml` + alone, and the root's reaches every crate beneath it, tier 4 included. - The no-panic proof needs unwinding: its README says "The attribute is useless in code built with `panic = "abort"`". Both kernel targets print `"panic-strategy": "abort"` from `rustc -Z unstable-options --print @@ -53,17 +50,19 @@ yet, and that is the present-state weakness. clippy (`src/clippy.rs:7-8`). **Stages, in order.** -1. Tier 1, one area at a time. An area's exit is its crate or kernel module - denying the lints under `cargo run -- --clippy`. `toyos-elf` is - `issues/design-debt/elf-domain-lint-line-not-yet-added.md`. +1. Tier 1, one area at a time. The first exit is one declaration of the set + that every tier-1 crate reads in place of its own copy. An area's exit is + its crate or kernel module forbidding the set under `cargo run -- --clippy`; + `forbid` refuses any inner `#[allow]` or `#[expect]` of it, the `#[allow]` + in `issues/design-debt/elf-domain-lint-line-not-yet-added.md`'s exit too. 2. The link-time proof on the parser entry points: works or not, measured. The - exit is a host build that refuses to link an entry point with a panicking - path, or a `rejected` issue with the measurement. -3. The kernel, one module at a time, under `--clippy`. The stage ends when - `kernel/src/main.rs` denies the lints for the whole crate. -4. The system services. A service's exit is its crate root denying the lints - under a clippy run that reaches userland. -5. The rule goes into `.claude/agents/reviewer.md` beside "Edges". - -**Mutation**: adding one `buf[i]` to a module that denies the lints turns -`cargo run -- --clippy` red. + exit is a step of `cargo run -- --ci host` whose host build refuses to link + an entry point with a panicking path, or a `rejected` issue with the + measurement. +3. The kernel, one module at a time. The stage ends when `kernel/src/main.rs` + denies the set and forbids `clippy::allow_attributes` and + `clippy::allow_attributes_without_reason` under `--clippy`, and a step of + `--ci host` refuses an inner `#![allow]`: then every exception is an + `#[expect(…, reason = "…")]`, and a bare `#[allow]` fails the gate. +4. The system services. The first exit is a userland shape in `src/clippy.rs`; + a service's exit is its crate root under stage 3's attributes. diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 5beca3f086..85c4228f97 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -372,9 +372,8 @@ user-writable-gsbase = [] # The one profile every guest binary is built with. # Optimised, because an unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. [profile.toyos] inherits = "dev" diff --git a/src/build.rs b/src/build.rs index f8932e7a13..f2ae538a9e 100644 --- a/src/build.rs +++ b/src/build.rs @@ -405,10 +405,7 @@ fn config_crates(root: &Path, config: &SystemConfig) -> Vec { /// it in. /// /// One name, passed to every `cargo build` here and declared by every crate -/// root the image is made of. `--release` used to be a flag on `cargo run`, and -/// it silently turned `debug-assertions` and `overflow-checks` off — the two -/// knobs `issues/`'s crafted-ELF panics were *found* by. There is -/// no longer a second profile to pick, which is why there is no longer a flag. +/// root the image is made of. pub const PROFILE: &str = "toyos"; /// What every guest `cargo` and `rustc` here runs with: the toolchain directory @@ -632,9 +629,7 @@ fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool { /// [`PROFILE`] states them and `--release` is gone from this build system, so /// the way they can still be lost is somebody editing `[profile.toyos]`. This /// asks the artifact rather than the manifest, which is the only question worth -/// asking: `issues/`'s two crafted-ELF kernel panics were both -/// *found* by an overflow check, and one of them had no configuration in which -/// it was an error return. +/// asking. fn assert_overflow_checked(what: &str, image: &[u8]) { let found = contains_subslice(image, OVERFLOW_CHECK_MARKER); assert!( diff --git a/userland/Cargo.toml b/userland/Cargo.toml index 55b8b482f9..70cbde36cf 100644 --- a/userland/Cargo.toml +++ b/userland/Cargo.toml @@ -65,9 +65,8 @@ winit = { git = "https://github.com/ToyOSOrg/winit", branch = "toyos-0.30.13" } # The one profile every guest binary is built with. # Optimised, because an unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. # # What it replaces here: dependencies at 2 through `package."*"`, doom and the From ec166f1b45e7a2dbdd80e2d9a3e7207ee628e1c9 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 10:11:17 +0200 Subject: [PATCH 13/24] The kernel takes on only what userland cannot, stated once in CLAUDE.md Review of 066e1a028 on #666 sent the branch back. - CLAUDE.md's Kernel line said new additions are "discussed" and named the filesystem a kernel job, against both rulings. It now says the kernel takes on only what userland cannot, keeps the job list without the filesystem, and points at the Capabilities paragraph, whose "Not yet true of files" sentence already records the machine-wide tree the kernel still holds. 132 characters replace 135. - implementer.md's closed list of kernel jobs was a second declaration of the kernel's scope that the owner never gave; deleted, along with the restated rule, which root CLAUDE.md now carries. - "or keeping" (implementer.md) and "or a kernel path the branch keeps" (reviewer.md) made every kernel-side defect fix a BLOCKER outside its fence; deleted. A kernel refusal is still "a kernel addition", and an ENOSYS dodge is still "a design made worse to spare the ABI". - "a userland server" became "userland" in both files: the owner's loader move puts relocation in a Ring 3 loader in the target's own address space, which is userland but no server. - implementer.md says a clean design that reaches past the fence blocks the implementer, so the ABI sentence does not widen the fence. - The Syscall ABI line drops "add, change or remove syscalls", which "completely unstable" already says. - issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md names an owner and lists the retired device classes 3 and 4. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- .claude/agents/implementer.md | 7 +++---- .claude/agents/reviewer.md | 5 ++--- CLAUDE.md | 4 ++-- .../the-abi-still-keeps-retired-syscall-numbers.md | 6 ++++++ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index b4680b297c..4012e6fe94 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -14,10 +14,9 @@ One brief, one worktree, one branch. What the brief does not name you do not tou 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. -Before adding or keeping kernel behaviour, ask whether a userland server can own it: the kernel -does only what only a kernel can (address spaces, scheduling, handles and capabilities, interrupts, -device arbitration). When the clean design changes the ABI, change the ABI; never pick a lesser -design to avoid that. +Before adding kernel behaviour, ask whether userland can own it. When the clean design changes the +ABI, change the ABI; never pick a lesser design to avoid that. A clean design that reaches past +your fence blocks you. ## Measure, build, test diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md index 164d54f64a..d4418ca63f 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -42,9 +42,8 @@ above; otherwise it is a NOTE. - **Fit.** Does the tree already do this? Is each new thing where it belongs: a pure decision in a pure crate, the user/kernel boundary in `toyos-userbound`, a device claim in a userland server? One declaration read by every reader, refusal by name, authority moved in by the parent. Zero - legacy: no shim, no workaround, no silent default. A BLOCKER each: a kernel addition, or a - kernel path the branch keeps, that a userland server could own; a design made worse to spare - the ABI. No new + legacy: no shim, no workaround, no silent default. A BLOCKER each: a kernel addition that + userland could own; a design made worse to spare the ABI. No new dependency or fetch. Nothing outside the brief's fence. Assembly, a naked function and a `core::arch` or `std::arch` path live only in an architecture's own module; `target_arch` only there, in its selector, in `src/arch.rs` and in diff --git a/CLAUDE.md b/CLAUDE.md index fa9f283de9..7a2bf0b0da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,13 +38,13 @@ A subdirectory `CLAUDE.md` loads when a file in that subtree is `Read`, and not > A snapshot, deliberately shallow — always read the code. -**Kernel** — minimal; new additions are discussed and justified. Resource management, scheduling, process lifecycle, filesystem, device arbitration. 2 MB pages, demand paging, PIE binaries, full SMP. +**Kernel** — takes on only what userland cannot. Resource management, scheduling, process lifecycle, device arbitration; files: see Capabilities. 2 MB pages, demand paging, PIE binaries, full SMP. **Userspace daemons** — compositor, netd, soundd, sshd, logd. Each claims a device or capability from the kernel and serves its function; crash one and the kernel is fine. **The log is a userland file.** `/system/bin/logd` reads records on a cursor and owns `/log`; the kernel keeps the record ring, the console and the panel, and writes no file. `SYS_FSYNC` reaches the device's cache flush because logd's durability claim rests on it. -**Syscall ABI** — `toyos-abi/`: struct layouts, syscall numbers, typed wrappers; completely unstable. The cleanest, most sustainable ABI beats convenience: add, change or remove syscalls; a removed number is free. `toyos/` builds on it with typed handles, IPC framing, ports, namespaces and `surface` — userland uses `toyos`, the kernel uses `toyos-abi` only. +**Syscall ABI** — `toyos-abi/`: struct layouts, syscall numbers, typed wrappers; completely unstable. The cleanest, most sustainable ABI beats convenience; a removed number is free. `toyos/` builds on it with typed handles, IPC framing, ports, namespaces and `surface` — userland uses `toyos`, the kernel uses `toyos-abi` only. **Capabilities** — a process holds exactly what its parent moved into it, and among kernel objects there is nothing it can name to get more. No registry, no connect-by-name, no pid-as-authority: `/system/bin/init` builds every program's namespace and device claims from `system.toml` before spawning it, and a handle a process does not hold is a bug in that process — the kernel ends it rather than answering a word it can ignore. **Isolation is non-negotiable, and the filesystem is inside it**: a process names only the paths in the view its parent built for it, the unit of isolation is the program, and a user is the part of the tree a session was handed. Not yet true of files: the kernel still resolves every path against one machine-wide tree until the storage track's per-program views land. diff --git a/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md b/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md index 3b0c3a2d7b..59429a59c1 100644 --- a/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md +++ b/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md @@ -17,6 +17,9 @@ still keeps them: or "retired and unused" entry per deleted syscall, `SYS_DEBUG` action and inbox op, `SYS_INBOX_SETUP`'s doc states the retirement rule, and `kernel/src/inbox/mod.rs` names op 2 retired. +- `toyos-abi/src/syscall.rs`'s `device_classes!` keeps device classes 3 (`Nic`) + and 4 (`Audio`) "retired rather than reused", and the decode test in + `toyos-abi/src/inventory.rs` refuses them as retired. - `tests/toyos-rust-tests/src/bin/log_hold.rs`, `tests/toyos-rust-tests/src/bin/panic_halts_first.rs`, `tests/common/origin.rs` and `tests/toyos.rs` take syscall 26 as their logged @@ -34,3 +37,6 @@ still keeps them: **Exit**: `retired_syscalls!` and every retirement entry are gone, a deleted number answers as an unassigned one does, the four test sites take their logged refusal from something live, and no issue plans by retirement. + +Owner: `toyos-abi` and `kernel/src/syscall/dispatch.rs`, whoever next changes +the ABI. From 64e468e8dfa5da9bf3738ea03864d8e5b9a6239e Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 14:01:51 +0200 Subject: [PATCH 14/24] #666: the kernel rule replaces the whole span; the ABI issue's test sites are main's Round 1 of #666's review asked that "the 'only what only a kernel can' rule replaces that 135-character span, written as a rule on what the kernel takes on and not as a snapshot". Round 2 kept the job list beside the rule, less the filesystem, with a "files: see Capabilities" pointer. The job list is the snapshot the review named, and the rule alone states what the kernel takes on; the pointer's subject is already the Capabilities paragraph's own "Not yet true of files". So the span is now the rule alone: "takes on only what userland cannot." The ABI issue listed four test sites that take syscall 26 as their logged refusal. Since #660 landed, `log_hold.rs` and `tests/common/origin.rs` are gone; `panic_halts_first.rs` and `tests/toyos.rs` remain, and the exit names both. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- CLAUDE.md | 2 +- .../the-abi-still-keeps-retired-syscall-numbers.md | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3a9ecf8534..f2e1ebf24c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ A subdirectory `CLAUDE.md` loads when a file in that subtree is `Read`, and not > A snapshot, deliberately shallow — always read the code. -**Kernel** — takes on only what userland cannot. Resource management, scheduling, process lifecycle, device arbitration; files: see Capabilities. 2 MB pages, demand paging, PIE binaries, full SMP. +**Kernel** — takes on only what userland cannot. 2 MB pages, demand paging, PIE binaries, full SMP. **Userspace daemons** — compositor, netd, soundd, sshd, logd. Each claims a device or capability from the kernel and serves its function; crash one and the kernel is fine. diff --git a/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md b/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md index 59429a59c1..379ba64d66 100644 --- a/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md +++ b/issues/design-debt/the-abi-still-keeps-retired-syscall-numbers.md @@ -20,10 +20,8 @@ still keeps them: - `toyos-abi/src/syscall.rs`'s `device_classes!` keeps device classes 3 (`Nic`) and 4 (`Audio`) "retired rather than reused", and the decode test in `toyos-abi/src/inventory.rs` refuses them as retired. -- `tests/toyos-rust-tests/src/bin/log_hold.rs`, - `tests/toyos-rust-tests/src/bin/panic_halts_first.rs`, - `tests/common/origin.rs` and `tests/toyos.rs` take syscall 26 as their logged - refusal and read `syscall 26 is retired`. +- `tests/toyos-rust-tests/src/bin/panic_halts_first.rs` and `tests/toyos.rs` + take syscall 26 as their logged refusal and read `syscall 26 is retired`. - Plans still follow the old rule: `issues/kernel/sys-clock-realtime-is-now-a-format-of-sys-clock-epoch.md`, `issues/kernel/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md`, @@ -35,7 +33,7 @@ still keeps them: "ABI brief" names a gate `.claude/agents/implementer.md` no longer has. **Exit**: `retired_syscalls!` and every retirement entry are gone, a deleted -number answers as an unassigned one does, the four test sites take their logged +number answers as an unassigned one does, both test sites take their logged refusal from something live, and no issue plans by retirement. Owner: `toyos-abi` and `kernel/src/syscall/dispatch.rs`, whoever next changes From 2b301bc13bf7c994d8a72258e7e082b3fbed3659 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 14:04:00 +0200 Subject: [PATCH 15/24] #613 keeps what is still true and not already said; CPU microcode is admitted #613's first review sent it back because root CLAUDE.md's line 3 grew and its ARM64 sentence contradicted the cores issue. Since then main's ARM64 track has stated the ruling itself ("no ARM hardware is a target and no work goes into one"), and the cores issue no longer says otherwise. What of #613 stays: - Line 3 goes back to main's text with two additions: "general-purpose", and "Its test machines decide its feature set, never its design." Both are the owner's ruling of 2026-09-29 ("ToyOS is a general-purpose OS for modern hardware; the T14 may decide the feature set but is not the design centre"), recorded at 314f874dd in the Raspberry Pi track and lost when that track was deleted; nothing on main says it now. #613's ARM64 sentence goes: main's track says it. - "Leave the machine as you found it" says "kills by PID what it started" where it said "stops what it started". By PID is the one thing the lesson adds to that bullet; "before reporting" is already its "never leaves ... running". - implementer.md keeps the rule that a pull request's evidence never lives only in /tmp, and that mutation patches are posted to the pull request. Nothing in implementer.md said either. - implementer.md loses the rule that rg without a path waits forever on stdin. In the agents' shell, foreground and background, stdin is a character device (`stat -f '%HT' /dev/fd/0`), and `rg -l ` with no path searched the working directory and exited 1, no match, in both. - The process-memory track keeps main's sentence that one paging design serves x86-64 and the ARM64 the tree is kept portable for. ARM64 under QEMU is still that second architecture; nothing in it contradicts the QEMU-only ruling. #645's one BLOCKER is answered here too: the firmware paragraph now says vendor firmware "never executes on the CPU, save the CPU's own microcode, which the kernel loads", #636's clause for the owner's ruling that the kernel loads CPU microcode signed by its maker and pinned by version and hash. Root CLAUDE.md is 16075 bytes; main's is 16077. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- .claude/agents/implementer.md | 2 -- CLAUDE.md | 6 +++--- ...memory-is-2-mib-pages-and-that-caps-the-process-count.md | 4 +++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index 85546460bc..803e458847 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -30,8 +30,6 @@ guess. Then build, then test before anyone reviews: - Long commands run in the background with output to a file under the job scratchpad the brief names. Stay inside one turn while anything runs: sleep at most two minutes, print a line, check again. Ten minutes of silence kills you, and ending a turn to announce a wait strands the work. -- In a script or non-interactive shell `rg` is always given an explicit path: without one it reads - stdin and waits forever. - 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.md b/CLAUDE.md index f2e1ebf24c..f675a3024f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # ToyOS -A general-purpose operating system built from scratch in Rust for modern hardware, held to a production-grade engineering bar — the bar is the changes, not yet the product. The machines ToyOS is tested on decide its feature set, never its design. x86-64 (2020+), UEFI only; keep the architecture portable: ARM64 runs under QEMU on the development machine, for quick runs and to keep the kernel clean, and no ARM hardware is a target. The quality bar is shipping software: correct, efficient, minimal, zero silent debt. A tracked weakness is still a weakness: the honest answer about current state is "known, tracked, still true" — never "we have an issue for that." +A general-purpose operating system built from scratch in Rust, held to a production-grade engineering bar — the bar is the changes, not yet the product. Modern x86-64 hardware (2020+), UEFI only; ARM64 planned — keep the architecture portable. Its test machines decide its feature set, never its design. The quality bar is shipping software: correct, efficient, minimal, zero silent debt. A tracked weakness is still a weakness: the honest answer about current state is "known, tracked, still true" — never "we have an issue for that." ## Where the rest of this lives @@ -60,7 +60,7 @@ A subdirectory `CLAUDE.md` loads when a file in that subtree is `Read`, and not **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. 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. -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. @@ -74,7 +74,7 @@ The testing rules live where they are enforced: the PR gate and the nightly in ` - `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. - **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 — before reporting it kills by PID anything of its own that still runs — removes the worktrees and scratch build output it no longer needs, and never leaves an emulator, a build or a watcher running. +- **Leave the machine as you found it.** The development machine is shared: every agent kills by PID 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. ## Repository layout diff --git a/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md b/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md index ea196086e6..4dea86b1b3 100644 --- a/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md +++ b/issues/kernel/process-memory-is-2-mib-pages-and-that-caps-the-process-count.md @@ -14,7 +14,9 @@ can do is a restriction, and 2 MiB-only paging caps the process count. **What is to be built:** process memory — stacks, thread-local blocks, heaps, small data and code segments, and device windows handed to a process — moves to 4 KiB pages. 2 MiB stays where it pays: large mappings, DMA grants, the -IOMMU's superpages, the kernel's own mappings. One paging design serves both. +IOMMU's superpages, the kernel's own mappings. One paging design serves both, +on x86-64 and on the ARM64 the tree is kept portable for +(`issues/kernel/toyos-runs-on-arm64.md`). **The constraint that makes this a track, measured on the T14** (run 29 readback, `PMM: 100/16038MB used` at 11 s with four user processes and three From dc3f158dc6f0c81d280df281716f5a9fc3e2d0a7 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 14:16:28 +0200 Subject: [PATCH 16/24] #665: every lossy cast, an expect per stop, and stage 3 reaches the kernel's crates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answers round 2 of #665's review (f469e9022). Blockers: - The set names every lossy cast: `cast_possible_wrap`, `cast_sign_loss` and `cast_precision_loss` beside `cast_possible_truncation`. Each changes a value silently, which "Fail fast" puts below a panic; that is the reason the track now gives, in place of "the owner names it". - Stage 3's `--ci host` step also refuses an `#[expect]` of the set on a `mod`, on an `impl`, and as an inner `#![expect]`: any of them passes every finding beneath it. - Stage 3 ends with every crate of this tree that `kernel/Cargo.toml` links under the same attributes, unless stage 1 already forbids the set in it. Notes: the set names `slice::split_at_mut` and `slice::clone_from_slice`; the clause on `issues/design-debt/elf-domain-lint-line-not-yet-added.md` goes, and that issue is left to its holder; stage 1's "one declaration" now states what was measured: Cargo's `[lints]` reaches test code and `cargo clippy --lib -- -F` does not, and neither reaches a single module, so an input boundary inside the kernel becomes a crate first. Removed: the clippy version line, "(fail fast)" and the unrepresentable-type sentence, everything after "No crate carries the set.", and the ELF clause. Measured on scratch crates with cargo 1.98.1 and clippy 0.1.98 (48a229ceae), each `cargo clippy -p ` unless named: - `#![forbid(clippy::indexing_slicing)]` and an `#[expect(…, reason)]` of it: E0453, exit 101. The same forbid and an inner `#![allow(…, reason)]` in a module: E0453, exit 101. - With `clippy::allow_attributes` and `allow_attributes_without_reason` forbidden: an outer `#[allow(…, reason)]` draws `allow_attributes`, exit 101; an `#[expect]` with no reason draws the second, exit 101; an inner `#![allow(…, reason)]` in a module draws neither, exit 0. - Under the same attributes and `deny` of indexing and arithmetic, one `#[expect(…, reason)]` on `mod drivers;` over five findings, an inner `#![expect]` over two and an `#[expect]` on an `impl` over two: exit 0, no warning, so every expectation was fulfilled. - `&s[1..]` on a `str` draws `string_slice`, and `indexing_slicing`, also denied, draws nothing: exit 101. - Shift left and right by a variable, `pow` and `abs` under the whole set denied: exit 0. Built with `-C overflow-checks=on`, each exits 101: "attempt to shift left with overflow", "attempt to shift right with overflow", "attempt to exponentiate with overflow", "attempt to negate with overflow". - `[lints.clippy] indexing_slicing = "forbid"`: `--all-targets` exits 101 at a `#[cfg(test)]` slice index, `--lib` exits 0; with `#![cfg_attr(test, allow(clippy::indexing_slicing))]` added, `--all-targets` gives E0453, exit 101. - `--lib -- -F clippy::indexing_slicing`: exit 101 on the library's index; exit 0 on a clean library whose test indexes a slice, which `--all-targets -- -F` refuses, exit 101; and E0453, exit 101, on an inner `#[expect(…, reason)]` of the lint. - `CLIPPY_CONF_DIR` naming the four methods and three macros: each of the seven fires, exit 101. The kernel count: in kernel/, with the root clippy.toml plus those methods and macros through `CLIPPY_CONF_DIR`, `cargo clippy --target x86_64-unknown-none --message-format=json -- -W ` exits 101 under the kernel's `-Dwarnings` with 2,282 errors, counted by code: 984 arithmetic_side_effects, 405 indexing_slicing, 392 cast_possible_truncation, 205 disallowed_macros (198 assert, 7 assert_eq), 107 expect_used, 55 panic, 39 disallowed_methods (all copy_from_slice), 29 unwrap_used, 20 cast_possible_wrap, 16 unreachable, 14 panic_in_result_fn, 9 cast_sign_loss, 5 string_slice, 2 cast_precision_loss. That is the kernel package alone: clippy lints no crate it links. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- issues/kernel/a-panic-is-never-an-accident.md | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/issues/kernel/a-panic-is-never-an-accident.md b/issues/kernel/a-panic-is-never-an-accident.md index 2276a72fb2..555b1bb0f7 100644 --- a/issues/kernel/a-panic-is-never-an-accident.md +++ b/issues/kernel/a-panic-is-never-an-accident.md @@ -9,11 +9,11 @@ opened: 2026-10-01 | Tier | Rule | |---|---| | 1. Input boundaries | No panic at all: the set below is forbidden. Where the build allows, each parser's entry point also carries a link-time no-panic proof. | -| 2. The kernel | No implicit panic: the set is denied. A deliberate stop for a broken internal invariant stays (fail fast), as an `#[expect(…, reason = "…")]` whose reason names the invariant. A type that makes the broken state unrepresentable is preferred over any stop. | +| 2. The kernel | No implicit panic: the set is denied. A deliberate stop for a broken internal invariant stays, as an `#[expect(…, reason = "…")]` whose reason names the invariant. | | 3. System services | The same rule as the kernel. | | 4. Apps, ports, test code and build tooling | Normal Rust. | -**The set**, every name checked against clippy 0.1.98 (`48a229ceae`): +**The set**: - indexing and slicing: `clippy::indexing_slicing`, and `clippy::string_slice`, because the first does not fire on slicing a `str`; - arithmetic: `clippy::arithmetic_side_effects`. `[profile.toyos]` sets @@ -21,18 +21,20 @@ opened: 2026-10-01 - `clippy::unwrap_used`, `clippy::expect_used`, `clippy::panic`, `clippy::unreachable`, `clippy::todo`, `clippy::unimplemented`, `clippy::panic_in_result_fn`; -- truncating casts: `clippy::cast_possible_truncation`. A truncating `as` does - not panic; the owner names it, and for tiers 1 to 3 that overrides its +- lossy casts: `clippy::cast_possible_truncation`, `clippy::cast_possible_wrap`, + `clippy::cast_sign_loss` and `clippy::cast_precision_loss`. A lossy `as` + does not panic; it changes the value silently, and `CLAUDE.md`'s "Fail fast" + puts panics over silent degradation. For tiers 1 to 3 that overrides their rejection in `issues/build/clippy-stage-two-is-lints-one-at-a-time.md`; -- standard functions: `clippy::disallowed_methods` naming `slice::split_at` and - `slice::copy_from_slice`, and `clippy::disallowed_macros` naming +- standard functions: `clippy::disallowed_methods` naming `slice::split_at`, + `slice::split_at_mut`, `slice::copy_from_slice` and + `slice::clone_from_slice`, and `clippy::disallowed_macros` naming `core::assert`, `core::assert_eq` and `core::assert_ne`. -**Today.** No crate carries the set: none names `string_slice`, -`panic_in_result_fn`, `todo`, `unimplemented` or `cast_possible_truncation`. -In `kernel/`, `cargo clippy --target x86_64-unknown-none` with the set as -warnings reports 2,274 findings, 992 of them `arithmetic_side_effects`, 408 -`indexing_slicing`, 394 `cast_possible_truncation` and 208 `disallowed_macros`. +**Today.** No crate carries the set. In the kernel package alone, `cargo +clippy --target x86_64-unknown-none` with the set as warnings reports 2,282 +findings, 984 of them `arithmetic_side_effects`, 405 `indexing_slicing`, 423 +lossy casts and 205 `disallowed_macros`. **Constraints.** - The set does not see a shift by a variable amount, `pow` or `abs`, though @@ -40,6 +42,10 @@ warnings reports 2,274 findings, 992 of them `arithmetic_side_effects`, 408 name, nor a panic inside a callee. - `clippy::allow_attributes` checks outer attributes alone: an inner `#![allow(…, reason = "…")]` passes it and `allow_attributes_without_reason`. +- Cargo's `[lints]` reaches a crate's `#[cfg(test)]` code, and its `forbid` + refuses the `cfg_attr(test, allow(…))` that would free it. `cargo clippy + --lib -- -F ` reaches the library alone, and refuses an inner + `#[expect]` of the lint. Each reaches a whole crate, never one module of it. - `disallowed_methods` and `disallowed_macros` read the nearest `clippy.toml` alone, and the root's reaches every crate beneath it, tier 4 included. - The no-panic proof needs unwinding: its README says "The attribute is useless @@ -50,19 +56,24 @@ warnings reports 2,274 findings, 992 of them `arithmetic_side_effects`, 408 clippy (`src/clippy.rs:7-8`). **Stages, in order.** -1. Tier 1, one area at a time. The first exit is one declaration of the set - that every tier-1 crate reads in place of its own copy. An area's exit is - its crate or kernel module forbidding the set under `cargo run -- --clippy`; - `forbid` refuses any inner `#[allow]` or `#[expect]` of it, the `#[allow]` - in `issues/design-debt/elf-domain-lint-line-not-yet-added.md`'s exit too. +1. Tier 1, one crate at a time; an input boundary inside the kernel is moved + into a crate of its own first. The first exit is one declaration of the set + that every tier-1 crate's library is linted under in place of its own copy, + its tests left at tier 4. An area's exit is its crate forbidding the set + under `cargo run -- --clippy`; `forbid` refuses any inner `#[allow]` or + `#[expect]` of it. 2. The link-time proof on the parser entry points: works or not, measured. The exit is a step of `cargo run -- --ci host` whose host build refuses to link an entry point with a panicking path, or a `rejected` issue with the measurement. 3. The kernel, one module at a time. The stage ends when `kernel/src/main.rs` - denies the set and forbids `clippy::allow_attributes` and - `clippy::allow_attributes_without_reason` under `--clippy`, and a step of - `--ci host` refuses an inner `#![allow]`: then every exception is an - `#[expect(…, reason = "…")]`, and a bare `#[allow]` fails the gate. + and every crate of this tree `kernel/Cargo.toml` links, unless stage 1 + already forbids the set in it, deny the set and forbid + `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` + under `--clippy`, and a step of `--ci host` refuses an inner `#![allow]`, + and an `#[expect]` of the set on a `mod`, on an `impl` or as an inner + `#![expect]`, any of which passes every finding beneath it: then every + exception is an `#[expect(…, reason = "…")]`, and a bare `#[allow]` fails + the gate. 4. The system services. The first exit is a userland shape in `src/clippy.rs`; a service's exit is its crate root under stage 3's attributes. From b250bc2c9118f2472089339f9b17ebb4e810cf8f Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 14:16:47 +0200 Subject: [PATCH 17/24] #604: the crate track's counts and premises, measured on main #604 was written against main as of 7e151819e, 574 commits back. Each premise was checked against this branch, and each count was relisted with `cargo test -p -- --list`, exit 0 for every package. Premises that moved: - The network stack's stage 3 landed as `toyos-net-ip`, `toyos-net-udp` and `toyos-dhcp`. Nothing but each other depends on them, so step 5 folds them into netd's library beside the four it already named. Stage 4's `toyos-net-shard` and `toyos-net-testnet` are what the network track still plans. - `toyos-gicv3` and `toyos-cpuvuln` arrived. `kernel/Cargo.toml` is the one manifest that names `toyos-gicv3`. `toyos-cpuvuln` has no dependent yet, and its track (`a-pure-function-decides-a-cpus-speculation-mitigations- as-linux-does.md`) has it "built by the kernel and by a host test". Both go into the kernel's library in step 3. - `src/redlist.rs` is deleted, so step 1's redlist check goes. `munmap_reissues_read_window` is still a test binary and rides `shared_metal`. - Three scheduler tests need a feature, not six, and `check` is the one they need: toyos-sched lists 92 tests bare, 95 with `check`, 92 with `protocol-port` and 95 with both. Counts, old and new: - The kernel's library: 391 becomes 495. dma 17, pci 75, pcid 6, proclife 34, ps2 24, sched 95, userbound 34, xhci 150, gicv3 8 and cpuvuln 52. toyos-symbols is still 9. - `kernel/loom`: 81 becomes 79 (kernel-loom 53, sched-loom 26). `kernel/sim` is still 99 (sched-sim 53, xhci-sim 46). - The merged crates, 157, 26, 45, 25 and 67, become 170, 26, 45, 26 and 67. toyos-boot is acpi 52, bootmap 43, rootimage 21, blackbox 33, tco 13 and quiesce 8. toyos-log is elide 12 and logstream 14. toyos-block is blockhold 9, blockring 19 and transport 17. Manifest 19 and swap 7, and fat32-check 67. - netd's library: 709 becomes 1105. dns 43, mdns 12, net-wire 292, net-tcp 363, net-ip 261, net-udp 58 and dhcp 76. The mixer is still 55, the desktop 95 and inspect 21. Still true, so unchanged: - The pcid issue. `src/ci.rs`'s CONTROLS has no toyos-pcid row, and `declared_model_controls` reads no toyos-pcid manifest. `cargo test -p toyos-pcid --features counting-allocator` exits 101 with `tests::two_live_address_spaces_never_share_a_pcid ... FAILED`. - The userland-lint issue. `cargo clippy --manifest-path userland//Cargo.toml --target aarch64-apple-darwin --all-targets -- -D warnings` exits 101 with 8 errors in soundd, 10 in netd's test target, and 1 in toyos-window under the compositor. - `the_corpus_is_reproduced_bit_for_bit` is still the mixer's. - Userland's `[profile.dev]` is still at opt-level 0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L --- ...ed-by-one-program-lives-in-that-program.md | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/issues/build/code-used-by-one-program-lives-in-that-program.md b/issues/build/code-used-by-one-program-lives-in-that-program.md index cf6d4fa186..4de987029b 100644 --- a/issues/build/code-used-by-one-program-lives-in-that-program.md +++ b/issues/build/code-used-by-one-program-lives-in-that-program.md @@ -13,31 +13,31 @@ kept by review (owner, 2026-09-29). Two open tracks plan single-program crates this one folds back: `toyos-supervisor` (`issues/isolation/the-supervisor-is-host-tested-and-owns-the-stop.md`) -and the stack's `toyos-net-*` (`issues/design-debt/toyos-has-its-own-network-stack.md`). -They are reconciled before step 5. +and the stack's `toyos-net-shard` and `toyos-net-testnet` +(`issues/design-debt/toyos-has-its-own-network-stack.md`). They are reconciled +before step 5. Every step lands green on `--ci host` and `--build-only`. Test counts are what `cargo test -p -- --list` lists today. 1. **Delete `toyos-userpin`.** It models the pin invariant and names nothing the kernel defines; `munmap_reissues_read_window` holds the kernel to it. - Check: `git grep toyos-userpin` is empty and that test has no - `src/redlist.rs` row. + Check: `git grep toyos-userpin` is empty. 2. **Lint userland first.** Close `issues/build/userland-programs-are-never-linted.md`: steps 3 to 5 move code out of the host workspace's clippy. Check: that issue's planted finding reds. 3. **The kernel's library.** `kernel/pure/` is the `kernel` package's lib, and its bin is `test = false`. `toyos-dma`, `-pci`, `-pcid`, `-proclife`, - `-ps2`, `-sched`, `-xhci`, `-userbound` and `toyos-symbols`' name budget move - in; `locate` and `file_range` go to `toyos-elf`. `kernel-loom` and - `toyos-sched/loom` become `kernel/loom/`, and the two simulators - `kernel/sim/`. The harness dev-depends on the kernel, and the build system - does not depend on it. The library has no `tests/`, since an integration + `-ps2`, `-sched`, `-xhci`, `-userbound`, `-gicv3`, `-cpuvuln` and + `toyos-symbols`' name budget move in; `locate` and `file_range` go to + `toyos-elf`. `kernel-loom` and `toyos-sched/loom` become `kernel/loom/`, + and the two simulators `kernel/sim/`. The harness dev-depends on the + kernel, and the build system does not depend on it. The library has no `tests/`, since an integration test builds the binary for the host. `--ci host` tests it with - `sched-check,sched-protocol-port`, the features six scheduler tests need. + `sched-check`, the feature three scheduler tests need. Closes `issues/build/the-pcid-negative-control-runs-nowhere.md`. - Check: the library lists at least 391 tests, and it and `toyos-elf` gain - `toyos-symbols`' 9 between them. `kernel/loom` lists 81 and `kernel/sim` 99. + Check: the library lists at least 495 tests, and it and `toyos-elf` gain + `toyos-symbols`' 9 between them. `kernel/loom` lists 79 and `kernel/sim` 99. Every moved control, and pcid's `counting-allocator`, reds with its verdict. An `unsafe {}` planted in a module that was `forbid(unsafe_code)` does not compile. `cargo tree -e normal -p toyos-build` names no `kernel`. @@ -46,17 +46,18 @@ Every step lands green on `--ci host` and `--build-only`. Test counts are what is `toyos-elide` and `-logstream`. `toyos-block` is `toyos-blockhold`, `-blockring` and `-transport`. `toyos-manifest` takes in `toyos-swap`, and `toyos-fat32-check` moves to `toyos-fat32/check/`. - Check: the merged crates list at least 157, 26, 45, 25 and 67 tests. The + Check: the merged crates list at least 170, 26, 45, 26 and 67 tests. The blockring and transport controls red, and every loader and kernel clippy shape is green. `cargo tree` in `bootloader/` names no package it did not name before, except `toyos-boot`. 5. **Userland code goes home.** `toyos-mixer` moves into soundd and - `toyos-desktop` into the compositor. `toyos-dns`, `-mdns`, `-net-wire` and - `-net-tcp` become netd's library, `filepicker-api` filepicker's library, and - `toyos-inspect` inspect's library. `toyos-libc-copies` moves to - `tests/libc-arch/`. Userland's host tests are built at opt-level 2, - since soundd's tests with the mixer take 34.31 s at 0 and 3.07 s at 2. - Check: soundd lists 55 more tests, the compositor 95, netd 709 and inspect + `toyos-desktop` into the compositor. `toyos-dns`, `-mdns`, `-net-wire`, + `-net-tcp`, `-net-ip`, `-net-udp` and `toyos-dhcp` become netd's library, + `filepicker-api` filepicker's library, and `toyos-inspect` inspect's + library. `toyos-libc-copies` moves to `tests/libc-arch/`. Userland's host + tests are built at opt-level 2, since soundd's tests with the mixer take + 34.31 s at 0 and 3.07 s at 2. + Check: soundd lists 55 more tests, the compositor 95, netd 1105 and inspect 21, and `the_corpus_is_reproduced_bit_for_bit` is listed under soundd. `--build-only` builds the guest tests against netd's and inspect's libraries. From dc45e237c6f208bb2203798185959be1d7ff13e0 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 14:18:54 +0200 Subject: [PATCH 18/24] #646: the LLVM-bytes defect gets a host exit, is told apart, and leaves M4 Answers round 2 of #646's review (865239cd6). - Told apart from the two-checkouts defect. Each file now says what it holds fixed and names the other. The two-checkouts defect holds the LLVM fixed and varies rustc's inputs. This one varies the LLVM's build. Every checkout on a host links the one LLVM its key names, so the two-checkouts gate cannot see an origin, and a comparison of two LLVM installs cannot see a panic path. - A host exit. LLVM's own `GenerateVersionFromVCS.cmake`, at the primary's `src/llvm-project` 52ed14fcd56a, ran with `cmake -DNAMES=LLVM -DLLVM_SOURCE_DIR=/llvm -DHEADER_FILE= -P