Skip to main content

PlanGate

Trait PlanGate 

Source
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§

Source

fn verdict(&self, namespace: &str, action: PlanAction) -> PlanVerdict

The ladder’s answer for namespace and action. An unknown namespace is Free, which is Allow.

Source

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.

Source

fn name(&self) -> &'static str

How this gate names itself in Control’s Debug and in the boot log.

Provided Methods§

Source

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.

Source

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.

Source

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".

Implementors§