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 api ↔ controller split.
Structs§
- Structural
Gate - One human-actionable structural gate: a condition
type, thestatusthat means BLOCKED, thereasonthe writer stamps, the CR kinds it appears on, and how loudly to report it.
Enums§
- Gate
Scope - Which CR kinds a structural gate’s condition is written on.
- Gate
Severity - How loudly a tripped gate should be reported.
Constants§
- CONDITION_
FALSE - The Kubernetes condition
statusstring"False". - CONDITION_
TRUE - The Kubernetes condition
statusstring"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-
Snapshothold the mass-deletion breaker applies. Released only by theallow-mass-deletionacknowledgement 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’stls.caBundleRefConfigMap (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
Secretis not in the workload namespace. Parks atphase: Pendinguntil the user creates it (or enables projection). - MISSING_
SERVICE_ ACCOUNT_ GATE - Same condition as
MISSING_CREDENTIALS_GATE, different missing dependency: the workload-identityServiceAccountthe backend names. kopiur never creates it. - POLICY_
REPOSITORY_ NOT_ READY_ GATE - A
SnapshotPolicyreferencing at least one repository that is notReady. 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-moversannotation out-of-band; until then the object sits atphase: Pending— the exact shape of #359. - REPOSITORY_
READ_ ONLY_ GATE - A backup refused because its repository is
mode: ReadOnly. - RESTORE_
REFERENT_ MISSING_ GATE - A
Restorewhose repository referent does not exist (issue #393): the explicitspec.repositoryobject, or thesource.fromPolicySnapshotPolicythe 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: Forbidthe 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 wedgedSnapshot), which is squarely “needs a human”, so: Fail. - SCHEDULE_
FANOUT_ CAPPED_ GATE - A
SnapshotSchedulewhose 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
Readyyet. - 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 earnFailon its own merits. It isWarnbecause 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 notReady, whichdoctor’s repository check fails on independently. - SEED_
SOURCE_ AUTH_ CONFLICT_ GATE - A migrate-mode
spec.seedwhose LOCAL backend and resolved SOURCE repository disagree on workload identity (issue #380). - SEED_
SOURCE_ EMPTY_ GATE - A
spec.seedwhose source is a real repository holding zero snapshots, withallowEmptySourceat itsfalsedefault. BlockingReadyis the point: a valid-but-empty mirror is nearly always mis-pointed. - SEED_
SOURCE_ NOT_ FOUND_ GATE - A
spec.seedwhose 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 becomesReady, and the fix (repointspec.seed.from) is squarely out-of-band. - SEED_
SOURCE_ NOT_ READY_ GATE - A
spec.seedin migrate mode whose SOURCE repository is missing or notReady(issue #380). The repository parksPendingwithSeeded=Falseand 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 atPendinglooks 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. TheSnapshotparks atphase: Pendingon the slow structural cadence and, once the controller’s missing-source deadline passes, flips terminallyFailed— 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.