Skip to content

chore: move the layout corpus to test/, and tidy the docs and comments - #332

Merged
sunib merged 16 commits into
mainfrom
chore/layout-corpus-to-test-and-docs-cleanup
Sep 3, 2026
Merged

sunib merged 16 commits into
mainfrom
chore/layout-corpus-to-test-and-docs-cleanup

Conversation

@sunib

@sunib sunib commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

The corpus is in the end a real test, so it moves out of docs/ into
test/fixtures/layout-corpus/. It sits next to gitops-layouts/ now, with a small README
saying which corpus is which, since those two get confused.

Checking the release against what it promises turned up three things:

  • One example set spec.commit.author, which is not a field. It applied cleanly and did
    nothing. There is a test now that strict-decodes every example against the real types, so
    a stale example fails the build instead of being pruned in silence.
  • Shape 7's refusal fixture was committed but nothing ran it. Wiring it up showed it
    described a refusal we do not produce. Fixture and README corrected.
  • The design docs used two header formats, and two pages disagreed with themselves about
    their own status. Every page has one label now: design, design decided, partly built,
    built, or deferred. INDEX.md no longer files built work under "open items".

I also cut about 800 comment lines. Mostly the design rationale in api/v1alpha3 that never
reaches the CRD, and the long essays in the write path. Every non-obvious reason survives,
just shorter. Generated CRDs are byte identical.

build-order.md is deleted: all three PRs it sequenced have shipped, and it still claimed
track A was unbuilt. Its referents point at the pages that own that work.

The status principle is written down in docs/spec/status-conditions-guide.md: status
writes are bounded by config changes and health, never by throughput.

task lint, task test and task test-e2e all pass.

🤖 Generated with Claude Code

sunib and others added 5 commits September 2, 2026 13:01
…ainst the real types

`docs/layout/specific-examples/prerequisites/config/gitprovider.yaml` set
`spec.commit.author`, which is not a field on `GitProvider`. Applied to a cluster it
was pruned in silence, so neither a reader nor a cluster could tell.

That folder drifted because it has no `input/`, which is exactly the case the layout
corpus's coverage guard skips. `TestExamplesDecodeStrictly` closes it over the
filesystem instead: every checked-in manifest of our own kinds under `config/samples`,
`docs/layout`, `test/playground` and `test/e2e/setup` is strict-decoded against the
real v1alpha3 types, so a field the API does not have fails the build rather than
disappearing on apply.

This matters more after the breaking wave than it did before it: the five fields that
release removed are pruned rather than refused, so a stale example is invisible at
every layer except this one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… corpus it is confused with

The shapes and the ecosystem examples lived in `docs/layout/`, which was true when they
were prose and stopped being true when `TestLayoutCorpus` started executing them. Every
folder is now seeded, written through the real plan-then-flush path and diffed against a
committed patch, so it is a test, and it belongs where the other executed fixtures are.

They land at `test/fixtures/layout-corpus/`, next to `test/fixtures/gitops-layouts/`
rather than at the top of `test/`. The two are mistaken for each other regularly and the
adjacency is the point: a new `test/fixtures/README.md` states the distinction in one
table. `gitops-layouts/` is Git-in, real-world shapes we did not write, recording
observations and never verdicts; `layout-corpus/` is Git-out, our own configuration and
the exact patch we expect.

The READMEs travelled with their fixtures. Splitting them would have recreated the drift
the corpus exists to prevent: a page describing a folder it no longer sits in is a page
that can be wrong without anything noticing.

Links were resolved rather than substituted, so the 37 that escape the moved tree were
recomputed from the new depth and the ones that stay inside it were left alone.
`docs/layout/` keeps the argument (the two contracts Go source cites by path, and the
model that produced them) and forwards to the evidence.

Also adds `test/fixtures/layout-corpus/README.md`: how to run the corpus, what `-update`
is and is not for, the four conventions that make it a specification rather than a
snapshot, and how to add a scenario.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… behavior we do not have

`expected-shared-layer-status.yaml` was committed, linked from its README as "the whole
result", and asserted by nothing. Wiring it into the corpus showed it was also wrong.

It claimed the refusal names `layers/observability` and its message reads "the changed
field is expressed in apps/checkout/layers/observability, outside this target's path".
The writer says `base/deployment.yaml` instead, because the layer's patch document and
the base's Deployment share an identity and the manifest store keeps the base and drops
the other as a duplicate. The edit is planned against the base and refused for escaping
the write scope, which is the same refusal shape 8 produces from a repository with no
layer in it at all. The fixture and the README now say that, including the negative
conclusion it leads to: a shared layer above a base does not change the answer.

The fixture was also unreadable by the harness, asserting a `Ready` condition where every
other status fixture asserts `GitPathAccepted` plus `Stalled`. That alone shows nothing
had ever parsed it.

Three things stop this recurring:

- `TestLayoutCorpus_EveryExpectationIsAsserted` closes the corpus over its expectations.
  The existing guard is folder-level, so a folder could be executed by one scenario and
  still carry an expectation no row named. An unasserted expectation is worse than an
  absent one: in review it reads as a pinned behavior and pins nothing.
- The controller's `Stalled` projection now covers this fixture too, so all three of its
  conditions are asserted rather than one.
- WatchRule fixtures decode with `UnmarshalStrict`, matching the GitTargets beside them.
  A stale field in a watchrule.yaml was previously ignored in silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g built work as open

Two header conventions were in use. Five pages carried YAML frontmatter (`status:`,
`date:`, `related:`) and 41 carried a blockquote stanza; two pages carried both and
disagreed with themselves, `watch-manager-ownership.md` reading `status: implemented` in
the frontmatter and **built.** in the stanza. Nothing reads the frontmatter, so its
`related:` lists had also gone stale unnoticed: two of them pointed at
`crd-relationships.md`, which does not exist.

The frontmatter is gone and its surviving links are folded into the stanza, which is the
form the other 41 pages already used and the only one that renders.

The labels are now a closed set of five, ordered by lifecycle: **design**,
**design, decided**, **partly built**, **built**, **deferred**. Every page in `design/`
carries exactly one, and four pages that had no status at all now say where they stand.

`INDEX.md` said "Eighteen other open items" above a table of twenty, four of which were
labelled **built.** in their own row. It is now split: sixteen open, and four built and
kept in `design/` under the rule the index already states, because Go source cites them by
path and `finished/` declares itself non-binding. Nothing moved to `finished/` as a result
of this pass: every shipped design page here is cited from Go, which is exactly the case
that exception exists for. The labels are what was missing, not the filing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The path substitution left four comments broken mid-clause or running past the
column the rest of the file keeps to. No behavior change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 198 files, which is 98 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

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 612980e3-2874-4a9f-acf3-1231cba6e8f6

📥 Commits

Reviewing files that changed from the base of the PR and between f994a90 and 6df9822.

