FM Release 2 baseline: FastAPI rewrite, OPG.04 E/WBI surface, OAuth2, SRM command loop

Closes #11, #13 (closed), #17, #18. Advances #12 (closed), #15, #19.

What this is

This is a complete refactor of the FM core specially to be released with Release 2, following GSMA OPG.04 v6 spec.

The legacy Connexion/Flask/MongoDB tree is untouched and still present; this adds src/federation_manager/ alongside it. Nothing in the old tree is wired to the new one (and it will be removed very soon).

Two federated FMs establish federation with each other over real OAuth2, and a partner's InstallApp travels all the way to a real SRM and back. Both are covered by tests that run against live infrastructure, not mocks.

Stack and structure (#11, #12 (closed))

  • FastAPI, hexagonal layout mirroring OEG and SRM: core/, domain/, application/, adapters/, api/.
  • Pydantic settings, pydantic models throughout, create_app(lifespan) composition root.
  • PostgreSQL via SQLAlchemy 2 async. Tables: partner_ops, federation_contexts, federation_agreements, routing_rules, federation_transactions.
  • RFC 7807 application/problem+json errors, with OPG.04's ProblemDetails declared on every E/WBI operation.
  • oop-commons does not yet exist, so commons and contract types are vendored per component, as OEG and SRM already do.

EWBI surface

Paths under /operatorplatform/federation/v1.

Operation Direction
CreateFederation inbound and outbound
GetFederationHealth inbound
DeleteFederationDetails inbound
InstallApp inbound
APIForwarding outbound
onInstanceStatusEvent outbound callback

POST /internal/federation/outbound is the OEG hand-off (RD §L.3), scoped to subscriber-identifier routing per ADR-0044.

Authorization (#13 (closed), #18)

  • Inbound: Bearer JWT validated against Keycloak's JWKS, checking issuer, expiry and the fed-mgmt scope, with the azp claim resolving the partner via partner_ops.
  • Outbound: OAuth2 Client-Credentials per partner, token cached until near expiry, secret read from a mounted file so rotation needs no redeploy.
  • Callbacks use fed-mgmt-notif, per OPG.04 Sec. 9.5.
  • No component outside FM is involved. Each partner's token endpoint comes from its own partner_ops row.

Partner addressing (#17)

X-Partner-Api-Root is gone. The partner's EWBI root is partner_ops.base_url, and every call is scoped by a federation_contexts row holding the directed federationContextId. Multi-operator federation works because partner location is registered state, not per-request input.

SRM integration (#19)

InstallApp resolves the agreement's (appId, appVersion, flavourId) mapping to a local service_specification_id, writes the transaction pending, publishes command.srm.service.deploy, then marks it in_progress. A durable JetStream consumer on OOP_EVENTS finalises the transaction from event.srm.operation.completed and notifies the partner. FM calls no TF-SDK, writes no SRM tables, and duplicates no placement logic.

Idempotency-Key is honoured: a replay returns the original instance, and the same key with a different body is a 409.

GSMA swagger improvements (to be reported to GSMA?)

docs/ vendors GSMA's OpenAPI byte-for-byte and records five defects in an OpenAPI Overlay 1.0.0 document, each citing the OPG.04 clause that settles it: two unresolvable $ref placeholders, a misplaced required on LockUnlockApplicationZone, APIForwarding requiring fields it never defines, apiResponse referencing the wrong schema, unconditional required where Table 197 makes members conditional, and FederationHealthInfo.federationStatus pointing at the alarm State object instead of the Status enum. scripts/apply_overlay.py regenerates the rendered profile. The base artifact is never edited.

Tests

137 tests. Unit tests use in-memory fakes; integration tests need PostgreSQL, NATS and Keycloak from docker-compose.dev.yaml.

  • test_two_stack_federation.py runs two FMs with two databases against one Keycloak: FM-A federates with FM-B over real client-credentials tokens and queries its health.
  • test_fm_srm_loop.py drives InstallApp through a real SRM and asserts the completion event comes back. Skips unless FM_SRM_URL answers; see docs/running-srm.md.

Spec changes made alongside this

In oop/architecture, branch fm-persistence-model:

  • X-Federation-Correlation-ID dropped from the E/WBI surface. OPG.04 defines no correlation header and a partner never stores or echoes one, so it gave no cross-operator traceability. federation_transactions.federation_correlation_id becomes external_txn_id, holding OPG.04's txnIdentifier.
  • federation_contexts.status enumerated from the OPG.04 Status enum plus terminated. There is no pending state: CreateFederation returns the context id synchronously.
  • InstallApp identifier forms recorded: appInstIdentifier is the app-instance UUID in hex because InstanceIdentifier forbids hyphens, while zoneInfo.zoneId is SRM's zone UUID per ADR-0017.
  • fm/persistence-model.md promoted and aligned.

Known gaps, deliberately not in this MR

  • /readyz and structured logging (REQ-FED-15, REQ-FED-17) are missing; #11 and #12 (closed) stay open until they land.
  • shared_resource_catalogues is not implemented, so catalogue sync (UC-FM-03) is absent.
  • No SRM capacity pre-flight (REQ-FED-09), no terminate path, no network capability (UC-FM-04), no inbound synchronous query (UC-FM-05), no admin API (UC-FM-06).
  • RenewFederation needs expiry/renewal columns on federation_contexts and a schema change; it is deliberately deferred rather than faked.
  • A failed partner callback is recorded, not retried.
  • FM_ALLOW_INSECURE_PARTNER_ENDPOINTS (default false) exists so local stacks without TLS can be tested. It must stay false outside development.

Merge request reports

Loading