Skip to content

feat(api)!: add GitTarget.spec.prune.mode and stop inferring deletions#260

Merged
sunib merged 5 commits into
mainfrom
feat/gittarget-prune-mode-pr5
Jul 21, 2026
Merged

feat(api)!: add GitTarget.spec.prune.mode and stop inferring deletions#260
sunib merged 5 commits into
mainfrom
feat/gittarget-prune-mode-pr5

Conversation

@sunib

@sunib sunib commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Phase 5 of source-namespace addressing, and the
change that makes PR #259's breaking scope rework non-destructive. It closes the do-not-release
window #259 opened.

The problem

A GitTarget removes a Git document for one of two very different reasons:

  • the source cluster told us the resource is gone — an explicit DELETE event;
  • a resync snapshot did not list it, so its absence was inferred (mark-and-sweep).

The second is only as trustworthy as the snapshot, and a scope mistake, a source-cluster outage, or
a narrowed RBAC grant all produce a snapshot smaller than reality. Until now both paths were always
on, so a bad watch scope deleted a tenant's manifests.

The change

GitTarget.spec.prune.mode controls the two paths separately:

Mode Explicit source DELETE Resync mark-and-sweep
never kept kept
onEvent — effective default mirrored kept
always — previous behaviour mirrored swept

Deleting a resource in the cluster still deletes its file. Only the inference changes.

The CRD default is deliberately not the compatibility mechanism: Kubernetes does not retro-fill
defaults into stored objects, so a GitTarget written before this field existed reaches the
controller with a nil spec.prune forever. Every reader goes through EffectivePruneMode()
instead, so an existing target becomes safe without being edited. The field is mutable, unlike
the destination fields, so a target can move to always once its scope is confirmed rather than
being deleted and recreated.

Where the gates sit, and why

  • Sweep — at the planner. A suppressed drop is never planned, not planned-then-filtered, so it
    cannot reach the plan's action list, its ordering, or the commit path.
  • Explicit delete — first in applyDelete, before the document is even located, so a retention
    touches no buffer, records no write intent, and cannot turn the kustomize oracle on. A retention
    has to be indistinguishable from the event never arriving.

Two zero values, both failing safe toward retaining

  • manifestanalyzer.SweepMode's zero value retains, so a planner caller added later that forgets
    the field keeps documents rather than deleting them. Every production caller sets it explicitly
    regardless.
  • PruneMode("") is unset, not never. Read literally it answers false to both predicates,
    which is never's behaviour and would silently stop mirroring deletes — so every internal carrier
    normalizes through OrDefault(). An unrecognized value (a downgrade from a newer build) is left
    alone and retains on both paths.

Surfacing retention

A suppressed drop leaves no other trace — no action, no commit, no ResyncStats entry — so
Plan.RetainedOrphans counts it, feeding a throttled log (one per target folder per 10 minutes,
since retention is a steady state while resyncs fire per type and namespace) and
gitopsreverser_prune_retained_documents_total. Retention is the configured outcome, so nothing
here raises a condition or a failure count.

Worth a second opinion before merge

Under onEvent the operator no longer removes a stray managed document a human added to the folder
by hand — the drift-heal resync runs through the same planner. This follows from the design rather
than extending it, but "the reverser stopped cleaning up junk I put in the folder" is the same
mechanism as "the reverser did not delete my tenant's manifests when the scope collapsed". It is
recorded in the design doc's Implementation notes and in UPGRADING.md; always restores both.

Tests

Unit coverage spans the API contract (legacy default, the documented two-path table, unset-is-not-
never, unrecognized-value-retains), the planner (a retained drop is absent from the whole plan,
scoped and unscoped alike, and retention is distinguished from an empty scope), and the writer
(both paths under all three modes, plus the unresolvable-target fallback and the log throttle).

Three e2e specs run three GitTargets in one repo, one per policy. Every retention assertion is
paired with a barrier, because "the file is still there" also passes when the pipeline is
asleep:

  • API contract — an omitted prune block stays absent (so the legacy case is genuinely
    exercised rather than silently defaulted), prune: {} defaults to onEvent, and a value outside
    the enum is rejected.
  • Delete path — the default target removing its copy proves the event reached the writer; only
    then is the never target's surviving copy asserted.
  • Sweep path — an orphan is pushed straight into the repo and ConfigMaps are toggled off/on to
    force a scoped replay resync. The always target's sweep proves a resync ran with the orphan in
    scope; only then is the default target's retained copy asserted, followed by a fresh ConfigMap to
    prove retention is not a stalled pipeline.

Validation

