Skip to main content

Module gates

Module gates 

Source
Expand description

The structural-gate registry: the one enumeration of the condition type/status/reason triples that mean “this object is blocked on something only a human can change”.

A structural gate is not a transient retry. It never self-heals: the reconciler parks the object (usually at phase: Pending) and waits for an out-of-band change — a namespace opt-in annotation, a credentials Secret, an acknowledgement timestamp. Because the phase itself stays unremarkable, anything that diagnoses a cluster by phase alone reports all-green while the work is wedged (issue #359: kubectl kopiur doctor passed all checks with a Snapshot stuck on MoverPermitted=False).

The fix is to make the gate set shared by construction. The controller writes conditions from these rows and the CLI’s doctor iterates the same rows, so a gate added on the server side cannot be invisible to the client side: there is exactly one list, in kopiur-api, which both depend on.

This module is pure data + pure functions — no kube::Client, no tokio — per the apicontroller split.

Structs§

StructuralGate
One human-actionable structural gate: a condition type, the status that means BLOCKED, the reason the writer stamps, the CR kinds it appears on, and how loudly to report it.

Enums§

GateScope
Which CR kinds a structural gate’s condition is written on.
GateSeverity
How loudly a tripped gate should be reported.

Constants§

CONDITION_FALSE
The Kubernetes condition status string "False".
CONDITION_TRUE
The Kubernetes condition status string "True". Named so a gate row’s polarity is spelled out rather than being a bare literal at the row.
DELETION_HELD_GATE
Inverted polarity: the per-Snapshot hold the mass-deletion breaker applies. Released only by the allow-mass-deletion acknowledgement on the repository, so it is squarely “needs a human”.
MASS_DELETION_HELD_GATE
The repository-level view of the same breaker: a whole wave is held.
MISSING_CA_BUNDLE_GATE
Same condition as MISSING_CREDENTIALS_GATE, a third missing dependency: the backend’s tls.caBundleRef ConfigMap (the PEM CA bundle for a private-CA S3 endpoint). kopiur never creates it, and a ConfigMap that never appears never self-heals — the exact phase-invisible park shape of #359.
MISSING_CREDENTIALS_GATE
The mover’s credential Secret is not in the workload namespace. Parks at phase: Pending until the user creates it (or enables projection).
MISSING_SERVICE_ACCOUNT_GATE
Same condition as MISSING_CREDENTIALS_GATE, different missing dependency: the workload-identity ServiceAccount the backend names. kopiur never creates it.
POLICY_REPOSITORY_NOT_READY_GATE
A SnapshotPolicy referencing at least one repository that is not Ready. The policy reconciler keeps processing the READY subset (multi-repository fan-out, #368) but defers backups/retention/adoption/verification against the rest, and requeues until every repository recovers. Warn, not Fail: a repository deliberately taken down (a migration, a powered-off NAS target) is a plausible operator choice, the ready subset keeps working, and the per-child parks behind the same outage already carry their own reporting (kopiur_snapshot_gated, KopiurRepositoryNotReady) — so this gate must explain, not independently turn a diagnostic red.
PRIVILEGED_MOVER_GATE
An elevated mover in a namespace that has not opted in. The admin adds the privileged-movers annotation out-of-band; until then the object sits at phase: Pending — the exact shape of #359.
REPOSITORY_READ_ONLY_GATE
A backup refused because its repository is mode: ReadOnly.
RESTORE_REFERENT_MISSING_GATE
A Restore whose repository referent does not exist (issue #393): the explicit spec.repository object, or the source.fromPolicy SnapshotPolicy the repository ref is derived from.
SCHEDULE_BLOCKED_GATE
Version skew, one kind removed: a previous run of this schedule sits at a phase string this build cannot interpret, so it can never be observed to finish. Under the default concurrencyPolicy: Forbid the schedule stops firing FOREVER while looking perfectly healthy — the concurrency gate is doing exactly what it was asked to. The out-of-band change that clears it is finishing the operator rollout (or deleting the wedged Snapshot), which is squarely “needs a human”, so: Fail.
SCHEDULE_FANOUT_CAPPED_GATE
A SnapshotSchedule whose members × repositories cross-product exceeds the fan-out cap: the fired slot was SKIPPED, and every future slot will keep skipping until the selector is narrowed or the policy’s repository list shrunk — no backups run while everything else looks healthy, the exact silent-wedge shape of #359. The writer asserts BOTH polarities each fire pass, so the gate self-clears the moment a slot mints fully. Promoted into the registry by the #368 M10 gates/doctor checklist (previously a controller-internal condition doctor could not see).
SEEDING_GATE
A seeding bootstrap Job is in flight (issue #380): the repository is copying a whole repository across and is legitimately not Ready yet.
SEED_INCOMPLETE_GATE
A migrate-mode seed whose post-verify found snapshots missing. The next attempt resumes the copy, so this converges on its own — but a seed that keeps being cut short needs a human to raise its deadline.
SEED_LEFT_EMPTY_GATE
A seed that was armed and left the repository holding ZERO snapshots. Like SEED_INCOMPLETE_GATE, the next attempt resumes the copy.
SEED_MOVER_TOO_OLD_GATE
The mover-skew guard: the running mover image predates spec.seed, ignored it, and initialized an EMPTY repository. Genuinely terminal and genuinely human-actionable (upgrade the image, delete the empty repository and the terminal bootstrap Job) — the ONE row here that would earn Fail on its own merits. It is Warn because the registry pins one severity per condition+scope and its siblings must not turn a healthy in-progress seed red; nothing is lost, because a repository in this state is also not Ready, which doctor’s repository check fails on independently.
SEED_SOURCE_AUTH_CONFLICT_GATE
A migrate-mode spec.seed whose LOCAL backend and resolved SOURCE repository disagree on workload identity (issue #380).
SEED_SOURCE_EMPTY_GATE
A spec.seed whose source is a real repository holding zero snapshots, with allowEmptySource at its false default. Blocking Ready is the point: a valid-but-empty mirror is nearly always mis-pointed.
SEED_SOURCE_NOT_FOUND_GATE
A spec.seed whose SOURCE answered but holds no kopia repository. Retried automatically every ~2 minutes, so it clears itself the moment the source exists — but until then the repository never becomes Ready, and the fix (repoint spec.seed.from) is squarely out-of-band.
SEED_SOURCE_NOT_READY_GATE
A spec.seed in migrate mode whose SOURCE repository is missing or not Ready (issue #380). The repository parks Pending with Seeded=False and re-checks; nothing in kopiur can bring the source up, so it is squarely “needs a human” — and the park is phase-INVISIBLE in the worst way: a brand-new repository sitting at Pending looks identical to one that is simply still bootstrapping, and it will sit there for as long as the source is down.
SOURCE_PVC_MISSING_GATE
A backup whose DIRECT source PVC (spec.sources[].pvc) does not exist at launch time. The Snapshot parks at phase: Pending on the slow structural cadence and, once the controller’s missing-source deadline passes, flips terminally Failed — a PVC that never reappears never self-heals, and only recreating it (or repointing the policy’s sources) can clear the block. Written on the Snapshot only for the direct-source case: a vanished operator-staged claim (copyMethod Snapshot/Clone) is a restage race and stays a plain transient retry, never this gate.
STRUCTURAL_GATES
Every human-actionable structural gate kopiur’s reconcilers can park an object on.