Skip to content

fix(placement): register a new file with its nearest ancestor kustomization - #319

Merged
sunib merged 1 commit into
mainfrom
fix/placement-nearest-ancestor-kustomization
Aug 28, 2026
Merged

fix(placement): register a new file with its nearest ancestor kustomization#319
sunib merged 1 commit into
mainfrom
fix/placement-nearest-ancestor-kustomization

Conversation

@sunib

@sunib sunib commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #295. Split out of #318 so the fix can ship on its own — #318 stays open for the layout docs and the maintainer-review feedback.

What was wrong

A declared path into a subdirectory of a kustomize folder produced a document that no kustomization.yaml lists, so kubectl apply -k never rendered it. One byType line was enough to put a file in Git that nothing applies:

placement:
  byType:
    v1/configmaps: "configmaps/{name}.yaml"

governingKustomization looked only in the file's own directory, plus a special case for the write scope's root, so registration depended on whether render-root scoping happened to be in force — a difference the user cannot see.

What changed

  • It now walks up from the file's own directory to the nearest kustomization, bounded by the write jail, because a kustomization above the write scope is a read-only base whose resources: list is not ours to edit. That also reaches namespaceIsInheritedFromContext, which was unreachable for these paths: the document used to carry a metadata.namespace line the folder's own documents omit.
  • IdentityCompletePlacementTemplate no longer requires {version}. The built-in canonical path is deliberately versionless, because two served versions of one group/resource are the same object, so a version segment separates no identities. Requiring it judged the canonical shape as not identity-complete, which is what would make any spec-level default fail our own validation gate.

Acceptance

  • A byType path into a subdirectory of a kustomize folder is registered with the root that governs it, with a test that fails before the change.
  • placement_kustomization_entries_total no longer counts these as failed.
  • IdentityCompletePlacementTemplate accepts the versionless canonical shape; the Secret-safety gate still rejects what it should.

task lint, task test and task test-e2e all pass locally (e2e: 80 passed, 0 failed, 23 skipped opt-in corners).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added documentation explaining how new files are registered with the nearest applicable kustomization.
    • Updated placement validation to accept the versionless canonical path format.
    • New files placed in subdirectories are now registered with their governing ancestor kustomization.
  • Bug Fixes

    • Improved placement handling for declared paths and kustomization resource registration.
    • Ensured namespace transformations continue to apply correctly to newly created files.

…zation

A declared path into a subdirectory of a kustomize folder produced a document
that no kustomization lists, so `kubectl apply -k` never rendered it: one
byType line was enough to put a file in Git that nothing applies.
governingKustomization looked only in the file's own directory, plus a special
case for the write scope's root, so registration depended on whether
render-root scoping happened to be in force — a difference the user cannot see.

It now walks up from the file's own directory to the nearest kustomization,
bounded by the write jail, because a kustomization above the write scope is a
read-only base whose resources: list is not ours to edit. That also reaches
namespaceIsInheritedFromContext, which was unreachable for these paths: the
document used to carry a metadata.namespace line the folder's own documents
omit.

Drop the {version} requirement from IdentityCompletePlacementTemplate. The
built-in canonical path is deliberately versionless, because two served
versions of one group/resource are the same object, so a version segment
separates no identities. Requiring it judged the canonical shape as not
identity-complete, which is what would make any spec-level default fail our own
validation gate.

Fixes #295

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

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 33042447-b31a-4229-be27-004d1dd9ab5d

📥 Commits

Reviewing files that changed from the base of the PR and between 870ee7e and 73d1e7f.

📒 Files selected for processing (6)
  • docs/spec/gittarget-new-file-placement-rules.md
  • internal/controller/gittarget_placement_validation_test.go
  • internal/git/placement_metrics_test.go
  • internal/git/placement_test.go
  • internal/manifestanalyzer/placement.go
  • internal/manifestanalyzer/placement_test.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change fixes declared placement registration in kustomize subdirectories and updates identity validation for the versionless canonical template. Documentation and regression tests cover ancestor registration, metrics, rendered content, and validation.

Changes

Placement corrections

Layer / File(s) Summary
Versionless identity-template validation
internal/manifestanalyzer/placement.go, internal/manifestanalyzer/placement_test.go, internal/controller/gittarget_placement_validation_test.go
IdentityCompletePlacementTemplate now accepts the versionless canonical shape when it contains {groupPath} and {resource}. Tests cover accepted and rejected templates.
Nearest ancestor kustomization registration
internal/manifestanalyzer/placement.go, internal/git/placement_test.go, internal/git/placement_metrics_test.go
governingKustomization walks upward to the nearest kustomization within the write jail. Tests verify resource registration and successful metrics for declared subdirectory paths.
Declared-path registration rules
docs/spec/gittarget-new-file-placement-rules.md
The specification documents ancestor registration for declared and fallback paths, including write-jail and render-root bounds.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🔵 Low · up to 73d1e

