From 9b98b6e654933c1dc698b3a6d115a9bba475d15c Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Sun, 20 Sep 2026 16:30:36 -0700 Subject: [PATCH] fix: reconcile prunes by GitHub PR state, not the KV index (v0.3.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.2.0's reconcile read desired state from the KV index over RESP. The preview nodes keep ePHPm's KV RESP listener off (no [kv.redis_compat] listener, no [kv] secret), so that reader was inert there — it could never read desired state and every pass aborted. Switch the prune authority to GitHub PR state, which needs no ePHPm-side change and is more authoritative than the KV index (which has an acknowledged lost-update window). Each pass: - lists every preview directory under sites_dir; - parses its canonical site key --pr- back to (repo, N); - asks GitHub what that PR is now, under the switchboard App creds the daemon already mints reporting tokens with (org installation token, minted once per pass); - open (or an unrecognised state) => keep; merged or closed => prune, via the existing KEEP-guarded, exact-path teardown::teardown_preview. Prune-only by design: re-deploying a missing but still-open preview stays on the webhook path. Fail-safe in every direction — uncertainty always keeps: - a site key that does not parse as --pr- (an infra vhost, or a hashed/overflow PreviewLabel) is skipped, never a candidate; - a per-PR GitHub read error keeps that preview; - a failure to mint the installation token aborts the whole pass, so a total GitHub outage prunes nothing; - the keep-list still applies (the API vhost site key is auto-added). Removes the RESP ClusterReader path and the add-direction plumbing (Queue::enqueue/has_job_for_label) added in #37, so there is one authority. Knobs: --reconcile-interval-secs, --reconcile-prune, --reconcile-keep-sites (all default-off/empty), plus --reconcile-owner (default "ephpm"). Requires --app-id/--app-key when enabled (validated). No ePHPm-side, [kv], or restart change is needed. Tests: merged/closed -> prune, open/unknown -> keep, API-error -> keep, missing-lookup -> keep, unparseable/hashed key -> keep, hyphenated-repo parsing, keep-list respected, dry-run removes nothing, real prune removes. --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 36 +- src/config.rs | 123 +++---- src/kv.rs | 263 -------------- src/main.rs | 141 +++++--- src/queue.rs | 181 ---------- src/reconcile.rs | 895 +++++++++++++++++++++-------------------------- 8 files changed, 553 insertions(+), 1090 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cbd5a3..1956469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1243,7 +1243,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "switchboard" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 327d966..056d0c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "switchboard" -version = "0.2.0" +version = "0.3.0" edition = "2024" rust-version = "1.85" license = "MIT" diff --git a/README.md b/README.md index 7290519..11c0957 100644 --- a/README.md +++ b/README.md @@ -472,27 +472,31 @@ database and `.toml` override on disk, every health check green. This is th class of fault behind override counts drifting between nodes and orphaned overrides lingering as 404s. -The reconcile pass is the **level-triggered** backstop. On its own interval, -independent of `gen`, it reads the KV desired state *directly* and converges this -node's on-disk previews to it. Pruning classifies each preview directory from its -**own** `switchboard:preview: