Skip to content

feat: mass-deletion protection (cascade guard, per-repo breaker, batched deletes) - #265

Merged
perfectra1n merged 18 commits into
mainfrom
feat/mass-deletion-protection
Jul 17, 2026
Merged

perfectra1n merged 18 commits into
mainfrom
feat/mass-deletion-protection

Conversation

@perfectra1n

Copy link
Copy Markdown
Collaborator

Why

A user's kro tooling briefly deleted and recreated their SnapshotSchedules. Every scheduled
Snapshot carries an ownerReference to its schedule, so Kubernetes GC cascade-deleted roughly 600
to 700 Snapshot CRs in one burst. Each CR defaulted to deletionPolicy: Delete, and each finalizer
spawned its own mover Job running kopia snapshot delete with no concurrency bound anywhere. The
result was hundreds of pods actively destroying backup history, stopped only because the user
noticed in time. Deleting all of your backups while making the cluster suffer should not be the
default behavior of a backup operator.

There were three uncapped delete surfaces: external cascades (this incident, but also GitOps prunes
and bulk kubectl deletes), the GFS retention prune, and the failed-history prune. Only the
namespace-deletion axis had a guard (onNamespaceDelete, default Orphan).

What

One discriminator plus three guards, all decided in pure, exhaustively matched functions
(ADR-0006 has the full rationale):

  1. pruned-by discriminator. The operator's own prune paths now stamp
    kopiur.home-operations.com/pruned-by: retention|failed-history before deleting, so the finalizer can
    tell Kopiur's lifecycle from an external deletion. Unrecognized values are treated as external
    (fail-safe).

  2. Schedule-deletion cascade guard. New SnapshotSchedule.spec.deletion.onScheduleDelete
    (default Retain), stamped onto every produced Snapshot and propagated on edit. When a Snapshot
    is deleted externally and its owning schedule is gone, terminating, or was replaced (UID
    mismatch), an effective Delete downgrades to a retain: the finalizer releases with zero repo
    contact, a Warning event is emitted, and the catalog rediscovers the kopia snapshots as
    origin: discovered (forced Retain) within the catalog interval. The pre-upgrade fleet is
    protected retroactively because an absent stamp resolves to Retain. Deliberate cascade remains
    available by setting onScheduleDelete: Delete.

  3. Per-repository mass-deletion breaker. New deletionProtection.threshold on both repository
    kinds (default 10, 0 disables). When pending external destructive deletions reach the
    threshold, they hold: no delete work happens, the Snapshot gets a DeletionHeld condition whose
    message contains the exact ack command and the value to copy, and the repository mirrors a
    MassDeletionHeld condition. Release is a timestamp-valued annotation
    (kopiur.home-operations.com/allow-mass-deletion) that only frees deletions requested at or before the
    acked instant, so a stale ack committed to Git is inert against any later wave. Operator prunes
    bypass the breaker by design; retention keeps working during a held wave.

  4. Batched, optionally capped execution. Deletions now aggregate per repository into one mover
    Job (up to 200 manifest ids over a single repo connect, ~10s accumulation window) instead of one
    Job per Snapshot. A lone delete is a batch of one. The old per-CR delete Job is retired (an
    adoption shim honors in-flight legacy Jobs across the upgrade, removal tracked for next
    release). A global concurrency cap exists as an opt-in backstop
    (maxConcurrentDeleteJobs, default uncapped) since batching already collapses the incident
    scenario to about three pods; a small default cap would only add head-of-line blocking.

The counting and firing filters deliberately diverge: breaker counting over-includes (trips
earlier, fail-safe), batch firing under-includes (held, namespace-terminating-orphan-destined,
unpinned-peer, and unsynced-schedule-owned members are excluded and simply retry). An excluded
member requeues; it is never silently dropped.

Bugs found and fixed along the way

The e2e tiers caught three real bugs during development, each now guarded by a regression test:

  • A spawned wait_until_ready() on the controller's own store races the applier's getter
    (kube-rs DelayedInit holds a single waker); the synced flag never flipped and every external
    deletion deferred forever. Fixed by flipping the flag from reconcile, which kube-runtime already
    gates behind the store's init swap.
  • The no-overlap exclusion originally covered only live Jobs, so members of an already-succeeded
    batch could be re-enrolled before their finalizers released, serializing mass drains at scale.
  • The batch fire set originally reused the breaker's inclusive counting filter, letting a routine
    retention prune sweep held members' data into its batch without an ack. The flagship e2e scenario
    (held wave survives a concurrent prune untouched) now pins the fix.

Testing

  • Hermetic: full workspace green; cognitive-complexity ratchet tightened 31 -> 29.
  • e2e (fresh kind clusters): 8 new mass-deletion scenarios including the incident's regression
    guard (schedule delete leaves kopia intact, zero delete Jobs, rediscovery), breaker hold/ack
    drain with real kopia-side count assertions, concurrent-wave disjointness, throttle cap,
    repo-outage retry convergence, and held-wave-vs-prune. Full suite green (known local-only NFS
    failures excepted, unchanged from main).
  • Adversarially reviewed: per-milestone reviews plus a whole-branch review that walked every path
    ending in kopia snapshot delete and every finalizer release that skips deletion.

Upgrade notes

  • Existing Snapshots need no migration: absent stamps resolve to Retain, and startup backfills the
    repository pin for pre-Snapshot deletion remains stuck when its credential-projecting SnapshotPolicy was deleted first #255 CRs so per-repo breaker counting sees them.
  • The breaker is ON by default (threshold 10). Repositories with legitimately more than 10
    concurrent external CR deletions should raise it or set 0.
  • Anything matching on {name}-delete Job names (dashboards, alerts) should switch to the
    snapdel-* batch Jobs and the new kopiur_snapshot_deletions* metrics.
  • Old-operator prune waves in flight during the upgrade auto-reclassify via the terminating-CR
    stamp; a very large wave may need one ack.

Closes the incident class discussed in Discord; supersedes nothing.

…letionProtection, pruned-by)

Lays down M0 of the mass-deletion-protection feature: the api-crate types,
consts, and validator only (later milestones wire controller behavior).

- ScheduleDeletePolicy (common): Retain/Delete cascade policy for a
  SnapshotSchedule's produced Snapshots on schedule deletion.
- SnapshotScheduleSpec.deletion (ScheduleDeletionSpec.onScheduleDelete,
  default Retain) + effective_on_schedule_delete resolver.
- SnapshotSpec.onScheduleDelete (no schema default; stamped by the
  schedule, consulted by the finalizer when external + owner gone).
- PrunedBy (Retention/FailedHistory) + annotation_value/parse, so the
  finalizer can distinguish operator prunes from external deletions.
- DeletionProtectionSpec.threshold (default 10, 0 disables) on both
  Repository and ClusterRepository: the mass-deletion circuit breaker.
- consts: PRUNED_BY_ANNOTATION, ALLOW_MASS_DELETION_ANNOTATION,
  DEFAULT_MASS_DELETION_THRESHOLD + effective_mass_deletion_threshold,
  MASS_DELETION_HELD_CONDITION.
- validate_backup_on_schedule_delete: origin: discovered may never set
  onScheduleDelete (their owner is a repository, not a schedule); wired
  into the existing validate_backup aggregate the webhook already calls,
  so no webhook changes were needed.

Regenerated deploy/crds, Helm CRDs, and docs/field-reference.md; updated
the snapshot/repository CRD insta snapshots.
Adds the mover-side batch snapshot-delete operation for mass-deletion
protection (M1): one Job deletes many manifest ids over a single kopia
connect, instead of one Job per Snapshot CR. Nothing emits this operation
yet, so it is an upgrade-safe no-op until a later milestone wires the
controller dispatcher.

- Extract delete_one (shared by the legacy single SnapshotDelete arm and
  the new batch loop) so the stale-id self-heal logic lives in one place.
- Data-loss fix (adversarial review): gate the self-heal on the anchor's
  start_time being present. Without it, the path(+identity)-only fallback
  in match_current_manifest can uniquely match a NEWER, unrelated
  snapshot at the same source path/identity and delete the wrong data.
