An all-tracks docs-vs-code audit (the same method that caught the storage drift) found 23 confirmed inaccuracies where a doc's build-status claim no longer matches the source — status markers that were never flipped after a track landed, and a few paths left over from completed flag-days. All verified against the code before editing; docs only, no behavior change. The systemic ones: - IOMMU enforcement (driver-model.md, drivers.md): docs said enforcement was not built and "device_claim = ring 0" / "memory-safe is not true yet". It is built (per-device VT-d/AMD-Vi domains programmed at device_claim, -ECONFINE rollback, dma_alloc buffers bound and torn down at death; fail-open only with no IOMMU). Restated; M16 marker flipped to done. - The FHS flag-day paths: /etc/devices.csv -> /system/configuration/devices.csv (devices-csv.md, new-driver-checklist.md, device-manager.md), /var/log -> /system/logs (logging.md, new-driver-checklist.md), /mnt/usb -> /volumes/usb (process-management.md). Following the old paths silently breaks driver match. - protocol-namespace P4 "remaining" -> landed (only P5 remains); shared-fate fan-out "not yet enforced" -> enforced; wall_clock "not built" -> built; SMP affinity + fault-recovery "left" -> built; process_enumerate raw-pointer trust model -> checked copyToUser/EFAULT; bounds.md maximum_devices static hole -> dynamic per-registrar quota; init spawns fat -> volume-manager; config "hardcoded, move to /etc" -> already CSV data files; vdso.md three- value call; zig-self-hosting library/ layout; python argv "new" -> built. Found and fixed by a multi-agent audit across 12 doc clusters, each finding adversarially verified against the source.
146 lines
7.3 KiB
Markdown
146 lines
7.3 KiB
Markdown
# Bounds: how a ceiling is declared
|
|
|
|
*Design, 2026-08-08. Follows [fixed-bounds-audit.md](../fixed-bounds-audit.md), which
|
|
found 235 compile-time ceilings in this tree: 139 on quantities we do not choose, 5
|
|
recorded anywhere with their reasoning, and 171 that pass in silence when reached.*
|
|
|
|
A bound is a number chosen at compile time that decides how much of something the code
|
|
can hold. `const maximum_devices = 64`. `var below: [64]Range`. `var blob: [512]u8`.
|
|
Different units — devices, firmware memory-map entries, bytes of a USB descriptor — but
|
|
one shape, and one recurring way of going wrong.
|
|
|
|
## Where a bound lives
|
|
|
|
**Where the thing it bounds lives.** A driver's transfer-ring size belongs to that
|
|
driver; a protocol's payload cap belongs to that protocol; the kernel's task-table size
|
|
belongs to the kernel. There is no central list and this document does not propose one.
|
|
|
|
`system/parameters.zig` is not a counter-example. It is kernel-only, and it exists for a
|
|
specific historical reason: tunables had accumulated inside the loader↔kernel handoff
|
|
contract, and splitting them out kept that contract to what it actually is. It is a
|
|
tidying of one file's contents, not a registry the rest of the system reports to.
|
|
|
|
This matters for the mechanism below. An earlier draft had every bound declared through
|
|
a shared `bounds` module — which would have meant adding a dependency to roughly eight
|
|
package manifests, including `library/protocol`, which deliberately depends on nothing.
|
|
That is a coupling the problem does not require: a bound is a local fact about local
|
|
storage, and the only thing worth sharing is the *shape of the statement*, not a module.
|
|
|
|
## The declaration
|
|
|
|
A structured doc comment, immediately above the declaration, in the file that owns it:
|
|
|
|
```zig
|
|
/// bound: logical CPUs the kernel tracks
|
|
/// decided-by: hardware
|
|
/// protects: the per-CPU bookkeeping arrays, which are sized at compile time
|
|
/// at-limit: degrade — surplus cores are left parked, never brought online
|
|
/// observed-by: platform.cpusDropped() -> the WARNING at kernel.zig:281
|
|
pub const maximum_cpus = 128;
|
|
```
|
|
|
|
Five fields, all mandatory:
|
|
|
|
| Field | Answers |
|
|
|---|---|
|
|
| `bound` | what is counted, in plain words |
|
|
| `decided-by` | `hardware`, `external`, or `ours` — who chooses how large it gets |
|
|
| `protects` | what this ceiling defends against |
|
|
| `at-limit` | `refuse` / `degrade` / `truncate` / `grow`, and the detail |
|
|
| `observed-by` | how an operator finds out it was reached |
|
|
|
|
`decided-by` is the classification the audit turned on. `hardware` means the machine
|
|
chooses — PCI functions, CPUs, ACPI rows, memory-map entries. `external` means a file,
|
|
disk structure or peer chooses. `ours` means we do: a stack size, a tick rate, our own
|
|
protocol's payload. A fixed bound on the first two is a defect rather than a tunable.
|
|
|
|
## What the build step enforces
|
|
|
|
A step in `build.zig` reads the tree and fails on:
|
|
|
|
1. **A bound with no declaration.** A fixed-size array or a `maximum_*`/`max_*` constant
|
|
with no `bound:` block above it. The 235 that exist today are allowlisted by
|
|
file+line+name, so only newly written ones are gated — the rule can land without a
|
|
235-site sweep in front of it.
|
|
2. **A missing field.** All five or it fails. This alone is the 97 bounds with no
|
|
comment at all and the 171 with no observability.
|
|
3. **An unspeakable `at-limit`.** The vocabulary is closed. There is no `silent`, no
|
|
`drop`, and nothing meaning *allow*. `truncate` is legal only with a marker the
|
|
reader can see — `klog_maximum_message` qualifies because the record carries
|
|
`klog_flag_truncated`; the USB configuration descriptor cut at 512 bytes does not,
|
|
because nothing records that anything was lost.
|
|
4. **A stale allowlist entry.** If a listed bound is fixed or deleted, its entry goes
|
|
too, so the list can only shrink.
|
|
|
|
Because this is text and not a Zig type, it also covers `boot/`, which imports almost
|
|
nothing, and `tools/*.py`, where the audit found bounds as well. One mechanism, whole
|
|
tree, no new dependency edges.
|
|
|
|
## Coupled bounds
|
|
|
|
Two numbers that must agree, agreeing in code rather than in a comment — no module
|
|
needed, just a `comptime` block where one of them lives:
|
|
|
|
```zig
|
|
comptime {
|
|
if (maximum_domains != devices_broker.maximum_devices)
|
|
@compileError("iommu.confined is indexed by device id; an id past its end is " ++
|
|
"left unconfined while confineDevice still reports success");
|
|
}
|
|
```
|
|
|
|
`maximum_domains = 64` and `maximum_devices = 64` once agreed only by a sentence in a
|
|
comment, and that agreement failed open — the clause with the live hole behind it, where
|
|
raising `maximum_devices` alone left every device id past the end of `iommu.confined`
|
|
unconfined while `confineDevice` still reported success, a privilege escalation rather
|
|
than a fix. The assert closed that: it held the two together while both stayed fixed, and
|
|
when the device table was later made dynamic — no `maximum_devices` any more, only a
|
|
per-registrar quota — that forced them apart, the assert having done its job. `confined`
|
|
now grows to cover every id the broker mints, and `confineDevice` refuses when it cannot
|
|
record a confinement rather than failing open.
|
|
|
|
## The worked bad case
|
|
|
|
`devices_broker.maximum_devices`, which had no comment at all, before it was made dynamic:
|
|
|
|
```zig
|
|
/// bound: device nodes for the whole machine — firmware-discovered plus registered
|
|
/// decided-by: hardware
|
|
/// protects: nothing; this is a sizing guess about someone else's computer
|
|
/// at-limit: refuse — ENOSPC from device_register, dropped++ during discovery
|
|
/// observed-by: kernel.zig:203 counts discovery drops only, NOT runtime refusals
|
|
const maximum_devices = 64;
|
|
```
|
|
|
|
Writing it out is the argument. `decided-by: hardware` alongside a `protects` that
|
|
admits there is no threat describes a bound that should not be fixed at all, and
|
|
`observed-by` cannot be filled in honestly. The build step does not reject this — it
|
|
makes it impossible to write down without noticing.
|
|
|
|
## The work-list this produces
|
|
|
|
`decided-by` is machine-readable, so the sweep is a query: every `hardware` or
|
|
`external` bound whose `at-limit` is not `grow`. That is 139 of the 235, and it is the
|
|
order the fixing takes — by class, not by our guesses about which machines get run.
|
|
Reachability is exactly what a new computer changes; the Ryzen's cap was unreachable
|
|
until it wasn't.
|
|
|
|
## What this does not do
|
|
|
|
**It is a statement, not a proof.** Nothing checks that the code does what `at-limit`
|
|
claims. The enforcement is completeness and vocabulary: you cannot leave the question
|
|
unanswered, and you cannot answer it with "silently".
|
|
|
|
**It carries no occupancy.** Knowing `system/configuration/protocol.csv` sits at 50 of
|
|
64 grant rows still needs a counter and somewhere to report it. The declarations make
|
|
that cheap to add later; it is not here.
|
|
|
|
**It resizes nothing.** Declaring `maximum_devices` honestly does not make the Ryzen
|
|
work. It makes the next machine's failure legible, and it names the 139 that need real
|
|
fixes.
|
|
|
|
**Enforcement is at build time, not compile time.** A Zig type could have made a missing
|
|
field a compile error. That version needed the shared module, and the module was not
|
|
worth the coupling — so a missing field is a failed build step instead. In practice both
|
|
mean `zig build` stops; the difference is which stage prints the message.
|