Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .coverage-baseline
Original file line number Diff line number Diff line change
@@ -1 +1 @@
76.4
76.5
14 changes: 9 additions & 5 deletions docs/design/support-boundary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,19 @@ flowchart LR
|---|---|
| Plain KRM and higher-level KRM documents | Editable in place, preserving document form where possible. A `HelmRelease`, `Application`, KRO resource, or Crossplane claim is ordinary intent-layer KRM. |
| Self-contained Kustomize | Local `resources`, `namespace`, `images`, and `replicas` are supported. Image and replica changes write to their declaring entry. |
| External-base overlay | The overlay may read `../../base` as context, but writes remain inside the target overlay. Existing overlay-local documents and declared image/replica entries are editable; base-owned fields are refused. |
| Path-based strategic-merge patch | The render is accepted; the patch is read-only context. We tolerate it but do not author or edit patches yet. |
| External-base overlay | The overlay may read `../../base` as context, but writes remain inside the target overlay. Existing overlay-local documents and declared image/replica entries are editable; a brand-new object is created as an overlay-local file registered in the overlay's own `resources:`; and changing a base-supplied image/replica in one environment **authors a new `images:`/`replicas:` entry** in the overlay. Other base-owned fields are refused. |
| Path-based strategic-merge patch | The render is accepted and a patch that **edits a field** is read-only context — never authored or edited. The one authored patch is **`$patch: delete`** for an object an overlay inherits from its base (a patch file plus a `patches:` entry), verified by re-render. |
| Render verification | A proposed batch is built with kustomize before any bytes are written. Mismatch or blast-radius change refuses the flush. |
| Write boundary | Writes never leave `spec.path`, and a file read by more than one render root is never edited in place. |
| Foreign-content boundary | A GitTarget subtree is operator-exclusive: loose scripts, binaries, and symlinks refuse the folder. Inert repo-hygiene passengers — documentation (`*.md`), a license, and `.gitignore`/`.gitattributes`/`.gitkeep` — are accepted so adopting an existing repo does not stall on them. Anything else is named in a root `.gittargetignore`. |

The remaining overlay gap is deliberately narrow: creating a **new object** and adding its
`resources:` entry needs a placement/write-path correction. It is planned, not shipped.
`scan-repo` now adopts external-base overlays too, matching the runtime: it reports a
Three overlay capabilities are now shipped and verified by re-render: creating a **new object**
(an overlay-local file plus its `resources:` entry); **authoring a missing `images:`/`replicas:`
entry** when a base-supplied image or replica count is changed in one environment; and
**`$patch: delete`** for an object the overlay inherits from its base — so editing a specific
environment adds the override, or the deletion, for you. The remaining overlay gap is a
strategic-merge patch for a base-owned *field* that is not an image, replica, or whole-object
delete. `scan-repo` now adopts external-base overlays too, matching the runtime: it reports a
`kustomize-overlay` candidate as accepted and lets its `editable` count show how much the
overlay owns (a pure passthrough overlay is adopted yet `editable: 0`).

Expand Down
11 changes: 8 additions & 3 deletions docs/design/support-boundary/kustomize-support-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ these fields a general reverse-edit feature.

An external-base overlay can edit an existing overlay-local document and a declared
image/replica entry. Its shared base is read-only. Creating a new overlay-local resource
and adding its `resources:` entry is **planned** pending a placement/write-path correction;
it must not be advertised as shipped yet. `scan-repo` still reports external-base overlays
as unsupported while its classification catches up with the runtime.
and adding its `resources:` entry is **shipped**, and so is **authoring a missing
`images:`/`replicas:` entry** when a base-supplied image or replica count is changed in one
environment — the writer creates the entry (and the section, if absent) in the overlay's own
kustomization, verified by re-render, rather than writing the base. A **`$patch: delete`** is
also authored for an object the overlay inherits from its base. A strategic-merge patch that
**edits a field** of a base-owned object is *not* authored — it stays read-only build context —
and that field-patch authoring is the one remaining overlay gap. `scan-repo` now adopts
external-base overlays too (a `kustomize-overlay` candidate is reported accepted).

