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