UNOFFICIAL/EXPERIMENTAL. Live receipt generated by scripts/run-prune-gap-proof.mjs; do not hand-edit. Regenerate with npm run prune-gap:proof.
Claim. The cub-direct (no-controller) delivery path is kubectl apply, which does NOT prune resources removed between versions. An upgrade that drops a resource ORPHANS it - the cluster silently keeps an object no longer in desired state, breaking the config-as-data promise on this path. Argo and Flux prune natively; cub-direct must use kubectl apply --prune (label/allowlist) or a controller for clean upgrades.
config-as-data promises the cluster matches desired state. Argo and Flux reconcile and prune. The doctrine's third delivery path - cub-direct (pull the OCI bundle + kubectl apply) - uses plain apply, which only creates/updates the objects handed to it. Proven live on a throwaway kind cluster:
| Step | Observation | Result |
|---|---|---|
apply v1, then v2 (drops ConfigMap/extra-config) | removed ConfigMap still present? true | orphaned (gap confirmed) |
kubectl apply --prune -l app=pruneproof | removed? true | pass |
Overall: watch. Confirmed: the cub-direct path orphans removed resources on upgrade (plain kubectl apply does not prune). --prune removes them - so the doctrine must require --prune (or a controller) for the no-controller upgrade path. Argo/Flux are unaffected (they prune declaratively).
- Affected: the cub-direct / no-controller delivery path only. Not affected: Argo CD and Flux, which prune declaratively.
- The fix: the no-controller upgrade path must use
kubectl apply --prune(with a label selector / allowlist) or hand a delete-set - otherwise every upgrade that removes a resource leaves an orphan. The doctrine should say so. - Receipt:
runs/prune-gap-proof/receipt.yaml.