Skip to content

[FEATURE] A layout is the declared thing, not a path: GitTarget.spec.layout #293

Closed as not planned

Description

@sunib

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.default cannot 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.default on 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)! on GitTarget), 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|MultiNamespace is a structural claim
    that must agree with the authorization bound allowedSourceNamespaces, and writeNamespace: FromContext|Always|Never replaces the inference that decides whether metadata.namespace is
    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

  1. sunib commented on Aug 28, 2026

    @sunib
    ContributorAuthor

    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 this
    issue'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.

    Premise Status
    byType into a subdirectory produces a file no kustomization lists fixed by #319; the class of bug is gone rather than made unstatable
    placement.default does the same to every type at once fixed by the same walk
    a CRD default cannot be added, because a non-empty template shadows the kustomize-root rung weakened to a legibility trade: a defaulted path is now registered, so it is no longer a correctness wall. Open question in model.md
    nothing in the model can create structure stands — and is answered by kustomizeRoot: Create
    a template cannot express namespace serialization stands — and is answered by serializeNamespace

    Two 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 carries
      metadata.namespace, and what has to be true for omitting it to be honest. Never is checked by
      a 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. Create is what bootstraps an empty repository, and it is what
      makes Never provable there.

    No kind, no scope, no discriminated union, no migration, no breaking change. Sequenced as
    PR 4 of docs/layout/implementation-plan.md, and
    the reason the wave (#294) lost its largest member.

    Six worked scenarios exercising both fields are in
    docs/layout/examples/; PR 1 turns them into an
    executable 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.

  2. added a commit that references this issue on Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions