pub trait PlanGate: Send + Sync {
// Required methods
fn verdict(&self, namespace: &str, action: PlanAction) -> PlanVerdict;
fn pack_quota(&self, namespace: &str) -> PlanVerdict;
fn name(&self) -> &'static str;
// Provided methods
fn pack_ceiling(&self, _namespace: &str) -> Option<PackCeiling> { ... }
fn lfs_allowance(&self, _namespace: &str) -> Option<Allowance> { ... }
fn applies_to_machine(&self) -> bool { ... }
}Expand description
One link of the front door’s chain: “may this namespace do this, right now”.
Nanoseconds, no I/O, no lock held across I/O. verdict is on the path
of every push and every anonymous clone; an implementation that reached a
network or a disk here would put a payment provider’s uptime in front of
git push, which is the outage the metered plan exists to forbid.
pack_quota may read the disk (it is the namespace’s byte count, or the
filesystem’s free space), and is asked only for a write that grows data.
Required Methods§
Sourcefn verdict(&self, namespace: &str, action: PlanAction) -> PlanVerdict
fn verdict(&self, namespace: &str, action: PlanAction) -> PlanVerdict
The ladder’s answer for namespace and action. An unknown namespace
is Free, which is Allow.
Sourcefn pack_quota(&self, namespace: &str) -> PlanVerdict
fn pack_quota(&self, namespace: &str) -> PlanVerdict
Is the namespace already at its caps["pack_bytes"]? Refuse
names the cap, what is held and the URL; Allow for a namespace with
no cap or room under it. Asked before a single pack byte is read, so a
tenant at the ceiling learns it from the first line and not after the
upload.
Provided Methods§
Sourcefn pack_ceiling(&self, _namespace: &str) -> Option<PackCeiling>
fn pack_ceiling(&self, _namespace: &str) -> Option<PackCeiling>
How many packfile bytes may this namespace still receive, right
now? None — the default, and every gate’s answer until one has a
reason — means no ceiling, and the pack is read to its end whatever its
size.
§The hole this closes, and why pack_quota could not
A pack header carries an object count, not a byte count, so the
size of a push is unknown at the moment it is authorised.
PlanGate::pack_quota therefore answers about bytes ALREADY held and
gunnar_policy::gates::limits answers about a pack ALREADY received —
and a tenant one byte under a 10 GiB cap could send 400 GiB between
them, because nothing in the receive loop was counting. This is the
number that loop counts against; gunnar-wire cuts the stream past it
and tells the client PackCeiling::refusal.
Asked at the same moment pack_quota is — a write that grows the
tenant’s data, after the grant check — and the smallest answer in
the chain wins, which is the byte-wise spelling of the chain’s own
first-Refuse-wins rule.
Cheap for the same reason pack_quota is allowed to be dear: it is
asked once per push, not once per byte.
Sourcefn lfs_allowance(&self, _namespace: &str) -> Option<Allowance>
fn lfs_allowance(&self, _namespace: &str) -> Option<Allowance>
How many LFS bytes may this namespace still receive? The same
question PlanGate::pack_ceiling asks about a pack, one wire over, and
None means the same thing: no cap, take what arrives.
§Why the pack ceiling could not cover this
A pack byte and an LFS byte land on the same filesystem and are billed
at the same rate, but they are different METERS — Usage.Read reports
pack_bytes and lfs_bytes separately and a signed fact caps them
separately — and they arrive down different code paths.
gunnar_wire::receive_pack counts pack bytes; the LFS session never sees
that loop. Until this existed, pack_ceiling was computed for an LFS
upload (the chain in Dispatch::ask_plan has always asked for one) and
then dropped on the floor, so a tenant at their git ceiling could put
8 GiB at a time into LFS for as long as the operator’s disk held out.
§No slack, unlike the pack ceiling
pack_ceiling is generous because a pack’s wire bytes and its stored
bytes are different numbers nobody knows in advance. An LFS object is
stored raw and verified against a size declared up front, so the two are
the same number and the answer here is exact. See
gunnar_lfs::Allowance.
Sourcefn applies_to_machine(&self) -> bool
fn applies_to_machine(&self) -> bool
Does this gate also bind the primary’s replication push? A
Machine principal is how a primary authenticates to its standby, and
by default a gate is NOT asked about it: replication is the primary’s
copy arriving, not a tenant growing their data, and a plan that refused
it would make a lapsed tenant’s standby diverge from the primary. The
disk gate answers true: a full standby must say so before the bytes,
the same as a full primary — there is no plan to exempt a disk from.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".