```mermaid
flowchart LR
Expand Down
8 changes: 8 additions & 0 deletions docs/design/support-boundary/patch-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ flowchart LR
than guessed or written through.
- Inline patches, JSON6902 patches, deprecated patch spellings, and out-of-tree paths remain
refused; they do not provide the narrow sparse-KRM unit this design relies on.
- **`$patch: delete` is now authored (shipped), as a slice distinct from field patching.**
Deleting an object the overlay inherits from its base writes a small `$patch: delete` document
under the overlay and names it in `patches:`, verified by the re-render oracle (the object must
leave the render, everything else unchanged; a non-matching patch is refused). It needs no
field attribution — the target is identified by apiVersion/kind/namespace/name — so it does not
wait on the scalar-attribution work below. See
[render-root scoping §1/§4](render-root-scoping.md). What is still refused is authoring a patch
that *edits a field* of a base-owned object.

## Narrow first slice

Expand Down
68 changes: 49 additions & 19 deletions docs/design/support-boundary/render-root-scoping.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Render-root scoping

> **Implementation record.** External-base overlay support is shipped for existing
> overlay-local documents and declared `images`/`replicas` entries. New-resource entry
> creation and patch authoring remain separate work.
> overlay-local documents, declared `images`/`replicas` entries, **creating a new
> overlay-local object** (a new file plus its `resources:` entry), **authoring a missing
> `images`/`replicas` entry** when a base-supplied image or replica count is changed in one
> environment, and **`$patch: delete`** for an object the overlay inherits from its base — all
> verified by re-render. Strategic-merge patch authoring for a base-owned *field* (not an
> image/replica) remains separate work.
>
> Related: [support contract](support-contract.md),
> [Kustomize boundary](kustomize-support-boundary.md),
Expand Down Expand Up @@ -33,11 +37,27 @@ flowchart LR
while the **write scope** remains `spec.path`.
- An existing overlay-local document is edited in place. An existing `images:` or `replicas:`
declaration receives the corresponding edit-through change.
- When a **base** supplies an image or replica count and it is changed in one environment (the
classic "bump the image in dev"), the writer **authors a new `images:`/`replicas:` entry** in
the overlay's own kustomization — creating the section if the overlay has none yet — rather
than writing the read-only base. The re-render verifies the entry produces the live value and
moves nothing else; an entry that would over-reach (an image name shared by another object) is
refused there, not committed.
- A **brand-new object** with no existing document is created as an overlay-local file and
registered in the overlay's **own** `resources:` entry — never the base's — with the
re-render verifying it before the commit. A new object whose field a folder-level
`images:`/`replicas:` entry would override is refused loudly by that oracle instead of
committed as a non-converging write.
- Deleting an object the overlay **inherits from its base** authors a `$patch: delete` in the
overlay (a patch file plus a `patches:` entry) rather than deleting the read-only base — the
base, and every other environment that reads it, is untouched. The re-render verifies the
object leaves the overlay's render; a patch that does not match is refused there, not committed.
- A source file reached by more than one render root is read-only. The entire flush is refused
before a commit when a plan would write it.
- A path-based strategic-merge patch is read-only build context. It no longer rejects the
whole overlay merely by existing.
- A base-owned field is refused rather than written through to the shared base.
- A base-owned *field* (not an image, replica, or whole-object delete) is refused rather than
written through to the shared base — strategic-merge field patch authoring is still planned.

The discovery report has caught up: `scan-repo` runs the same adoption gate over an
external-base overlay's render scope, so a `kustomize-overlay` candidate is now reported
Expand Down Expand Up @@ -78,26 +98,36 @@ refuses the edit. See [render attribution](render-attribution.md) for the method

| Observed change in selected overlay | Destination | Status |
|---|---|---|
| Image tag or repository | declared overlay `images:` entry | Editable |
| Replica count | declared overlay `replicas:` entry | Editable |
| Image tag or repository | overlay `images:` entry — edited if declared, **authored if the base supplies it** | Editable |
| Replica count | overlay `replicas:` entry — edited if declared, **authored if the base supplies it** | Editable |
| Existing overlay-local document field | that document | Editable |
| New object | overlay-local file plus `resources:` entry | **Planned** — placement/write-path correction required |
| Base-owned field | operator-authored strategic-merge patch | Refused pending [patch authoring](patch-authoring.md) |
| Delete inherited object in one overlay | `$patch: delete` | Refused |

Entry creation is distinct from editing an entry. The current writer does not safely add a
missing `images:`/`replicas:` declaration or create a resource entry for a new object, so the
contract labels those paths planned rather than relying on the existing generic placement
feature.
| New object | overlay-local file plus `resources:` entry | **Editable** — placed in the overlay, registered in its own kustomization, verified by re-render |
| Delete inherited object in one overlay | overlay `$patch: delete` (patch file plus `patches:` entry) | **Editable** — authored in the overlay, verified by re-render; the base is untouched |
| Base-owned field (not image/replica/delete) | operator-authored strategic-merge patch | Refused pending [patch authoring](patch-authoring.md) |

Entry creation is distinct from editing an entry, and both are now shipped for images/replicas.
Creating a **new object** — an overlay-local file plus a `resources:` entry in the overlay's
**own** kustomization (the base's is out of the write jail) — is shipped. **Authoring a missing
`images:`/`replicas:` entry** is too: when a base supplies an image component or replica count
and the environment changes it, the writer creates the entry (and the section, if absent) in the
overlay rather than writing the read-only base, and the re-render adjudicates the proposal.
**Deleting an inherited object** authors a `$patch: delete` in the overlay under the same
verification. What remains is a strategic-merge patch for a base-owned field that is *not* an
image, replica, or whole-object delete.

## 5. Remaining work

1. Correct overlay-local new-object placement, including a deterministic `resources:` entry.
2. Add missing `images:` and `replicas:` declarations only under the same verification proof.
3. Discovery classification is flipped (an external-base overlay reports accepted); a dedicated
cluster end-to-end overlay case remains.
4. Author a narrow scalar strategic-merge patch for base-owned fields; lists, deletes, and
structural merges require separate decisions.
1. Add a dedicated cluster end-to-end overlay case (discovery classification is already flipped —
an external-base overlay reports accepted).
2. Author a narrow scalar strategic-merge patch for a base-owned *field*; list merges, element
deletes, and structural merges require separate decisions.

Overlay-local new-object placement (a new file plus its `resources:` entry), authoring a missing
`images:`/`replicas:` entry, and `$patch: delete` for an inherited object are all done, covered by
`TestPlacement_ExternalBaseOverlay_NewObject`, `TestOverlayAuthors_ImageEntry_ForBaseSuppliedImage`
/ `..._ReplicaEntry_ForBaseSuppliedCount`, and `TestOverlayAuthors_DeletePatch_ForInheritedObject`;
they left the list as the write path proved them end-to-end. `scan-repo`'s discovery flip landed
separately in #243.

The earlier exploratory prompt material was consolidated into this implementation record and
[patch authoring](patch-authoring.md). Git history retains the detailed experiments without
Expand Down
6 changes: 4 additions & 2 deletions docs/design/support-boundary/support-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ templates, and multi-input ResourceSet templates alike. It is specified once, in
| Construct | Verdict | Why |
|---|---|---|
| kustomize `resources`, `namespace`, `images`, `replicas` | **Editable** | invertible; `images`/`replicas` edit-through is shipped |
| kustomize base + un-fancy overlays | **Editable, narrow** | render-root scoping reads `../../base` as read-only context and writes existing overlay-local documents plus declared image/replica entries in the overlay; the base is never written. `scan-repo` still reports these as `overlay-fan-out-unsupported` — the discovery-side flip is a follow-up |
| New object in an external-base overlay | **Planned** | needs an overlay-local file **and** a correct `resources:` entry; the current placement/write path does not yet prove that route |
| kustomize base + un-fancy overlays | **Editable, narrow** | render-root scoping reads `../../base` as read-only context and writes only inside the overlay; the base is never written. It edits existing overlay-local documents and declared image/replica entries, and **authors a new `images:`/`replicas:` entry** when a base-supplied image/replica is changed in one environment. `scan-repo` still reports these as `overlay-fan-out-unsupported` — the discovery-side flip is a follow-up |
| New object in an external-base overlay | **Editable** | placed as an overlay-local file and registered in the overlay's own `resources:` entry (never the base's), verified by re-render before the commit; proven by `TestPlacement_ExternalBaseOverlay_NewObject`. A new object a folder `images:`/`replicas:` entry would override is refused loudly by the oracle, not committed |
| Change a base-supplied image/replica in one overlay | **Editable** | the writer authors a new `images:`/`replicas:` entry in the overlay's own kustomization (the base stays read-only), verified by re-render; proven by `TestOverlayAuthors_ImageEntry_ForBaseSuppliedImage` / `..._ReplicaEntry_ForBaseSuppliedCount` |
| Delete a base-inherited object in one overlay | **Editable** | the writer authors a `$patch: delete` (patch file plus `patches:` entry) in the overlay, verified by re-render that the object leaves the render; the base is untouched. Proven by `TestOverlayAuthors_DeletePatch_ForInheritedObject`. A patch that fails to match is refused by the oracle, not committed |
| kustomize base shared by >1 overlay, edited in place | **Refused** | fan-in > 1 |
| kustomize `patches:` — a strategic-merge document named by `path:` | **Tolerated, not authored** | the folder is accepted and the render is mirrored; the patch is read-only build context. An edit to a field the patch OWNS is refused per object, not per folder |
| kustomize `patches:` — inline, JSON6902, or a path outside the tree | **Refused** | not a sparse KRM document we can read; refused by name |
Expand Down
Loading