📒 Files selected for processing (198)
  • .coverage-baseline
  • api/v1alpha3/clusterprovider_types.go
  • api/v1alpha3/clusterwatchrule_types.go
  • api/v1alpha3/examples_test.go
  • api/v1alpha3/gittarget_types.go
  • api/v1alpha3/namespace_matcher.go
  • api/v1alpha3/prune_policy.go
  • api/v1alpha3/watchrule_types.go
  • docs/INDEX.md
  • docs/UPGRADING.md
  • docs/design/azure-devops-multi-ack.md
  • docs/design/build-order.md
  • docs/design/created-root-namespace.md
  • docs/design/data-plane-triggering.md
  • docs/design/docs-linting.md
  • docs/design/e2e-coverage-gaps-and-improvements-plan.md
  • docs/design/e2e-finish-plan.md
  • docs/design/e2e-git-server-choice.md
  • docs/design/gittarget-api-wave.md
  • docs/design/gittarget-configuration-freshness.md
  • docs/design/metrics-observability-plan.md
  • docs/design/multi-source-audit-ingress-hardening.md
  • docs/design/placement-visibility-and-declared-defaults.md
  • docs/design/reconcile-triggering.md
  • docs/design/release-image-reuse-plan.md
  • docs/design/sensitive-resource-diagnostics-follow-up.md
  • docs/design/source-scope-simplification.md
  • docs/design/support-boundary/patch-authoring.md
  • docs/design/support-boundary/render-root-scoping.md
  • docs/design/target-watch-plan.md
  • docs/design/watch-and-catalog-architecture.md
  • docs/design/watch-manager-ownership.md
  • docs/design/watch-plane-status-convergence-failures.md
  • docs/layout/README.md
  • docs/layout/model.md
  • docs/layout/shapes/7-kustomize-layered/expected-shared-layer-status.yaml
  • docs/spec/status-conditions-guide.md
  • internal/controller/clusterwatchrule_controller.go
  • internal/controller/commitrequest_controller.go
  • internal/controller/constants.go
  • internal/controller/gitprovider_controller_test.go
  • internal/controller/gittarget_controller.go
  • internal/controller/gittarget_controller_unit_test.go
  • internal/controller/gittarget_layout_test.go
  • internal/controller/gittarget_placement_validation_test.go
  • internal/controller/gittarget_status_test.go
  • internal/controller/status.go
  • internal/controller/stream_status_test.go
  • internal/controller/watchrule_controller.go
  • internal/git/branch_worker.go
  • internal/git/inplace_overrides_test.go
  • internal/git/kustomization_bootstrap_test.go
  • internal/git/layout_corpus_test.go
  • internal/git/manifestedit/kustomization.go
  • internal/git/placement_metrics_test.go
  • internal/git/plan_flush.go
  • internal/git/render_fidelity_gate.go
  • internal/git/render_fidelity_gate_test.go
  • internal/git/resync_push_test.go
  • internal/git/resync_scope_test.go
  • internal/git/serialize_namespace_test.go
  • internal/git/types.go
  • internal/layoutfixture/layoutfixture.go
  • internal/manifestanalyzer/acceptance.go
  • internal/manifestanalyzer/kustomization_parse.go
  • internal/manifestanalyzer/layout.go
  • internal/manifestanalyzer/overrides_projection.go
  • internal/manifestanalyzer/placement.go
  • internal/manifestanalyzer/plan.go
  • internal/manifestanalyzer/scan_repo.go
  • internal/manifestanalyzer/store.go
  • internal/queue/author_fact.go
  • internal/queue/fact_index.go
  • internal/telemetry/exporter.go
  • internal/types/cell.go
  • internal/watch/event_router.go
  • internal/watch/event_router_test.go
  • internal/watch/git_path_acceptance_test.go
  • internal/watch/gitpath_events.go
  • internal/watch/owner.go
  • internal/watch/render_fidelity_gate.go
  • internal/watch/retention_rollup.go
  • internal/watch/retention_rollup_test.go
  • internal/watch/target_watch.go
  • internal/watch/target_watch_plan.go
  • internal/watch/watch_plane_state.go
  • test/e2e/e2e_test.go
  • test/e2e/helmrelease_mirror_edit_e2e_test.go
  • test/e2e/prune_mode_e2e_test.go
  • test/e2e/source_cluster_e2e_test.go
  • test/e2e/watchrule_configmap_secret_e2e_test.go
  • test/fixtures/README.md
  • test/fixtures/layout-corpus/README.md
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/README.md
  • 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/watchrule.yaml
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/expected-checkout-config.patch
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/input/checkout-config.yaml
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/repository/mirror/prod/billing-invoices.yaml
  • test/fixtures/layout-corpus/shapes/1-flat-serialized/repository/mirror/prod/shop-web.yaml
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/README.md
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/config/consumer-flux-kustomization.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/2-flat-namespace-free/config/watchrule-second-namespace.yaml
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/config/watchrule.yaml
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/expected-checkout-config.patch
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/expected-second-namespace-status.yaml
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/input/checkout-config.yaml
  • test/fixtures/layout-corpus/shapes/2-flat-namespace-free/repository/apps/checkout/web.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/README.md
  • 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/watchrule.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/expected-checkout-config.patch
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/input/checkout-config.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/repository/clusters/home/_cluster/rbac.authorization.k8s.io/clusterroles/homelab-viewer.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/repository/clusters/home/billing/configmaps/invoices.yaml
  • test/fixtures/layout-corpus/shapes/3-tree-serialized/repository/clusters/home/shop/apps/deployments/web.yaml
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/README.md
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/config/consumer-argocd-application.yaml
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/config/gittarget.yaml
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/config/watchrule.yaml
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/expected-checkout-config.patch
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/input/checkout-config.yaml
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/repository/apps/checkout/configmaps/web.yaml
  • test/fixtures/layout-corpus/shapes/4-tree-namespace-free/repository/apps/checkout/deployments/web.yaml
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/README.md
  • 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/5-kustomize-single-folder/config/watchrule.yaml
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/expected-checkout-config.patch
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/expected-empty-folder-first-write.patch
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/input/checkout-config.yaml
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/repository/apps/checkout/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/5-kustomize-single-folder/repository/apps/checkout/web.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/README.md
  • 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/6-kustomize-base-and-overlays/config/watchrule-prod.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/expected-app-root-status.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/expected-checkout-config.patch
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/input/checkout-config.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/repository/apps/checkout/base/deployment.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/repository/apps/checkout/base/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/repository/apps/checkout/overlays/acceptance/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/repository/apps/checkout/overlays/prod/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/6-kustomize-base-and-overlays/repository/apps/checkout/overlays/test/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/README.md
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/config/gittarget-prod.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/config/watchrule-prod.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/expected-checkout-config.patch
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/expected-shared-layer-status.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/input/checkout-config.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/input/deployment-scrape-changed.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/repository/apps/checkout/base/deployment.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/repository/apps/checkout/base/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/repository/apps/checkout/envs/prod/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/repository/apps/checkout/envs/test/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/repository/apps/checkout/layers/observability/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/7-kustomize-layered/repository/apps/checkout/layers/observability/scrape-annotations.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/README.md
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/config/gittarget-prod.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/config/watchrule-prod.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/expected-env-change-status.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/expected-image-bump.patch
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/input/deployment-env-changed.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/input/deployment-image-bumped.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/repository/apps/checkout/base/deployment.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/repository/apps/checkout/base/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/8-base-owned-field-edit/repository/apps/checkout/overlays/prod/kustomization.yaml
  • test/fixtures/layout-corpus/shapes/README.md
  • test/fixtures/layout-corpus/specific-examples/README.md
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/README.md
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/config/gittarget.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/config/watchrule.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/expected-paperless.patch
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/input/paperless.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/repository/bootstrap/argocd-applications/application-jellyfin.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/repository/bootstrap/argocd-applications/application-nextcloud.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-argocd/repository/bootstrap/argocd-applications/kustomization.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/README.md
  • 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/homelab-flux/config/watchrule-media.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/config/watchrule.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/expected-bitnami.patch
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/input/bitnami.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/repository/apps/home/media/helmrelease-jellyfin.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/repository/apps/home/media/kustomization.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/repository/infrastructure/home/sources/gitrepository-homelab.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/repository/infrastructure/home/sources/helmrepository-jellyfin.yaml
  • test/fixtures/layout-corpus/specific-examples/homelab-flux/repository/infrastructure/home/sources/kustomization.yaml
  • test/fixtures/layout-corpus/specific-examples/prerequisites/README.md
  • test/fixtures/layout-corpus/specific-examples/prerequisites/config/gitprovider.yaml
  • test/mutationlab/e2e/workload_scenarios_test.go

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

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 Sep 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.85714% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/controller/gittarget_controller.go 78.5% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

