81 lines
5.1 KiB
Markdown
81 lines
5.1 KiB
Markdown
# REPORT — hub v0.70.1: the ghost customer's Delete button must exist
|
||
|
||
**Date:** 2026-07-22 · **Repo:** `felhom.eu` (hub only) · **XS render-gate fix, DEPLOYED** ·
|
||
**STOP honoured: the ghost delete itself is NOT run — awaiting the operator's click.**
|
||
|
||
## The defect (fourth inert seam — a template gate this time)
|
||
|
||
v0.70.0's ghost-delete was fully implemented server-side (`handleCustomerDelete`/`Preview` accept
|
||
`cfg == nil`) and fully unreachable: the Danger-zone card — and the `customerDeleteOpen/Submit`
|
||
script — sat inside the `{{if .HasConfig}}` block that also wraps the RESET card
|
||
(`customer_unified.html` old ~L736–L945). A ghost rendered no Danger zone at all. Handler tests
|
||
POST directly and proved nothing about reachability. Confirmed live on `demo-vm-felhom` pre-fix.
|
||
|
||
## The change
|
||
|
||
- **`configs.go` `handleCustomerUnified`**: new `Deletable` flag = the **exact negation** of the
|
||
preview's 404 predicate (`cfg == nil && no hosts && residue empty`). Hosts were already fetched
|
||
for the Host tab, so only the residue count is a new read — and it runs solely on the ghost
|
||
shape. Lookup error → log + `Deletable=false` (fail toward hiding a destructive control).
|
||
- **Template**: gate split — RESET card + its JS stay `{{if .HasConfig}}`; Danger zone + delete
|
||
dialog + delete JS gate on `{{if .Deletable}}`; Block/Unblock forms get an inner
|
||
`{{if .HasConfig}}`; ghost shape gets a one-sentence intro prefix (the dialog explains the rest).
|
||
|
||
## Tests (`customer_ghost_delete_render_test.go`) + red-proofs
|
||
|
||
Render tests against the real template set, real store on `t.TempDir()`, asserting the delete
|
||
form's `action` attribute and the `customerDeleteOpen(` call site (not free-text):
|
||
|
||
- **A — ghost with residue** (reports only, no config, 0 hosts): delete form + opener + script
|
||
present; RESET form ABSENT; Block/Unblock ABSENT. **PASS**
|
||
- **B — configured customer**: every affordance of today present (RESET, Block, delete machinery);
|
||
plus the blocked→Unblock branch (fixture note: `SaveCustomerConfig` never writes status —
|
||
blocking goes through `SetCustomerConfigStatus`, same as the /block handler). **PASS**
|
||
- **C — nothing left**: page-level **404**, mirroring the preview's semantics. There is **no
|
||
renderable `Deletable=false` state** with real store reads — `customer != nil` requires a report
|
||
row (`GetCustomer` selects from `reports`), and a report row makes `residue.Total() ≥ 1`. The
|
||
only false-branch shape short of that is a residue-lookup error (fail-safe, compile-trivial).
|
||
|
||
**Red-proof 1 (RUN):** Danger-zone gate reverted to `{{if .HasConfig}}` → test A **FAILED** on all
|
||
three delete-affordance assertions ("ghost page must render the delete form … dead UI"). Restored →
|
||
green.
|
||
**Red-proof 2 (RUN):** RESET card gated on `Deletable` (the lookalike) → the RESET-absent assertion
|
||
**FAILED** ("RESET card must NOT render for a ghost (found action=/configs/ghost/reset)").
|
||
Restored → green. `git diff` clean of scaffolding (0 REDPROOF markers).
|
||
|
||
Green gate: `go build ./... && go vet ./... && go test ./...` — **green** (web suite 12.0 s).
|
||
|
||
## Ship (GitOps)
|
||
|
||
| Step | Evidence |
|
||
|---|---|
|
||
| Feature commit | `f4c2c41` (code + template + tests + CHANGELOG + ROADMAP + CONTEXT + CLAUDE.md) |
|
||
| Image | `felhom-hub:0.70.1` built + pushed, ID `f1741b46bfbb` |
|
||
| Manifest bump | **separate chore commit** `068bce6` (operator preference honoured) |
|
||
| Sync | ArgoCD hard-refresh + patch-sync → **Synced/Healthy**, rollout complete |
|
||
| Live verify | pod image `felhom-hub:0.70.1`, imageID digest `sha256:f1741b46bfbb…` (= the built image), clean startup log, `/login` 200. Footer is on password-gated pages — the visual check lands on the operator's next login (endpoint-level method stated per convention) |
|
||
|
||
## AWAITING OPERATOR — the live leg is yours
|
||
|
||
Open `demo-vm-felhom` → Edit tab (Danger zone now renders with the ghost prefix) → **Delete
|
||
customer…** → three acks + typed id → confirm. Expected: customer gone from the Customers list,
|
||
journal legs `residue=ok customer_delete=ok` with `skipped_no_config` on the Hetzner/descriptor
|
||
legs, staleness emails stop. That click doubles as the v0.70.0 + v0.70.1 live validation; the
|
||
ROADMAP R-25b row flips its "PENDING" note once done.
|
||
|
||
## Docs also in this train
|
||
|
||
- `hub/CHANGELOG.md` v0.70.1 (inert-seam framing, template/handler lines cited).
|
||
- `CLAUDE.md`: **the seam-wiring rule now covers template gates** — any conditional UI affordance
|
||
ships with a render test per branch.
|
||
- ROADMAP: R-25b annotated (v0.70.1 render fix, operator live leg PENDING); **R-17 → CLOSED**
|
||
(operator console action: `u629193-sub1` deleted, no sub-accounts remain on PBS-storage-1,
|
||
2026-07-22).
|
||
|
||
## Observations (recorded, not acted on)
|
||
|
||
- Pre-existing page-reachability quirk: a customer with host rows but neither config nor reports
|
||
404s on the unified page (configs.go `cfg == nil && customer == nil`) while the delete preview
|
||
would 200. No such state exists in practice (hosts imply enrollment implies reports); noted only.
|
||
- The Create Config button for ghosts is untouched (re-adopting a ghost stays a legitimate path).
|