UC5: honour PP removal from a council end-to-end - #4
Merged
Conversation
The single-PP standin now reacts to being removed from its council in three places, all converging on the council's authoritative membership-status endpoint (200 ACTIVE / 202 PENDING / 404 REJECTED): - Boot convergence (membership). Alongside the existing channel convergence, on boot the watcher asks the council whether this PP is still a member of each council it has a row for, so a provider_removed that landed while the standin was down is honoured even if the in-memory cursor missed it. - Inbound notice. POST /api/v1/provider/council/removed is the council's low-trust live signal. It carries no operator auth and is not believed on its own: it triggers the same re-query+converge, demoting only what the council confirms is gone. The on-chain event-watcher stays the can't-miss path; this just reacts immediately. - Bundle gate. Once removed (a REJECTED membership and no surviving ACTIVE one), new bundle submissions are refused with 403 so users move to a different PP. Gated only on an observed removal, never on the mere absence of a membership. The convergence loop is extracted into core::pipelines:: membership_convergence::converge_membership_statuses and reused by boot, the inbound notice, and the existing operator-driven membership sync (post_membership now delegates to it). Tests (TestDb + wiremock): 404 -> REJECTED via convergence and via the inbound endpoint; still-ACTIVE left untouched; removed PP -> bundle 403. Bumps 0.1.2 -> 0.1.3 (Cargo.lock synced).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Part of the UC5 PP removal & continuity slice. This is the provider-stack (Rust single-PP standin) piece — it must honour its own removal, mirroring provider-platform's semantics in Rust.
How
All three paths converge on the council's authoritative
GET /api/v1/public/provider/membership-statusendpoint (200 ACTIVE / 202 PENDING / 404 REJECTED):provider_removedthat landed while the standin was down is honoured on boot even though the cursor is in-memory only. (event_watcher::run)POST /api/v1/provider/council/removed— the council's low-trust live signal. No operator auth; it is not believed on its own. It triggers the same re-query+converge and demotes only what the council confirms is gone. The on-chain event-watcher (which already setsREJECTEDon theprovider_removedevent) remains the can't-miss path; this just reacts immediately.REJECTEDmembership and no survivingACTIVEone),POST /provider/entity/bundlesreturns 403 so users move to a different PP. Gated only on an observed removal — never on the mere absence of a membership — so stacks operating without a council row are unaffected.The convergence loop is extracted into
core::pipelines::membership_convergence::converge_membership_statusesand reused by boot, the inbound notice, and the existing operator-drivenpost_membershipsync (which now delegates to it — no behaviour change).The contract is unchanged;
remove_provideralready emitsProviderRemoved, which the watcher already decodes intoREJECTED.Tests (
TestDb+ wiremock)council_removed_http.rs: convergence demotes toREJECTEDon 404 (the boot/downtime case); leaves anACTIVEmembership untouched on 200; the inboundPOST /provider/council/removedendpoint demotes (202,deactivated:1).bundle_submit_http.rs::removed_from_council_rejects_new_bundles: a bundle that would be 201 while active is 403 once removed.event_watcher_tick::provider_removed_event_marks_membership_rejectedandcouncil_httptests still pass (thepost_membershiprefactor is behaviour-preserving).Run locally with CI's exact toolchain (cargo 1.96.0 / rustfmt 1.9) and flags:
cargo fmt --check,cargo clippy --workspace --all-targets --no-default-features -- -D warnings,cargo test --workspace --no-default-featuresagainst a throwawaypostgres:18, andcd frontend && deno task build— all green. (The docker-smoke image build re-assembles these same validated artifacts and was not run locally.)Version: 0.1.2 → 0.1.3 (Cargo.lock synced).
Coordination
Completes the slice with council-platform (outbound notify on removal) and provider-platform (inbound notify endpoint).