task lint, task test, and task test-e2e all pass. e2e: 65 passed, 0 failed, with all three
new specs confirmed passed in the Ginkgo report. Unit coverage is within the ratchet tolerance of
the committed baseline.

BREAKING CHANGE: a resync no longer deletes Git documents whose resources are absent from the
desired snapshot. Set spec.prune.mode: always on a GitTarget that needs full desired-state
convergence.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added GitTarget.spec.prune.mode with never, onEvent (effective default), and always.
    • Deletion behavior now distinguishes explicit source DELETEs from resync-inferred mark-and-sweep orphan deletions (resync deletions are suppressed under the default).
    • Added retention visibility via GitTarget.status.retention, plus throttled logs and a retention metric.
  • Bug Fixes
    • Improved prune/retention reporting accuracy during resync and replay scenarios.
  • Documentation
    • Updated upgrade and configuration docs to cover the new prune policy and how to validate impact.

A GitTarget removes a Git document for one of two very different reasons: the
source cluster told us the resource is gone (an explicit DELETE event), or a
resync snapshot did not list it and its absence was INFERRED. The second is only
as trustworthy as the snapshot, and a scope mistake, a source-cluster outage, or
a narrowed RBAC grant all produce a snapshot smaller than reality.

spec.prune.mode controls the two paths separately — never / onEvent / always —
and the effective default is onEvent: observed deletes are still mirrored,
inferred ones are not. `always` restores the previous mark-and-sweep.

The CRD default is not the compatibility mechanism. Kubernetes does not
retro-fill defaults into stored objects, so a GitTarget written before this
field existed reaches the controller with a nil spec.prune forever; every reader
goes through EffectivePruneMode instead, and an existing target becomes safe
without being edited. The field is deliberately mutable, unlike the destination
fields, so a target can be moved to `always` once its scope is confirmed.

The sweep is gated at the PLANNER, not at apply time: a suppressed drop is never
planned, so it cannot reach the plan's action list, its ordering, or the commit.
The explicit delete is gated first in applyDelete, before the document is even
located, so a retention touches no buffer and cannot turn the kustomize oracle
on. A retention must be indistinguishable from the event never arriving.

Two zero values needed deciding, and both fail safe in the direction that
retains:

  - manifestanalyzer.SweepMode's zero value RETAINS, so a caller added later
    that forgets the field keeps documents rather than deleting them. Every
    production caller sets it explicitly anyway.
  - PruneMode("") is UNSET, not `never`. Read literally it answers false to both
    predicates, which would silently stop mirroring deletes, so every internal
    carrier normalizes through OrDefault. An unrecognized value is left alone
    and retains on both paths.

Retention is otherwise invisible — no action, no commit, no ResyncStats entry —
so Plan.RetainedOrphans counts it, feeding a throttled log and
gitopsreverser_prune_retained_documents_total. It is the configured outcome, so
nothing here raises a condition or a failure count.

BREAKING CHANGE: a resync no longer deletes Git documents whose resources are
absent from the desired snapshot. Set spec.prune.mode: always on a GitTarget
that needs full desired-state convergence. Deleting a resource in the cluster
still deletes its file. Ships in the same release as the rule-kind scope change,
which it exists to make non-destructive.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sunib, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cbb5de78-2136-4baf-a5a2-89ad5cc4fc4e

📥 Commits

Reviewing files that changed from the base of the PR and between 2cfdf7d and b9c2e49.

📒 Files selected for processing (15)
  • api/v1alpha3/gittarget_types.go
  • api/v1alpha3/prune_policy.go
  • api/v1alpha3/prune_policy_test.go
  • config/crd/bases/configbutler.ai_gittargets.yaml
  • docs/UPGRADING.md
  • docs/configuration.md
  • docs/design/watchrule-source-namespace/README.md
  • docs/design/watchrule-source-namespace/pr4-cluster-scope-only.md
  • docs/design/watchrule-source-namespace/pr5-gittarget-deletion-safety.md
  • docs/design/watchrule-source-namespace/pr5-retention-visibility.md
  • docs/design/watchrule-source-namespace/pr5-review-followups.md
  • docs/future/flux-maintainer-review-status-and-config-model.md
  • internal/git/resync_flush.go
  • internal/git/retention_report_test.go
  • test/e2e/prune_mode_e2e_test.go
📝 Walkthrough

Walkthrough

Adds configurable GitTarget.spec.prune.mode behavior, separating explicit DELETE mirroring from inferred resync orphan sweeps. Retained documents are counted and exposed through status, logs, and telemetry, with planner, watch, documentation, and end-to-end coverage.

Changes

GitTarget prune policy

Layer / File(s) Summary
Prune API contract
api/v1alpha3/..., config/crd/...
Defines prune modes and effective defaults, adds retention status, CRD validation/defaulting, deepcopy support, and API tests.
Planner sweep policy
internal/manifestanalyzer/..., cmd/manifest-analyzer/main.go, pkg/manifestanalyzer/folder.go
Adds explicit sweep modes, suppresses inferred orphan drops when retaining, counts retained orphans, and enables convergence for folder scans.
Git write and resync paths
internal/git/...
Threads prune modes through event and resync flows, gates DELETE handling, tightens replay decisions, and reports retained documents.
Watch retention rollup
internal/watch/..., internal/controller/...
Tracks mode transitions and per-scope retention, projects rollups into GitTarget status, and clears stale target state.
Telemetry and end-to-end validation
internal/telemetry/..., test/e2e/...
Registers retained-document telemetry and verifies API, deletion, retention, and mode-transition behavior end to end.
Documentation and design records
docs/...
Documents upgrade behavior, configuration, retention visibility, implementation follow-ups, and related status compatibility findings.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the repository template or include the required checkbox sections and issue/screenshot fields. Rewrite the PR description using the required template sections, including Type of Change, Testing, Checklist, Related Issues, Screenshots, and Additional Notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the core API and behavior change in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 88.06% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gittarget-prune-mode-pr5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.34615% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
api/v1alpha3/prune_policy.go 50.0% 14 Missing ⚠️
internal/git/branch_worker.go 92.3% 1 Missing and 1 partial ⚠️
internal/git/resync_flush.go 96.0% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

PR 5 made a suppressed sweep the default, and a suppressed sweep produces no
plan action, no commit, and no ResyncStats entry — deliberately, since a
retention must be indistinguishable from the event never arriving. The
consequence is that "converged" and "deliberately retaining stale documents"
look identical from kubectl, from git log, and from the folder itself.

Today the only signal that names the target is a throttled log line: the
retention metric is labelled by prune_mode alone, so it cannot answer which
GitTarget is keeping anything.

This is the plan for closing that, to be implemented in this same PR. It
proposes a status.retention roll-up (an observation, never a condition — PR 5's
"healthy, not failed" rule still holds), records that the eviction problem is
already solved by RenderFidelityGate's epoch mechanism rather than needing a
second scope tracker, states the pull-based projection's staleness property
instead of hiding it, and separates the cheap metric-label fix from the status
work so the two can be sequenced independently.

