Skip to content

feat!: replace ClusterProvider with a namespaced KubeProvider - #430

Merged
sunib merged 10 commits into
mainfrom
feat/kubeprovider
Oct 22, 2026
Merged

sunib merged 10 commits into
mainfrom
feat/kubeprovider

Conversation

@sunib

@sunib sunib commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Phase 2 of docs/design/gitbranch-and-kubeprovider-plan.md.

What changes

  • KubeProvider is a namespaced kind naming a source cluster. ClusterProvider and its CRD are removed; a CRD cannot change scope, so nothing converts one. Every ClusterProvider* reason and identifier is renamed with no alias: KubeProviderNotFound, KubeProviderReady, KubeProviderNotReady.
  • One credential arm. spec takes exactly one of secretRef (a kubeconfig Secret in the provider's own namespace) and inCluster: {}, enforced by CEL and immutable. client is refused beside inCluster. The kubeconfig safety checks and their reasons are unchanged.
  • inCluster needs the installation's consent. --in-cluster-kube-providers takes exact namespace/name entries (chart: controllerManager.inClusterKubeProviders, plus the chart's own provider added automatically). An unlisted provider reports Validated=False, Stalled=True (InClusterNotAuthorized).
    • The refusal sits in the watch plane's source-cluster resolver, which every credential use goes through: watches, discovery and the appliers. No path leads from an omitted, malformed or refused credential to the operator's identity.
    • On a GitTarget the refusal shows as SourceClusterReachable=False with that reason, because a refused credential keeps its own reason instead of reading as unreachable.
  • Own namespace admitted. A provider admits consumers in its own namespace; accessFrom admits the others. allowCrossNamespace keeps its meaning.
  • References are required. kubeProviderRef is a required, immutable NamespacedObjectReference on GitTarget, FluxTrigger and ArgoCDTrigger, with no default fallback. clusterProviderRef stays in each schema only to be refused by name, the pattern phase 1 used for the branch fields.
  • Audit routes are held per namespace. An omitted auditRoute is <namespace>.<name>.
    • A route is held by the namespace its first segment names, when a provider there carries it, and otherwise by the namespace of the oldest carrier.
    • A provider elsewhere that carries it reports Stalled=True (AuditRouteConflict), and authz refuses everything that would mirror or apply through it.
    • The first-segment precedence closes a squat the plan did not cover: a tenant spelling out another namespace's default route before that namespace's provider exists.
    • Its siblings on the route are woken when the holder goes or moves.
  • Watch plane keyed by namespace/name. Source-cluster contexts and the source_cluster metric label carry <namespace>/<name>. The resolver's version token carries the provider UID, and refresh now re-resolves in-cluster sources as well. A provider deleted and recreated under the same name therefore rebuilds its clients and re-plans its watches, whichever credential arm it uses. A provider dropped from the allow-list loses the operator's identity on the next refresh.
  • Chart. It renders a KubeProvider named local in the release namespace and lists it in the flag. The quick start references it. clusterProvider.* values are gone; kubeProvider.* replaces them.

Also fixed: an unadmitted GitTarget could hold a path

Not about KubeProvider; it dates from #428/#429. The overlap check counted every GitTarget on a GitBranch, admitted or not, and the older one won. Any namespace that knew a GitBranch's name could therefore create a GitTarget at . first and block every folder on that branch with TargetConflict. An existing target now holds its path only while its GitProvider and KubeProvider admit it. The docs also say that an accessFrom label selector is only as strong as the right to set the labels it matches.

Decisions taken with the reviewer before coding

  • Flag: --in-cluster-kube-providers / controllerManager.inClusterKubeProviders.
  • Chart provider name: local.
  • Field name: secretRef, per definitions.md rule 2.
  • Audit route conflict: a reconcile-time condition, not an admission refusal. where-validation-lives.md puts cross-object checks in the reconciler, the webhook is optional and fail-open, and admission cannot see a provider that changes its route later. The plan's "Done when" line is updated to match.

Commits

  1. API added beside the old field (optional reference), so no fixture is converted twice.
  2. Readers switched, including the watch-plane keying and the reason renames. These are folded in because keying by namespace/name only means something once providers are namespaced, and the renamed conditions live in the code paths this commit rewrites.
  3. ClusterProvider removed; the reference required and the old field refused.
  4. Docs, chart README, samples, layout corpus, UPGRADING and the e2e suite.

