From 066e1a0280d1fa05f9c90f940fd4ffccc8a10438 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 09:41:33 +0200 Subject: [PATCH 1/2] 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 a4d1bb91c6a..b4680b297c9 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 c780b0dee34..164d54f64aa 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 0b910305b39..fa9f283de95 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 00000000000..3b0c3a2d7b5 --- /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 ec166f1b45e7a2dbdd80e2d9a3e7207ee628e1c9 Mon Sep 17 00:00:00 2001 From: japabu Date: Thu, 1 Oct 2026 10:11:17 +0200 Subject: [PATCH 2/2] 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 b4680b297c9..4012e6fe943 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 164d54f64aa..d4418ca63fb 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 fa9f283de95..7a2bf0b0da0 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 3b0c3a2d7b5..59429a59c1b 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.