Open questions are listed rather than decided: whether `never` should also
report its suppressed explicit deletes, whether zero-vs-absent earns its extra
state, whether to enqueue on 0->n given the flake history in this projection
class, and whether to ship only the metric fix if closing the release window
matters more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/e2e/prune_mode_e2e_test.go`:
- Around line 94-95: Remove the package-wide SetDefaultEventuallyTimeout and
SetDefaultEventuallyPollingInterval calls from the prune-mode spec setup.
Configure the 60-second timeout and 2-second polling interval on the relevant
local Eventually calls, or move the shared defaults to the suite bootstrap so
spec execution order cannot affect them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c50fca73-f0f6-48b5-836e-d616c7d5c5f4

📥 Commits

Reviewing files that changed from the base of the PR and between 982e816 and f37a7ba.

📒 Files selected for processing (39)
  • api/v1alpha3/gittarget_types.go
  • api/v1alpha3/prune_policy.go
  • api/v1alpha3/prune_policy_test.go
  • api/v1alpha3/zz_generated.deepcopy.go
  • cmd/manifest-analyzer/main.go
  • config/crd/bases/configbutler.ai_gittargets.yaml
  • docs/UPGRADING.md
  • docs/configuration.md
  • docs/design/watchrule-source-namespace/README.md
  • docs/design/watchrule-source-namespace/pr5-gittarget-deletion-safety.md
  • docs/design/watchrule-source-namespace/pr5-retention-visibility.md
  • internal/git/acceptance_gate_test.go
  • internal/git/branch_worker.go
  • internal/git/branch_worker_split_test.go
  • internal/git/commit_executor.go
  • internal/git/fieldpatch_flush_test.go
  • internal/git/inplace_edit_test.go
  • internal/git/kustomize_oracle_test.go
  • internal/git/pending_writes.go
  • internal/git/placement_test.go
  • internal/git/plan_flush.go
  • internal/git/plan_flush_test.go
  • internal/git/prune_mode_test.go
  • internal/git/render_fidelity_test.go
  • internal/git/render_scope_test.go
  • internal/git/resync_flush.go
  • internal/git/resync_flush_test.go
  • internal/git/resync_scope_test.go
  • internal/git/types.go
  • internal/git/write_boundary_precondition_test.go
  • internal/manifestanalyzer/plan.go
  • internal/manifestanalyzer/plan_test.go
  • internal/manifestanalyzer/scan.go
  • internal/manifestanalyzer/scoped_plan_test.go
  • internal/manifestanalyzer/sweep_policy_test.go
  • internal/telemetry/exporter.go
  • pkg/manifestanalyzer/folder.go
  • test/e2e/prune_mode_e2e_test.go
  • test/e2e/templates/manager/gittarget-prune.tmpl

Comment thread test/e2e/prune_mode_e2e_test.go
sunib and others added 2 commits July 21, 2026 15:13
…kept

Two policy-TRANSITION defects found by review of #260, plus the retention
visibility half of PR 5. The shipped implementation applied spec.prune.mode
correctly wherever it was read; both defects were about what happens when the
value CHANGES, in opposite directions.

R1 — widening to `always` did not converge a quiet target. The watch set is
keyed by what is watched, not by what may be deleted, so a prune edit replaced
nothing, and a reconnect resumes from its cursor rather than replaying. The only
production path that enqueues a resync is a fresh replay, so a target could sit
under `always` indefinitely without sweeping. DeclareForGitTarget now takes the
effective mode and forces a replay on the edge INTO a sweeping mode: edge- not
level-triggered, and deliberately one-directional.

R2 — tightening away from `always` could be outrun by a write already committed
locally. A rebase replay re-plans against the rebased worktree under the policy
captured at planning time, so it can decide deletions the first apply never
made. tightenPendingPruneModes lowers each retained write to the more
restrictive of (captured, current) before the replay. Loosening still does not
escalate a planned write; an unreadable policy returns an error so the push
retries rather than guessing; a deleted GitTarget replays under `never`.

R3 — the retention log and metric now name the GitTarget, which both docs
already promised. The throttle key was the same defect: a path is not an
identity.

R4 — the docs claimed a failure mode the code does not have. The gather is
fail-closed on a failed list/watch and on a replay cut short before its
initial-events bookmark, so an outage stops a sweep rather than shrinking one.
The real risk is a snapshot that is complete but scoped wrong.

status.retention reports the effective mode and a bounded retained count,
epoch-keyed off the render-fidelity epoch so a scope leaving the watch plan
takes its count with it. Absent means nothing has reported; zero means a resync
found nothing to retain.

Gates: lint 0 issues, unit coverage 77.8% (at baseline), e2e 75 passed /
22 skipped / 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`GitTarget.spec.prune.mode` accepted `never` / `onEvent` / `always`. Kubernetes
API conventions call for UpperCamelCase enum values — `imagePullPolicy:
Always|Never|IfNotPresent`, `persistentVolumeReclaimPolicy: Retain|Delete` — so
the values are now `Never` / `OnEvent` / `Always`.

Taken now because now is the last moment it is free. The field is new and
unreleased, and this branch already carries two breaking API commits, so the
rename rides along in a release users must read UPGRADING.md for anyway. After
that release it would be a second migration bought for a cosmetic gain, which
is a price nobody would pay — leaving the non-conventional spelling permanent.

The typed constants (PruneNever, PruneOnEvent, PruneAlways) keep their names, so
no Go call site moves. What changes is the wire value: the CRD enum, the CRD
default, and every doc, sample and message that quotes one.

Found as F12 in docs/future/flux-maintainer-review-status-and-config-model.md,
which is updated to record this item as the one taken pre-release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
internal/watch/retention_rollup_test.go (1)

21-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename tests to follow the TestFunctionName_Scenario convention.

As per coding guidelines, tests must follow the naming convention TestFunctionName_Scenario(t *testing.T). These tests currently use conceptual component names rather than the exact function names being tested.

  • internal/watch/retention_rollup_test.go#L21-L23: Rename tests to use the function name, e.g., TestRetentionForGitTarget_SumsEveryScope or TestMarkTargetRetention_SumsEveryScope.
  • internal/watch/prune_declaration_test.go#L91-L93: Rename the test to use the function name, e.g., TestRememberGitTargetPruneMode_IsPerGitTarget.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/watch/retention_rollup_test.go` around lines 21 - 23, Rename the