Upgrade impact

Audit routes default to <namespace>.<name>, so the chart's provider posts under /audit-webhook/<release namespace>.local, and the API server's audit webhook URL must change. The e2e cluster config does too; locally that means one task clean-cluster. UPGRADING.md has the full migration.

Tests

  • Unit: new tests for the resolver (consent, Secret namespace, UID in the version), source-context rebuild on recreate, route holding, the sibling wake handler, admission and the flag. task lint and task test pass.
  • e2e: the source-cluster corner covers each "Done when" item:
    • a tenant's own provider and Secret, and a second tenant refused without accessFrom;
    • an unlisted inCluster provider;
    • two same-named providers in two namespaces across a recreate (kcp).
  • The audit-route spec covers a provider borrowing the operator namespace's route.

🤖 Generated with Claude Code

sunib and others added 4 commits October 22, 2026 07:09
KubeProvider is a namespaced source cluster: a kubeconfig Secret in its own
namespace (spec.secretRef) or the operator's own identity (spec.inCluster),
exactly one of the two, immutable. It admits its own namespace and those
spec.accessFrom admits, and an omitted audit route is <namespace>.<name>.

GitTarget, FluxTrigger and ArgoCDTrigger gain spec.kubeProviderRef. Nothing
reads either yet; the references are optional until clusterProviderRef goes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every reader names its source cluster with spec.kubeProviderRef: the
GitTarget reconciler and its watch plane, the WatchRule and ClusterWatchRule
gates, and the apply triggers. ClusterProvider is no longer read.

A KubeProvider's kubeconfig Secret is read from its own namespace. Its
spec.inCluster is honored only for a provider listed in the new
--in-cluster-kube-providers flag; the resolver refuses any other with
InClusterNotAuthorized, so the watches, discovery and appliers cannot reach
the operator's identity through it. A provider admits its own namespace, and
accessFrom admits the others. An omitted audit route is <namespace>.<name>,
and a provider carrying a route another namespace holds reports
AuditRouteConflict and is not used; its siblings on the route are woken when
the holder goes.

The watch plane keys a source cluster by the provider's namespace/name, and
the resolver's version carries the provider's UID, so a provider recreated
under the same name, inCluster ones included, rebuilds its clients. A
credential the operator refuses keeps its reason on SourceClusterReachable.

The ClusterProvider reasons and conditions are renamed: KubeProviderNotFound,
KubeProviderReady, KubeProviderNotReady. The chart renders a KubeProvider
named local in the release namespace and lists it in the flag.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ClusterProvider kind and its CRD are removed: a CRD cannot change its
scope, so nothing converts one into a KubeProvider. GitTarget, FluxTrigger
and ArgoCDTrigger require spec.kubeProviderRef, with no default.
spec.clusterProviderRef stays in each schema only so that setting it is
refused, naming spec.kubeProviderRef. The triggers' Cluster column reads
spec.kubeProviderRef.

The settings index describes the KubeProvider.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… suite to it

The reference docs, chart README, samples and layout corpus name a
namespaced KubeProvider: its secretRef or inCluster credential, the
--in-cluster-kube-providers consent, own-namespace admission, and audit
routes held per namespace. UPGRADING describes the migration from
ClusterProvider.

The e2e install's KubeProvider is local in the operator's namespace, so the
cluster's audit webhook posts under /audit-webhook/gitops-reverser.local.
Every e2e GitTarget and trigger names it. The source-cluster corner covers a
tenant's own provider and Secret, a second tenant refused without
accessFrom, an inCluster provider the installation does not list, and two
same-named providers in two namespaces kept apart across a recreate. The
audit-route spec moves its dedicated provider into the operator's namespace
and covers a provider borrowing that namespace's route.

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

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 226 files, which is 126 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9f6c98cf-35da-4f5b-abb9-8b612c24bd55
📥 Commits

Reviewing files that changed from the base of the PR and between 17c0c96 and b99a443.

📒 Files selected for processing: 226
  • .vale/styles/HouseStyle/Headings.yml
  • PROJECT
  • README.md
  • SECURITY.md
  • Taskfile-build.yml
  • api/v1alpha3/applytrigger_types.go
  • api/v1alpha3/argocdtrigger_types.go
  • api/v1alpha3/audit_route_test.go
  • api/v1alpha3/clusterprovider_types.go
  • api/v1alpha3/clusterwatchrule_types.go
  • api/v1alpha3/examples_test.go
  • api/v1alpha3/fluxtrigger_types.go
  • api/v1alpha3/gittarget_types.go
  • api/v1alpha3/helpers_test.go
  • api/v1alpha3/kubeprovider_types.go
  • api/v1alpha3/kubeprovider_types_test.go
  • api/v1alpha3/namespace_matcher.go
  • api/v1alpha3/namespace_matcher_test.go
  • api/v1alpha3/status_accessors.go
  • api/v1alpha3/status_accessors_test.go
  • api/v1alpha3/watchrule_types.go
  • api/v1alpha3/zz_generated.deepcopy.go
  • charts/gitops-reverser/README.md
  • charts/gitops-reverser/templates/NOTES.txt
  • charts/gitops-reverser/templates/_helpers.tpl
  • charts/gitops-reverser/templates/clusterprovider-default.yaml
  • charts/gitops-reverser/templates/deployment.yaml
  • charts/gitops-reverser/templates/kubeprovider.yaml
  • charts/gitops-reverser/templates/quickstart.yaml
  • charts/gitops-reverser/templates/validate-quickstart.yaml
  • charts/gitops-reverser/values.schema.json
  • charts/gitops-reverser/values.yaml
  • cmd/main.go
  • cmd/main_audit_server_test.go
  • cmd/main_chart_notes_test.go
  • cmd/mutation-capture-lab/main.go
  • config/clusterprovider-default.yaml
  • config/crd/bases/configbutler.ai_argocdtriggers.yaml
  • config/crd/bases/configbutler.ai_clusterwatchrules.yaml
  • config/crd/bases/configbutler.ai_fluxtriggers.yaml
  • config/crd/bases/configbutler.ai_gittargets.yaml
  • config/crd/bases/configbutler.ai_kubeproviders.yaml
  • config/crd/bases/configbutler.ai_watchrules.yaml
  • config/crd/kustomization.yaml
  • config/deployment.yaml
  • config/kubeprovider-local.yaml
  • config/kustomization.yaml
  • config/rbac/role.yaml
  • config/samples/README.md
  • config/samples/argocdtrigger.yaml
  • config/samples/clusterprovider.yaml
  • config/samples/fluxtrigger.yaml
  • config/samples/kubeprovider.yaml
  • config/samples/quickstart-gittarget.yaml
  • docs/INDEX.md
  • docs/UPGRADING.md
  • docs/api-first-publication.md
  • docs/architecture.md
  • docs/attribution-setup-guide.md
  • docs/audit-setup/cluster/audit/webhook-config.yaml
  • docs/azure-devops-getting-started.md
  • docs/bi-directional-argocd.md
  • docs/bi-directional-flux.md
  • docs/bi-directional.md
  • docs/components.md
  • docs/config-flag-conventions.md
  • docs/configuration.md
  • docs/definitions.md
  • docs/demo/commit-window.md
  • docs/design/apply-trigger.md
  • docs/design/gitbranch-and-kubeprovider-plan.md
  • docs/facts/subresources.md
  • docs/finished/multi-cluster-author-attribution.md
  • docs/interpreting-metrics.md
  • docs/layout/new-file-placement-rules.md
  • docs/quickstart.md
  • docs/rbac.md
  • docs/security-model.md
  • docs/spec/attribution.md
  • docs/spec/e2e-bi-directional-corner.md
  • docs/spec/status-conditions-guide.md
  • docs/spec/where-validation-lives.md
  • docs/style-guide.md
  • hack/crdfields/fields.yaml
  • hack/e2e/install-plain-manifests.sh
  • hack/generate-audit-webhook-kubeconfig.sh
  • internal/applier/applier.go
  • internal/applier/dispatcher.go
  • internal/applier/dispatcher_test.go
  • internal/applier/flux_guide_test.go
  • internal/applier/holder.go
  • internal/applier/holder_test.go
  • internal/applier/references.go
  • internal/applier/status_writer_test.go
  • internal/attribution/route_health.go
  • internal/audit/outcome/outcome.go
  • internal/authz/gitbranch_admission_test.go
  • internal/authz/kubeprovider_admission.go
  • internal/authz/kubeprovider_admission_test.go
  • internal/authz/source_namespace.go
  • internal/authz/source_namespace_test.go
  • internal/controller/applytrigger_controller.go
  • internal/controller/applytrigger_controller_test.go
  • internal/controller/applytrigger_holder_test.go
  • internal/controller/applytrigger_rule_test.go
  • internal/controller/applytrigger_schema_test.go
  • internal/controller/clusterprovider_controller.go
  • internal/controller/clusterprovider_controller_test.go
  • internal/controller/clusterprovider_controller_unit_test.go
  • internal/controller/clusterprovider_predicate_test.go
  • internal/controller/clusterwatchrule_admission_test.go
  • internal/controller/clusterwatchrule_controller.go
  • internal/controller/commitrequest_outcome_metric_test.go
  • internal/controller/constants.go
  • internal/controller/dependency_watches_test.go
  • internal/controller/duration_fields_admission_test.go
  • internal/controller/fluxtrigger_hold.go
  • internal/controller/fluxtrigger_hold_test.go
  • internal/controller/gitbranch_controller_test.go
  • internal/controller/gittarget_apply_trigger_test.go
  • internal/controller/gittarget_controller.go
  • internal/controller/gittarget_controller_test.go
  • internal/controller/gittarget_controller_unit_test.go
  • internal/controller/gittarget_dependency_status.go
  • internal/controller/gittarget_gitbranch_test.go
  • internal/controller/gittarget_immutability_test.go
  • internal/controller/gittarget_placement_validation_test.go
  • internal/controller/gittarget_shared_branch_test.go
  • internal/controller/gittarget_source_cluster.go
  • internal/controller/gittarget_source_cluster_test.go
  • internal/controller/gittarget_status_test.go
  • internal/controller/gittarget_worker_repoint_test.go
  • internal/controller/kubeprovider_audit_facts.go
  • internal/controller/kubeprovider_audit_facts_test.go
  • internal/controller/kubeprovider_controller.go
  • internal/controller/kubeprovider_controller_test.go
  • internal/controller/kubeprovider_controller_unit_test.go
  • internal/controller/kubeprovider_kstatus_test.go
  • internal/controller/kubeprovider_predicate_test.go
  • internal/controller/live_message_migration_test.go
  • internal/controller/object_selector_rule_test.go
  • internal/controller/removed_allow_any_source_namespace_test.go
  • internal/controller/resource_condition_metrics.go
  • internal/controller/resource_condition_metrics_test.go
  • internal/controller/status_observed_generation_test.go
  • internal/controller/stream_status.go
  • internal/controller/suite_test.go
  • internal/controller/superseded_fields_admission_test.go
  • internal/controller/watchrule_controller.go
  • internal/controller/watchrule_controller_test.go
  • internal/controller/watchrule_source_namespace.go
  • internal/controller/watchrule_source_namespace_test.go
  • internal/controller/wiring/readiness_wiring_test.go
  • internal/git/apply_groups.go
  • internal/git/apply_groups_test.go
  • internal/git/apply_request.go
  • internal/git/apply_trigger_test.go
  • internal/git/refusal_reapply_test.go
  • internal/git/types.go
  • internal/kubeconfig/kubeconfig.go
  • internal/kubeconfig/kubeconfig_test.go
  • internal/queue/key_prefix.go
  • internal/telemetry/apply_requests.go
  • internal/telemetry/branch_targets.go
  • internal/telemetry/branch_targets_test.go
  • internal/watch/applier_dynamic_client_test.go
  • internal/watch/audit_route_test.go
  • internal/watch/author_resolver.go
  • internal/watch/author_resolver_test.go
  • internal/watch/bootstrap_admission_test.go
  • internal/watch/cluster_context.go
  • internal/watch/cluster_context_test.go
  • internal/watch/cluster_model_test.go
  • internal/watch/config_plane_split_review_fixes_test.go
  • internal/watch/kube_provider_cluster_test.go
  • internal/watch/manager.go
  • internal/watch/manager_catalog.go
  • internal/watch/manager_startup_test.go
  • internal/watch/materialization.go
  • internal/watch/source_cluster_resolver.go
  • internal/watch/source_cluster_resolver_test.go
  • internal/watch/source_namespace_test.go
  • internal/watch/target_watch.go
  • internal/watch/watch_plane_state.go
  • internal/watch/watchrule_compile.go
  • internal/webhook/audit_fact_publish_test.go
  • internal/webhook/audit_handler.go
  • internal/webhook/audit_handler_test.go
  • internal/webhook/audit_metrics_test.go
  • test/e2e/Taskfile-e2e.yml
  • test/e2e/apply_trigger.go
  • test/e2e/audit_route_attribution_e2e_test.go
  • test/e2e/cluster/audit/webhook-config.yaml
  • test/e2e/commit_request_e2e_test.go
  • test/e2e/helpers.go
  • test/e2e/prune_mode_e2e_test.go
  • test/e2e/setup/demo-only/podinfos-intent/2-reverse-gitops.yaml
  • test/e2e/setup/demo-only/voter-gitops/test/coffeeconfig-reverse-gitops.yaml
  • test/e2e/shared_gitbranch_e2e_test.go
  • test/e2e/source_cluster_e2e_test.go
  • test/e2e/source_namespace_e2e_test.go
  • test/e2e/suspend_e2e_test.go
  • test/e2e/templates/gittarget.tmpl
  • test/e2e/templates/manager/gittarget-prune.tmpl
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/config/clusterprovider.yaml
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/config/gittarget.yaml
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/config/kubeprovider.yaml
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/config/gittarget-second-namespace.yaml
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/config/gittarget.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/config/clusterprovider.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/config/gittarget.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/config/kubeprovider.yaml
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/config/gittarget.yaml
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/config/gittarget-empty-folder.yaml
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/config/gittarget.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/config/gittarget-app-root.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/config/gittarget-prod.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/config/gittarget-test.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/config/gittarget-prod.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/config/gittarget-prod.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/config/gittarget.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/config/gittarget-media.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/config/gittarget.yaml
  • test/fixtures/layout-corpus/specific-examples/prerequisites/README.md
  • test/mutationlab/README.md
  • test/playground/config/gittarget.yaml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Review on demand using usage pricing
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 Oct 22, 2026 •

