From 18b870a9b1c09473752787221fe0d756864bfd0c Mon Sep 17 00:00:00 2001 From: Simon Koudijs Date: Thu, 9 Jul 2026 07:12:00 +0000 Subject: [PATCH] docs(gitops-api): onboarding repo-discovery design (F8); archive shipped F1/F7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the F8 design — repo-wide GitTarget discovery in the manifest-analyzer CLI/library, never the operator — and move the shipped F1/F7 feature docs into finished/, updating inbound links and the feature ladder. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/architecture.md | 2 +- docs/configuration.md | 2 +- docs/design/gitops-api/README.md | 17 +- .../f8-repo-discovery-and-onboarding-scan.md | 243 ++++++++++++++++++ .../f1-images-replicas-edit-through.md | 11 +- .../f7-higher-level-krm-documents.md | 29 ++- ...mize-support-boundary-and-product-model.md | 2 +- .../unreflectable-edits-and-write-gating.md | 2 +- .../testdata/contextual-namespace/README.md | 2 +- 9 files changed, 281 insertions(+), 29 deletions(-) create mode 100644 docs/design/gitops-api/f8-repo-discovery-and-onboarding-scan.md rename docs/design/gitops-api/{ => finished}/f1-images-replicas-edit-through.md (95%) rename docs/design/gitops-api/{ => finished}/f7-higher-level-krm-documents.md (85%) diff --git a/docs/architecture.md b/docs/architecture.md index ea7c519d..f0a5f394 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -910,7 +910,7 @@ hydrates only touched files into buffers for the commit, and flushes only change entry in the document's kustomization chain is written back to that entry (comment-preserving, only fields the entry already declares); the source manifest keeps its bytes. Anything the inversion cannot express falls back to the plain in-place patch. See - [gitops-api/f1-images-replicas-edit-through.md](design/gitops-api/f1-images-replicas-edit-through.md). + [gitops-api/finished/f1-images-replicas-edit-through.md](design/gitops-api/finished/f1-images-replicas-edit-through.md). * **Deletes:** use the manifest identity index, so a moved manifest can still be deleted even when it is not at the canonical path. * **Field patches** (currently `/scale` → parent `spec.replicas`) are intentionally narrow: they only diff --git a/docs/configuration.md b/docs/configuration.md index 64f50241..c06b68cc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -542,7 +542,7 @@ entry). These fallbacks are recorded as store diagnostics — visible in the ana the running operator, in the logs at debug verbosity (`manifest store diagnostic`). For design details and the exact boundary, see -[design/gitops-api/f1-images-replicas-edit-through.md](design/gitops-api/f1-images-replicas-edit-through.md). +[design/gitops-api/finished/f1-images-replicas-edit-through.md](design/gitops-api/finished/f1-images-replicas-edit-through.md). ## `WatchRule` diff --git a/docs/design/gitops-api/README.md b/docs/design/gitops-api/README.md index 1c3684b0..fa22d702 100644 --- a/docs/design/gitops-api/README.md +++ b/docs/design/gitops-api/README.md @@ -28,6 +28,11 @@ The division of responsibility is deliberate and already a recorded decision `Pushed=True` + `status.sha` + `status.branch`). - **Product layer**: session/branch lifecycle policy, PR creation and merge, branch cleanup, and any UI. +- **Onboarding (CLI / library)**: repo-wide discovery — enumerate candidate + `GitTarget`s, classify each folder's layout, report supported-vs-refused with + reasons, and propose the CRs — built on the shared analyzer engine, writing + nothing. See + [f8-repo-discovery-and-onboarding-scan.md](f8-repo-discovery-and-onboarding-scan.md). The operator never gains Git-host knowledge (no GitHub/GitLab API calls). @@ -115,7 +120,8 @@ exactly what prices F3. Ordered by delivery priority for the launch path above (F-numbers are stable identifiers, not an ordering). Each feature gets its own design doc in this -folder when work starts. The cross-cutting scope decisions behind the ladder — the +folder when work starts; a shipped feature's doc moves to +[finished/](finished/). The cross-cutting scope decisions behind the ladder — the kustomization field taxonomy, the supported-layout allowlist, who runs kustomize, the multi-environment product model (promotion, "factor into base"), and the mirror-mode vs. intent-cluster topology — live in @@ -123,15 +129,16 @@ base"), and the mirror-mode vs. intent-cluster topology — live in | # | Feature | Design doc | Status | |---|---------|-----------|--------| -| F1 | Kustomize `images:` / `replicas:` edit-through — a live change produced by an override entry is written back to that entry, never through into the source manifest | [f1-images-replicas-edit-through.md](f1-images-replicas-edit-through.md) | implemented ([#198](https://github.com/ConfigButler/gitops-reverser/pull/198)) | -| F7 | Higher-level KRM objects as first-class documents — corpus + e2e pinning that Flux `HelmRelease`/`Kustomization`, Argo CD `Application`, KRO resources, and core resources mirror and edit like any KRM document (they should already; F7 *proves* it), plus "install an app = add KRM" user docs | [f7-higher-level-krm-documents.md](f7-higher-level-krm-documents.md) | implemented (manifestedit corpus for HelmRelease/Application/KRO; HelmRelease mirror+edit e2e; [installing-apps-as-krm.md](../../installing-apps-as-krm.md) user docs) | +| F1 | Kustomize `images:` / `replicas:` edit-through — a live change produced by an override entry is written back to that entry, never through into the source manifest | [finished/f1-images-replicas-edit-through.md](finished/f1-images-replicas-edit-through.md) | implemented ([#198](https://github.com/ConfigButler/gitops-reverser/pull/198)) | +| F7 | Higher-level KRM objects as first-class documents — corpus + e2e pinning that Flux `HelmRelease`/`Kustomization`, Argo CD `Application`, KRO resources, and core resources mirror and edit like any KRM document (they should already; F7 *proves* it), plus "install an app = add KRM" user docs | [finished/f7-higher-level-krm-documents.md](finished/f7-higher-level-krm-documents.md) | implemented (manifestedit corpus for HelmRelease/Application/KRO; HelmRelease mirror+edit e2e; [installing-apps-as-krm.md](../../installing-apps-as-krm.md) user docs) | | F2 | Render-root scoping — a GitTarget declares its render root (e.g. `overlays/env1`); base files reached through `../../base` become read-only context, dissolving overlay fan-out ambiguity. Launch scope: overlay `images:`/`replicas:` entries + overlay-local documents, shipped **with** the per-edit `FullyReflected` accounting | — | not designed — **launch-critical** | | F4 | New-file placement rules — sibling inference + `spec.placement` template so new resources land in the folder's convention, not the canonical REST path; includes creating the `resources:` entry when the target folder carries a kustomization | designed in [version2/gittarget-new-file-placement-rules.md](../manifest/version2/gittarget-new-file-placement-rules.md) | implemented (v1: declared policy + sibling inference steps 1/2/4 + kustomize `resources:` entry; step 3 and ordered-rule Option A deferred) | | F5 | Branch/session ergonomics — base-branch selection, opt-in remote branch cleanup, a GitTarget-level quiescence condition | — | not designed — **launch-critical** | | F3 | Restricted patch authoring — write/update scalar-field strategic-merge patches in an overlay ("live drift lands in the environment's overlay"); turns the launch-time unreflected class (per-env edits of base-owned fields) into reflected edits | — | post-launch; priced by tier-2 metrics | | F6 | Admission preflight gate — opt-in intent-cluster webhook rejecting edits that cannot be reflected into the folder (fail-open; the underlying per-edit `FullyReflected` accounting ships with the F2/F4 launch unit) | [unreflectable-edits-and-write-gating.md](unreflectable-edits-and-write-gating.md) | designed, unbuilt — value highest while F3 is absent | +| F8 | Repo discovery & onboarding scan (**CLI / product-layer**) — walk a whole repo, enumerate candidate `GitTarget`s, classify each folder's layout (plain / kustomize-single / kustomize-overlay / refused), report supported-vs-refused with reasons, and propose the CRs; the product layer (GitHub App) consumes the report to generate `GitTarget`s and open PRs. Built on the shared analyzer engine — **ships no operator code** | [f8-repo-discovery-and-onboarding-scan.md](f8-repo-discovery-and-onboarding-scan.md) | designed, unbuilt | -## What already works (baseline, 2026-07-06) +## What already works (baseline, 2026-07-09) - Raw-YAML folders (explicit namespaces): in-place, comment-preserving edits; match-first placement; mark-and-sweep resync. @@ -146,7 +153,7 @@ base"), and the mirror-mode vs. intent-cluster topology — live in - Higher-level KRM documents (Flux `HelmRelease`, Argo CD `Application`, KRO resources) mirror and edit exactly like core resources — the pipeline is kind-agnostic, now pinned by F7's corpus + HelmRelease e2e - ([f7-higher-level-krm-documents.md](f7-higher-level-krm-documents.md)). + ([finished/f7-higher-level-krm-documents.md](finished/f7-higher-level-krm-documents.md)). ## Known boundary (what stays refused) diff --git a/docs/design/gitops-api/f8-repo-discovery-and-onboarding-scan.md b/docs/design/gitops-api/f8-repo-discovery-and-onboarding-scan.md new file mode 100644 index 00000000..6c2a79c9 --- /dev/null +++ b/docs/design/gitops-api/f8-repo-discovery-and-onboarding-scan.md @@ -0,0 +1,243 @@ +# F8: Repo discovery and onboarding scan (CLI / product-layer) + +> Status: designed, unbuilt — CLI/onboarding tooling, not an operator write-model +> feature. The engine it needs is already shipped; this is a new entry point plus +> a report contract. +> Captured: 2026-07-09 +> Related: +> [README.md](README.md), +> [kustomize-support-boundary-and-product-model.md](kustomize-support-boundary-and-product-model.md), +> [finished/f1-images-replicas-edit-through.md](finished/f1-images-replicas-edit-through.md), +> [finished/f7-higher-level-krm-documents.md](finished/f7-higher-level-krm-documents.md), +> [../manifest/file-agnostic-placement.md](../manifest/file-agnostic-placement.md), +> [../../finished/current-manifest-support-review.md](../../finished/current-manifest-support-review.md) + +## Why this exists + +The product front door is "point a GitHub App at a repository you own and get an +easy Kubernetes API over it" (the intent-mode direction of +[kustomize-support-boundary-and-product-model.md §8](kustomize-support-boundary-and-product-model.md)). +Between "here is a repo" and "here is your API" sits an **onboarding** step the +operator deliberately does **not** perform: + +- Which folders in this repo can become `GitTarget`s? +- What **layout** is each (plain per-env folder, single-context kustomize, + base + overlays)? +- Which are **supported today**, which are **refused**, and *why*? +- What namespace does each resolve to, and what `GitTarget`/`WatchRule` would + express it? + +None of this is the operator's job, and it must stay that way. This doc designs +the discovery capability as an extension of the existing `manifest-analyzer` +CLI + its shared `internal/manifestanalyzer` engine, and defines the +machine-readable report the product layer consumes. + +## The boundary this respects: the operator never discovers + +The operator is intentionally the opposite of Argo/Flux — it does not scan a +repo to find apps. A `GitTarget` is told **exactly one** subtree: +`spec.path` is required, `MinLength=1`, has no default, and is immutable +(`api/v1alpha3/gittarget_types.go`); every scan is hard-scoped to +`filepath.Join(root, spec.path)` (`scanWorktreeSubtree`, +`internal/git/plan_flush.go`); and overlap between `GitTarget`s is actively +refused to preserve one-owner-per-folder (`checkForConflicts`, +`internal/controller/gittarget_path_overlap.go`). + +Repo-wide target discovery is therefore a **new axis**, not a new operator +feature. Putting it in the operator would fight both the simplicity goal and the +one-owner invariant. It belongs in the CLI/library and, above that, the product +layer — exactly the division already recorded in +[file-agnostic-placement.md](../manifest/file-agnostic-placement.md) and the +[README](README.md) responsibilities table: + +| Layer | Owns | Gains repo-wide discovery? | +|---|---|---| +| **GitOps Reverser (operator)** | watch live state, edit one subtree, push a branch, expose `CommitRequest` status | **No.** Unchanged. One `GitTarget` = one subtree. | +| **`manifest-analyzer` CLI + `internal/manifestanalyzer` library** | folder walk, acceptance gate, placement, refusal reasons — already shared with the operator | **Yes.** New repo-wide entry point + report. Read-only, writes nothing, needs no cluster. | +| **Product / GitHub App layer** | repo access, CR generation, PR creation, session/branch policy | Consumes the report → generates `GitProvider`/`GitTarget`/`WatchRule` → opens PRs. Still no operator Git-host knowledge. | + +## The engine is already there + +The discovery pass is mostly reuse. The CLI and the live reconciler already +share one engine (`Scan` is documented as "the one planner shared by the +manifest-analyzer CLI and the controller's scan path", +`internal/manifestanalyzer/scan.go`), and `manifest-analyzer` already scans a +**single** folder read-only with no cluster: +`ScanDir(ctx, dir, nil, nil, policy)` (`--mode scan`). + +The building blocks a repo-wide pass needs all exist: + +| Need | Existing symbol | +|---|---| +| Walk a tree, classify each entry | `collectFiles` (`analyzer.go`), `ClassifyEntry` (`gittargetignore.go`) — roles `ManagedYAML` / `OperatorArtifact` (kustomizations recognized) / `Foreign` (refused) | +| Enumerate render roots | `renderRoots` (`overrides.go`) — "kustomization directories no other kustomization references" | +| Run the adoption gate | `Accept` / `AcceptStructureOnly` (`acceptance.go`), refusal reasons `AcceptanceRefusedError` / `RefusalError` (`acceptance_refusal.go`) | +| Resolve namespaces / variants | `resolveNamespaceContext`, `NamespaceSource` (`Explicit`/`Kustomize`/`None`) (`store.go`) — variant→namespace is free when overlays set `namespace:` | +| Detect target overlap | mirror `checkForConflicts` (`gittarget_path_overlap.go`) so no two proposed targets overlap | + +What is genuinely new: **a whole-repo pass** (today's `ScanDir` is subtree-only), +**candidate enumeration**, **layout classification**, and the **report +contract**. + +## The discovery algorithm + +```mermaid +flowchart TD + A[Walk whole repo once
respect .git + .gittargetignore] --> B[Build kustomization graph
across the repo] + B --> C[Enumerate candidate subtrees] + C --> C1[render roots
renderRoots] + C --> C2[plain leaf KRM folders
no kustomization] + C1 --> D{Per candidate:
run acceptance gate} + C2 --> D + D --> E[classify layout +
namespace/variants +
refusal reasons] + E --> F[Detect overlap / nesting
between candidates] + F --> G[Emit report:
text + JSON + exit code] +``` + +1. **Walk the whole repo once.** Reuse `collectFiles` semantics but over the + full tree rather than a single subtree; skip `.git`; honour a root + `.gittargetignore`; never follow symlinks (matches `ScanDir`). +2. **Build the kustomization graph** across the repo and compute render roots + (`renderRoots`). A render root reached by no other kustomization is a natural + target unit; its inferred `namespace:` (when set) is the variant identity. +3. **Enumerate candidates:** every render root, plus every plain leaf folder of + KRM documents that no kustomization owns (many GitOps tools apply a directory + of manifests directly — the "one plain folder per environment" launch layout). +4. **Run the same acceptance gate the operator runs** per candidate, scoped to + the candidate subtree plus the read-only context it reaches (bases via + `../../base`). This yields, per candidate: accepted?, refusal reasons, layout + class, inferred namespace(s)/variants, KRM vs non-KRM document counts. +5. **Detect overlap/nesting** between candidates (mirroring + `gittarget_path_overlap`) so the product never proposes two `GitTarget`s that + would be mutually refused. +6. **Emit** the report (below). + +### Layout classification + +Discovery reports the layout **and** current operator acceptance as two distinct +truths — they diverge during the F2 gap (see below). + +| `layout` | Meaning | `acceptedByOperator` today | +|---|---|---| +| `plain` | Directory of KRM docs, explicit namespaces, no kustomization | ✅ shipped | +| `kustomize-single` | One render root; `namespace` + `resources`/`images`/`replicas` | ✅ shipped | +| `kustomize-overlay` | Base + N overlay roots, one namespace per overlay | ⛔ until **F2** — reason `overlay-fan-out-needs-f2` | +| `refused-structural` | Helm inflation, generators with hash suffixes, `components`, `namePrefix`/`nameSuffix`, remote bases, `configurations`/`openapi`/`crds` | ⛔ permanent — the support contract | +| `refused-fleet-root` | `clusters/` + `apps/` + `infra/` cluster-root repo (a `GitTarget` points at an app subtree, never a cluster root) | ⛔ out of scope by design | +| `refused-out-of-band` | Namespace/transform injected outside the folder (Flux `postBuild`/`targetNamespace`, Argo Application-level overrides) | ⛔ permanent — round-trip cannot hold | + +The distinction between `overlay-fan-out-needs-f2` and `refused-structural` is +the load-bearing one: the first is a **forward-looking** "not yet" that flips to +accepted when [F2](README.md) render-root scoping lands; the second is the +permanent boundary. Discovery must never collapse them into one "refused." + +## The report contract + +Two renderings, matching the existing `--format text|json` split. JSON is the +product's interface; per candidate: + +```json +{ + "path": "apps/podinfo/overlays/test", + "layout": "kustomize-overlay", + "acceptedByOperator": false, + "refusalReasons": [ + { "code": "overlay-fan-out-needs-f2", "detail": "base reached by 3 overlay roots; render-root scoping (F2) required" } + ], + "renderRoot": true, + "readScope": ["apps/podinfo/base"], + "inferredNamespace": "podinfo-test", + "documents": { "krm": 4, "nonKrm": 0 }, + "proposedGitTarget": { + "spec": { + "providerRef": { "name": "" }, + "branch": "", + "path": "apps/podinfo/overlays/test" + } + } +} +``` + +Plus a repo-level summary: candidate counts by layout, accepted vs. refused, +overlap conflicts, and the set of unsupported constructs seen (so the product +can say "this repo uses Helm inflation in `infra/`, which we don't manage"). + +**Exit codes** reuse the CLI convention (`exitOK=0`, `exitRefused=1`, +`exitUsage=2`). In repo mode, `--policy report` always exits 0 (pure report); +`--policy refuse` exits 1 when **zero** candidates are acceptable — i.e. "is this +repo onboardable at all?", the useful CI/onboarding gate. (Per-candidate refusals +are data, not a failure, under `report`.) + +## CLI surface + +A new mode on `manifest-analyzer`, e.g.: + +``` +manifest-analyzer --mode repo --format json [--policy report|refuse] +``` + +Naming note: the existing `--mode discovery` is **Kubernetes API discovery** (a +served-GVR dump against a kubeconfig), unrelated to repo discovery. To avoid +confusion this doc uses `--mode repo`; a follow-up should consider renaming the +existing mode to `--mode api-discovery` so "discovery" is not overloaded. + +The report should also be exposed as a **library function** (the product is +likely part of the same Go binary family), with the CLI mode as the thin wrapper ++ CI gate. Library-first keeps the product from shelling out and re-parsing JSON. + +## Boundaries and non-goals + +- **Not deploy discovery.** We are the reverse of Argo/Flux; this finds *editing* + targets, never "apps to deploy." +- **Writes nothing, no Git-host calls, no cluster required.** Structure-only, + read-only, symlinks never followed — same posture as `ScanDir`. +- **Proposes, never creates.** The CLI/library emits candidate `GitTarget`s; the + product layer decides, renders the actual manifests (naming, labels, + ownerRefs), and opens PRs. CR generation stays out of the operator. +- **Moves no boundary.** Refused layouts stay refused; discovery only *reports* + them. It does not make the operator accept overlays — [F2](README.md) does. +- **`WatchRule` proposal is optional (open question).** Discovery can suggest + `WatchRule`s from the kinds present in a folder, but watch selection is about + *which cluster resources flow* ([F7](finished/f7-higher-level-krm-documents.md)), + orthogonal to the Git path — it may be cleaner to stop at `GitTarget` and leave + watch scoping to the user/product. + +## Relationship to the ladder + +- **Dependencies are all shipped:** the acceptance gate, render-root computation + ([F1](finished/f1-images-replicas-edit-through.md)), and namespace inference. + Discovery is additive tooling on top. +- **F2 is the one that changes discovery's *output*, not its code:** pre-F2, + overlay candidates report `acceptedByOperator: false` + + `overlay-fan-out-needs-f2`; once F2 ships they flip to accepted with an + `images:`/`replicas:` + overlay-local-file capability summary. The report is + designed to be correct across that transition. +- **Intent mode (§8) reuses the same report** as its onboarding front door: the + set of accepted candidates and their namespaces is exactly what tells the + intent cluster which overlays to hydrate. +- **Not itself an operator write-model feature** — hence the "CLI / product-layer" + tag. It is tracked in the ladder for one delivery surface, but it ships no + operator code. + +## Test plan + +- A **discovery corpus**: repo fixtures of each shape — plain-per-env, single + kustomize, base+overlays, fleet-root (`clusters/`+`apps/`+`infra/`), + Helm-hybrid, and nested/overlapping candidates — each with a golden JSON + report. +- Assertions: render-root enumeration, layout classification, refusal-reason + codes (especially `overlay-fan-out-needs-f2` vs `refused-structural`), overlap + detection, namespace/variant inference, and proposed `GitTarget` paths. +- Reuse existing corpora where they fit + (`internal/manifestanalyzer/testdata/contextual-namespace`, the `ambiguous-*` + folders) rather than duplicating fixtures. + +## Open questions + +1. **Mode naming / the `discovery` collision** — rename the existing K8s-API mode + to `api-discovery`? +2. **Stop at `GitTarget`, or also propose `WatchRule`s?** +3. **Read-scope depth for overlays pre-F2** — how far up the tree to follow + `../../base` when the operator would still refuse the folder. +4. **Library vs. CLI as the product's integration point** — recommend + library-first, CLI as wrapper + CI gate. diff --git a/docs/design/gitops-api/f1-images-replicas-edit-through.md b/docs/design/gitops-api/finished/f1-images-replicas-edit-through.md similarity index 95% rename from docs/design/gitops-api/f1-images-replicas-edit-through.md rename to docs/design/gitops-api/finished/f1-images-replicas-edit-through.md index 0c2f88a5..9d754564 100644 --- a/docs/design/gitops-api/f1-images-replicas-edit-through.md +++ b/docs/design/gitops-api/finished/f1-images-replicas-edit-through.md @@ -1,12 +1,13 @@ # F1: Kustomize `images:` / `replicas:` edit-through -> Status: implemented (phases A–C landed together with this doc; the ambiguity -> refusal projection into GitTarget status remains future work) +> Status: shipped ([#198](https://github.com/ConfigButler/gitops-reverser/pull/198)) — +> phases A–C landed together with this doc; the ambiguity refusal projection into +> GitTarget status remains future work. Filed under `finished/`. > Captured: 2026-07-06 > Related: -> [README.md](README.md), -> [../manifest/contextual-namespace-and-kustomize-folder-editing.md](../manifest/contextual-namespace-and-kustomize-folder-editing.md), -> [../../../internal/git/manifestedit/DECISION.md](../../../internal/git/manifestedit/DECISION.md) +> [../README.md](../README.md), +> [../../manifest/contextual-namespace-and-kustomize-folder-editing.md](../../manifest/contextual-namespace-and-kustomize-folder-editing.md), +> [../../../../internal/git/manifestedit/DECISION.md](../../../../internal/git/manifestedit/DECISION.md) ## Problem diff --git a/docs/design/gitops-api/f7-higher-level-krm-documents.md b/docs/design/gitops-api/finished/f7-higher-level-krm-documents.md similarity index 85% rename from docs/design/gitops-api/f7-higher-level-krm-documents.md rename to docs/design/gitops-api/finished/f7-higher-level-krm-documents.md index 962362ae..eb0a227c 100644 --- a/docs/design/gitops-api/f7-higher-level-krm-documents.md +++ b/docs/design/gitops-api/finished/f7-higher-level-krm-documents.md @@ -1,14 +1,15 @@ # F7: Higher-level KRM objects as first-class documents -> Status: implemented — corpus + e2e pins land with this doc; **no product code -> changed** (the guarantee already held; F7 makes it load-bearing) +> Status: shipped ([#203](https://github.com/ConfigButler/gitops-reverser/pull/203)) — +> corpus + e2e pins landed with this doc; **no product code changed** (the guarantee +> already held; F7 makes it load-bearing). Filed under `finished/`. > Captured: 2026-07-07 > Related: -> [README.md](README.md), -> [kustomize-support-boundary-and-product-model.md](kustomize-support-boundary-and-product-model.md), +> [../README.md](../README.md), +> [../kustomize-support-boundary-and-product-model.md](../kustomize-support-boundary-and-product-model.md), > [f1-images-replicas-edit-through.md](f1-images-replicas-edit-through.md), -> [../../future/idea-application-editing.md](../../future/idea-application-editing.md), -> [../../architecture.md](../../architecture.md) +> [../../../future/idea-application-editing.md](../../../future/idea-application-editing.md), +> [../../../architecture.md](../../../architecture.md) ## Premise: the pipeline is already kind-agnostic @@ -22,22 +23,22 @@ carries a per-kind allowlist**: *served* API surface — any served GVR (core, CRD, or aggregated) is watchable. A `WatchRule`/`ClusterWatchRule` names plural resources and optional `apiGroups`; an omitted group is resolved from discovery - ([../../../api/v1alpha3/watchrule_types.go](../../../api/v1alpha3/watchrule_types.go), + ([../../../../api/v1alpha3/watchrule_types.go](../../../../api/v1alpha3/watchrule_types.go), `ResourceRule` — "resolves the resource name across all served API groups"). There is no set of "supported kinds." - **The in-document editor** (`internal/git/manifestedit`) edits generic `yaml.Node` trees keyed only by object identity (apiVersion/kind/namespace/name). It has no notion of any specific kind - ([../../../internal/git/manifestedit/DECISION.md](../../../internal/git/manifestedit/DECISION.md)). + ([../../../../internal/git/manifestedit/DECISION.md](../../../../internal/git/manifestedit/DECISION.md)). - **Placement** is GVR-driven: the cold-start default is `{namespace}/{group}/{resource}/{name}.yaml`, and sibling inference / `spec.placement` are equally generic - ([../../architecture.md](../../architecture.md), *What It Writes to Git*). + ([../../../architecture.md](../../../architecture.md), *What It Writes to Git*). The one kind-keyed *filter* in the pipeline is a small **exclusion** list of noisy built-ins (`pods`, `events`, `endpoints`, `endpointslices`, `leases`, `controllerrevisions`, `flowschemas`, `jobs`, …) — a deny list, never an allow -list ([../../../internal/watch/resource_policy.go](../../../internal/watch/resource_policy.go)). +list ([../../../../internal/watch/resource_policy.go](../../../../internal/watch/resource_policy.go)). `HelmRelease`, `Application`, and KRO kinds are not on it, so they are default-allowed. The remaining kind-specific branches are **write-safety** and **override** rules, and higher-level KRM falls through all of them: @@ -56,7 +57,7 @@ default-allowed. The remaining kind-specific branches are **write-safety** and This kind-agnosticism is **already e2e-proven for a generic CRD**: the icecream-order lifecycle spec installs an arbitrary CRD and pins create→file / update→file / delete→file-removed with `status` never committed -([../../../test/e2e/crd_lifecycle_e2e_test.go](../../../test/e2e/crd_lifecycle_e2e_test.go)). +([../../../../test/e2e/crd_lifecycle_e2e_test.go](../../../../test/e2e/crd_lifecycle_e2e_test.go)). What F7 adds is the same proof for a **named, real higher-level control-plane type** (Flux `HelmRelease`) plus the un-installed kinds at the unit level. @@ -96,7 +97,7 @@ launch-critical for two reasons: Flux CRDs (`helmreleases.helm.toolkit.fluxcd.io`, `kustomizations.kustomize.toolkit.fluxcd.io`) are established during standard -e2e setup ([../../../test/e2e/Taskfile.yml](../../../test/e2e/Taskfile.yml), +e2e setup ([../../../../test/e2e/Taskfile.yml](../../../../test/e2e/Taskfile.yml), `_flux-installed`), so the live pin uses a `HelmRelease`. The Argo and KRO controllers are not in the base cluster; their kinds are pinned at the unit level, where the analyzer/editor need only the document bytes — no controller, @@ -119,7 +120,7 @@ Mirrors launch use case 2 ("roll out a new version — a chart version on a Flux 4. Patch live `spec.chart.spec.version` → `Y`. 5. Assert the file shows `Y`, the comment survives, and `X` is gone — an in-place, comment-preserving edit of a control-plane CR, identical to the - `ConfigMap` case ([inplace_edit_e2e_test.go](../../../test/e2e/inplace_edit_e2e_test.go)). + `ConfigMap` case ([inplace_edit_e2e_test.go](../../../../test/e2e/inplace_edit_e2e_test.go)). ### Unit corpus — `internal/git/manifestedit/testdata/corpus` @@ -154,6 +155,6 @@ field so the gates exercise the properties that matter for these kinds. code — the gates already glob `testdata/corpus/*.yaml`. - **e2e:** `HelmRelease` mirror + chart-version-bump edit, comment-preserving, under the `manager` + `f7-higher-level-krm` labels. -- **User docs:** [../../installing-apps-as-krm.md](../../installing-apps-as-krm.md) +- **User docs:** [../../../installing-apps-as-krm.md](../../../installing-apps-as-krm.md) — "adding an app is adding a KRM document," with `HelmRelease`/`Application` examples and the chart-inflation boundary. diff --git a/docs/design/gitops-api/kustomize-support-boundary-and-product-model.md b/docs/design/gitops-api/kustomize-support-boundary-and-product-model.md index 477b3688..710134da 100644 --- a/docs/design/gitops-api/kustomize-support-boundary-and-product-model.md +++ b/docs/design/gitops-api/kustomize-support-boundary-and-product-model.md @@ -4,7 +4,7 @@ > Captured: 2026-07-06 > Related: > [README.md](README.md), -> [f1-images-replicas-edit-through.md](f1-images-replicas-edit-through.md), +> [finished/f1-images-replicas-edit-through.md](finished/f1-images-replicas-edit-through.md), > [../manifest/contextual-namespace-and-kustomize-folder-editing.md](../manifest/contextual-namespace-and-kustomize-folder-editing.md), > [../unsupported-folder-refusal-plan.md](../unsupported-folder-refusal-plan.md), > [../manifest/version2/gittarget-new-file-placement-rules.md](../manifest/version2/gittarget-new-file-placement-rules.md) diff --git a/docs/design/gitops-api/unreflectable-edits-and-write-gating.md b/docs/design/gitops-api/unreflectable-edits-and-write-gating.md index 974c611b..15b382e5 100644 --- a/docs/design/gitops-api/unreflectable-edits-and-write-gating.md +++ b/docs/design/gitops-api/unreflectable-edits-and-write-gating.md @@ -6,7 +6,7 @@ > Captured: 2026-07-06 > Related: > [README.md](README.md), -> [f1-images-replicas-edit-through.md](f1-images-replicas-edit-through.md), +> [finished/f1-images-replicas-edit-through.md](finished/f1-images-replicas-edit-through.md), > [../unsupported-folder-refusal-plan.md](../unsupported-folder-refusal-plan.md) ## Problem diff --git a/internal/manifestanalyzer/testdata/contextual-namespace/README.md b/internal/manifestanalyzer/testdata/contextual-namespace/README.md index 85eef272..feb81984 100644 --- a/internal/manifestanalyzer/testdata/contextual-namespace/README.md +++ b/internal/manifestanalyzer/testdata/contextual-namespace/README.md @@ -14,7 +14,7 @@ asserts the outcome. The `images-overlay`, `replicas-overlay`, and `ambiguous-images` folders pin the F1 override-chain attribution the same way (`overrides_test.go`); see -`docs/design/gitops-api/f1-images-replicas-edit-through.md`. +`docs/design/gitops-api/finished/f1-images-replicas-edit-through.md`. See `docs/design/manifest/contextual-namespace-and-kustomize-folder-editing.md` (the "Supported and unsupported example folders" matrix). Add a new folder here