Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ToyOS is a general-purpose operating system written from scratch in Rust. Its ch
- **Rust is first-class.** Prefer compile-time safety to runtime checks, and runtime checks to tests. Validate external representations once before constructing typed values; validate each later message at its own boundary. Nothing a type makes impossible needs a runtime test. A kernel object type and an application's protocol type are separate contracts.
- **No silent debt.** Remove a discovered compromise or record its evidence, owner and exit in `issues/`. Filing it does not make it acceptable or complete. Never weaken audible or visual quality without the owner's explicit agreement.

Never use a fixed delay where an event can be awaited. A wait has a failure timeout. A hardware-mandated delay with no notification is the exception, with the requirement cited at the operation.
Never use a fixed delay where an event can be awaited. A wait has a failure timeout; on expiry it returns an error where its interface can carry one and otherwise reports the expiry, except a wait for swapped memory, which behaves like RAM and is reported. A hardware-mandated delay with no notification is the exception, with the requirement cited at the operation.

## Architecture and sources of truth

Expand Down
3 changes: 3 additions & 0 deletions docs/src/architecture/boot.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@

- The disk carries two slots, each a boot volume and a ROOT, named by a slot
table. An update is written to the slot the machine is not running.
- Updates fix forward. The loader boots the slot marked to boot next and no
other; a slot that is refused or fails is reported, never replaced by the
previous one automatically. The remedy is a newer image.
- Updating is an ordinary program: a signed image on standard input, written to
the idle slot and marked to boot next. The signature is its whole authority,
and how the bytes arrived does not matter.
Expand Down
4 changes: 3 additions & 1 deletion docs/src/architecture/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ prefix. An empty view names nothing.
- **Sharing is granted, never reached.** A file outside a program's view
reaches it only through the file picker, which hands over that one file.
- **No special cases.** The boot partition is in the updater's view and nobody
else's; login data is in the login authority's view alone.
else's; login data is in the login authority's view alone; `/apps` is
writable only in the installer service's view, and every other program sees
it read-only.

The working directory is the client's: a spawn states the child's starting
directory, which the child resolves in its own view.
Expand Down
4 changes: 2 additions & 2 deletions docs/src/architecture/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ preallocated on internal storage, with keys that live for one boot.

- Commitment counts usable RAM plus reserved swap, never hoped-for compression
or growth. Swap adds capacity, not responsiveness.
- Swapped memory may make a load, a store or a copying system call wait on
storage. The wait advances with storage progress and is reported when
- Swapped memory behaves like RAM: it may make a load, a store or a copying
system call wait on storage. The wait advances with storage progress and is reported when
overdue; a timeout never turns it into an error, and a non-blocking
descriptor does not cover the caller's own buffer.
- A page is lost only when a running memory service establishes the loss: a
Expand Down
5 changes: 3 additions & 2 deletions docs/src/architecture/scheduling.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
- CPU time is shared by jobs, not threads. A process's and its helpers'
threads share one job, so helpers buy no extra share; authorised launchers
create independent jobs.
- Nothing reserves CPU time: no program is promised a budget, and nothing is
admitted against one.
- Every job has equal weight, across users too. Window focus changes
responsiveness, not sustained entitlement.
- Each CPU runs EEVDF over the job groups it holds, and again among each
Expand All @@ -26,8 +28,7 @@
- Each CPU has an aggregate real-time allowance, replenished each interval,
that protects ordinary progress. Once it is exhausted, that CPU's real-time
workers run only when no ordinary work wants the CPU, until it refills.
- There is no per-stream reservation and no deadline admission. New streams
are accepted best-effort; genuine overload may cause dropouts, and a late
- New streams are accepted best-effort; genuine overload may cause dropouts, and a late
client contributes silence without holding back the others.

## Power
Expand Down
6 changes: 4 additions & 2 deletions docs/src/architecture/sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
to ask for a session. The session's view is the user's home, the system
read-only and the session's own temporary directory.
- A program in one user's session cannot name another user's files.
- The local desktop is a login session. SSH authentication starts the user's
session, whose shell inherits no host key and no SSH service authority.
- The local desktop is a login session like any other: the local greeter asks
for it, and it has the same per-session view and isolation. SSH
authentication starts the user's session, whose shell inherits no host key
and no SSH service authority.

## Ending a session

Expand Down
6 changes: 3 additions & 3 deletions docs/src/architecture/trust.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ DMA stays behind the IOMMU.