sunib and others added 8 commits September 2, 2026 15:54
…er reaches a reader

Production Go was 27% comments. Three cuts, in descending order of how safe they are.

**API design rationale (185 lines).** api/v1alpha3 carried two comment blocks per field,
separated by a blank line: the second becomes the CRD description a user reads through
`kubectl explain`, the first never leaves the source. Twenty of them opened with the same
boilerplate sentence saying so. The detached halves are compressed to the fact a reader
cannot derive (why *bool, why top level, why the field is retained purely to fail), and
one 18-line block explaining that commitWindow moved off GitProvider became two lines
citing UPGRADING.md, which already holds that story in full.

`config/crd/` and zz_generated.deepcopy.go are byte-identical after regeneration, which
is the whole reason this cut is first: it is provable rather than argued.

**The densest write-path files (295 lines).** plan_flush.go, placement.go and
internal/git/types.go carried multi-paragraph essays where a sentence does. Every
non-obvious invariant survives — why pruneMode must be read through OrDefault, why the
sweep scope must equal the gathered scope, why intentFor takes the live object and not
the event — in about a third of the words.

**Doc references (47 of 309).** Removed where the citation was sequencing, backlog or an
investigation log: a reader chasing docs/design/target-watch-plan.md from a comment about
cell identity learns nothing the comment did not already say. Kept everywhere the document
is the contract rather than the reasoning: spec/, facts/, layout/, and the
support-boundary pages cited by section, which define behavior the code cannot state.

Nothing moved to a doc; nothing was deleted that only existed here. Where a comment
duplicated a document, it now cites it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…vious core

Second pass, same rule as the first: keep the fact a reader cannot derive from the code,
drop the restatement around it. store.go, branch_worker.go, overrides_projection.go,
scan_repo.go, plan.go, kustomization_parse.go and acceptance.go.

What survives is every invariant that would cost a bug to rediscover — why a patch file is
a build input and not a manifest, why the allowlist is filename-based (a real
kustomization.yaml has no metadata.name, so a GVK match would never see it), why tag and
digest clear each other in kustomize's own transformer, why aligning two lists by position
is measurably wrong rather than merely risky, why a nil DesiredResource cannot be skipped
in a sweeping planner.

What goes is the narration: restating a signature in prose before explaining it, naming
the milestone a function shipped in, and re-deriving a conclusion the next sentence states
anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same rule. The tier-precedence argument in Lookup, the sticky removal pointer, why Name
came back to AuthorFact after being dropped, and why the shared-snapshot refresh runs off
the loop all survive; the narration around them does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lly takes

Review found the guard skipping three cases silently, each of which is a way an example
goes stale rather than a way it belongs to somebody else:

- **malformed YAML** returned nil from the probe unmarshal and was passed over;
- **an unknown kind** in our own group fell through the switch to nil;
- **an older API version** (`configbutler.ai/v1alpha2`) failed the exact-string match on
  `apiVersion` and read as an unrelated schema.

Every document is now parsed before anything decides to skip it, and the group is compared
on the parsed GroupVersion rather than a string match, so a document in `configbutler.ai`
must name the version we serve and a kind we register. The neighbouring
`examples.configbutler.ai` and `manifestanalyzer.configbutler.ai` groups still skip, which
is why the comparison is on the group and not a prefix.

Documents are read through k8s.io/apimachinery's YAML reader instead of splitting on
"\n---\n". That split was wrong twice over: a document may open with a separator, and the
sequence can appear inside a block scalar.

Each of the four failure modes was verified by planting a fixture and watching the guard
name it, rather than assumed from the code.

`TestExampleKindsCoverTheScheme` closes the remaining hole: adding a CRD and an example for
it in one change would otherwise leave that example unchecked, since an unregistered kind
returns nil. Meta kinds are identified by their package, because apimachinery injects
GetOptions and friends into every registered group version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two review findings, both introduced by the status-stanza pass on this branch.

