Skip to content

[FEATURE] serializeNamespace and kustomizeRoot: the two things a path template cannot say #322

Description

@sunib

Successor to #293, which proposed replacing the placement path template with a spec.layout
discriminated union and was reversed. The template stays; the two things it genuinely cannot express
become additive fields. Design:
docs/layout/model.md, sequenced as PR 4 of
docs/layout/implementation-plan.md (both land
with #318).

The fields

serializeNamespace: Auto | Always | Never — whether a written document carries
metadata.namespace.

  • Auto (default): omit it when the governing kustomization already sets this resource's namespace.
    Today's inferred behavior, now named.
  • Always: always write it. The only safe choice when nothing downstream supplies it.
  • Never: never write it, and prove something else does.

The name deliberately avoids writeNamespace: "write" is the most loaded word in this API (the
write boundary, the write jail, WriteBoundaryRefused), and writeNamespace: Never invites the
reading "never write to this namespace", which is a permission — precisely what the neighbouring
sourceNamespace fields are.

kustomizeRoot: Adopt | Create | Require — what the target does about the folder's root.

  • Adopt (default): use the one that is there; do nothing if there is none.
  • Create: write a kustomization.yaml the user did not author, which is what bootstraps an empty
    repository and what makes serializeNamespace: Never provable there — the operator owns the file
    the omission depends on.
  • Require: refuse to write when no root governs the path, instead of committing documents that
    land in whatever namespace the applier defaults to.

CreatePerDirectory is deliberately deferred, with its trigger written down in model.md.

Why this is not breaking

Both defaults equal today's behavior, so this ships in any release without a coordinated consumer
bump. That is the largest consequence of the reversal: the whole placement story (PRs 1–4) is
additive, and #294's wave lost its largest member.

What has to ship with it

A post-scan validation pass, in PR 3, because these two fields make promises no CEL rule can
check — the precondition is a property of the observed folder, not of the spec:

Rule Precondition
serializeNamespace: Never requires a namespace supplier a kustomization with namespace: governs the path
kustomizeRoot: Require needs a root one governs the path
a declared single-root assertion the folder has exactly one root

Done when

The six scenarios in docs/layout/examples/ run
unskipped. PR 1 builds them as an executable corpus with every case that needs these fields skipped
and naming this work in its skip message, so this is finished when the last skip is gone — which
is the definition of done the plan is built to produce.

Open questions carried from the design

  • Does serializeNamespace: Never need to name its supplier (KustomizeRoot,
    FluxTargetNamespace, Asserted) so the post-scan pass checks the guarantee rather than infers
    which one was meant?
  • Is Require the right default for a folder that already contains a kustomization when the target
    is created?
  • Should placement.default gain a CRD default now that the shipped ancestor walk (fix(placement): register a new file with its nearest ancestor kustomization #319) registers
    a defaulted path with the kustomization that governs it? The remaining objection is legibility,
    not correctness.

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