Copy link
Copy Markdown

sunib and others added 6 commits October 22, 2026 08:08
…uster

source_cluster carries the KubeProvider's namespace/name, so the CommitRequest
spec looks for the install's provider rather than "default".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…er.spec.secretRef

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

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

The overlap check counted every GitTarget on a GitBranch, admitted or not,
and the older one won. A GitTarget in a namespace the branch's GitProvider
does not admit writes nothing, yet any namespace that knew a GitBranch's
name could create one at "." first and hold every folder on that branch
against the targets allowed to write it. An existing target now holds its
path only while its GitProvider and KubeProvider admit it; the admission is
read only for an older target that overlaps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tion intro up to date

An accessFrom label selector admits every namespace whose labels match, so
it is only as strong as the right to set those labels. security-model.md and
configuration.md say so and point at platform-only labels or names.

configuration.md's intro and usual flow name the GitBranch between the
GitProvider and the GitTarget, and state that an unadmitted GitTarget holds
no path. architecture.md lists GitBranch among the kinds, counts nine, names
audit-webhook path segments as routes, and says a recreated or no longer
listed inCluster provider rebuilds or drops its clients.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sunib
sunib merged commit 5d7d5f8 into main Oct 22, 2026
38 of 39 checks passed
@sunib
sunib deleted the feat/kubeprovider branch October 22, 2026 10:46
sunib added a commit that referenced this pull request Oct 23, 2026
ClusterProvider became KubeProvider in #430; the overview and
config-basics diagrams still said ClusterProvider, in the drawn text and
the embedded Excalidraw scene alike. config-cluster.excalidraw.svg is
referenced nowhere. The structural redraw of config-basics (GitBranch,
KubeProvider, the optional applier claim row) is left for Excalidraw.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sunib added a commit that referenced this pull request Oct 23, 2026
…oCDClaim (#438)

* docs: plan the applier claim rename, and name coordinated folders

The plan of record for renaming FluxTrigger/ArgoCDTrigger to
FluxClaim/ArgoCDClaim (spec.applierClaimRef), with the doc corrections
that ship alongside it. definitions.md and the bi-directional guide gain
"coordinated" / "uncoordinated shared folder" for a folder's setup.

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

* refactor(applier)!: rename FluxTrigger/ArgoCDTrigger to FluxClaim/ArgoCDClaim

The resources never declared when to apply (the branch worker decides
that); they exclusively take an applier and hand it back. "Trigger" also
already named watch-plan, API-surface and Argo Events triggers.

Hard cut, with no users yet:

- kinds FluxClaim and ArgoCDClaim (fluxclaims, argocdclaims)
- GitTarget.spec.applierClaimRef replaces spec.applyTriggerRef
- condition ApplierClaimReady, reasons ApplierClaimNotFound and
  HeldByAnotherClaim
- finalizers configbutler.ai/flux-claim and configbutler.ai/argocd-claim
- Go: the ApplierClaim interface, ClaimKind(), and the files with them

Kept: every Applier* name (the applier is unchanged), Held and its
reasons, status.operation, status.previousSuspendState, ApplyReason, the
apply_* metrics and the applier_kind label. A folder an applier claim
coordinates is a "coordinated folder" in comments.

Also: the KubeProvider godoc describes a connection and an identity, not
a source cluster, and a comment no longer mentions a sibling's flush,
gone since #428.

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

* docs(images): name KubeProvider in the diagrams, and drop an unused one

ClusterProvider became KubeProvider in #430; the overview and
config-basics diagrams still said ClusterProvider, in the drawn text and
the embedded Excalidraw scene alike. config-cluster.excalidraw.svg is
referenced nowhere. The structural redraw of config-basics (GitBranch,
KubeProvider, the optional applier claim row) is left for Excalidraw.

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

* docs: applier claims, and four corrections to how the docs describe them

The guides, the reference pages and the definitions say applier claim,
FluxClaim and ArgoCDClaim; a folder a GitTarget coordinates through one
is a coordinated folder. definitions.md defines the applier claim as a
declaration of control over apply timing only, keeps Bound distinct from
Held, covers ArgoCDClaim in Held since #437, and adds "trigger" to the
words we do not use. Design and review pages keep the names they were
written with; apply-trigger.md says so at the top.

Corrections:

- C1: "the API is the source of truth" says for what, how a Git edit
  reaches the cluster, and that the applier's echoes are not mirrored.
- C2: a KubeProvider is a connection and an identity, not a source
  cluster: an applier claim reaches a management cluster through it.
- C3: the text about other GitProviders' branch workers flushing for an
  apply, and the exposure it described, are gone since #428.
- O1: one applier claim holds an applier per KubeProvider, not per
  cluster; two providers for one cluster are not arbitrated, so name one.

UPGRADING has the hard-cut entry.

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

* fix(applier): adopt an applier anew after a hand-back that kept its value

A hand-back that leaves an applier as it stood can keep the value under
GitOps Reverser's field manager: spec.suspend: true for a Kustomization
suspended before, enabled: false for an Application that was manual and
whose switch was taken while held. Referenced again, the applier claim
took that kept value for a hold in place:

- an ArgoCDClaim recorded nothing, so its next hand-back gave the switch
  up and the Application synced on its own;
- a FluxClaim recorded nothing and stamped no token, and waited forever
  for a suspend Flux could never report handled, since the hand-back
  gave its requestedAt up and Flux still reports the old one.

An applier claim with no previousSuspendState, no Held=True and no
operation in flight is adopting: a held field is then one a hand-back
kept. Once an uncached read finds no other applier claim holding the
applier, it records how the applier stands and holds it anew, a
Kustomization with a fresh token, as it takes over any suspend.

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

* fix(e2e): restore the playground ConfigMap's trigger key

The rename turned --from-literal=trigger=tilt into two shell words, and
kubectl refused the command ("exactly one NAME is required, got 2"). The
key was never the apply trigger: it says what made the edit.

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

* lets do some graphics!

* docs: show the new configuration diagrams where they explain the model

config-platform-shared shows a platform namespace sharing a GitProvider
and a GitReceiver with team namespaces, beside the text that says so;
config-bi-directional shows a coordinated folder in the bi-directional
model and under who applies this folder. config-basics, redrawn with
GitBranch and KubeProvider, gets alt text that says what it shows.

Also restores a sentence the rename garbled: the applier's own schedule
is off, not its "own applier claims".

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

* docs(configuration): sharing connections across namespaces, with push routing

"Why both provider types are namespaced" becomes a practical section on
what a platform team shares and how teams reach it: the GitProvider and
KubeProvider admit namespaces with accessFrom, the GitBranch sits beside
its credential, and a GitReceiver needs no wiring at all, since a push
wakes every GitProvider whose spec.url names the repository, for the
branch it names. An example shows a platform namespace and a team's
GitTarget writing its own branch through it.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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