test in internal/watch/retention_rollup_test.go:21-23 to use the exact function
under test with the _SumsEveryScope scenario suffix, choosing the appropriate
symbol such as RetentionForGitTarget or MarkTargetRetention. Also rename the
test in internal/watch/prune_declaration_test.go:91-93 to follow the exact
function name RememberGitTargetPruneMode with the _IsPerGitTarget suffix.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/design/watchrule-source-namespace/pr5-gittarget-deletion-safety.md`:
- Around line 126-130: Update the retention-observability paragraph to replace
the ambiguous “no stat” wording with the precise absence of a managed-drop
action, commit, or ResyncStats entry. Preserve that suppressed retention remains
observable through the throttled log line, retention metrics, and
status.retention, consistent with Plan.RetainedOrphans and the linked retention
visibility page.

In `@docs/design/watchrule-source-namespace/pr5-retention-visibility.md`:
- Around line 34-53: Mark the outdated retention-signal discussion as explicitly
pre-Step-0 historical context, or revise it to reflect the implemented target
identity in both the throttled log and metric. Update the “today” claim and
surrounding statements about missing GitTarget identity while preserving the
table’s purpose as historical rationale.

In `@internal/watch/retention_rollup.go`:
- Around line 79-91: Update the comment above priorTotal and priorMode to
acknowledge that resetting scopes on a newer epoch temporarily produces partial
totals, may mark each incoming scope as changed, and can trigger extra
reconciles for multi-scope targets; do not imply that the existing logic fully
debounces same-total epoch replacements.

---

Nitpick comments:
In `@internal/watch/retention_rollup_test.go`:
- Around line 21-23: Rename the test in
internal/watch/retention_rollup_test.go:21-23 to use the exact function under
test with the _SumsEveryScope scenario suffix, choosing the appropriate symbol
such as RetentionForGitTarget or MarkTargetRetention. Also rename the test in
internal/watch/prune_declaration_test.go:91-93 to follow the exact function name
RememberGitTargetPruneMode with the _IsPerGitTarget suffix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ac45046-b078-47d1-bcc3-da13ce89535f

📥 Commits

Reviewing files that changed from the base of the PR and between f37a7ba and 2cfdf7d.

📒 Files selected for processing (34)
  • api/v1alpha3/gittarget_types.go
  • api/v1alpha3/prune_policy.go
  • api/v1alpha3/zz_generated.deepcopy.go
  • config/crd/bases/configbutler.ai_gittargets.yaml
  • docs/UPGRADING.md
  • docs/configuration.md
  • docs/design/watchrule-source-namespace/README.md
  • docs/design/watchrule-source-namespace/pr5-gittarget-deletion-safety.md
  • docs/design/watchrule-source-namespace/pr5-retention-visibility.md
  • docs/design/watchrule-source-namespace/pr5-review-followups.md
  • docs/future/flux-maintainer-review-status-and-config-model.md
  • internal/controller/gittarget_controller.go
  • internal/controller/gittarget_source_cluster_test.go
  • internal/controller/gittarget_status_test.go
  • internal/git/branch_worker.go
  • internal/git/placement_test.go
  • internal/git/prune_mode_test.go
  • internal/git/prune_replay_test.go
  • internal/git/render_fidelity_test.go
  • internal/git/resync_flush.go
  • internal/git/resync_flush_test.go
  • internal/git/resync_scope_test.go
  • internal/git/retention_report_test.go
  • internal/git/types.go
  • internal/telemetry/exporter.go
  • internal/watch/event_router.go
  • internal/watch/manager.go
  • internal/watch/materialization.go
  • internal/watch/prune_declaration.go
  • internal/watch/prune_declaration_test.go
  • internal/watch/retention_rollup.go
  • internal/watch/retention_rollup_test.go
  • internal/watch/target_watch.go
  • test/e2e/prune_mode_e2e_test.go
🚧 Files skipped from review as they are similar to previous changes (10)
  • docs/UPGRADING.md
  • docs/configuration.md
  • internal/telemetry/exporter.go
  • internal/git/resync_scope_test.go
  • docs/design/watchrule-source-namespace/README.md
  • internal/git/placement_test.go
  • internal/git/resync_flush_test.go
  • internal/git/resync_flush.go
  • internal/git/prune_mode_test.go
  • internal/git/render_fidelity_test.go

Comment thread docs/design/watchrule-source-namespace/pr5-retention-visibility.md
Comment thread internal/watch/retention_rollup.go
Follow-up to the enum rename: three test comments and two Ginkgo spec
descriptions still named the modes in the old lowercase. No behavior change —
these are prose, not values — but a comment that quotes a wire value nobody can
apply is worse than no comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sunib
sunib merged commit f103ca8 into main Jul 21, 2026
18 of 19 checks passed
@sunib
sunib deleted the feat/gittarget-prune-mode-pr5 branch July 21, 2026 16:14
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