Skip to content

fix: reconcile prunes by GitHub PR state, not KV — runs on the preview nodes as-is (v0.3.0) [review, do not merge yet] - #38

Merged
luthermonson merged 1 commit into
mainfrom
fix/reconcile-github-pr-authority
Sep 20, 2026
Merged

luthermonson merged 1 commit into
mainfrom
fix/reconcile-github-pr-authority

Conversation

@luthermonson

Copy link
Copy Markdown
Contributor

Follow-up to #37 (Option B). Makes the level-triggered reconcile's prune authority GitHub PR state, removing the RESP/[kv] dependency so it runs on the preview nodes unchanged (they keep ePHPm's KV RESP listener off). STOP for review + merge — I'll cut v0.3.0, deploy, and re-run the canary→prune→verify delete-proof after you merge.

Why

v0.2.0's reconcile (#37) read desired state from the KV index over RESP. On the live preview nodes there is no [kv.redis_compat] listener and no [kv] secret (127.0.0.1:6379 is refused), so that reader was inert — every pass aborted "KV unreadable", pruning nothing. Confirmed on node-2 during the v0.2.0 canary.

What changed

Prune authority is now GitHub PR state — 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 dir under sites_dir;
  • parses its canonical site key <owner>-<repo>-pr-<N>(repo, N) (parse_preview_site_key; owner from --reconcile-owner, default ephpm);
  • mints one org installation token per pass (installation_token_for_owner — reuses the App-JWT path github.rs/main.rs already use, SWITCHBOARD_APP_ID/--app-key) and asks GitHub what each PR is now;
  • open (or an unrecognised state) ⇒ keep; merged or closed ⇒ prune, via the existing KEEP-guarded, exact-path teardown::teardown_preview (site dir, <key>.db*, <key>.toml, /tmp/ephpm-vhosts/<key>-<hash>, preserving *.single-db-bak).

Prune-only — re-deploying a missing but still-open preview stays on the existing webhook path (noted in the module docs).

Fail-safe (uncertainty always keeps)

  • a site key that does not parse as <owner>-<repo>-pr-<N> — an infra vhost (switchboard, site-a, apex) or a hashed/overflow PreviewLabel — is skipped, never a candidate;
  • a per-PR GitHub read error keeps that preview;
  • a token-mint failure aborts the whole pass → a total GitHub outage prunes nothing;
  • the keep-list still applies (--reconcile-keep-sites; the API vhost site key auto-added).
  • Rate-limit-aware: one token + one GET per preview per pass (a handful), well under any limit.

Removed / simplified

  • the RESP ClusterReader/parse_index path from fix: level-triggered reconcile so previews converge across the cluster [DO NOT MERGE — review] #37 (kv.rs) — one authority now;
  • the add-direction plumbing (Queue::enqueue/has_job_for_label) — prune-only;
  • knobs --reconcile-deploy-missing, --reconcile-max-prunes-per-cycle, --reconcile-api-site. Kept: --reconcile-interval-secs, --reconcile-prune, --reconcile-keep-sites (all default-off/empty); added --reconcile-owner (default ephpm). validate() now requires --app-id/--app-key when the reconcile is enabled (no silent no-op).

Confirmed: NO ePHPm-side / [kv] / restart change

The daemon queries GitHub directly with the App credentials it already has. No [kv] secret, no [kv.redis_compat] listener, no ePHPm restart. Deploying is a binary swap + a --reconcile-* drop-in.

Tests (all green locally)

reconcile.rs: merged_and_closed_prune_open_and_unknown_keep, an_api_error_keeps_the_preview, a_missing_lookup_keeps_the_preview, an_unparseable_key_is_never_a_candidate_even_if_a_lookup_exists, parses_clean_preview_keys_including_hyphenated_repos (php-sdk, switchboard-api, ephpm-ephpm, repo-containing--pr-), refuses_non_preview_and_hashed_keys, label_reconstruction_without_a_suffix_strips_the_domain, list_preview_dirs_filters_infra_staging_and_files, keep_list_excludes_a_dir_from_candidacy, dry_run_reports_would_prune_and_removes_nothing, prune_removes_the_orphan_directory, a_missing_sites_dir_is_an_empty_list.

Gates: cargo check --all-targets, cargo clippy --all-targets -- -D warnings, cargo test (321 passed), cargo fmt --all -- --check, cargo +1.85 check --all-targets (MSRV). Version bumped to 0.3.0 (not tagged — cut the release after merge).

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 <owner>-<repo>-pr-<N> 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 <owner>-<repo>-pr-<N> (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.
@ephpm

ephpm Bot commented Sep 20, 2026

Copy link
Copy Markdown

ePHPm Preview — removed

Preview deployment has been torn down.

@ephpm
ephpm Bot temporarily deployed to preview-pr-38 September 20, 2026 23:32 Inactive
@ephpm
ephpm Bot temporarily deployed to preview-pr-38 September 20, 2026 23:32 Inactive
@luthermonson
luthermonson merged commit 6db14ea into main Sep 20, 2026
4 checks passed
@luthermonson
luthermonson deleted the fix/reconcile-github-pr-authority branch September 20, 2026 23:34

This branch was successfully deployed

1 active deployment
preview-pr-38 9b98b6e6 Deployed Sep 20, 2026 by ephpm[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant