UC5: accept inbound PP-removal notice and converge#127
Merged
Conversation
Wire the empty council router stub into a real inbound endpoint: POST /api/v1/council/removed, the live signal a council sends when it removes this provider. The notice is low-trust: it is not believed on its own. For each PP with an ACTIVE membership in the named council we re-query the council's authoritative /public/provider/membership-status endpoint and demote to REJECTED only when the council confirms NOT_FOUND. A forged or stale notice, or a transient council error, can never knock a still-valid membership offline. The PP's own on-chain event-watcher stays the can't-miss path; this just reacts immediately instead of on next poll. - New env-free service core/service/council-notify/handle-removal.ts (unit-testable convergence logic with injectable council query). - Integration test drives the HTTP route through PGlite: 404 -> REJECTED, 200 -> untouched, missing councilId -> 400. Bumps 0.9.3 -> 0.9.4.
AquiGorka
added a commit
that referenced
this pull request
Jun 24, 2026
## What Removes the inbound `POST /api/v1/council/removed` notice endpoint added in #127 (it inverted the pull model and added an unauthenticated inbound surface), and moves removal-honouring fully onto the pull path. ## How **Removed:** - `src/core/service/council-notify/` (handle-removal.ts + its test) - `tests/integration/http/council-removed.test.ts` - the `POST /council/removed` route; `buildCouncilRouter` is an empty router again. **Pull-path verification (with the endpoint gone):** - **Watcher demotes on `provider_removed`** → REJECTED via `deactivateMembership` — already true, unchanged. - **Bundle submission refuses a removed membership** — already true: `resolveChannelContext` throws "no active membership" via `getActiveForPp` (`channel-resolver.ts:108`), unchanged. - **Boot converge-by-query for MEMBERSHIP was MISSING** — only channel-status converged on boot; a removal during downtime relied on retention-bound event replay. **Added** `convergeMembershipStatusesOnBoot`: on boot, query each council's authoritative `/public/provider/membership-status` and demote on 404 via the same `deactivateMembership` path. Env-free + unit-tested. This is the sibling of the existing channel-status boot convergence — pull direction (provider→council), no inbound surface. ## Tests Full suite locally on pinned **deno 2.8.2**: `fmt --check`, `lint`, `check src/main.ts`, CI pay-test job (116), `test:unit` (132, incl. 4 new boot-converge tests), `test:integration` (99) — all green. **local-dev lifecycle e2e** (deploy → pay → on-chain `remove_provider`) against this branch: **passed (exit 0)**. On provider (re)boot the membership is demoted ACTIVE→REJECTED **via the boot converge-by-query** (proven by the live DB transition; the on-chain watcher start even failed on the restart, so the converge backstop alone demoted it — exactly its purpose), with **no notify endpoint in the path**; post-demotion there are 0 active memberships so the bundle gate refuses new submissions. Version: 0.9.4 → 0.9.5 (standalone bump commit). ## Coordination Lands with council-platform (removes the outbound push) and provider-stack (removes the matching inbound endpoint).
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-platform piece: the inbound counterpart to the council-platform removal notify.
The council router was an empty stub. This wires it into a real inbound endpoint —
POST /api/v1/council/removed— the live signal a council sends when it removes this provider.How
ACTIVEmembership in the named council, we re-query the council's authoritative/public/provider/membership-statusendpoint and demote toREJECTEDonly when the council confirmsNOT_FOUND(404).ACTIVE(200) /PENDING(202) / unreachable → left untouched. A forged/stale notice or a transient council error can never knock a still-valid membership offline.REJECTEDon theprovider_removedevent and replays a removal seen during downtime from its ledger cursor on boot). This endpoint just reacts immediately instead of waiting for the next poll.core/service/council-notify/handle-removal.tsso it is unit-testable with an injectable council query.Tests
handle-removal.test.ts): NOT_FOUND → demote; still-ACTIVE → ignore forged/stale notice; UNKNOWN/unreachable → untouched; different council → skip; already-non-ACTIVE → skip (idempotent).tests/integration/http/council-removed.test.ts): drives the real HTTP route through PGlite — 404 → membershipREJECTED(202, deactivated:1); 200 → untouched (deactivated:0); missingcouncilId→ 400.Full suite run locally on pinned deno 2.8.2:
fmt --check,lint, CI pay-test job (116),test:unit(133),test:integration(103) — all green.Version: 0.9.3 → 0.9.4.
Coordination
Pairs with the council-platform outbound notify PR and the provider-stack (Rust standin) removal-honouring PR. Contract unchanged —
remove_provideralready emitsProviderRemoved.