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.
7.3 KiB
Bounds: how a ceiling is declared
Design, 2026-08-08. Follows 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:
/// 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:
- A bound with no declaration. A fixed-size array or a
maximum_*/max_*constant with nobound: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. - 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.
- An unspeakable
at-limit. The vocabulary is closed. There is nosilent, nodrop, and nothing meaning allow.truncateis legal only with a marker the reader can see —klog_maximum_messagequalifies because the record carriesklog_flag_truncated; the USB configuration descriptor cut at 512 bytes does not, because nothing records that anything was lost. - 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:
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:
/// 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.