Skip to main content

Module capacity

Module capacity 

Source
Expand description

Admin.Capacity: “if a tenant’s cap rose to N bytes, could you serve it?” — the question monetize must ask before it buys a disk. Answers with a verdict measured off the SAME statvfs the push door refuses under, and, independently, whether this data set can ever grow at all: the appliance is SEALED (an md set laid once at install, no runtime growth path, PID 1 with no shell), so a disk bought for it is billed and invisible. “If a tenant’s cap rose to N bytes, could you serve it?” — Admin.Capacity.

The question a seller must ask before the money moves. monetize grows an “increase my quota” button: a tenant on 10 GB asks for 100 GB and something upstream is about to buy 90 GB from a cloud vendor and charge for it. gunnar is the only component that knows whether those bytes can ever reach a repository, and on the appliance the honest answer is frequently no.

§Two facts, not one number

A single free-bytes answer is the dangerous one: a caller reads a small number as “buy another disk”, and on the appliance that is money converted into nothing. So answer returns a verdict — can this request be served out of what exists right now — and, independently, a growth — can this data set ever hold more than it does, and by whose hand. growth is reported on the yes arm as well, because “yes, and this is the last capacity this box will ever have” is a different sale from “yes, and there is more where that came from”.

§When the appliance is SEALED — and when it is NOT

SEALED was once the only answer, on three facts:

  • /data is an md set laid once by korp-installer’s lay_set (edda/crates/korp-installer/src/install.rs), which runs only on InstallMode::Fresh.
  • gunnar had no data-set growth of any kind.
  • gunnar is PID 1 with no shell, so nothing on the machine can xfs_growfs a newly attached disk either.

The second is no longer true, and decide_growth must not say it is. korp-start carries the boot-time grow engine (korp_installer::grow, DATA-SET-GROWTH-DESIGN.md) and, under the grow-runtime feature, THIS server links the same engine’s runtime half (korp_installer::grow::runtime, driven by crate::grow). So the verdict is a ladder over MEASURED facts — the journal on the box (GrowthFacts::grow), this build’s engine (GrowthFacts::runtime_engine) and the kernel’s PCI hot-plug (GrowthFacts::hotplug) — and SEALED is only its bottom rung, reached when this box keeps no grow journal. Nothing here may claim “no such path in any build”: that is a statement about the binary, and the binary is measured one field away.

On that bottom rung a disk bought and attached to the box is billed and invisible. That is what SetGrowth::GrowthSealed says, in those words, in the reason line — so a caller that renders nothing but the sentence still cannot mistake it for “not enough space right now”.

§Measured, never declared, and measured ONCE (one implementation)

The bytes are gunnar_ssh::DiskHeadroomGate::headroom — the same statvfs the push door refuses under, so the capacity a seller is quoted and the floor a push dies at cannot disagree. The set’s shape is crate::dashboard::probe::disk_kind — the same /proc/mounts + md sysfs read the console’s header band prints 4×75G raid0 xfs from. This module opens no third spelling of either question; it decides over their output.

answer is pure over Question precisely so the SEALED arm can be driven red on a developer box, where gunnar is never PID 1 and /data is never an md set.

§The box: cores and RAM, measured (product scope)

Disk is a tenant’s: a cap is bytes in one namespace. Cores and RAM are the box’s: no tenant owns a core, so the ceiling a seller may sell either up to is whatever this machine has — and that must be read off the machine, not declared in a config that was true of the plan somebody ordered. MachineFacts reads them the way the rest of the server already does (one implementation): available_parallelism is what crate::budget::Machine sizes the thread pools from, /proc/meminfo is crate::dashboard::probe::meminfo behind the header band’s ram cell, the cgroup clamp is [crate::budget::cgroup_memory]’s, and the load is the band’s load1.

Every box field on the wire is optional, and absent means unmeasured — never 0. A 0 is a number a caller will divide by, sell against, or read as “this box has no RAM”; an absence with machine_reason naming the OS error is the same UNMEASURED discipline the disk half has.

Structs§

GrowthFacts
What the machine says about the set under the data root. Two measured facts and nothing inferred; decide_growth turns them into a verdict.
MachineFacts
What the box has: cores, RAM, and how busy it is. Product-scope facts, each either measured or carrying the reason it was not; machine_fields turns them into the wire’s optional fields and the machine_reason.
Question
Everything answer decides from. Built by crate::control_plane::CoreControlPlane out of the disk gate and the namespace’s meter; built by hand in the tests, which is the point.

Functions§

answer
The answer. Pure: every field below is arithmetic over Question.
decide_growth
The growth verdict and the clause the reason line carries for it.
disk_fields
The wire’s disk_* fields out of one crate::disktest::Outcome, and nothing else set — the same shape and the same contract as machine_fields, so both arms of answer fill them the same way.
disks_to_wire
The snapshot, as the wire carries it. Pure, so a recorded snapshot can be checked field by field: an unmeasured mount keeps measured = false and its reason, and its numbers stay 0 rather than being invented.
machine_fields
The wire’s box fields out of MachineFacts — and nothing else set, so a caller builds a response as CapacityResponse { ..., ..machine_fields(&m) } and both arms of answer fill these the same way (one implementation).
measure_disks
Every disk on the box, for Admin.Capacity { disks: true } (2026-09-28).
serialdat_breakdown
What is on /serialdat, thing by thing — the known paths gunnar keeps there, each measured as the blocks it holds (a directory summed), plus other: the mount’s used bytes no known path accounts for.