Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ so you never have to read a proposal to find out it already shipped.
| Doc | Open question |
|---|---|
| [`open-asks-priority.md`](design/open-asks-priority.md) | **the work queue.** Merges three overlapping backlogs — the gitops-api consumer asks, the API-surface block left unbuilt by the status and configuration-model review, and the config-surface proposal (B1–B6) — into one ordered queue under four stated tests, and says where we deliberately do **not** do what was asked. The standing caveat narrowed once the layout model reversed: a Tier 2 entry belongs to postponed [#294](https://github.com/ConfigButler/gitops-reverser/issues/294) only if it breaks a `GitTarget` field, and everything else is independently schedulable. Makes one design call against what was asked: **delete Option C sibling inference** rather than ship an off-switch for it, because it let a human's edit to the repository change operator behavior with nothing in status recording the move. That deletion has shipped, and "what the deletion taught" records what building it found. **F9 is Tier 1**: the only item whose answer is unknown rather than whose work is unscheduled, and it gates planning the enum work |
| [`placement-visibility-and-declared-defaults.md`](design/placement-visibility-and-declared-defaults.md) | **design, half built.** The three questions the inference deletion left, decided and then built in two goes. PR #291 shipped the deletion and none of the eight items queued behind it; the residue was filed as [#295](https://github.com/ConfigButler/gitops-reverser/issues/295) — **which shipped in 0.42.1 via [#319](https://github.com/ConfigButler/gitops-reverser/pull/319) and is what reversed the layout model** — and [#296](https://github.com/ConfigButler/gitops-reverser/issues/296), whose largest half then shipped in [#326](https://github.com/ConfigButler/gitops-reverser/pull/326): `status.layout` became `status.placement`, and the ambiguous render root came with it as a refusal rather than a third counter outcome. The `declared` metric value has since split into `by_type`/`default` too, which was the one row with an external consumer because it changes a label a dashboard may select on. What still stands and is still unbuilt is the rest of the legibility set: the canonical path as a template constant, and `{kindLower}`. Its Question 2, a CRD default for `placement.default`, is **re-opened rather than superseded** and now lives as an open question in [`layout/model.md`](layout/model.md); the argument against it is legibility, since #319 removed the correctness objection |
| [`gittarget-api-wave.md`](design/gittarget-api-wave.md) | **partly built**, filed as [#294](https://github.com/ConfigButler/gitops-reverser/issues/294). One breaking wave on `GitTarget`, of which steps 2 to 7 have **shipped**: `spec.suspend`, `status.placement` and the reconcile-request annotation in [#326](https://github.com/ConfigButler/gitops-reverser/pull/326), and B4's `commitWindow`/`commit.message` move plus the source-scope deletion in [#330](https://github.com/ConfigButler/gitops-reverser/pull/330). Organizing principle: **the folder is described on the GitTarget, the connection describes only the connection** — and this is where that became a struct boundary rather than a sentence, since grouping a field is free only in a release that is already breaking. `spec.mode` and `GitTarget.spec.interval` are both **dropped**, with re-open triggers, and `status.lastHandledReconcileAt` was dropped in the build for a stated reason. What is left is **step 8, the riders** — asserted `CommitRequest.spec.author`, the `CommitRequest` lifecycle hole, `meta.LocalObjectReference` for our six reference shapes, the `TooManyStreams` cap, the `default` `ClusterProvider` message — so #294 wants narrowing to those rather than closing. Also records that staying `v1alpha3` on loud rejections is a **one-consumer countdown**, and that the residue this pattern has actually left is one field: `ClusterWatchRule.spec.rules[].scope`, whose "removed one release from now" has stood since 0.39.0 |
| [`placement-visibility-and-declared-defaults.md`](design/placement-visibility-and-declared-defaults.md) | **design, half built.** The three questions the inference deletion left, decided and then built in two goes. PR #291 shipped the deletion and none of the eight items queued behind it; the residue was filed as [#295](https://github.com/ConfigButler/gitops-reverser/issues/295) — **which shipped in 0.42.1 via [#319](https://github.com/ConfigButler/gitops-reverser/pull/319) and is what reversed the layout model** — and [#296](https://github.com/ConfigButler/gitops-reverser/issues/296), whose largest half then shipped in [#326](https://github.com/ConfigButler/gitops-reverser/pull/326): `status.layout` became `status.placement`, and the ambiguous render root came with it as a refusal rather than a third counter outcome. The `declared` metric value has since split into `by_type`/`default` too, which was the one row with an external consumer because it changes a label a dashboard may select on. What still stands and is still unbuilt is the rest of the legibility set: the canonical path as a template constant, and `{kindLower}`, tracked on [#339](https://github.com/ConfigButler/gitops-reverser/issues/339) now that #296 is closed; the Event on the GitTarget was dropped. Its Question 2, a CRD default for `placement.default`, is **re-opened rather than superseded** and now lives as an open question in [`layout/model.md`](layout/model.md); the argument against it is legibility, since #319 removed the correctness objection |
| [`gittarget-api-wave.md`](design/gittarget-api-wave.md) | **partly built**, filed as [#294](https://github.com/ConfigButler/gitops-reverser/issues/294). One breaking wave on `GitTarget`, of which steps 2 to 7 have **shipped**: `spec.suspend`, `status.placement` and the reconcile-request annotation in [#326](https://github.com/ConfigButler/gitops-reverser/pull/326), and B4's `commitWindow`/`commit.message` move plus the source-scope deletion in [#330](https://github.com/ConfigButler/gitops-reverser/pull/330). Organizing principle: **the folder is described on the GitTarget, the connection describes only the connection** — and this is where that became a struct boundary rather than a sentence, since grouping a field is free only in a release that is already breaking. `spec.mode` and `GitTarget.spec.interval` are both **dropped**, with re-open triggers, and `status.lastHandledReconcileAt` was dropped in the build for a stated reason. What is left is **step 8, the riders** — asserted `CommitRequest.spec.author`, the `CommitRequest` lifecycle hole, the `default` `ClusterProvider` message — tracked on [#339](https://github.com/ConfigButler/gitops-reverser/issues/339); #294 is closed. The reference collapse and the `scope` removal shipped in [#333](https://github.com/ConfigButler/gitops-reverser/pull/333); the `TooManyStreams` cap and the Event on a changed resolution were dropped. Also records that staying `v1alpha3` on loud rejections is a **one-consumer countdown** |
| [`target-watch-plan.md`](design/target-watch-plan.md) | **partly built.** The diff is built and applied; removal semantics are not. The companion to [`watch-manager-ownership.md`](design/watch-manager-ownership.md): the ownership page says WHO applies a plan, this one says WHAT a plan is and what changing it may touch. A cell — group, resource, namespace, deliberately no served version — is the one identity the watch stream, the render-fidelity scope and the mark-and-sweep boundary all agree on, because a key that does not round-trip to the scope it sweeps under is the class of error that deletes user data. The plan is diffed into `keep`/`start`/`restart`/`stop` and applied per cell, so adding one WatchRule stops replaying every unrelated cell into a queue shared with other tenants; a `restart` is a served-version change, which is why the version is spec DATA rather than identity. Readiness and the fidelity revision are per scope, so a KEPT cell holds the result its own replay produced rather than being asked to prove itself again over an unrelated edit. `stop` never touches files — removal is a Git-side sweep under the target's existing `spec.prune.mode`, not a watch-layer delete. "Cut at the producer" is the accepted consequence: nothing fences the queue, so a deselected cell may leave a short tail of writes, bounded by the queue and converged afterwards. Still open: the `stop` classification wants a settled `TypeRemoved` from `typeset` (see TODO), and removal on INTENT is undecided. |
| [`attribution-removal-wait-options.md`](design/attribution-removal-wait-options.md) | a removal now waits for evidence about the DELETION rather than accepting the object's last write, which stopped it naming whoever last edited the object as the author of a deletion they did not perform. Enumerates the eight situations a resolution can be in and shows the cost is concentrated in exactly one: a removal for which no delete fact will ever arrive (a graceful pod delete, a status-only removal, a type the audit policy skips) spends the whole grace to return the answer it had at t=0, measured at ~3.1s against ~70ms when evidence is present. Prices five options against that, and recommends a per-route watermark — stop waiting once the fact stream has demonstrably moved past this event — over a second timeout flag whose right value lives in the API server's config rather than ours. Open: the decision, and how common the case is outside the e2e suite |
| [`watch-and-catalog-architecture.md`](design/watch-and-catalog-architecture.md) | the target three-layer watch model — **needs a human call before building** |
Expand Down
27 changes: 8 additions & 19 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,14 @@ This file is meant to track the smaller current backlog, not historical notes.
`placement.default` (legibility only, since #319 removed the correctness objection), and a
namespace-local `GitProvider` so three targets in three namespaces need not copy one credential.

- [ ] Remove `ClusterWatchRule.spec.rules[].scope`. It is the **whole** deprecation graveyard in the
CRDs: everything else this project has removed was removed outright, with
[`UPGRADING.md`](UPGRADING.md) as the migration. The field is retained purely so that re-applying
a manifest still saying `Namespaced` fails, and its own doc comment says "Removed one release from
now" — written for 0.39.0, and it has now stood through 0.40, 0.41 and 0.42. Delete the field, the
`DeclaresNamespacedScope` compile-path refusal that backs it, and the `Deprecated:` line in the
CRD. Breaking, so it wants a release that is breaking anyway; the reasoning is in
[`design/gittarget-api-wave.md`](design/gittarget-api-wave.md#version-strategy-stay-v1alpha3).

- [ ] Build the riders that are all that is left of the `GitTarget` API wave
([`design/gittarget-api-wave.md`](design/gittarget-api-wave.md), filed as
[#294](https://github.com/ConfigButler/gitops-reverser/issues/294), which wants narrowing to
them): an asserted `CommitRequest.spec.author`, the `CommitRequest` lifecycle hole,
`meta.LocalObjectReference` for our six reference shapes, the `TooManyStreams` cap, and the
`default` `ClusterProvider` message. **Only the reference-shape collapse is breaking**, and it is
breaking in the quiet way: `GitProviderReference`, `ClusterProviderReference`,
`LocalSecretReference` and friends each carry a defaulted, enum-of-one `group`/`kind`, so every
stored object has those persisted and collapsing the shape prunes them with no error. It belongs
in a breaking release or in none.
- [ ] Build what is left of the `GitTarget` API wave and the placement-visibility set, tracked as
[#339](https://github.com/ConfigButler/gitops-reverser/issues/339): an asserted
`CommitRequest.spec.author`, the `CommitRequest` lifecycle hole, the `default` `ClusterProvider`
message, the canonical path as a template constant, and `{kindLower}`. All
additive. The `scope` field, the reference-shape collapse and the metric split shipped in
[#333](https://github.com/ConfigButler/gitops-reverser/pull/333); the `TooManyStreams` cap, a
`corev1.Event` on the GitTarget for a canonical fall-back, and a movable destination were
**dropped**, with the reasons on that issue.

- [ ] Reduce duplication between `WatchRule` and `ClusterWatchRule` code paths where it makes sense.

Expand Down
38 changes: 19 additions & 19 deletions docs/design/gittarget-api-wave.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
> **partly built**: a sequencing proposal. Steps 2 to 7 have all **shipped** — `spec.suspend`,
> `status.placement` and the reconcile-request annotation in
> [#326](https://github.com/ConfigButler/gitops-reverser/pull/326), B4 and the source-scope deletion
> in [#330](https://github.com/ConfigButler/gitops-reverser/pull/330) on 2026-09-01. **Step 8, the
> riders, is the only unbuilt member**, trimmed under this page's own rule; step 5 (an Event on a
> changed resolution) is additive and also unbuilt. What that leaves of
> [#294](https://github.com/ConfigButler/gitops-reverser/issues/294) is the riders alone, so the
> issue wants narrowing rather than closing.
> in [#330](https://github.com/ConfigButler/gitops-reverser/pull/330) on 2026-09-01, and the
> reference collapse in [#333](https://github.com/ConfigButler/gitops-reverser/pull/333). **Step 8,
> the riders, is the only unbuilt member**, trimmed under this page's own rule to three items. Step 5
> (an Event on a changed resolution) and the `TooManyStreams` cap were **dropped**, not deferred.
> [#294](https://github.com/ConfigButler/gitops-reverser/issues/294) is closed; the riders are
> tracked on [#339](https://github.com/ConfigButler/gitops-reverser/issues/339).
> Index: [`../INDEX.md`](../INDEX.md)
> Date: 2026-08-28 (originally 2026-07-30).
>
Expand All @@ -33,7 +34,7 @@
|---|---|---|
| **B4**: `commitWindow` and `commit.message` move off the connection | `GitProvider` → `GitTarget` | fields change object |
| The source-scope deletion ([`source-scope-simplification.md`](source-scope-simplification.md)) | `GitTarget`, `ClusterProvider`, `WatchRule` | one removal, two renames, one redefinition |
| The riders: **#5** asserted `CommitRequest.spec.author`, the `CommitRequest` lifecycle hole, `meta.LocalObjectReference` for our six reference shapes, the `TooManyStreams` cap, the `default` `ClusterProvider` message | various | shape changes |
| The riders: **#5** asserted `CommitRequest.spec.author`, the `CommitRequest` lifecycle hole, the `default` `ClusterProvider` message; `meta.LocalObjectReference` for our six reference shapes shipped in #333 | various | shape changes |

Additive, and therefore **not** wave members even though they are discussed here: `spec.suspend`,
`status.placement` and the post-scan pass, the reconcile-request annotation, and the two placement
Expand Down Expand Up @@ -148,14 +149,14 @@ review's central complaint: *this controller writes to a Git repository and ther
it stop that is not deleting the object.* So `suspend` is not a rider here, it is a precondition —
and it must stop bootstrap creation specifically, not only resource writes.

### 4. The Events question is answered by the recorder that shipped
### 4. The Events question is closed: no Event

[`open-asks-priority.md`](open-asks-priority.md) left open whether a fall-back to canonical
should raise an Event, and reasoned it was expensive because placement runs on the branch worker with
no recorder. An `EventRecorder` now ships on every reconciler, and the roll-up seam projects
data-plane facts into status with an enqueue on change. So the Event is: emit when `status.placement`
changes in a way a human should know about — `LayoutResolved` becoming `Ambiguous`, or a type falling
back for the first time. One Event per persisted change, the pattern already established for `Ready`.
should raise a `corev1.Event` on the GitTarget. Decided against on 2026-09-04
([#339](https://github.com/ConfigButler/gitops-reverser/issues/339)). The metric already names the
target and the exact `byType` key that is missing, and `status.placement` is the
durable record a bug report contains. A third surface for the same fact would be one more thing to
keep consistent, for a notification that is deduplicated and expires within the hour anyway.

### 5. The source-scope deletion is the only member that shrinks the API

Expand All @@ -172,11 +173,10 @@ Two interactions worth naming, because they change what gets built rather than m
you would do before you do it". They are separate conditions on separate objects and neither
depends on the other, so the SAR work stays additive and can ship after the wave — but whoever
writes the second should read the first.
- **`TooManyStreams` is sized by the `*` decision**
- **The `TooManyStreams` cap is dropped by the `*` decision**
([definition of record](source-scope-simplification.md#sourcenamespace--needs-its-own-decision)),
which removes the fan-out the cap was queued for. What is left to bound is explicit enumeration,
so the cap is still worth a `Stalled` reason and a bound — sized against enumerated rules, and not
planned before the `*` change lands.
which removes the fan-out the cap was queued for. Explicit enumeration is bounded by whoever
writes the rules; a cap on it would be a guard against a list a human typed. It was never in code.

### 6. Two facts kept from arguments that dissolved

Expand Down Expand Up @@ -316,13 +316,13 @@ Dependencies first, then the things that only need the object to be breaking.
write that already happened, rather than previewing one that has not.
4. **`requestedAt`.** On-demand refresh of step 3. Shipped without the
`lastHandledReconcileAt` echo, for the reason recorded above.
5. **Events on a changed resolution**, over the existing recorder.
5. ~~**Events on a changed resolution**, over the existing recorder.~~ Dropped; see interaction 4.
6. **B4**, as `spec.commit`. Last of the principle items, and the one that makes the object coherent.
7. **The source-scope deletion.** Independent of every step above, so it can be written in parallel;
placed here because a deletion reviews better once the additions it is not entangled with are
settled.
8. **The riders.** `TooManyStreams` must come after step 7, which removes the fan-out it was written
to bound.
8. **The riders.** Three remain: `CommitRequest.spec.author`, the `CommitRequest` lifecycle hole and
the `default` `ClusterProvider` message. The `TooManyStreams` cap was dropped by step 7.

Steps 2 to 5 are additive and need no bump. Steps 6 to 8 are one release; step 1 gates the planning;
step 8 can be trimmed if the wave gets too big to review, since nothing else depends on it. The
Expand Down
Loading