`build-order.md` still opened its corpus section with "it is not wired up yet" and said the
fixtures were "read today by nothing but a human: no Go file references either directory".
Neither is true on this branch: `TestLayoutCorpus` executes fifteen scenarios over eleven
folders, and the fixtures moved under `test/fixtures/` precisely because a test runs them.
The section now records where each of its three rules landed, including the one that only
came out when the corpus was wired up: shape 7's refusal fixture was committed and asserted
by nothing, and turned out to describe a refusal the writer does not produce.

`gittarget-configuration-freshness.md` said "Not an active implementation proposal," twice,
from folding the old frontmatter into the blockquote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eal owners

The page sequenced five in-flight changes across three PRs. All three shipped, so the
question it answered ("what ships together, what can ship alone") no longer has a subject,
and about 150 of its 225 lines were an argument about an order already taken.

It had also gone stale in a way that misleads rather than merely ages: "Track A. All of it
is unbuilt" was false in every part. `spec.suspend`, `spec.placement`, `status.placement`,
`LayoutResolved`, the `Ambiguous` rule, `serializeNamespace`, `useKustomize`, the
one-source-namespace refusal and creating a `kustomization.yaml` are all in the tree.

Nothing is lost, because every open item it tracked is owned elsewhere and was already
written up in more detail there:

- track C, including that steps 2 and 3 shipped and the same one-and-a-half-to-two-week
  ballpark, is in support-boundary/patch-authoring.md;
- the `SelfSubjectAccessReview` pass is in source-scope-simplification.md;
- the trimmed riders are in gittarget-api-wave.md.

Five referents moved to those owners rather than being dropped. The load-bearing one is the
corpus's live skip, which names the track's definition of done from Go and is checked by
`task lint-docs`: it now cites patch-authoring.md step 1, which is where that work actually
lives. `model.md` kept its PR table as a record and lost the cross-track pointer, since both
its PRs have shipped and the other two tracks were never dependencies of them.

The generated behavior reference the page proposed is dropped deliberately, not re-homed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The guide already said how status writes are suppressed. It did not say which fields should
exist in the first place, so the rule was rediscovered per field: once on
status.placement ("No counters, those are metrics, and a counter in status is a status
write per event") and again on resolvedAtRevision, which is deliberately not re-stamped per
scan because that would write status once per commit to the branch.

Stated once now: status writes are bounded by configuration changes and health transitions,
never by data-plane throughput. It is a rule about a field's RATE, not its subject, which is
why a reconcile-request handshake passes (bounded by how often a human pokes an annotation)
and a per-type record does not (grows with types AND is rewritten per scan).

Three reasons, and only the first is cost: a resourceVersion bump invalidates every
watcher's cache, a status write feeds the self-triggering reconcile edge the next section
exists to suppress, and a value that never settles cannot be read as a claim about
convergence.

The table is the shipped fields as worked examples rather than invented ones, so the rule
can be checked against the code that already follows it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gitTargetRetentionStatus stamped metav1.Now() when a reported summary carried no
ObservedTime. Nothing can produce that summary: reportRetention sets scope.reported and
state.observed in the same mutation, and the projection is only reached when anyReported()
is true, so a reported summary always carries a real timestamp.

The only caller that reached the branch was the test asserting it, which had to construct a
summary the producer never emits in order to do so. That assertion is gone; the test keeps
its actual subject, which is that an ABSENT retention block and a reported zero mean
different things.

The invariant is now stated on RetentionSummary.ObservedTime, where it is established,
rather than defended in the consumer. Defensive code for an impossible state is worse than
none: it makes the state look reachable to the next reader, and the timestamp it would have
written is exactly the shape docs/spec/status-conditions-guide.md now forbids, so it also
read as a live example of the anti-pattern.

Behavior is unchanged. Unit coverage returns to 77.0% from 76.9%, because the branch that
went was one nothing executed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sunib sunib changed the title chore: move the layout corpus into test/, execute shape 7's refusal, and give every design page one status label chore: move the layout corpus to test/, and tidy the docs and comments Sep 3, 2026
sunib and others added 3 commits September 3, 2026 08:35
…c lock

A reconcile whose status patch is rejected by the optimistic lock publishes
nothing, and nothing re-enqueues it: the winning write is status-only and every
For() carries a GenerationChangedPredicate to filter those out. The object is
left holding an older answer until its periodic requeue, so anything reading it
-- a WatchRule mirroring Ready, a spec waiting on a reason to advance -- reads
the stale value until then.

Two things kept that window open.

stall() never asked writeLost(). It returned its gate's result directly and was
the only status path that skipped the check, which is why a GitTarget that
stalled on "provider not validated yet" kept publishing ValidationFailed for a
full steady interval after it had already computed the recovery. It now routes
its requeue through st.requeueAfter() like every other path.

The recovery cadence also borrowed RequeueStreamSettleInterval (10s), and there
is nothing to settle for: the patch was rejected against a resourceVersion this
reconcile had already read, so the only thing that must happen before a retry
succeeds is the informer cache catching up with a write that is already durable.
RequeueWriteLostInterval (100ms) says that, and stops two unrelated cadences
sharing one constant -- a coincidence that put the recovery in a dead heat with
the 10s condition waits watching the object.

Pulling on blockedGate.result showed it carried nothing: every override in the
chain was RequeueSteadyInterval, which is already stall()'s fallback. It is now
a plain duration set only by the encryption gate, and evaluateValidatedGate,
validateProviderAndBranch and checkForConflicts each drop a return value and
every ctrl.Result behind it.

Also states, in commit(), what the conflict actually is. There is exactly one
status writer per kind here and controller-runtime never runs two reconciles for
one key concurrently, so it is not contention -- a reconcile reads rv=1 from the
cache after its predecessor stored rv=2, and patches if-match rv=1. The old
comment said "somebody else wrote the object", which sends the reader looking
for a second writer that does not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eueAfter

Both rule kinds returned RequeueStreamSettleInterval directly from the CONVERGING
branch of commitRule, which is where a lost write is easiest to miss: a rule
still coming up already polls on the fast settle loop, so the cadence looks
close enough to correct. It is not the same thing. The settle interval is for
watching streams converge; a write the API server rejected needs the cache to
catch up, and until the retry lands the rule publishes the older answer — the
same stale-reason race this series is closing, ten seconds wide.

Each controller now selects its cadence and applies st.requeueAfter once, so the
lost-write check cannot be bypassed by whichever branch picked the cadence.

CommitRequest is deliberately untouched: it does not use reconcileStatus at all,
polling its own interval with Status().Update.

Also trims the commit() comment to the invariant. The measured incident it used
to recite already lives in TestStatusCommit_LostRaceIsRecordedSoTheCallerComesBack,
which is where a reader can act on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…why one is exempt

"Every controller in this package" was wrong: CommitRequestReconciler does not
use this helper. The exposure needs BOTH halves — commit() dropping the write and
a GenerationChangedPredicate filtering the status-only update that won — so the
accurate scope is every controller using the helper.

CommitRequest carries neither half, and the reason is worth stating where the
next reader will ask it rather than leaving it as an absence, so its Reconcile now
records it: no predicate on its For() (a status write re-enqueues it), uncached
APIReader (the cache lag that loses the lock cannot re-drive work), and an
opposite and deliberate write policy — re-running that reconcile would re-finalize
an already-flushed window and mis-report NoOpenWindow, so writeTerminalStatus
retries in place and gives up rather than requeue.

Also carries the coverage baseline the new tests raised, 77.0% → 77.1%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sunib
sunib merged commit b2e8b14 into main Sep 3, 2026
19 checks passed
@sunib
sunib deleted the chore/layout-corpus-to-test-and-docs-cleanup branch September 3, 2026 09:31
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