# Prune gap — does cub-direct prune removed resources on upgrade?

**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`.
