Repository navigation
Description
What
Replace the placement path template as the thing a user declares with a declared layout: what the
folder is. GitTarget.spec.layout, a discriminated union on kind
(Auto | Kustomize | Tree | Flat | Template), plus byType overrides that stay valid under every
kind.
Full design, with seven worked examples and a mechanical migration for every current configuration:
docs/design/gittarget-layout-model.md.
Why
Placement today is a ladder of four rungs, three of which are path templates and one of which is not
(byType → default → the folder's one kustomize root → canonical). Every open placement question
traces to that one mismatch:
- a CRD default for
placement.defaultcannot be added, because a non-empty template consumes the
slot in front of the rung that is not a template; byType: {v1/configmaps: "configmaps/{name}.yaml"}in a kustomize folder produces a file no
kustomization lists, so it is committed and never rendered (see the sibling issue on placement
correctness) — one line of user config;placement.defaulton a kustomize folder does the same thing to every type at once;- nothing in the model can create structure, so a repository that needs a
kustomization.yaml
before it can render cannot be bootstrapped by the thing that writes into it.
A path template cannot express "beside this folder's one kustomization", cannot be read at a glance,
and cannot bring a folder into existence.
The two rules that carry the value
- Whatever chose the path, the file is registered with the kustomization that governs it. That
makes the unrendered-file bug unstatable rather than fixed. - A structural kind excludes a blanket
default, so a declared template can no longer silently
disable the render root.
kind: Auto is a safe CRD default precisely because it names the structural rule instead of
standing in front of it. It is declared inference, which is a different thing from the undeclared
sibling inference that was deleted in #291.
Scope notes
- Breaking (
feat(api)!onGitTarget), and cheaper as part of the API wave than alone. See the
wave issue. - The layout is immutable except a widening transition: GitTarget has no finalizer, so recreating
one re-adopts every document by identity. - It carries the namespace half too:
scope: SingleNamespace|MultiNamespaceis a structural claim
that must agree with the authorization boundallowedSourceNamespaces, andwriteNamespace: FromContext|Always|Neverreplaces the inference that decides whethermetadata.namespaceis
written — the one inference an empty folder cannot perform. - Deliberately not its own CRD: a shared object changing where N folders write, with nothing on
the GitTarget recording it, is the same defect as sibling inference with a different actor.
Open
Whether scope should be derived and materialized at creation instead of declared.
Not in 0.41.0
Deliberately postponed. 0.41.0 is already a large breaking release (the whole attribution fact-stream
model plus the placement break), and this is the start of a separate wave.
Activity
docs: one attribution spec, and the GitTarget wave is postponed rathe…
fix(placement)!: repository state must not move where we write, and a…
docs: carry the mode and interval drops into TODO and the index
sunib commentedon Aug 28, 2026
Reversed, and closing on the evidence that reversed it. The path template stays. What replaces
this proposal is two additive fields, and the design is
docs/layout/model.md(lands with #318, which also moves thisissue's design link out of
docs/design/gittarget-layout-model.md).Leaving the argument here rather than rewriting it, because it was a good argument that lost to
something specific.
What killed which premise
This issue rested on five claims. #319 (in 0.42.1) retired three of them at once by making
kustomization registration an invariant: a new file is now registered with its nearest ancestor
kustomization inside the write jail, whatever path put it there.
byTypeinto a subdirectory produces a file no kustomization listsplacement.defaultdoes the same to every type at oncemodel.mdkustomizeRoot: CreateserializeNamespaceTwo claims survived, and neither needs a discriminator to answer it.
What ships instead
Two additive fields on
GitTarget, defaults equal to today's behavior:serializeNamespace: Auto | Always | Never— whether a document carriesmetadata.namespace, and what has to be true for omitting it to be honest.Neveris checked bya post-scan pass rather than trusted.
kustomizeRoot: Adopt | Create | Require— what happens when the folder's root is missing,present, or must not be assumed.
Createis what bootstraps an empty repository, and it is whatmakes
Neverprovable there.No
kind, noscope, no discriminated union, no migration, no breaking change. Sequenced asPR 4 of
docs/layout/implementation-plan.md, andthe reason the wave (#294) lost its largest member.
Six worked scenarios exercising both fields are in
docs/layout/examples/; PR 1 turns them into anexecutable corpus, and PR 4 is finished when the last skip is gone.
Follow it at #294 and the plan. Re-open only if a folder shape appears that neither field can
describe.
docs: point the queue at #322, and strike what 0.42.1 shipped