Commit c991b17a authored by George Papathanail's avatar George Papathanail
Browse files

update: update specs fm establishment decisions

parent 6032500b
Loading
Loading
Loading
Loading
+1 −1
Changes for decisions/0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ status: "accepted"

# ADR-0046: Operator-triggered federation establishment and zone subscription proxy synchronously through OEG to FM; no DataBus, no internal schema

**Status:** accepted
**Status:** accepted — the *transport* (synchronous internal HTTP OEG→FM, no DataBus, no `command.fm.federation.*`, stateless OEG) stands. The *scope* (three operations), *payload* (trimmed operator-facing shape, "not a raw `FederationRequestData`", FM maps it to OPG.04), and *namespace* (`/platform/v1/federation/*`) are superseded by [ADR-0048](/decisions/0048-oeg-fronts-federation-lifecycle-ewbi-byte-for-byte.md): seven canonical OPG.04 operations under `/operatorplatform/federation/v1/*`, raw GSMA payload forwarded byte-for-byte, operator-triggered outbound only, split from inbound EWBI at the ingress.
**Date:** 2026-09-08
**Affects:** [oeg/architecture.md](/oeg/architecture.md), [oeg/interface-contract.md](/oeg/interface-contract.md), [fm/overview-and-architecture.md](/fm/overview-and-architecture.md), [fm/requirements-and-design.md](/fm/requirements-and-design.md), [platform/architecture.md](/platform/architecture.md)

+1 −1
Changes for decisions/0047-oeg-federation-lifecycle-proxy-errors-are-gsma-problem-json.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ status: "accepted"

# ADR-0047: OEG's federation-lifecycle proxy endpoints return the GSMA OPG.04 problem+json error model, not CAMARA ErrorInfo

**Status:** accepted
**Status:** accepted — the error-model decision stands unchanged. The endpoint paths named below were later corrected by [ADR-0048](/decisions/0048-oeg-fronts-federation-lifecycle-ewbi-byte-for-byte.md): the surface is the seven canonical OPG.04 operations under `/operatorplatform/federation/v1/*`, not three under `/platform/v1/federation/*`. Read every `/platform/v1/federation/partners…` path in this ADR as the corresponding canonical OPG.04 path; the `ProblemDetails` rule applies to all seven.
**Date:** 2026-09-08
**Affects:** [oeg/interface-contract.md](/oeg/interface-contract.md), [oeg/architecture.md](/oeg/architecture.md), [fm/requirements-and-design.md](/fm/requirements-and-design.md), [platform/glossary.md](/platform/glossary.md), [platform/architecture.md](/platform/architecture.md)

+169 −0
Changes for decisions/0048-oeg-fronts-federation-lifecycle-ewbi-byte-for-byte.md: 169 added lines, 0 removed lines.
Original line number Diff line number Diff line
---
type: Decision Record
title: "ADR-0048: OEG fronts seven GSMA federation-lifecycle operations on the canonical OPG.04 prefix, forwarding the raw payload byte-for-byte to FM (operator-triggered outbound only)"
description: "The operator-triggered outbound federation-lifecycle surface is the canonical OPG.04 paths under /operatorplatform/federation/v1/*, carrying the raw GSMA request/response byte-for-byte through OEG to FM; OEG does no reshaping. Corrects ADR-0046's operator-facing-shape / three-operation / platform-namespace scoping; keeps ADR-0046's synchronous-HTTP transport, stateless-OEG, and no-DataBus decisions and ADR-0047's error model."
tags: [adr]
timestamp: 2026-09-08T00:00:00Z
status: "accepted"
---

# ADR-0048: OEG fronts seven GSMA federation-lifecycle operations on the canonical OPG.04 prefix, forwarding the raw payload byte-for-byte to FM (operator-triggered outbound only)