- Batch semantics are attempt-all-then-fail: every member is tried
  independently since kopia's delete is idempotent, so a retry of the
  whole batch converges instead of wedging on the first failure.
- Add StatusReporter::log_only, hand-selected for SnapshotDeleteBatch
  (whose targetRef names the Job itself, with no RBAC to PATCH it) so a
  status PATCH is never even attempted for this op.
…r, batch)

M2 of mass-deletion protection: rewrites plan_deletion around a single
DeletionFacts input, absorbing namespace_delete_plan into an exhaustive
decision table (skip-cleanup > namespace cascade > schedule cascade guard >
operator prune > external breaker gate), and adds a new pure batch.rs module
(pending-member filtering, fire/throttle/requeue decisions, deterministic
Job naming) for the upcoming mass-delete batching. handle_deletion's call
site is rewired to the new signature with owner/breaker facts that are
provably behavior-preserving (M4/M5 wire the real owner GET and breaker
count). Everything stays pure and clock-injected; every match over the new
enums is exhaustive.
… surface, metrics)

Adds the infrastructure M4/M5 will consume to enforce mass-deletion
protection, with no reconcile-behavior change beyond an additive backfill:

- KOPIUR_MAX_CONCURRENT_DELETE_JOBS config knob (+ full Helm surface).
- Context gains OnceLock-backed snapshot/schedule reflector stores + synced
  flags, mirroring the maintenance_store precedent.
- ResolvedRepository gains deletion_protection + mass_deletion_ack fields.
- New consts: batch-delete op/label/annotation, DeletionHeld condition, and
  the mass-deletion event reasons.
- New metrics: kopiur_snapshot_deletions, kopiur_snapshots_cascade_retained,
  kopiur_snapshot_delete_batch_jobs/_members counters, plus
  kopiur_snapshot_deletions_pending_external/_held store-backed gauges.
- Snapshot's startup backfill now also stamps status.resolved.repository for
  pre-#255 CRs, so the mass-deletion breaker's per-repo count isn't blind to
  the legacy fleet.
Maintainer amendment: batching already drains one Job per repository per
accumulation window, not one per Snapshot, so a small default concurrency cap
risked head-of-line-blocking every other repository's deletions behind one
slow/failing one. KOPIUR_MAX_CONCURRENT_DELETE_JOBS now defaults to 0
(uncapped), matching the repo's threshold-0-disables idiom; the resolved
Context/ControllerConfig field is Option<NonZeroUsize> so "no cap" can't be
mistaken for "cap of zero" at any call site. throttle_verdict adapted:
None always Proceeds, Some(n) behaves as before. Helm surface (values.yaml,
schema minimum 0, deployment test, regenerated README) updated to match.
… Snapshot finalizer

M4a makes the mass-deletion-protection semantics real. Thin IO over the M2
pure decision layer: gather live facts, feed the tested functions, execute the
two new plan variants, and surface the observability.

- Stamp produced Snapshots with the schedule's effective onScheduleDelete, and
  propagate a schedule edit to existing drifted children
  (children_needing_cascade_stamp); skip firing into a terminating slot twin.
- Prune discriminator (io/prune.rs): stamp pruned-by before deleting so an
  operator prune bypasses the breaker/cascade guard; GFS hardening reclassifies
  an in-flight prune wave straddling the upgrade (partition_retention_prune).
- Real deletion facts (gather_deletion_facts): live owner GET via the schedule
  store/get_opt, and the store-gated per-repo breaker (pending_members +
  unacked_breaker_count), fail-safe to requeue when the snapshot store is
  unset/unsynced, conservative on missing refs, ignore+warn on an unparseable
  ack (InvalidMassDeletionAck).
- Execute the new plans: RetainSnapshotOnScheduleDelete (retain + cascade
  counter + event) and HoldSnapshotDeletion (keep finalizer, DeletionHeld=True
  via live_conditions_source, transition-only event, 5m requeue, ack command +
  escape hatch in the message); clear DeletionHeld when a held deletion
  proceeds. Single inc_snapshot_deletion point per terminal outcome.
- Ack-drain watch: Snapshot controller watches Repository/ClusterRepository ->
  deleting snapshots resolving to that repo. Lazy non-blocking MassDeletionHeld
  condition on both repo kinds (mirrors IndexBlobHealth).

handle_deletion stays under the complexity ratchet (facts-gathering extracted).
…_until_ready getter

The M4a mass-deletion breaker gates destructive deletion on `snapshot_synced` /
`schedule_synced`, which were flipped by a spawned `store.wait_until_ready()` on
the very `Controller::store()` the applier already awaits internally
(`delay_tasks_until(store.wait_until_ready())`). kube-rs's `DelayedInit`
multiplexes a single-waker `oneshot` behind a `Mutex`, so the two concurrent
getters race: the loser's waker is overwritten and it hangs forever. When our
getter lost, the flag never flipped and EVERY external `deletionPolicy: Delete`
deletion deferred (`StoresNotSynced`) indefinitely — breaking normal snapshot
deletion, not only the breaker.