- The same driver runs with and without an IOMMU; there is never a second
driver.
- Where the hardware genuinely has no isolation, a driver the signed image
ships may claim a device and no other may, and the machine states plainly
that it runs without device isolation.
- Where the hardware genuinely has no isolation, any driver the signed image
ships may claim its device with its ordinary claim, and no other driver may.
The machine states plainly that it runs without device isolation.
- External DMA-capable devices always need isolation.
- Unknown isolation, unsupported isolation hardware and a failed setup are
errors, not evidence that isolation is absent. There is no machine-specific
Expand Down
5 changes: 3 additions & 2 deletions docs/src/product/privacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ These are the user-visible rules. The mechanisms behind them are

## Permissions

- Programs request resources at run time, and the answer, approval or denial,
can be remembered.
- Programs ask for a right at run time, when they need it, and the answer,
approval or denial, can be remembered. There is no up-front permission
list.
- Choosing a file grants that file, not its folder. Remembered access does not
follow renames. Writable access to a document includes replacing it safely,
scoped to that file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ kernel arms the chipset's watchdog only on the `watchdog` parameter
which
`issues/every-job-test-runner-starts-holds-its-whole-system-capability.md`
fences.
- `watchdogd` feeds from a thread under `rt`, until
`issues/cpu-time-is-a-band-and-not-a-reservation.md` gives it a
reservation.
- `watchdogd` feeds from a thread under `rt`.
- **Every guest runs with `-action watchdog=none`.** q35's TCO counts
`QEMU_CLOCK_VIRTUAL`, which a loaded host advances while it starves a guest,
and its second expiry does what `-action watchdog=` says (QEMU 11.1.1,
Expand Down
6 changes: 5 additions & 1 deletion issues/a-machine-without-an-iommu-refuses-every-claim.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ opened: 2026-09-29
# A machine without an IOMMU refuses every claim; signed in-image drivers should claim there

Owner ruling, 2026-09-29: on a machine with no IOMMU unit a driver signed and
shipped in the ToyOS image may claim a device, and no other may.
shipped in the ToyOS image may claim a device, and no other may. On
2026-11-05, answering #1191's open decisions, the owner confirmed it is
image-wide: any driver the signed image ships claims its device there with its
ordinary claim, and no `system.toml` row grants DMA without an IOMMU per
program.
It is the same driver code as on a machine with a unit, never a second
driver: the kernel's DMA layer hands it a physical address where there is no
unit and a domain address where there is. A claim's space is an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ the binary in a ToyOS guest is this track's harness's job, not gbae's.
stay for crates, `rust/` and LLVM. Recipes and patches live in this
repository, where `NOTICE` and the licence gate carry them, not in a ports
repository.
- **The installer is an ordinary program**, `/system/bin/pkg`, with no
authority a shell does not have: it fetches, verifies, unpacks and writes
under `/apps` because `/apps` is writable to it, and it asks the user
- **The installer** is `/system/bin/pkg`. Who may write `/apps` (owner
ruling, 2026-11-05) is staged in stage 2 of
`issues/every-program-sees-only-the-files-it-was-given.md`. `pkg`
fetches, verifies, unpacks and commits, and it asks the user
before it installs — at install, the moment the user typed the command,
never at first run, so a refusal leaves nothing on disk and no answer has
to be stored. `pkg install <name>`, `pkg install <file>`, `pkg remove
Expand Down Expand Up @@ -97,19 +98,13 @@ The storage track's users and mount-protocol stages do not block this one.
the session user's home, `/home/toy/Apps/<name>`, which is its `HOME` and
the one part of the home it sees; its own `/apps/<name>` is read-only to
it (`toyos_manifest::Program::view`).
6. **An app's rights are its request ∩ the user's grant ∩ the image's
ceiling** (owner ruling, 2026-09-24; the ceiling's shape, 2026-09-26). The
package's manifest *requests* rights; the user *grants* them per user
(from a user action — the file picker hands over one file — or asked on
first use for the risky ones, remembered and revocable; harmless ones such
as a window, sound and the app's own data directory are free); the image
holds the *ceiling*. **The ceiling names rights, never apps**: it is the
set any installed app may be granted at all, so installing an app never
edits the image and the image never lists an app by name. The `[apps]`
row of stage 1 becomes that ceiling. **Exit**: an app installed by `pkg`
from a release runs with exactly the intersection, a request beyond the
ceiling is refused by name, and no file under `/apps` can widen what it
gets.
6. **App rights** (owner ruling, 2026-11-05, superseding the 2026-09-24
up-front request; the ceiling's shape, 2026-09-26). Built: the run-time
request path from an app to the user's grant, and the `[apps]` row of
stage 1 becoming the image's ceiling. **Exit**: an app installed by `pkg`
from a release starts holding only the free rights, a run-time request
beyond the ceiling is refused by name without asking the user, and no file
under `/apps` can widen what it gets.
7. **The apps leave this repository.** Each app (snake first, as the pilot:
it builds unchanged for every OS) moves to its own repository, built with
only the published SDK crates and the released toolchain, and published as
Expand Down
6 changes: 1 addition & 5 deletions issues/all-cores-are-assumed-equal-and-arm64-breaks-that.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,4 @@ task character (bursty/latency-sensitive work on big cores, throughput work on
efficient ones); and eventually an energy model deciding when saving power beats
finishing sooner (the EAS school on ARM).

What is already done so this does not have to be retrofitted: reservation
admission sums against **each CPU's own capacity** — a reservation is a fraction
of a specific CPU, and "all cores equal" is never baked into the arithmetic.
That one decision is the cheap half; this issue is the expensive half, and it
waits for ARM64.
It waits for ARM64.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The floor's need ends at stage 3 of
`issues/the-loader-does-only-what-must-precede-the-handover.md`, which
deletes `floor::Scope` and names every floor for its key alone, and the
attempts record's at its stage 5, which replaces that record with the slot
table's tries. The black box's does not: stage 9 moves its check into the
table's good flag. The black box's does not: stage 9 moves its check into the
kernel, still keyed on the log GUID.

**Owner**: `issues/toyos-builds-itself.md`, whose M4 exits on a guest build
Expand Down
2 changes: 1 addition & 1 deletion issues/an-installed-gbae-browses-to-no-rom.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Evidence, in a QEMU guest on `tests/images/proctreecase.toml`, a package launche
**Exit**: an installed gbae, started from the desktop, loads a ROM the user
picked from outside its own folder. The designed answer is the file picker,
which hands an app the one file the user chose: the package track's stage 6
(an app's rights are its request, the user's grant and the image's ceiling),
(an app asks for a right at run time, within the image's ceiling),
and the isolation track's "Sharing is granted, never reached"
(`issues/every-program-sees-only-the-files-it-was-given.md`). Until then gbae
plays only a ROM placed in its own folder and given on its command line.
110 changes: 15 additions & 95 deletions issues/cpu-time-is-a-band-and-not-a-reservation.md
Original file line number Diff line number Diff line change
@@ -1,102 +1,22 @@
---
status: open
kind: track
status: none
kind: rejected
opened: 2026-08-14
---

# CPU time is a band, not a reservation, so nothing anyone is promised is priced
# CPU time is a band, not a reservation: reservations are rejected

Real-time work outranks normal work by right, fairness is a share of whatever is
left, and no entity is promised anything a number can check. Measured
consequence: **93.3 ms of audio starvation behind a fair storm**, in a 24-period
gap of 70 ms. Nothing below is built.
**Declined by the owner on 5 November 2026**, answering the open decisions of
#1191: ToyOS's scheduler is fair scheduling, EEVDF by job, with a per-CPU
real-time allowance. CPU time is not reserved.

**The commitment: CPU time is a reservation.** Every entity holds a budget and a
period, admission refuses a set that does not fit, and no runnable entity is
served below what admission priced. One invariant replaces four pairwise
derivations that each held for one workload shape. **There is no band whose
precedence is unbounded in either direction** — both absolutes were tried and
both starve, and neither is expressible under reservations.
The declined proposal made every entity hold a budget and a period: a per-CPU
ledger with admission arithmetic, real-time clients as earliest-deadline
reservation clients, a dying server per CPU, a reserved urgency mark,
reservation rows in the system manifest and a service invariant gated against
them. Its full text is in this file's history.

Exactly three entity kinds and no fourth: a real-time client, one fair class per
CPU, one dying server per CPU. A killed thread unwinding is the dying server's
work — not a right-holder and not a band. Interrupt and scheduling-pass time is
charged to a system reserve, to no entity.

## The chunks, and the invariant each must preserve

- **The reservation type, the per-CPU ledger, the admission arithmetic.**
Admission is integer, checked, rounds against the holder, and runs per CPU
against that CPU's own ledger, so overcommit cannot be admitted by a wrapped
multiply.
- **The dying server as an entity**, with its queue, demotion, replenishment,
timer discipline and preemption predicate. However many corpses a CPU holds,
they are one entity with one reservation, so no count of them can close the
rotation on anything. *Smaller than it was staged as: the corpse-aging cluster
it was to dismantle has already left the tree.*
- **Real-time clients become reservation clients.** Dispatch is earliest
deadline among entities holding budget, under a total tie-break, so a replay
of the same choices dispatches the same entity and no band outranks another by
right.
- **The urgency mark.** Precedence is bounded, totally ordered, and charged to
the marked thread's own class, so no party's service depends on another
party's behaviour. *Hard prerequisite: the simulator has no resource-holding
op, so the waiter/holder trigger cannot be staged at all until one exists.*
- **The manifest row, the parser refusals, init's endowment check, the build
gate.** A reservation is refused where it is created, by name, with the
arithmetic in the message, and never observed later as a latency.
- **The service invariant and its scenario matrix.** The law and the instrument
are one predicate, compared cumulatively rather than per period, and the
fraction of periods actually compared is gated alongside the verdict.
- **The kill path's counters, its report, and its one fixed-hop tripwire.** The
kill path asserts only over what its own CPU can observe and no workload can
scale; everything composed is reported.
- **Measurement: the audio gate, soundd's budget, the system reserve.** A budget
is twice the worst measured spend, and a measurement that will not fit is
escalated to the owner, never absorbed by moving the ceiling, the floor or the
reserve.

## Decisions already made, and the numbers behind them

- Capacity 1000 permille, system reserve 100 (**provisional and unmeasured** —
everything resting on it moves when it is measured), admissible 900. Fair
class 500 (5 ms per 10 ms), dying server 100 (1 ms per 10 ms), real-time
ceiling 300. soundd is 580 µs per 2,902,494 ns = **200 permille**; 800
admitted, 100 slack.
- **Exhaustion demotes into a work-conserving background tier** — never a
throttle, never a silent extension. A reservation is a floor and never a cap.
- Replenishment happens only at a period boundary, on a grid anchored at
admission that nothing moves. A waking entity may spend at most its
utilisation times the time to its deadline. An overrun is charged back in full
at the next boundary, which is why both coordinates are floored at the
delivery granularity — 200,000 ns, which is 34.5 % of soundd's budget.
- **One urgency mechanism**: a mark, ordered ahead of unmarked threads inside
its own class only, spending its own class's budget, totally ordered by set
time, capped at half the class's budget, ending at 1 ms of running time. **No
budget moves anywhere**, so a mark inside one process buys that process
nothing. **Rejected, each falsified by an ordinary workload:** the lent band,
the wake grant, the budget donation, and every derived bound on a blocked
wait.
- The kill path asserts nothing of its own beyond one fixed-hop tripwire over
the kick, its delivery and the drain. **Rejected:** an arm-time queue-depth
snapshot (blind to in-flight siblings), a queue-occupancy assertion, and a
progress marker — the last measured at **14.4 ms between two legal bumps** on
a host port of the kernel's own allocator and ticket lock.
- Every sleep lock in the fixed set declares a hold bound, and a lock that
declares none may not be taken where a real-time client can block on it. The
filesystem lock can honestly declare nothing shorter than the 2 s device
timeout, **so no real-time path may take it** — true of soundd today. **The
process lock is the owner's open question**: soundd takes it every period, and
a holder inside a sync parks under the whole device chain.
- Governance, and it is the point: four concepts and no fifth, three layers and
two seams. **Every future feature enters as data or as policy, never as a new
mechanism.** Heterogeneous capacity, topology, EEVDF, hierarchical shares and
DVFS each cost one sentence and nothing until implemented.
- The manifest's period truncation is 0.11 ppm — one full period of slip per
7.07 hours, against 21,638× headroom. 11 corpses is where the old rotation
closed on itself.

**Two rules the current scheduler states as law that this design abolishes**, so
whoever lands it changes both deliberately: a ready real-time task always
preempting the normal band, and a real-time writer lending its band to a blocked
reader. Both are true of the tree today.
The scheduler's stages are `issues/the-scheduler-and-the-clocks-never-talk.md`
(owner decisions J1 and J2). The reservation model was to replace the dying
corpses' per-corpse deferral; that weakness is
`issues/k-dying-corpses-take-k-deferral-grants-on-one-cpu.md`.
Loading