When creating a new manifest, a failure to update its governing kustomization can still commit the file without rendering it, and later updates may not repair that registration; the change is otherwise confined to the writable subtree, so it is mergeable with explicit owner awareness or follow-up.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 5 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #295 by registering declared subdirectory placements with the governing kustomization, covering the metric regression, and accepting the versionless canonical identity-comple…
Out of Scope Changes check ✅ Passed The documentation, implementation, and regression tests directly support the objectives in issue #295. No unrelated code changes are evident.
Title check ✅ Passed The title clearly and concisely describes the primary change: registering new files with their nearest ancestor kustomization.
Description check ✅ Passed The description explains the defects, implementation changes, linked issue, acceptance criteria, regression tests, and local test results. It does not reproduce every template section, but it provides…
Full details: Linked Issues check

Explanation

The changes satisfy issue #295 by registering declared subdirectory placements with the governing kustomization, covering the metric regression, and accepting the versionless canonical identity-complete template.

Full details: Docstring Coverage

Explanation

Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 5 files. (1 skipped: 1 unsupported.)

Full details: Description check

Explanation

The description explains the defects, implementation changes, linked issue, acceptance criteria, regression tests, and local test results. It does not reproduce every template section, but it provides the required review context.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/placement-nearest-ancestor-kustomization

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 Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/manifestanalyzer/placement.go 92.3% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@sunib
sunib merged commit 5b7536b into main Aug 28, 2026
19 checks passed
@sunib
sunib deleted the fix/placement-nearest-ancestor-kustomization branch August 28, 2026 15:02
sunib added a commit that referenced this pull request Aug 28, 2026
…t express

Reverses this document's own thesis. It argued that a path template is the wrong
primitive and proposed replacing spec.placement with a spec.layout discriminated
union. That argument rested on five points, and three of them were retired by
#319: a new file is now registered with the nearest kustomization that governs
it, whatever chose its path. Once the template is no longer asked to express
"beside this folder's one kustomization", the discriminator has nothing left to
discriminate — registration was always the best idea in the model, and it is the
part that already shipped.

So the template stays and two additive fields join it, each with a default equal
to today's behavior.

serializeNamespace (Auto, Always, Never) is the one thing a path genuinely cannot
express: kustomize takes metadata.namespace from either the document or a
governing root, and where the file sits decides neither. It is not called
writeNamespace because "write" is this API's most loaded word — mode: Write, the
write boundary, the write jail, WriteBoundaryRefused — so writeNamespace: Never
invites the reading "never write to this namespace", a permission, which is
exactly what the neighbouring sourceNamespace fields are.

kustomizeRoot (Adopt, Create, Require) answers "do we want kustomize" on one
axis: what to do when no kustomization governs the path. When one does, every
value registers, because that is the invariant. Adopt is today's behavior, Create
is the empty-repository bootstrap that was the last surviving argument from the
old thesis, and Require is the safety value the Never guard needs — if the root
disappears, stop writing rather than commit files nothing renders.

Four kustomize facts are measured rather than recalled, and three contradict
assumptions the earlier model was built on: a root does not require a flat folder,
nested roots work one per subfolder and supply their own namespace, there is no
ambient pickup (globs and bare directories both fail), and an unlisted file in a
listed subdirectory renders nothing. The first is why the path may be anything;
the third is why registration must be an invariant.

Two values are considered and not taken, with reasons. Ignore is rejected:
spec.path already expresses it, since the ancestor walk is bounded by the write
jail, and it is the only candidate that changes what happens when a root IS
present — the half that should be invariant. CreatePerDirectory is deferred with
its trigger recorded, because fact 2 proves it would work and it is what would
make Never safe in a multi-namespace tree.

Also takes the maintainer review's status findings, since they are decisions this
document owes: renderRootReason becomes a condition reason rather than a bespoke
field, the accumulating counters go to metrics where placements_total already
carries them, and conditions plus observedGeneration are shown.

The headline is what this deletes: spec.layout, kind, scope, kustomize.create,
the LayoutProfile question, the immutability machinery, and the migration. The
layout model was the largest breaking change in the queue; on this shape it is
not a breaking change at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sunib added a commit that referenced this pull request Aug 28, 2026
The plan still described the #295 correctness fixes as PR 1's first half and
called PR 1 "this PR". Both fixes are on main and released in 0.42.1 via #319:
the ancestor walk and the versionless identity gate. PR 1 is the corpus alone
now, and the shipped fixes become the first thing it asserts, which makes the
corpus regression cover from its first commit rather than scaffolding for later
PRs. Worth stating plainly that the ancestor walk is also why this plan reversed.

The visibility page kept two claims the reversal invalidated. It said the CRD
default for placement.default was superseded because layout.kind is the
defaultable thing; there is no layout.kind, the template stays, and the question
is live again — with a weaker objection than before, because a defaulted path
into a subdirectory is now registered with the kustomization that governs it. And
status.layout is status.placement since spec.layout stopped existing, so the
section says so once at the top rather than being renamed throughout, since this
page is still where the field is argued.

Also fixes a Go comment pointing at docs/design/manifest/version2/, a path that
has not existed for some time and that the folder move only half-corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sunib added a commit that referenced this pull request Aug 28, 2026
#293 is closed with the evidence that reversed it, and its successor is #322:
serializeNamespace and kustomizeRoot as additive fields, which is why the row
leaves the wave. The Tier 1 placement-correctness row shipped in 0.42.1 via #319,
and B2 carries the status.placement rename.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] A declared placement path in a kustomize subdirectory is never rendered, and the identity gate rejects the versionless canonical path

1 participant