**Status:** accepted
**Date:** 2026-09-08
**Affects:** [oeg/architecture.md](/oeg/architecture.md), [oeg/interface-contract.md](/oeg/interface-contract.md), [fm/requirements-and-design.md](/fm/requirements-and-design.md), [fm/implementation-and-delivery.md](/fm/implementation-and-delivery.md), [fm/overview-and-architecture.md](/fm/overview-and-architecture.md), [platform/architecture.md](/platform/architecture.md), supersedes the scope/payload/namespace parts of [ADR-0046](/decisions/0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md), builds on [ADR-0047](/decisions/0047-oeg-federation-lifecycle-proxy-errors-are-gsma-problem-json.md), [ADR-0042](/decisions/0042-ewbi-oauth2-client-credentials-mtls-deferred.md)

## Context

[ADR-0046](/decisions/0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md) settled the
transport for the operator-triggered federation-establishment trigger — a synchronous internal
HTTP call OEG → FM, no DataBus, no `command.fm.federation.*` subject family, OEG holding no
federation state. That part is sound and this decision keeps it.

ADR-0046 also carried forward three assumptions from [ADR-0043](/decisions/0043-oeg-proxied-federation-establishment.md)
that a later review of the actual request shape showed to be wrong:

1. **Operation set.** ADR-0046 scopes OEG to *three* actions — establish a federation, and
   subscribe / unsubscribe a zone — and keeps read, update, and delete of an existing federation
   on FM's L.2 internal admin API only (REQ-FED-19).
2. **Payload.** ADR-0046 says the request body is "the operator-facing shape FM's L.2 internal
   admin API already defines (`partnerEndpoint`, `mccMnc`, `supportedApis`, `zones`) … **not** a
   raw OPG.04 `FederationRequestData`", with FM mapping that trimmed shape to OPG.04.
3. **Namespace.** ADR-0046 mounts the trigger under OEG's own platform namespace
   (`POST /platform/v1/federation/partners` and siblings, ADR-0020), explicitly "not the EWBI
   prefix".

The request body the caller actually sends is the GSMA `FederationRequestData` schema verbatim —
`origOPFederationId`, `origOPCountryCode`, `origOPMobileNetworkCodes` (`mcc` / `mncs`),
`origOPFixedNetworkCodes`, `initialDate`, `partnerStatusLink`,
`partnerCallbackCredentials` (`tokenUrl` / `clientId` / `clientSecret`). None of ADR-0046's four
trimmed fields appear in it. The caller (the operator Portal / operator tooling) speaks raw GSMA;
there is no intermediate operator schema to define, and OEG has nothing to map.

Consequently the surface is the canonical OPG.04 Federation Management paths, and it is not three
operations but seven: the full create / read / update / delete of a federation, the
federation-context-id lookup, and zone subscribe / unsubscribe.

This does not put OEG on the *inbound* EWBI path. A partner OP establishing federation **with us**
still terminates at FM. The operator-triggered **outbound** direction — our operator standing up
or managing a federation **with a partner** — is what goes through OEG, and it happens to use the
same canonical paths. The two are separated at the ingress, not by URL.

Options considered:

- **Option A: keep ADR-0046 as written** — three operations, trimmed operator payload, OEG
  platform namespace, FM maps to OPG.04. Rejected: the payload assumption is factually wrong, so
  the "operator shape → FM maps it" step describes work that does not exist, and read/update/delete
  would be split across two call paths (OEG for create, FM's L.2 for the rest) for no reason.
- **Option B (this decision):** OEG fronts all seven canonical OPG.04 federation-lifecycle
  operations on `/operatorplatform/federation/v1/*`, forwards the raw GSMA request and response
  byte-for-byte to FM, and keeps every other ADR-0046 property (synchronous HTTP, no DataBus,
  stateless OEG, FM owns all federation state and `federationContextId`).

## Decision

Option B.

### The seven operations

OEG serves these on the canonical OPG.04 server prefix `/operatorplatform/federation/v1`, gated by
an operator JWT carrying the `operator` role:

| Method | Path | OPG.04 operation | Purpose |
|---|---|---|---|
| `POST` | `/partner` | `CreateFederation` | establish a directed federation with a partner |
| `GET` | `/{federationContextId}/partner` | `GetFederationDetails` | read an existing federation |
| `PATCH` | `/{federationContextId}/partner` | `UpdateFederation` | update an existing federation |
| `DELETE` | `/{federationContextId}/partner` | `DeleteFederationDetails` | decommission a federation |
| `GET` | `/fed-context-id` | `GetFederationContextId` | resolve the federation-context id |
| `POST` | `/{federationContextId}/zones` | `ZoneSubscribe` | subscribe to a federated zone |
| `DELETE` | `/{federationContextId}/zones/{zoneId}` | `ZoneUnsubscribe` | unsubscribe from a federated zone |

Read, update, and delete of an existing federation are **on this path** now — ADR-0046's carve-out
keeping them on FM's L.2 internal admin API only is reversed. L.2's endpoints remain for
break-glass / migration use (REQ-FED-19), not as the primary path.

### Byte-for-byte, no reshaping

The caller sends the raw GSMA payload — `FederationRequestData` on `POST /partner`, the
corresponding request schema on each other operation. OEG forwards the request body to FM
**unaltered**, and relays FM's response body (`FederationResponseData` on `POST /partner`, etc.)
and status **unaltered**. OEG builds, parses, and maps nothing on this path. There is no OEG↔FM
envelope and no intermediate schema. FM validates the payload, applies agreement checks, topology
hiding, and persistence, mints `federationContextId` on `POST /partner` and returns it on the same
synchronous response, and drives the outbound OPG.04 handshake with the partner FM.

### Direction and routing

This surface is **operator-triggered outbound only**: our operator managing a federation with a
partner. Inbound federation — a partner OP calling `/operatorplatform/federation/v1/*` at us — is
unchanged: it terminates at FM, which validates the partner `fed-mgmt` token (REQ-FED-01) and runs
OPG.04. That inbound ingress (Nginx → `fm:8082`) is not deployed today, so inbound federation from
external partner OPs is a future release (OEG interface-contract §K.0).

The two directions share the canonical path strings and are separated **at the ingress**: the
operator-facing ingress routes `/operatorplatform/federation/v1/*` to OEG; the (future)
partner-facing ingress routes it to FM. Each component's deployed surface for these paths is
disjoint — a given request reaches exactly one of them, decided by which ingress terminated it.

### Carried over unchanged

- **From ADR-0046:** synchronous internal HTTP OEG → FM; no DataBus, no `command.fm.federation.*`;
  OEG holds no federation state (no `operations` row, no `OperationType`, no domain model,
  repository, migration, or completion consumer); OEG does no OPG.04 protocol logic; FM
  authenticates the OEG → FM call with a platform service credential (OAuth2 client-credentials
  from the platform Keycloak, ADR-0042) and keeps `/internal/*` off any Nginx/Ingress route; FM
  owns all federation state including `federationContextId`; FM also supports self-initiated
  bootstrap federation from startup configuration.
- **From ADR-0047:** every error on these endpoints is OPG.04 `application/problem+json`
  `ProblemDetails` — FM's errors relayed byte-for-byte, OEG's own `400` / `401` / `403` / `502` /
  `503` from a fixed template — never CAMARA `ErrorInfo`.

### Secret handling

`FederationRequestData` carries `partnerCallbackCredentials.clientSecret`, and federation
responses can carry credentials too. On this path OEG **must not log or persist the request or
response body** — only method, path, status, `X-Correlator`, and timing. This matches the
obligation FM already carries ([fm/implementation-and-delivery.md §S.4](/fm/implementation-and-delivery.md)).

## Consequences

What becomes easier:

- No intermediate operator schema to define, version, or keep in lockstep with GSMA; OEG's
  federation code is a reverse proxy with auth and correlation, nothing more.
- Read / update / delete of a federation use one call path, not two.
- The caller speaks one dialect (GSMA) for the whole federation lifecycle.

What becomes harder / must still be done (open items):

- **OPG.04 contract version + vendoring.** The exact OPG.04 / Federation Management OpenAPI
  version this surface conforms to is not pinned here, and no artifact is vendored under
  `external-specs/` (per [ADR-0026](/decisions/0026-external-specs-vendoring-with-commit-pinning.md)).
  The `federation-manager.yaml` currently in the `refactor/fm` code branch is version `1.2.0`
  with snake_case operationIds and ~20 operations, which does not match the "OPG.04 v6.0 OpenAPI
  v1.4.0 / 74 operations" language in the FM docs. Reconciling that and vendoring the chosen
  artifact is a separate task and blocks the conformance gate below.
- **OEG-side conformance gate.** FM's generated OpenAPI is CI-checked against the canonical
  contract ([fm/implementation-and-delivery.md §S.3](/fm/implementation-and-delivery.md)). OEG now
  serves seven of those operations and nothing checks OEG's generated OpenAPI for the same paths.
  An equivalent OEG-side check must be added once the artifact is vendored.
- **Alias carve-out in FM's conformance gate.** With both FM and OEG declaring
  `POST /operatorplatform/federation/v1/partner` and the other six, FM's gate — which "fails on an
  undocumented alias" — needs an explicit note that the ingress split makes the two deployed
  surfaces disjoint, so this is not a violation.
- OEG must terminate the operator-facing ingress for `/operatorplatform/federation/v1/*` and route
  it to OEG, kept separate from the future partner-facing ingress to FM.

What stays the same:

- Inbound EWBI is untouched; FM terminates the whole prefix for partner-originated traffic and
  runs OPG.04.
- No change to the SRM → FM federated-deploy hand-off (ADR-0044) or to the DataBus subject
  registry.

Revisit trigger:

- A requirement to accept and durably queue federation-lifecycle requests while FM is unavailable,
  or to make any of these operations genuinely long-running, would reopen the transport question
  (as ADR-0046 already noted).
+2 −1
Changes for decisions/index.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -48,5 +48,6 @@ the reasoning lives here. Never renumber; supersede instead (see the record-deci
* [ADR-0043](0043-oeg-proxied-federation-establishment.md) - operator-triggered federation establishment is proxied through OEG over the DataBus (command.fm.federation.*); FM also supports config-driven bootstrap federation (transport superseded by ADR-0046)
* [ADR-0044](0044-srm-owns-federated-zone-locality-and-hand-off.md) - SRM resolves Edge Cloud Zone locality for deployment targets and forwards non-local deploys to FM; interim layer pending a future runtime-coordinator component
* [ADR-0045](0045-pin-narrows-candidates-not-probing.md) - a zone/domain pin narrows the device-targeted candidate set rather than bypassing target probing; probing is triggered by how many candidates survive the pin (amends ADR-0038's unpinned scoping)
* [ADR-0046](0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md) - operator-triggered federation establishment and zone subscription proxy synchronously through OEG to FM (no DataBus, no internal schema); scope and payload unchanged from ADR-0043, inbound EWBI stays entirely with FM (supersedes ADR-0043's transport)
* [ADR-0046](0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md) - operator-triggered federation establishment and zone subscription proxy synchronously through OEG to FM (no DataBus, no internal schema); scope and payload unchanged from ADR-0043, inbound EWBI stays entirely with FM (supersedes ADR-0043's transport; scope/payload/namespace corrected by ADR-0048)
* [ADR-0047](0047-oeg-federation-lifecycle-proxy-errors-are-gsma-problem-json.md) - OEG's federation-lifecycle proxy endpoints (§N.7, ADR-0046) return OPG.04 `application/problem+json` `ProblemDetails` for every error - FM's errors relayed unchanged, OEG's own 400/401/403/502/503 from a fixed template; ADR-0030's `ErrorInfo` rule is scoped to OEG's CAMARA northbound surface
* [ADR-0048](0048-oeg-fronts-federation-lifecycle-ewbi-byte-for-byte.md) - OEG fronts seven canonical OPG.04 federation-lifecycle operations under `/operatorplatform/federation/v1/*` (create/read/update/delete federation, fed-context-id lookup, zone subscribe/unsubscribe), forwarding the raw GSMA request/response byte-for-byte to FM; operator-triggered outbound only, separated from inbound EWBI at the ingress; keeps ADR-0046's sync-HTTP/stateless/no-DataBus transport and ADR-0047's error model (corrects ADR-0046's three-operation, trimmed-operator-payload, platform-namespace scoping)
+2 −0
Changes for fm/implementation-and-delivery.md: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -446,6 +446,7 @@ remove the FM fleet from load balancing.
### S.3 Contract Tests

- **OPG.04 compliance:** Vendor the published OPG.04 v6.0 OpenAPI v1.4.0 artifact unchanged, apply only a separately reviewed/versioned profile overlay for recorded upstream defects, and compare FM's generated external OpenAPI against the resulting contract by method/path, parameters, required headers, request/response media types and schemas, status codes, callbacks, security-scheme names, and scopes. The canonical contract has 74 operations under `/operatorplatform/federation/v1`; CI fails on an undocumented alias or deviation.
- **Federation-lifecycle proxy alias (ADR-0048):** OEG also serves seven of these canonical operations — `CreateFederation`, `GetFederationDetails`, `UpdateFederation`, `DeleteFederationDetails`, `GetFederationContextId`, `ZoneSubscribe`, `ZoneUnsubscribe` — for the operator-triggered *outbound* direction, forwarding the raw GSMA payload byte-for-byte to FM. Both FM and OEG declaring these paths is **not** a gate violation: the two are separated at the ingress (partner-facing → FM, operator-facing → OEG), so each component's deployed surface is disjoint. Two follow-ups remain open: (1) FM still declares all 74 operations in its generated OpenAPI regardless — record this alias explicitly so the "undocumented alias" check does not flag it; (2) OEG's generated OpenAPI for those seven paths needs its own conformance check against the same vendored artifact + overlay, which does not exist yet. The OPG.04 contract version to pin and vendor is itself open (ADR-0048): the `federation-manager.yaml` in the `refactor/fm` code branch is `1.2.0` with snake_case operationIds and ~20 operations, inconsistent with the "v1.4.0 / 74 operations" language here.
- **Security metadata and runtime:** Preserve the root OpenAPI OR between `oAuth2ClientCredentials: [fed-mgmt]` and `notifClientCredentials: [fed-mgmt-notif]`, plus callback-specific notification security. Runtime policy must implement the declared operation security exactly or record a deliberate profile deviation; a runtime scope check alone does not repair incorrect OpenAPI metadata.
- **SRM interface:** Validate that `command.srm.*` messages published by FM conform to `CommandEnvelopeV1` and the per-subject payload schemas defined in [srm/interface-contract.md](/srm/interface-contract.md).

@@ -635,3 +636,4 @@ This validates the core architectural claim: FM publishes the same `command.srm.
| Version | Date | Summary |
|---------|------|---------|
| 2.0 | 2026-07-07 | OOP Release 2.0. |
| 2.0 | 2026-09-08 | S.3 Contract Tests: added the federation-lifecycle proxy alias note (ADR-0048) — OEG serves seven canonical OPG.04 operations for the operator-triggered outbound direction, disjoint from FM's surface by ingress split; open follow-ups recorded (explicit alias entry for the 74-op gate, an OEG-side conformance check, and the still-open OPG.04 contract version/vendoring). |
Loading