Fix: publish the store handle only (`publish_store`, no second getter — which
also removes the latent risk of starving the applier's own getter), and flip
each `*_synced` from that kind's reconcile. A dispatched reconcile is itself
proof the reflector completed its initial LIST (the applier's readiness gate) —
a reliable signal with no `DelayedInit` contention. Unit test on the idempotent
flip helper.

Exposed by crates/e2e mass_deletion.rs (breaker held forever) and lifecycle.rs
backup_restore_delete_lifecycle (normal delete never drained).
New crates/e2e/tests/mass_deletion.rs proves the M4a mass-deletion protection
end-to-end against a live operator in kind:

- schedule_cascade_delete_leaves_kopia_intact_and_recatalogs — THE incident's
  regression guard: deleting a SnapshotSchedule (default onScheduleDelete:
  Retain) drains the produced Snapshot CRs with ZERO delete Jobs and an
  UNCHANGED kopia count, fires one SnapshotRetainedOnScheduleDelete Warning per
  CR, and the catalog rediscovers them as forced-Retain discovered rows.
- mass_deletion_breaker_holds_and_ack_drains — a wave >= threshold is HELD
  (DeletionHeld/MassDeletionBreaker, repo MassDeletionHeld, kopia untouched);
  the allow-mass-deletion ack (exact RFC3339 value parsed from the surfaced
  message) drains it and really deletes the kopia snapshots; a sub-threshold
  single delete is never held.
- retention_prune_not_held_by_breaker — operator GFS prunes bypass the breaker
  even at an aggressive threshold (1).

Plus a webhook admission case in webhook.rs (origin: discovered + onScheduleDelete
rejected; a manual Snapshot with the field accepted). Assertions are OUTCOMES
(CRs drained, kopia counts, conditions/events), never delete-Job names, so they
survive the M5 batch dispatcher. Adds massdel-{cascade,breaker,prune} to
REPO_SUBPATHS and the node-seed loop (kept in lockstep).
…CR delete Jobs

Replace the legacy per-CR `{name}-delete` Job with a per-repository BATCH
delete dispatcher (mass-deletion protection M5a). One mover Job
(`SnapshotDeleteBatch`, M1) deletes MANY members' kopia manifests over a
single connect, gated by the M2 pure decision layer, CREATEd (never
SSA-applied), reaped explicitly, and optionally throttled.

Dispatcher (`drive_batch_deletion`): honor a legacy `{name}-delete` Job via an
upgrade shim (both old placements); resolve placement via `batch_job_placement`
(repo-home ns / operator ns, else orphan); gate on the snapshot store being
synced (pruned deletions are NOT breaker-gated, so the dispatcher repeats the
fail-safe); LIST the repo's batch Jobs ONCE, reap the drained/aged terminal
ones (single whole-Job metric point), classify THIS CR against that same list,
and — if not yet enrolled — fire a fresh wave (no-overlap: fireable = pending
minus in-flight UIDs from the SAME list; deterministic name + 409 dedup).

The finalizer is released ONLY on a SUCCEEDED batch covering the CR's UID (or
the legacy shim's succeeded Job); the no-snapshot-id release survives.

New pure classifiers in snapshot/batch.rs (TDD): `member_disposition`,
`in_flight_uids`, `reap_targets` (+ `BatchJobView`/`BatchJobState`). Batch Job
construction reuses the retired path's semantics N-at-a-time: pin-first repo
resolution, projection HARDCODED OFF, moverDefaults inheritance, RBAC minting;
NO ttlSecondsAfterFinished, sorted-UID members annotation, ownerRef only for a
namespaced Repository.

Also:
- sweep.rs: leak backstop for terminal batch Jobs whose members all drained
  (logged, not metered — the dispatcher owns the counter); pure eligibility fn.
- batch.rs M2 leftovers: UID tiebreak in `oldest_first_truncated`,
  `batch_job_name` non-empty debug_assert + doc.
- context.rs: one-shot "informer cache synced" log on the first flip (swap).
- Retire `delete_snapshot_via_job`, `delete_projection_enabled`,
  `pinned_mover_identity`, `CredsPrefix::snapshot_delete` (now dead); the mover
  keeps decoding the old op for wire compat. Ratchet complexity budget 30 -> 29.
…rios

Add M5b e2e scenarios 5-8 to crates/e2e/tests/mass_deletion.rs proving the
per-repository batch delete dispatcher end-to-end (they LIST batch Jobs by the op
label + managed-by and read the delete-members UID annotation), plus the M4b
scenario-2 settle hardening:

- single_snapshot_delete_still_releases: batch-of-1 unification (exactly one
  SnapshotDeleteBatch Job whose delete-members is exactly the CR UID; CR drains;
  kopia -1).
- concurrent_batches_do_not_overlap: two disjoint delete waves -> two disjoint,
  separate batch Jobs (uncapped default allows concurrency); all drain, kopia -5.
- throttle_caps_concurrent_batch_jobs: KOPIUR_MAX_CONCURRENT_DELETE_JOBS=1 patched
  onto the controller Deployment (+ rollout wait) -> at most one live batch Job at
  any instant; both waves drain; env restored to uncapped on every exit.
- batch_delete_retries_after_repo_outage: repo dir flipped read-only (root chmod
  pod) so the batch mover fails EACCES -> Job fails, is reaped, refires (>=2 Job
  generations by UID) with no finalizer release; restore writability -> all drain +
  real kopia deletion.
- Scenario 2 hardening: settle on all three deletionTimestamps before asserting the
  breaker hold, so a loaded box can't evaluate the breaker before the wave registers.
- Lockstep: 4 new subpaths (massdel-single/nooverlap/throttle/outage) added to both
  crates/e2e/src/consts.rs REPO_SUBPATHS and the crates/e2e/mise.toml node-seed list.

fix(controller): the batch no-overlap exclusion set must exclude members covered by a
LIVE *or SUCCEEDED* job, not only LIVE (surfaced by the concurrent-batches e2e). A
member whose kopia snapshot a succeeded batch already deleted is still "pending" to
pending_members until its finalizer is released, so an uncapped second wave fired
before it drained re-enrolled it: the second batch became set_a U set_b instead of
set_b, and at scale (oldest-first MAX_BATCH_MEMBERS truncation) every new wave keeps
re-selecting already-deleted members, serializing a mass deletion. Rename in_flight_uids
-> covered_uids filtering Live|Succeeded (FAILED members stay fireable for a genuine
retry, mirroring member_disposition's precedence); a succeeded job is never reaped until
its members drain, so an excluded member is never stranded. +2 unit tests.
…es) + ADR-0006

Documents the M0-M5b mass-deletion-protection feature for all three audiences:

- docs/backups.md: what happens when a SnapshotSchedule is deleted (the cascade
  guard, default Retain, rediscovery, opting into cascade, propagation-on-edit),
  batched delete execution (quiet window, batch-of-1 latency), and the
  post-fire skip-snapshot-cleanup caveat.
- docs/repositories.md: extended the onNamespaceDelete cascade table to all
  three deletion axes (single Snapshot / namespace / schedule), plus a new
  `deletionProtection` section covering the breaker, the exact ack flow,
  0-disables, prune bypass, and the namespace-teardown corner.
- deploy/examples/34-schedule-deletion-protection.yaml: a schedule opting into
  the cascade + a repository with a raised breaker threshold.
- docs/dev/design-rationale.md: the design reasoning behind the 2-variant
  ScheduleDeletePolicy, the pruned-by discriminator, the valued (not
  presence-only) timestamp ack, batched create-not-apply execution, and the
  uncapped-by-default concurrency throttle.
- docs/adr/0006-mass-deletion-protection.md: the ADR recording the incident,
  the decisions, and upgrade notes.
- CLAUDE.md: one-line-each mention of the cascade guard, breaker, and batched
  deletes in the Deletion row of the locked technical decisions table.

No .rs files touched. Verified every field/annotation/metric name against
crates/api, crates/controller/src/consts.rs, and the actual reconcile code
before writing prose (see the M6 report for corrections made against the
task brief). mise run test / fmt-check / gen-check / docs all green.
- The Helm value for the delete-Job concurrency cap is top-level
  maxConcurrentDeleteJobs (deploy/helm/kopiur/values.yaml:124) — this chart has
  no `controller:` nesting. Fixed the wrong `controller.maxConcurrentDeleteJobs`
  path in docs/backups.md, docs/adr/0006-mass-deletion-protection.md, and the
  uncommitted release-notes scratch file.
- docs/repositories.md overstated what the InvalidMassDeletionAck Warning event
  reports: publish_invalid_ack_event names the annotation key, not the
  malformed value the user set. Reworded to "flagging the annotation as
  unparseable RFC3339".
…NT set

The mass-deletion pending COUNT is deliberately maximally INCLUSIVE (over-count
trips the breaker earlier — the fail-safe direction for a count), but fire_batch
reused that same set as the FIRE set. A breaker-exempt trigger (an operator
prune — never held by design — or an acked older wave) could therefore enroll
members the count only over-includes and delete their kopia data with no ack.
Introduce a pure, maximally-EXCLUSIVE fire-eligibility layer
(fire_eligible/FireEligibility), run before the no-overlap exclusion, whose
fail-safe direction is under-fire + requeue:

- CRITICAL-1: while the breaker is tripping (unacked >= threshold over the FULL
  pending set), exclude HELD external peers (external && deletion_timestamp > ack).
- IMPORTANT-2: exclude onNamespaceDelete:Orphan peers whose namespace is
  terminating (resolved once per distinct member namespace; a namespace that
  can't be read is treated as terminating — under-fire).
- IMPORTANT-3a: exclude schedule-owned EXTERNAL peers while the schedule store is
  unsynced (its owner lookup defaults to Alive, disabling the cascade guard);
  3b: flip schedule_synced at startup via a bounded authoritative LIST when the
  cluster has zero SnapshotSchedules (a reconcile never runs to flip it there).
- IMPORTANT-4: exclude unpinned PEERS (the count matches them to every repo key,
  but their manifest ids must not ride an unrelated repo's batch). The triggering
  self CR is always fireable — the finalizer path already authorized it.

Also in this wave:
- MINOR-7: emit kopiur_snapshot_delete_batch_members{outcome="failed"} once, at
  the single FAILED-Job reap point (its members never drain their own finalizer).
- MINOR-8: drop the dead MASS_DELETION_HELD_REASON const (condition-only surface).
- MINOR-9: reshape member_disposition/covered_uids to exhaustive match over
  BatchJobState so a new state fails closed (no behavior change).
- MINOR-10: mark the legacy {name}-delete adoption shim + delete_job_placement
  TODO(one-release).

Every filter is TDD-guarded by the review's counterexamples (the tests fail
against the pre-fix pass-through set).
…ICAL-1)

Scenario 9 (held_wave_not_swept_by_concurrent_prune) — the final-review flagship
counterexample. On ONE repository (threshold 2): an aggressive keepLatest:1
scheduled policy prunes continuously (breaker-EXEMPT batches firing on the repo),
while 3 manual externals are bulk-deleted into a HELD wave. keepLatest:1 never
keeps >=3 scheduled snapshots at once, so the counterexample window observes >=2
DISTINCT prune drains OVER TIME while the schedule stays live (as scenario 3
collects >=3 distinct produced names), asserting on every poll that the 3 held
manuals stay terminating with DeletionHeld=True and never drain. Then the schedule
is suspended + converged (still asserting held) and the allow-mass-deletion ack
drains the manuals — kopia count drops by exactly 3. A manual draining before the
ack is the regression (a prune's oldest-first batch sweeping a held external).

Adds the isolated `massdel-heldprune` repo subpath in lockstep to REPO_SUBPATHS
(crates/e2e/src/consts.rs) and the node-seed enumeration (crates/e2e/mise.toml).
…ch reality

- ADR-0006 / design-rationale: record the load-bearing polarity the original
  design left implicit — the breaker's pending COUNT is inclusive (over-count is
  fail-safe), while the set a reconcile FIRES is exclusive (under-fire is
  fail-safe), and the fire path drops held/ns-orphan/unsynced-schedule-owned/
  unpinned members that still count toward the breaker.
- backups.md (MINOR-6): rewrite the stale "what Delete needs" bit — the batch
  delete runs at the repository's home namespace against the canonical Secret
  with projection off, so there is no per-run <snapshot>-delete-creds-N copy.
- observability.md (MINOR-7): document the per-member batch metric semantics —
  `deleted` per member on drain, `failed` once per member at the FAILED reap.
The M2 feature commit (1d636ba) rewrote the node entries in .mise/mise.lock
(linux-x64 prebuilt tarball -> generic source tarball + install="source", plus
source-install variants) — the known mise install/uninstall lock-rewrite gotcha,
staged by a hook. Restore .mise/mise.lock to main (94f9a15); the toolchain
re-resolves and the hermetic gate still passes.
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds mass-deletion protection for Snapshot cleanup paths. The main changes are:

  • Schedule deletion now defaults to retaining produced kopia snapshots unless explicitly configured to cascade.
  • Repository and ClusterRepository specs now include a mass-deletion breaker with timestamp-based acknowledgement.
  • Snapshot deletes are batched per repository through snapdel-* mover Jobs instead of one Job per Snapshot.
  • Operator prune paths stamp a pruned-by annotation so retention and failed-history cleanup bypass external-deletion guards.
  • Controller watches, CRDs, Helm values, docs, metrics, and e2e coverage were updated for the new deletion flow.

Confidence Score: 5/5

Safe to merge based on the reviewed changed paths.

No verified runtime, data-loss, or security issues remained after reviewing the deletion planning, batching, schedule stamping, prune annotation, mover execution, and repository acknowledgement wiring.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Verified the environment readiness by confirming cargo and rustc are available, while docker, kind, kubectl, and helm are not.
  • Ran the mass deletion controller snapshot tests using the documented command and working directory; the test completed with EXIT_CODE: 0.
  • Ran the mass deletion mover workspec tests using the documented command and working directory; the test completed with EXIT_CODE: 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
crates/controller/src/snapshot/mod.rs Wires live deletion facts into the finalizer and dispatches repository-scoped batch delete Jobs.
crates/controller/src/snapshot/batch.rs Adds batch membership, breaker counting, fire eligibility, throttling, and batch Job lifecycle helpers.
crates/controller/src/snapshot/plan.rs Centralizes deletion decisions for cascade retention, namespace deletion, prune bypass, and breaker holds.
crates/controller/src/snapshot_schedule.rs Stamps and propagates schedule deletion policy onto produced Snapshots.
crates/controller/src/io/prune.rs Adds stamp-then-delete helpers for retention and failed-history prunes.
crates/mover/src/main.rs Runs batch snapshot deletes and reports batch Jobs through log-only status handling.
crates/mover/src/workspec/mod.rs Adds the batch snapshot-delete work-spec payload.
crates/e2e/tests/mass_deletion.rs Adds e2e coverage for cascade retention, breaker hold/ack, batching, throttling, and prune interaction.

Reviews (1): Last reviewed commit: "chore(mise): revert unintended node lock..." | Re-trigger Greptile

@perfectra1n
perfectra1n merged commit 038ea6d into main Jul 17, 2026
31 checks passed
@perfectra1n
perfectra1n deleted the feat/mass-deletion-protection branch July 17, 2026 15:16
doonga pushed a commit to greyrock-labs/home-ops that referenced this pull request Jul 21, 2026
…0.7.5 ➔ 0.8.0) (#107)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/home-operations/charts/kopiur](https://github.com/home-operations/kopiur) | minor | `0.7.5` → `0.8.0` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/11) for more information.

---

### Release Notes

<details>
<summary>home-operations/kopiur (ghcr.io/home-operations/charts/kopiur)</summary>

### [`v0.8.0`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#080-2026-07-19)

[Compare Source](home-operations/kopiur@0.7.5...0.8.0)

##### ⚠ BREAKING CHANGES

- SnapshotPolicy deletion cascade + auto-adoption of discovered snapshots ([#&#8203;272](home-operations/kopiur#272))

##### Features

- **deps:** update rust crate tokio (1.52.4 → 1.53.0) ([#&#8203;264](home-operations/kopiur#264)) ([6cab0c6](home-operations/kopiur@6cab0c6))
- mass-deletion protection (cascade guard, per-repo breaker, batched deletes) ([#&#8203;265](home-operations/kopiur#265)) ([038ea6d](home-operations/kopiur@038ea6d))
- SnapshotPolicy deletion cascade + auto-adoption of discovered snapshots ([#&#8203;272](home-operations/kopiur#272)) ([9da8c26](home-operations/kopiur@9da8c26))

##### Bug Fixes

- **deps:** update rust crate anyhow (1.0.103 → 1.0.104) ([#&#8203;269](home-operations/kopiur#269)) ([4d81496](home-operations/kopiur@4d81496))
- **deps:** update rust crate clap (4.6.1 → 4.6.2) ([#&#8203;257](home-operations/kopiur#257)) ([c238df1](home-operations/kopiur@c238df1))
- **deps:** update rust crate futures (0.3.32 → 0.3.33) ([#&#8203;268](home-operations/kopiur#268)) ([926b52d](home-operations/kopiur@926b52d))
- **deps:** update rust crate serde (1.0.228 → 1.0.229) ([#&#8203;270](home-operations/kopiur#270)) ([5fbd713](home-operations/kopiur@5fbd713))
- **deps:** update rust crate thiserror (2.0.18 → 2.0.19) ([#&#8203;271](home-operations/kopiur#271)) ([d83730f](home-operations/kopiur@d83730f))
- **deps:** update rust crate tokio (1.52.3 → 1.52.4) ([#&#8203;261](home-operations/kopiur#261)) ([accf6ae](home-operations/kopiur@accf6ae))
- writable backup source ([#&#8203;254](home-operations/kopiur#254)), stuck deletion finalizer ([#&#8203;255](home-operations/kopiur#255)), kopia epoch parameters ([#&#8203;258](home-operations/kopiur#258)) ([#&#8203;263](home-operations/kopiur#263)) ([94f9a15](home-operations/kopiur@94f9a15))

##### Miscellaneous Chores

- **krew:** kopiur 0.7.5 manifest ([16d7390](home-operations/kopiur@16d7390))
- **mise:** Update tool aqua:astral-sh/uv (0.11.28 → 0.11.29) ([#&#8203;256](home-operations/kopiur#256)) ([f822335](home-operations/kopiur@f822335))
- **mise:** Update tool cosign (3.1.1 → 3.1.2) ([#&#8203;267](home-operations/kopiur#267)) ([649ba2e](home-operations/kopiur@649ba2e))

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/New_York)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjAuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI3Mi4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/107
eleboucher pushed a commit to eleboucher/homelab that referenced this pull request Jul 23, 2026
…0.7.4 ➔ 0.8.0) (#1627)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/home-operations/charts/kopiur](https://github.com/home-operations/kopiur) | minor | `0.7.4` → `0.8.0` |

---

### Release Notes

<details>
<summary>home-operations/kopiur (ghcr.io/home-operations/charts/kopiur)</summary>

### [`v0.8.0`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#080-2026-07-19)

[Compare Source](home-operations/kopiur@0.7.5...0.8.0)

##### ⚠ BREAKING CHANGES

- SnapshotPolicy deletion cascade + auto-adoption of discovered snapshots ([#&#8203;272](home-operations/kopiur#272))

##### Features

- **deps:** update rust crate tokio (1.52.4 → 1.53.0) ([#&#8203;264](home-operations/kopiur#264)) ([6cab0c6](home-operations/kopiur@6cab0c6))
- mass-deletion protection (cascade guard, per-repo breaker, batched deletes) ([#&#8203;265](home-operations/kopiur#265)) ([038ea6d](home-operations/kopiur@038ea6d))
- SnapshotPolicy deletion cascade + auto-adoption of discovered snapshots ([#&#8203;272](home-operations/kopiur#272)) ([9da8c26](home-operations/kopiur@9da8c26))

##### Bug Fixes

- **deps:** update rust crate anyhow (1.0.103 → 1.0.104) ([#&#8203;269](home-operations/kopiur#269)) ([4d81496](home-operations/kopiur@4d81496))
- **deps:** update rust crate clap (4.6.1 → 4.6.2) ([#&#8203;257](home-operations/kopiur#257)) ([c238df1](home-operations/kopiur@c238df1))
- **deps:** update rust crate futures (0.3.32 → 0.3.33) ([#&#8203;268](home-operations/kopiur#268)) ([926b52d](home-operations/kopiur@926b52d))
- **deps:** update rust crate serde (1.0.228 → 1.0.229) ([#&#8203;270](home-operations/kopiur#270)) ([5fbd713](home-operations/kopiur@5fbd713))
- **deps:** update rust crate thiserror (2.0.18 → 2.0.19) ([#&#8203;271](home-operations/kopiur#271)) ([d83730f](home-operations/kopiur@d83730f))
- **deps:** update rust crate tokio (1.52.3 → 1.52.4) ([#&#8203;261](home-operations/kopiur#261)) ([accf6ae](home-operations/kopiur@accf6ae))
- writable backup source ([#&#8203;254](home-operations/kopiur#254)), stuck deletion finalizer ([#&#8203;255](home-operations/kopiur#255)), kopia epoch parameters ([#&#8203;258](home-operations/kopiur#258)) ([#&#8203;263](home-operations/kopiur#263)) ([94f9a15](home-operations/kopiur@94f9a15))

##### Miscellaneous Chores

- **krew:** kopiur 0.7.5 manifest ([16d7390](home-operations/kopiur@16d7390))
- **mise:** Update tool aqua:astral-sh/uv (0.11.28 → 0.11.29) ([#&#8203;256](home-operations/kopiur#256)) ([f822335](home-operations/kopiur@f822335))
- **mise:** Update tool cosign (3.1.1 → 3.1.2) ([#&#8203;267](home-operations/kopiur#267)) ([649ba2e](home-operations/kopiur@649ba2e))

### [`v0.7.5`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#075-2026-07-16)

[Compare Source](home-operations/kopiur@0.7.4...0.7.5)

##### Bug Fixes

- **readme:** update readme admonition ([987957c](home-operations/kopiur@987957c))
- **secctx:** SecurityContextCompatible is verified, not asserted; inherit merges with explicit ([#&#8203;259](home-operations/kopiur#259)) ([cbfd97d](home-operations/kopiur@cbfd97d))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/1627
hbjydev pushed a commit to hbjydev/phoebe that referenced this pull request Aug 27, 2026
…0.7.3 → 0.10.4) (#273)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/home-operations/charts/kopiur](https://github.com/home-operations/kopiur) | minor | `0.7.3` → `0.10.4` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/141) for more information.

---

### Release Notes

<details>
<summary>home-operations/kopiur (ghcr.io/home-operations/charts/kopiur)</summary>

### [`v0.10.4`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#0104-2026-08-24)

[Compare Source](home-operations/kopiur@0.10.3...0.10.4)

##### Bug Fixes

- **chart:** give flowSchema its doc comment and timings a clean schema entry ([#&#8203;397](home-operations/kopiur#397)) ([7c8dd3f](home-operations/kopiur@7c8dd3f))
- **mover:** read status.resolved via the /status subresource ([#&#8203;408](home-operations/kopiur#408)) ([06fc2f9](home-operations/kopiur@06fc2f9))

##### Miscellaneous Chores

- **github-action:** update action docker/github-builder (v1.16.0 → v1.17.0) ([#&#8203;407](home-operations/kopiur#407)) ([b6d7d04](home-operations/kopiur@b6d7d04))
- **mise:** update mise tools ([#&#8203;400](home-operations/kopiur#400)) ([b1a83f8](home-operations/kopiur@b1a83f8))
- **mise:** update tool kubectl (1.36.3 → 1.36.4) ([#&#8203;405](home-operations/kopiur#405)) ([274e3d6](home-operations/kopiur@274e3d6))
- **mise:** update tool lefthook (2.1.10 → 2.1.11) ([#&#8203;406](home-operations/kopiur#406)) ([2ba700d](home-operations/kopiur@2ba700d))
- **mise:** update tool rust (1.97.1 → 1.98.0) ([#&#8203;403](home-operations/kopiur#403)) ([c655005](home-operations/kopiur@c655005))
- **mise:** update tool yq (4.53.3 → 4.53.4) ([#&#8203;402](home-operations/kopiur#402)) ([d1ed8a7](home-operations/kopiur@d1ed8a7))
- **mise:** update tool yq (4.53.4 → 4.53.6) ([#&#8203;404](home-operations/kopiur#404)) ([dd5878c](home-operations/kopiur@dd5878c))

### [`v0.10.3`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#0103-2026-08-18)

[Compare Source](home-operations/kopiur@0.10.2...0.10.3)

##### Features

- **repository:** seed a fresh repository from its replica — DR for replicated repositories ([#&#8203;380](home-operations/kopiur#380)) ([#&#8203;396](home-operations/kopiur#396)) ([74665dc](home-operations/kopiur@74665dc))

##### Bug Fixes

- **rust:** update crate cel (0.14.2 → 0.14.3) ([#&#8203;387](home-operations/kopiur#387)) ([bf9aa0c](home-operations/kopiur@bf9aa0c))
- **rust:** update crate http-body-util (0.1.4 → 0.1.5) ([#&#8203;381](home-operations/kopiur#381)) ([6354e17](home-operations/kopiur@6354e17))

##### Miscellaneous Chores

- **github-action:** update action jdx/mise-action (v4.2.4 → v4.2.5) ([#&#8203;388](home-operations/kopiur#388)) ([1040c9e](home-operations/kopiur@1040c9e))
- **mise:** update tool aqua:astral-sh/uv (0.12.3 → 0.12.4) ([#&#8203;390](home-operations/kopiur#390)) ([bf3916c](home-operations/kopiur@bf3916c))
- **mise:** update tool aqua:astral-sh/uv (0.12.4 → 0.12.5) ([#&#8203;395](home-operations/kopiur#395)) ([b919878](home-operations/kopiur@b919878))
- **mise:** update tool helm (4.2.3 → 4.2.4) ([#&#8203;389](home-operations/kopiur#389)) ([5b134fa](home-operations/kopiur@5b134fa))
- **mise:** Update tool oxfmt (0.62.0 → 0.63.0) ([#&#8203;386](home-operations/kopiur#386)) ([65db237](home-operations/kopiur@65db237))

### [`v0.10.2`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#0102-2026-08-13)

[Compare Source](home-operations/kopiur@0.10.1...0.10.2)

##### Performance Improvements

- **controller:** store-backed reads, LIST dedup, bounded PVC retries, client-side request metrics ([#&#8203;382](home-operations/kopiur#382)) ([#&#8203;383](home-operations/kopiur#383)) ([2b07022](home-operations/kopiur@2b07022))

### [`v0.10.1`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#0101-2026-08-11)

[Compare Source](home-operations/kopiur@0.10.0...0.10.1)

##### Bug Fixes

- **mise:** something about mise warnings ([#&#8203;373](home-operations/kopiur#373)) ([fde8073](home-operations/kopiur@fde8073))
- **rust:** update crate cel (0.14.1 → 0.14.2) ([#&#8203;379](home-operations/kopiur#379)) ([4a60221](home-operations/kopiur@4a60221))
- **rust:** update crate futures (0.3.33 → 0.3.34) ([#&#8203;378](home-operations/kopiur#378)) ([ff657b8](home-operations/kopiur@ff657b8))
- **rust:** update crate rcgen (0.14.8 → 0.14.9) ([#&#8203;377](home-operations/kopiur#377)) ([e3b3b3f](home-operations/kopiur@e3b3b3f))

##### Miscellaneous Chores

- **mise:** Update tool aqua:astral-sh/uv (0.12.2 → 0.12.3) ([#&#8203;375](home-operations/kopiur#375)) ([952af6d](home-operations/kopiur@952af6d))

### [`v0.10.0`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#0100-2026-08-09)

[Compare Source](home-operations/kopiur@0.9.5...0.10.0)

##### ⚠ BREAKING CHANGES

- **rust:** Update crate base64 (0.22.1 → 0.23.1) ([#&#8203;366](home-operations/kopiur#366))

##### Features

- **rust:** Update crate base64 (0.22.1 → 0.23.1) ([#&#8203;366](home-operations/kopiur#366)) ([2c6e3e7](home-operations/kopiur@2c6e3e7))
- SnapshotReplication (logical snapshot-level replication) + SnapshotPolicy multi-repository fan-out ([#&#8203;370](home-operations/kopiur#370)) ([4e06e42](home-operations/kopiur@4e06e42))

##### Continuous Integration

- **github-action:** Update action Swatinem/rust-cache (v2.9.1 → v2.9.2) ([#&#8203;372](home-operations/kopiur#372)) ([e4ce173](home-operations/kopiur@e4ce173))

##### Miscellaneous Chores

- **mise:** Update mise tools ([#&#8203;367](home-operations/kopiur#367)) ([d6f82f4](home-operations/kopiur@d6f82f4))
- **mise:** Update tool cosign (3.1.2 → 3.1.3) ([#&#8203;371](home-operations/kopiur#371)) ([558b9f4](home-operations/kopiur@558b9f4))

### [`v0.9.5`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#095-2026-08-08)

[Compare Source](home-operations/kopiur@0.9.4...0.9.5)

##### Bug Fixes

- deliver s3 tls.caBundleRef to every kopia invocation ([#&#8203;365](home-operations/kopiur#365)) ([cda0237](home-operations/kopiur@cda0237))
- doctor misses structurally-blocked work ([#&#8203;359](home-operations/kopiur#359)) + phase/gate exhaustiveness ratchet ([#&#8203;363](home-operations/kopiur#363)) ([b844974](home-operations/kopiur@b844974))
- **rust:** update crate thiserror (2.0.19 → 2.0.20) ([#&#8203;360](home-operations/kopiur#360)) ([4aa646d](home-operations/kopiur@4aa646d))

##### Continuous Integration

- **github-action:** Update action docker/github-builder (v1.15.0 → v1.16.0) ([#&#8203;362](home-operations/kopiur#362)) ([db32818](home-operations/kopiur@db32818))

### [`v0.9.4`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#094-2026-08-08)

[Compare Source](home-operations/kopiur@0.9.3...0.9.4)

##### Features

- **docs:** Update README.md ([#&#8203;358](home-operations/kopiur#358)) ([05d2506](home-operations/kopiur@05d2506))

##### Bug Fixes

- implement pvcSelector/groupBy ([#&#8203;346](home-operations/kopiur#346)) and model deduped backups as Unchanged ([#&#8203;351](home-operations/kopiur#351)) ([#&#8203;354](home-operations/kopiur#354)) ([a2b9ae6](home-operations/kopiur@a2b9ae6))
- **rust:** update crate clap (4.6.5 → 4.6.6) ([#&#8203;357](home-operations/kopiur#357)) ([e9ca396](home-operations/kopiur@e9ca396))

##### Miscellaneous Chores

- **mise:** Update tool node (24.18.1 → v24.19.0) ([#&#8203;353](home-operations/kopiur#353)) ([2f28c6d](home-operations/kopiur@2f28c6d))
- **mise:** Update tool oxfmt (0.61.0 → 0.62.0) ([#&#8203;356](home-operations/kopiur#356)) ([e95df6b](home-operations/kopiur@e95df6b))

### [`v0.9.3`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#093-2026-08-05)

[Compare Source](home-operations/kopiur@0.9.2...0.9.3)

##### Features

- repository-level circuit breaker — stop fanning out doomed Jobs when the backend is unreachable ([#&#8203;350](home-operations/kopiur#350)) ([5d787b9](home-operations/kopiur@5d787b9))

##### Bug Fixes

- **rust:** update crate base64 (0.23.0 → 0.23.1) ([#&#8203;349](home-operations/kopiur#349)) ([11e7aa8](home-operations/kopiur@11e7aa8))

##### Continuous Integration

- **github-action:** Update action jdx/mise-action (v4.2.3 → v4.2.4) ([#&#8203;347](home-operations/kopiur#347)) ([1c9cf3b](home-operations/kopiur@1c9cf3b))

##### Miscellaneous Chores

- **renovate:** keep 0.x minors out of automerge ([#&#8203;352](home-operations/kopiur#352)) ([95d672a](home-operations/kopiur@95d672a))

### [`v0.9.2`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#092-2026-08-02)

[Compare Source](home-operations/kopiur@0.9.1...0.9.2)

##### Features

- **repository:** object-lock blob retention ([#&#8203;332](home-operations/kopiur#332)); fix blank volumeSnapshotClassName ([#&#8203;344](home-operations/kopiur#344)) ([dab8fdd](home-operations/kopiur@dab8fdd))

##### Bug Fixes

- **ci:** fail the merge gate on cancelled jobs, and cut e2e artifact time ([#&#8203;330](home-operations/kopiur#330)) ([1847a8a](home-operations/kopiur@1847a8a))
- **rust:** update crate time (0.3.54 → 0.3.55) ([#&#8203;337](home-operations/kopiur#337)) ([ee1b951](home-operations/kopiur@ee1b951))

##### Continuous Integration

- **github-action:** Update action docker/github-builder (v1.14.0 → v1.15.0) ([#&#8203;327](home-operations/kopiur#327)) ([f5514fc](home-operations/kopiur@f5514fc))
- **github-action:** Update action docker/login-action (v4.5.1 → v4.5.2) ([#&#8203;331](home-operations/kopiur#331)) ([d90efdf](home-operations/kopiur@d90efdf))
- **github-action:** Update action docker/login-action (v4.5.2 → v4.6.0) ([#&#8203;339](home-operations/kopiur#339)) ([b783bcc](home-operations/kopiur@b783bcc))
- **github-action:** Update action home-operations/.github/actions/workflow-lint (v1.0.2 → v1.0.3) ([#&#8203;343](home-operations/kopiur#343)) ([4af16d5](home-operations/kopiur@4af16d5))
- update shared actions and use self-repository syntax ([#&#8203;341](home-operations/kopiur#341)) ([559e23f](home-operations/kopiur@559e23f))

##### Miscellaneous Chores

- **deps:** lock file maintenance (pep621) ([#&#8203;334](home-operations/kopiur#334)) ([e19ae0e](home-operations/kopiur@e19ae0e))
- **mise:** Lock file maintenance tool (mise) ([#&#8203;335](home-operations/kopiur#335)) ([666afb5](home-operations/kopiur@666afb5))
- **mise:** prune lockfile to used platforms ([#&#8203;342](home-operations/kopiur#342)) ([741d6a4](home-operations/kopiur@741d6a4))
- **mise:** Update tool aqua:astral-sh/uv (0.12.0 → 0.12.1) ([#&#8203;333](home-operations/kopiur#333)) ([3ed2766](home-operations/kopiur@3ed2766))
- **mise:** Update tool promtool (3.13.1 → 3.13.2) ([#&#8203;329](home-operations/kopiur#329)) ([ac1752e](home-operations/kopiur@ac1752e))
- **mise:** Update tool zizmor (1.28.0 → 1.29.0) ([#&#8203;340](home-operations/kopiur#340)) ([410dc26](home-operations/kopiur@410dc26))
- **release-please:** standardize the release pull request title pattern ([#&#8203;338](home-operations/kopiur#338)) ([21013a3](home-operations/kopiur@21013a3))
- **rust:** lock file maintenance crate (cargo) ([#&#8203;336](home-operations/kopiur#336)) ([8606177](home-operations/kopiur@8606177))

### [`v0.9.1`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#091-2026-07-29)

[Compare Source](home-operations/kopiur@0.9.0...0.9.1)

##### Features

- **rust:** update crate http (1.4.2 → 1.5.0) ([#&#8203;323](home-operations/kopiur#323)) ([cf0b149](home-operations/kopiur@cf0b149))

##### Bug Fixes

- **controller:** survive a slow API server instead of abdicating on the first one ([#&#8203;319](home-operations/kopiur#319)) ([#&#8203;324](home-operations/kopiur#324)) ([df7e90c](home-operations/kopiur@df7e90c))
- **rust:** update crate rustls (0.23.42 → 0.23.43) ([#&#8203;326](home-operations/kopiur#326)) ([2f5123e](home-operations/kopiur@2f5123e))

##### Tests

- **e2e:** retry flaky tests with cargo-nextest ([#&#8203;322](home-operations/kopiur#322)) ([558ac85](home-operations/kopiur@558ac85))

##### Miscellaneous Chores

- **mise:** Update tool aqua:astral-sh/uv (0.11.33 → 0.12.0) ([#&#8203;320](home-operations/kopiur#320)) ([66ec350](home-operations/kopiur@66ec350))
- **mise:** Update tool node (24.18.0 → v24.18.1) ([#&#8203;325](home-operations/kopiur#325)) ([1c833a4](home-operations/kopiur@1c833a4))

### [`v0.9.0`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#090-2026-07-28)

[Compare Source](home-operations/kopiur@0.8.1...0.9.0)

##### ⚠ BREAKING CHANGES

- **rust:** Update crate serial\_test (3.5.0 → 4.0.1) ([#&#8203;307](home-operations/kopiur#307))
- **secctx:** identity-aware mover merge, recorded snapshot identity, restore inherit-from-snapshot ([#&#8203;304](home-operations/kopiur#304))

##### Features

- **rust:** Update crate serial\_test (3.5.0 → 4.0.1) ([#&#8203;307](home-operations/kopiur#307)) ([4392e4a](home-operations/kopiur@4392e4a))
- **secctx:** identity-aware mover merge, recorded snapshot identity, restore inherit-from-snapshot ([#&#8203;304](home-operations/kopiur#304)) ([0632a29](home-operations/kopiur@0632a29))

##### Bug Fixes

- **rust:** update crate cel (0.14.0 → 0.14.1) ([#&#8203;317](home-operations/kopiur#317)) ([00cf539](home-operations/kopiur@00cf539))
- **rust:** update crate schemars (1.2.1 → 1.2.2) ([#&#8203;313](home-operations/kopiur#313)) ([b6e6268](home-operations/kopiur@b6e6268))

##### Build System

- **mise:** add actionlint and refresh the lockfile ([#&#8203;309](home-operations/kopiur#309)) ([e74c9b3](home-operations/kopiur@e74c9b3))

##### Continuous Integration

- adopt the shared workflow-lint and docs-build actions ([#&#8203;310](home-operations/kopiur#310)) ([7d4b329](home-operations/kopiur@7d4b329))
- gate pull requests on Build Success and share the docs build ([#&#8203;306](home-operations/kopiur#306)) ([1b693aa](home-operations/kopiur@1b693aa))
- **github-action:** Update action docker/login-action (v4.5.0 → v4.5.1) ([#&#8203;312](home-operations/kopiur#312)) ([29d00b1](home-operations/kopiur@29d00b1))
- **github-action:** Update action jdx/mise-action (v4.2.1 → v4.2.2) ([#&#8203;311](home-operations/kopiur#311)) ([65bbca4](home-operations/kopiur@65bbca4))
- **github-action:** Update action jdx/mise-action (v4.2.2 → v4.2.3) ([#&#8203;315](home-operations/kopiur#315)) ([5f517d4](home-operations/kopiur@5f517d4))
- skip release-please PRs in checks and drop nightly e2e ([#&#8203;305](home-operations/kopiur#305)) ([ee6d4f5](home-operations/kopiur@ee6d4f5))

##### Miscellaneous Chores

- **krew:** kopiur 0.8.1 manifest ([84df757](home-operations/kopiur@84df757))
- **mise:** Update tool aqua:astral-sh/uv (0.11.32 → 0.11.33) ([#&#8203;318](home-operations/kopiur#318)) ([f63bc3a](home-operations/kopiur@f63bc3a))
- **mise:** Update tool oxfmt (0.60.0 → 0.61.0) ([#&#8203;314](home-operations/kopiur#314)) ([bdbf6c6](home-operations/kopiur@bdbf6c6))
- **renovate:** drop the stale automerge overrides ([#&#8203;308](home-operations/kopiur#308)) ([c13a3d5](home-operations/kopiur@c13a3d5))
- standardize release-please changelog sections ([#&#8203;316](home-operations/kopiur#316)) ([273d0f8](home-operations/kopiur@273d0f8))

### [`v0.8.1`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#081-2026-07-24)

[Compare Source](home-operations/kopiur@0.8.0...0.8.1)

##### Features

- **deps:** update rust crate base64 (0.22.1 → 0.23.0) ([#&#8203;293](home-operations/kopiur#293)) ([5ab14d5](home-operations/kopiur@5ab14d5))
- **deps:** update rust crate kube (4.0.0 → 4.2.0) ([#&#8203;288](home-operations/kopiur#288)) ([b067458](home-operations/kopiur@b067458))
- httpRequest hook headers ([#&#8203;290](home-operations/kopiur#290)) + recovery-aware alerts ([#&#8203;280](home-operations/kopiur#280)) ([#&#8203;296](home-operations/kopiur#296)) ([f388a14](home-operations/kopiur@f388a14))
- **renovate:** automerge if it passes CI ([66f243f](home-operations/kopiur@66f243f))

##### Bug Fixes

- **controller:** survive an API-server outage without exhausting file descriptors ([#&#8203;287](home-operations/kopiur#287)) ([ce184b6](home-operations/kopiur@ce184b6))
- **deps:** update rust crate clap (4.6.2 → 4.6.3) ([#&#8203;277](home-operations/kopiur#277)) ([e3d4113](home-operations/kopiur@e3d4113))
- **deps:** update rust crate libc (0.2.186 → 0.2.189) ([#&#8203;283](home-operations/kopiur#283)) ([2b2e636](home-operations/kopiur@2b2e636))
- **deps:** update rust crate serde\_json (1.0.150 → 1.0.151) ([#&#8203;275](home-operations/kopiur#275)) ([1410f42](home-operations/kopiur@1410f42))
- **deps:** update rust crate time (0.3.53 → 0.3.54) ([#&#8203;276](home-operations/kopiur#276)) ([ff250ff](home-operations/kopiur@ff250ff))
- **deps:** update rust crate tokio (1.53.0 → 1.53.1) ([#&#8203;281](home-operations/kopiur#281)) ([82082e9](home-operations/kopiur@82082e9))
- **helm:** stamp Chart.yaml version on release + document image sub-fields ([#&#8203;289](home-operations/kopiur#289)) ([041549a](home-operations/kopiur@041549a))
- **probe:** stamp the health probe at launch so it stops recycling its Job ([#&#8203;273](home-operations/kopiur#273)) ([#&#8203;278](home-operations/kopiur#278)) ([7fa3657](home-operations/kopiur@7fa3657))
- **release:** use the generic updater for Chart.yaml + stamp README badges ([#&#8203;292](home-operations/kopiur#292)) ([f4a3141](home-operations/kopiur@f4a3141))
- retention-aware adoption stops the adopt/prune/rediscover livelock ([#&#8203;299](home-operations/kopiur#299)) ([4093f12](home-operations/kopiur@4093f12))

##### Styles

- indent markdown at 2 to match embedded yaml ([#&#8203;279](home-operations/kopiur#279)) ([06885e0](home-operations/kopiur@06885e0))

##### Miscellaneous Chores

- **github-release:** Update release helm-unittest/helm-unittest (v1.1.1 → v1.1.2) ([#&#8203;301](home-operations/kopiur#301)) ([e62272d](home-operations/kopiur@e62272d))
- **krew:** kopiur 0.8.0 manifest ([2be416f](home-operations/kopiur@2be416f))
- **mise:** Update tool aqua:astral-sh/uv (0.11.29 → 0.11.30) ([#&#8203;282](home-operations/kopiur#282)) ([e12f96c](home-operations/kopiur@e12f96c))
- **mise:** Update tool aqua:astral-sh/uv (0.11.30 → 0.11.31) ([#&#8203;286](home-operations/kopiur#286)) ([a3f31e6](home-operations/kopiur@a3f31e6))
- **mise:** Update tool aqua:astral-sh/uv (0.11.31 → 0.11.32) ([#&#8203;298](home-operations/kopiur#298)) ([9d8c171](home-operations/kopiur@9d8c171))
- **mise:** Update tool kubectl (1.36.2 → 1.36.3) ([#&#8203;291](home-operations/kopiur#291)) ([ef290f8](home-operations/kopiur@ef290f8))
- **mise:** Update tool oxfmt (0.59.0 → 0.60.0) ([#&#8203;284](home-operations/kopiur#284)) ([ad8a18a](home-operations/kopiur@ad8a18a))
- **mise:** Update tool rust (1.96.1 → 1.97.1) ([#&#8203;222](home-operations/kopiur#222)) ([fbeb2fa](home-operations/kopiur@fbeb2fa))
- **mise:** Update tool zizmor (1.27.0 → 1.28.0) ([#&#8203;285](home-operations/kopiur#285)) ([9ef35ce](home-operations/kopiur@9ef35ce))

### [`v0.8.0`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#080-2026-07-19)

[Compare Source](home-operations/kopiur@0.7.5...0.8.0)

##### ⚠ BREAKING CHANGES

- SnapshotPolicy deletion cascade + auto-adoption of discovered snapshots ([#&#8203;272](home-operations/kopiur#272))

##### Features

- **deps:** update rust crate tokio (1.52.4 → 1.53.0) ([#&#8203;264](home-operations/kopiur#264)) ([6cab0c6](home-operations/kopiur@6cab0c6))
- mass-deletion protection (cascade guard, per-repo breaker, batched deletes) ([#&#8203;265](home-operations/kopiur#265)) ([038ea6d](home-operations/kopiur@038ea6d))
- SnapshotPolicy deletion cascade + auto-adoption of discovered snapshots ([#&#8203;272](home-operations/kopiur#272)) ([9da8c26](home-operations/kopiur@9da8c26))

##### Bug Fixes

- **deps:** update rust crate anyhow (1.0.103 → 1.0.104) ([#&#8203;269](home-operations/kopiur#269)) ([4d81496](home-operations/kopiur@4d81496))
- **deps:** update rust crate clap (4.6.1 → 4.6.2) ([#&#8203;257](home-operations/kopiur#257)) ([c238df1](home-operations/kopiur@c238df1))
- **deps:** update rust crate futures (0.3.32 → 0.3.33) ([#&#8203;268](home-operations/kopiur#268)) ([926b52d](home-operations/kopiur@926b52d))
- **deps:** update rust crate serde (1.0.228 → 1.0.229) ([#&#8203;270](home-operations/kopiur#270)) ([5fbd713](home-operations/kopiur@5fbd713))
- **deps:** update rust crate thiserror (2.0.18 → 2.0.19) ([#&#8203;271](home-operations/kopiur#271)) ([d83730f](home-operations/kopiur@d83730f))
- **deps:** update rust crate tokio (1.52.3 → 1.52.4) ([#&#8203;261](home-operations/kopiur#261)) ([accf6ae](home-operations/kopiur@accf6ae))
- writable backup source ([#&#8203;254](home-operations/kopiur#254)), stuck deletion finalizer ([#&#8203;255](home-operations/kopiur#255)), kopia epoch parameters ([#&#8203;258](home-operations/kopiur#258)) ([#&#8203;263](home-operations/kopiur#263)) ([94f9a15](home-operations/kopiur@94f9a15))

##### Miscellaneous Chores

- **krew:** kopiur 0.7.5 manifest ([16d7390](home-operations/kopiur@16d7390))
- **mise:** Update tool aqua:astral-sh/uv (0.11.28 → 0.11.29) ([#&#8203;256](home-operations/kopiur#256)) ([f822335](home-operations/kopiur@f822335))
- **mise:** Update tool cosign (3.1.1 → 3.1.2) ([#&#8203;267](home-operations/kopiur#267)) ([649ba2e](home-operations/kopiur@649ba2e))

### [`v0.7.5`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#075-2026-07-16)

[Compare Source](home-operations/kopiur@0.7.4...0.7.5)

##### Bug Fixes

- **readme:** update readme admonition ([987957c](home-operations/kopiur@987957c))
- **secctx:** SecurityContextCompatible is verified, not asserted; inherit merges with explicit ([#&#8203;259](home-operations/kopiur#259)) ([cbfd97d](home-operations/kopiur@cbfd97d))

### [`v0.7.4`](https://github.com/home-operations/kopiur/blob/HEAD/CHANGELOG.md#074-2026-07-15)

[Compare Source](home-operations/kopiur@0.7.3...0.7.4)

##### Features

- bound Snapshot CR reconcile cost at scale ([#&#8203;249](home-operations/kopiur#249)) ([#&#8203;253](home-operations/kopiur#253)) ([3697aa9](home-operations/kopiur@3697aa9))
- **docs:** clean up admonitions in docs ([71c5d85](home-operations/kopiur@71c5d85))
- multi-cluster shared repository support — cluster identity, foreign-aware discovery, collision-free maintenance leases ([#&#8203;251](home-operations/kopiur#251)) ([a4593d8](home-operations/kopiur@a4593d8))

##### Bug Fixes

- batch of six open-issue fixes ([#&#8203;248](home-operations/kopiur#248) [#&#8203;238](home-operations/kopiur#238) [#&#8203;245](home-operations/kopiur#245) [#&#8203;196](home-operations/kopiur#196) [#&#8203;237](home-operations/kopiur#237) [#&#8203;250](home-operations/kopiur#250)) ([#&#8203;252](home-operations/kopiur#252)) ([045a5a5](home-operations/kopiur@045a5a5))
- **deps:** update rust crate http-body-util (0.1.3 → 0.1.4) ([#&#8203;244](home-operations/kopiur#244)) ([4878006](home-operations/kopiur@4878006))
- **deps:** update rust crate rustls (0.23.41 → 0.23.42) ([#&#8203;243](home-operations/kopiur#243)) ([13b1dc4](home-operations/kopiur@13b1dc4))

##### Miscellaneous Chores

- **krew:** kopiur 0.7.3 manifest ([d521675](home-operations/kopiur@d521675))
- **mise:** Update tool aqua:EmbarkStudios/cargo-deny (0.19.9 → 0.20.2) ([#&#8203;226](home-operations/kopiur#226)) ([9c109b4](home-operations/kopiur@9c109b4))
- **mise:** Update tool oxfmt (0.58.0 → 0.59.0) ([#&#8203;246](home-operations/kopiur#246)) ([41625e9](home-operations/kopiur@41625e9))
- **mise:** Update tool zizmor (1.26.1 → 1.27.0) ([#&#8203;247](home-operations/kopiur#247)) ([fe10a6a](home-operations/kopiur@fe10a6a))

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/London)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjAuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjQ2LjUiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbInJlbm92YXRlL2NvbnRhaW5lciIsInR5cGUvbWlub3IiXX0=-->

Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/273
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant