Commit b6f12259 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

Merge branch 'oeg-fm' into 'develop'

OEG - FM latest updates

See merge request !3
parents 033ddc66 f04a18bc
Loading
Loading
Loading
Loading
+1 −1
Changes for decisions/0043-oeg-proxied-federation-establishment.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ status: "accepted"

# ADR-0043: Operator-triggered federation establishment is proxied through OEG over the DataBus; FM also supports config-driven bootstrap federation

**Status:** accepted
**Status:** accepted — the transport (async `command.fm.federation.*` over the DataBus) and the `PENDING` operation-record requirement are superseded by [ADR-0046](/decisions/0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md), which makes the OEG→FM hop a stateless synchronous HTTP call; everything else (OEG-routed rather than direct against FM's L.2 admin API; FM config-bootstrap federation) stands
**Date:** 2026-09-04
**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)

+132 −0
Changes for decisions/0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md: 132 added lines, 0 removed lines.
Original line number Diff line number Diff line
---
type: Decision Record
title: "ADR-0046: Operator-triggered federation establishment and zone subscription proxy synchronously through OEG to FM; no DataBus, no internal schema"
description: "Operator-triggered federation establishment and zone subscription proxy synchronously through OEG to FM; no DataBus, no internal schema"
tags: [adr]
timestamp: 2026-09-08T00:00:00Z
status: "accepted"
---

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

**Status:** accepted — the *transport* (synchronous internal HTTP OEG→FM, no DataBus, no `command.fm.federation.*`, stateless OEG) and the *namespace* (`/platform/v1/federation/*`) stand. The *scope* (three operations) and *payload* (trimmed operator-facing shape, "not a raw `FederationRequestData`", FM maps it to OPG.04) are superseded by [ADR-0051](/decisions/0051-oeg-fronts-federation-lifecycle-ewbi-byte-for-byte.md): eight operations, raw GSMA payload forwarded byte-for-byte, each scoped by an explicit `partnerOpId` path segment, operator-triggered outbound only, split from inbound EWBI at the ingress. ADR-0051's first version (2026-09-08) had also moved the namespace onto the bare canonical OPG.04 paths; that part was reverted in its 2026-09-15 amendment, which restores this ADR's namespace decision.
**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)

## Context

[ADR-0043](/decisions/0043-oeg-proxied-federation-establishment.md) decided that operator-triggered
federation establishment is proxied through OEG asynchronously over the DataBus: OEG persists a
`PENDING` `operations` row and publishes a new `command.fm.federation.create` /
`command.fm.federation.zone_subscribe` subject family — a thin envelope wrapping the same payload
shape FM's L.2 endpoints already define — which FM consumes. ADR-0043 left the mandatory inbound
EWBI termination (partner OPs calling our `POST /partner` directly) with FM, reached via Nginx
routing `/operatorplatform/federation/v1/*` straight to FM. A follow-up discussion on 2026-09-07
narrowed OEG's role to a stateless proxy and proposed carrying the OEG→FM hop as NATS
request/reply over the DataBus with two OEG-side GSMA↔internal mapper functions; that transport
and that mapping layer were never written into the specs.

The 2026-09-08 call revisited both and reversed the transport and the proposed OEG-side mapping.
Federation establishment is request/response by nature — the operator triggers it and expects
`federationContextId` back on the same call — with no fan-out and no durability requirement beyond
the life of the HTTP request, so an async command family plus a completion channel is machinery
with no payoff. And a new OEG↔FM internal schema is redundant: FM's L.2 internal admin API already
defines the partner/agreement payload shape, so OEG can forward that shape unchanged and let FM
map it to OPG.04 — the 2026-09-07 plan's two OEG-side GSMA↔internal mapper functions are
duplicated work and a second schema to keep in lockstep.

Options considered:

- Option A: Keep ADR-0043 as written — async `command.fm.federation.*` over the DataBus, OEG
  persists an operation record.
- Option B: Keep ADR-0043's operator-facing trigger and its payload, but carry the OEG→FM hop as
  a synchronous internal HTTP call instead of an async DataBus command — OEG relays FM's response
  unchanged, holds no federation state, writes no `operations` row.

## Decision

Option B — a transport change to ADR-0043, nothing more. **The scope, the operator-facing
endpoint, and the payload are exactly ADR-0043's**; only the OEG→FM mechanism changes from async
`command.fm.federation.*` over the DataBus to a synchronous internal HTTP call. The
`command.fm.federation.*` subject family is not created; the DataBus stays scoped to the
`command.srm.*` / `event.srm.*` flows FM already uses.

**Scope — operator-triggered outbound only.** OEG exposes an operator-facing trigger for standing
up a new *outbound* federation and for managing its zone subscriptions, under OEG's own platform
namespace (`POST /platform/v1/federation/partners` and the `.../zones` sub-resource, ADR-0020),
gated by an operator JWT with the `operator` role. Reading, updating, and decommissioning an
existing federation stay on FM's L.2 internal admin API directly (REQ-FED-19) — ADR-0046 does not
move them. The request body is the operator-facing shape FM's L.2 internal admin API already
defines (`POST /internal/partners` / `/internal/agreements`) — **not** a raw OPG.04
`FederationRequestData`. OEG performs no GSMA/OPG.04 mapping or protocol logic; FM alone maps the
operator request to the outbound OPG.04 `CreateFederation` / `ZoneSubscribe` handshake, stores the
partner-issued `federationContextId`, and returns it on the same synchronous response, which OEG relays to the
operator.

**Inbound EWBI termination is unchanged.** Partner OPs calling `/operatorplatform/federation/v1/*`
directly (`CreateFederation`, `InstallApp` and application LCM, `APIForwarding`, all of it) are
routed by Nginx straight to FM, exactly as before — no path split, OEG is not on that path. FM
validates the partner `fed-mgmt` token (REQ-FED-01) and executes OPG.04 for those requests.

**Auth on the OEG→FM internal call.** FM authenticates the internal call itself. OEG presents a
platform service credential — an OAuth2 client-credentials token from the platform Keycloak
(ADR-0042) — which FM validates on `/internal/federation/*`. OEG forwards no resolved partner
identity and no `partner_op_id`; it forwards only `X-Correlator`. `fm:8082/internal/*` is never
exposed through Nginx or an Ingress — it is reachable only cluster-internally. Client mTLS on this
hop is deferred defense-in-depth (ADR-0042).

OEG holds no federation state for any of this — no `operations` row, no `OperationType` value, no
domain model, repository, migration, or completion consumer; the only "state" is the in-flight
HTTP request awaiting FM's reply. OEG applies its normal edge concerns (TLS termination, operator
authentication, correlation-id propagation, structured logging) and nothing else. FM performs all
OPG.04 parsing, validation, agreement checks, topology hiding, and persistence, and owns all
federation state including `federationContextId`. FM remains the sole owner of OPG.04 protocol
execution, inbound and outbound, including the outbound `CreateFederation` / `ZoneSubscribe`
handshake with a partner FM.

This supersedes ADR-0043's transport and its operation-record requirement. ADR-0043's remaining
substance stands: federation establishment is a product-facing action routed through OEG for
auth/policy/audit rather than direct against FM's L.2 admin API, and FM also supports self-initiated
bootstrap federation from startup configuration, which this decision does not touch.

## Consequences

What becomes easier:

- No new DataBus subject family, no stream or consumer topology to design, no completion channel.
  ADR-0043's open item — whether FM gets a second consumer binding on `OOP_TASKS` or a dedicated
  stream — disappears.
- No new schema: OEG forwards the L.2 admin payload shape FM already defines, so there is nothing
  to version or keep in lockstep, and the 2026-09-07 OEG-side mapper stubs are not built.
- OEG stays genuinely thin for federation: a routing and edge-concern layer with no
  federation-domain code, model, or persistence.
- `federationContextId` reaches the operator on the same call, so OEG never bridges an async
  result back onto a synchronous response.

What becomes harder:

- FM must expose an internal HTTP surface for OEG to call, distinct from the EWBI surface it
  terminates directly; the endpoint paths and bodies are specified in
  [fm/requirements-and-design.md](/fm/requirements-and-design.md) §L.3b.
- The operator's establishment request now couples OEG's request path to FM availability — if FM
  is down it fails synchronously rather than being queued (ADR-0043's `PENDING` `operations` row
  is gone). Acceptable: there is no requirement to accept a federation-establishment request while
  FM is unavailable.
- FM must authenticate the OEG→FM internal call (platform service credential, ADR-0042) and keep
  `/internal/*` off any Nginx/Ingress route.

What stays the same:

- Inbound EWBI is untouched — no Nginx path split; `/operatorplatform/federation/v1/*` still
  terminates entirely at FM, which validates the partner token and runs OPG.04.

Deferred or excluded:

- No change to the SRM→FM federated-deploy hand-off (ADR-0044) or to subscriber-identifier routing
  for synchronous query APIs (UC-FM-02 / §G3).

Revisit trigger:

- A requirement to accept and durably queue federation-establishment requests while FM is
  unavailable, or a move to make any of these operations genuinely long-running, would reopen the
  transport question.
+124 −0
Changes for decisions/0050-oeg-federation-lifecycle-proxy-errors-are-gsma-problem-json.md: 124 added lines, 0 removed lines.
Original line number Diff line number Diff line
---
type: Decision Record
title: "ADR-0050: OEG's federation-lifecycle proxy endpoints return the GSMA OPG.04 problem+json error model, not CAMARA ErrorInfo"
description: "The operator-facing federation-lifecycle proxy endpoints (POST/DELETE /platform/v1/federation/partners..., ADR-0046) use OPG.04 application/problem+json ProblemDetails for every error - relayed FM errors and OEG-originated ones (operator auth, body schema, FM unreachable); ADR-0030's ErrorInfo rule is scoped to OEG's CAMARA northbound surface"
tags: [adr]
timestamp: 2026-09-08T00:00:00Z
status: "accepted"
---

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

**Status:** accepted — the error-model decision stands unchanged. The endpoint set named below was later extended by [ADR-0051](/decisions/0051-oeg-fronts-federation-lifecycle-ewbi-byte-for-byte.md): the surface is eight operations under `/platform/v1/federation/partners/{partnerOpId}/federations/...` (the namespace and paths below are current, only widened with the `partnerOpId` segment and `GetZoneData`), not three. The `ProblemDetails` rule applies to all eight.
**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)

## Context

[ADR-0030](/decisions/0030-oeg-error-model-is-camara-errorinfo-not-rfc7807.md) established that all
outward OEG errors use CAMARA Commonalities' `ErrorInfo` shape (`status`/`code`/`message`), and
that OEG "never uses RFC 7807 `application/problem+json`." That decision was made when OEG's only
outward surface was the CAMARA northbound API (EAM, QoD, Traffic Influence). `oeg/interface-contract.md`
§K.6 states the rule with no scope qualifier.

[ADR-0046](/decisions/0046-oeg-fronts-federation-lifecycle-ewbi-as-sync-proxy.md) then added a
second outward surface: the operator-facing federation-lifecycle proxy endpoints
(`POST /platform/v1/federation/partners`, `POST /platform/v1/federation/partners/{federationContextId}/zones`,
`DELETE /platform/v1/federation/partners/{federationContextId}/zones/{zoneId}` — OEG
[interface-contract.md §N.7](/oeg/interface-contract.md), FM
[requirements-and-design.md §L.3b](/fm/requirements-and-design.md)). On that surface OEG is a
stateless synchronous pass-through to FM, and FM returns OPG.04 `application/problem+json`
`ProblemDetails` (FM §L.5). §N.7 step 5 and §L.3b already say OEG relays FM's response body and
status **unchanged**, which contradicts §K.6's absolute `ErrorInfo` rule. Two gaps:

1. **No carve-out.** §K.6, REQ-OEG-04, and `oeg/architecture.md`'s A.1 / In-Scope text all state
   the `ErrorInfo` rule with no exception, so they directly contradict the ADR-0046 relay
   behaviour.
2. **OEG's own faults on that path have no declared body.** §L.3b says only "If FM is unreachable,
   OEG returns `503`" — no body shape. The same gap exists for the errors OEG raises before it ever
   calls FM (operator JWT / `operator` role rejected, operator request body fails schema
   validation) and for the case where FM answers with something OEG cannot use.

Options considered:

- Option A: Translate on the boundary — OEG converts FM's `problem+json` into `ErrorInfo` on the
  relay path and emits `ErrorInfo` for its own `4xx`/`5xx` on that path, keeping §K.6 absolute.
- Option B: Scope §K.6's `ErrorInfo` rule to OEG's CAMARA northbound surface, and make the
  federation-lifecycle proxy endpoints a GSMA-aligned surface that uses OPG.04
  `application/problem+json` `ProblemDetails` for **every** error — both the errors OEG relays from
  FM and the errors OEG originates itself on that path.

Option A was rejected. ADR-0046 makes OEG a "pure stateless pass-through" here with "no
GSMA/OPG.04 mapping in OEG"; a boundary translator is exactly the mapping layer ADR-0046 removed.
It would also hand the operator tooling two error formats from one endpoint — FM's `problem+json`
on the relay path, `ErrorInfo` for OEG's own `503` — when that same tooling already parses
`problem+json` from FM's L.2 admin API. One surface, one error model.

## Decision

Option B.

**ADR-0030's `ErrorInfo` rule is scoped to OEG's CAMARA northbound surface** — the vendored
CAMARA API mount paths (EAM, QoD, Traffic Influence, Device Location, and the OEG-owned
`/platform/v1/` resources that back them, e.g. `/platform/v1/operations/{operationId}`). On that
surface nothing changes: `ErrorInfo` (`status`/`code`/`message`), `X-Correlator` response header,
never `application/problem+json`.

**The federation-lifecycle proxy endpoints (§N.7 / §L.3b, ADR-0046) are a GSMA-aligned surface.**
Every error response on those three endpoints uses the OPG.04 `application/problem+json`
`ProblemDetails` schema (FM §L.5), `Content-Type: application/problem+json`, with the
`X-Correlator` response header still set:

| Case | Status | Body |
|---|---|---|
| Operator JWT invalid / missing `operator` role | `401` / `403` | OEG-authored `ProblemDetails` |
| Operator request body fails schema validation | `400` | OEG-authored `ProblemDetails` |
| FM returns an OPG.04 error | passed through unchanged | FM's `ProblemDetails` |
| FM unreachable / connection timeout | `503` | OEG-authored `ProblemDetails` |
| FM answered but the response is unusable (unparseable, or a status/shape not in FM's contract) | `502` | OEG-authored `ProblemDetails` |

OEG does not rewrite, re-key, or re-wrap FM's error bodies — they are relayed byte-for-byte. OEG's
own `ProblemDetails` follow FM §L.5's field meanings (`type`, `title`, `status`, `detail`,
`cause`); `detail` carries no internal diagnostics. Illustrative OEG-authored `503`:

```json
{
  "type": "urn:oop:ewbi:error:upstream-unavailable",
  "title": "Federation Manager unreachable",
  "status": 503,
  "detail": "The federation-establishment request could not be forwarded to FM.",
  "cause": "UPSTREAM_UNAVAILABLE"
}
```

This is not a change to OEG's federation role (still a stateless pass-through), to the endpoints,
or to their success payloads — only the error contract on this one surface is being pinned down.

## Consequences

What becomes easier:

- §K.6 / §N.7 / §L.3b stop contradicting each other, and a caller of the federation-lifecycle
  endpoints parses exactly one error format regardless of whether OEG or FM produced it.
- OEG's `503`/`502`/`4xx` on this path now have a declared, testable body shape.
- No boundary translator to build or maintain — consistent with ADR-0046's "no GSMA/OPG.04 mapping
  in OEG": relaying an opaque body and emitting a fixed `ProblemDetails` template is not protocol
  mapping.

What becomes harder:

- `oeg/interface-contract.md` §K.6 can no longer be read as an absolute; it now carries an
  explicit surface scope and a single documented exception (§K.6a).

What stays the same:

- OEG's CAMARA northbound error model is unchanged — `ErrorInfo`, per ADR-0030.
- OEG holds no federation state and does no OPG.04 mapping (ADR-0046).
- FM's EWBI error model is unchanged — OPG.04 `ProblemDetails`, per REQ-FED-14 / §L.5.

Revisit trigger:

- OEG gains a third outward surface that is neither CAMARA-northbound nor GSMA-aligned, forcing a
  general per-surface error-model statement rather than one carve-out.
- The vendored CAMARA EAM snapshot is re-synced to a Commonalities release that changes the error
  model (per ADR-0015 / ADR-0030's own revisit trigger).
+209 −0

File added.

Preview size limit exceeded, changes collapsed.

+4 −1
Changes for decisions/index.md: 4 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -45,11 +45,14 @@ the reasoning lives here. Never renumber; supersede instead (see the record-deci
* [ADR-0040](0040-target-serveability-is-a-tri-state.md) - can_serve_target answers can_serve | unknown_subscriber | unsupported_identifier; a boolean cannot carry the 404/422 split §E.2 requires, and 404 needs positive evidence (supersedes ADR-0038's boolean)
* [ADR-0041](0041-tfsdk-owns-adapter-construction.md) - TF-SDK builds adapters behind a factory keyed by adapter kind; SRM resolves control_path_ref to an instance but never enumerates adapter kinds
* [ADR-0042](0042-ewbi-oauth2-client-credentials-mtls-deferred.md) - E/WBI authorization is OAuth2 Client-Credentials: local Keycloak for inbound, partner Authorization Server for outbound; mTLS deferred
* [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
* [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; scope/payload/namespace corrected by ADR-0051)
* [ADR-0047](0047-terminal-record-read-follows-camara-schema.md) - terminal-record read behaviour follows each family's own vendored CAMARA schema: Traffic Influence returns 200 with `state: deleted`, QoD stays 404 (amends ADR-0034's blanket 404 rule and its 204 delete code)
* [ADR-0048](0048-platform-roles.md) - three realm roles (app-provider, operator, platform-admin); new users get app-provider but call only the API products granted to them (each CAMARA scope gated by a product role); tenancy is the `organization` claim, else `sub`; development mode switches login off on localhost (amends ADR-0006's tenant identity and ADR-0035's entitlement note)
* [ADR-0050](0050-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-0051](0051-oeg-fronts-federation-lifecycle-ewbi-byte-for-byte.md) - OEG fronts eight GSMA federation-lifecycle operations on its own platform namespace, `/platform/v1/federation/partners/{partnerOpId}/federations/...`, scoped by a registered `partnerOpId` (create/read/update/delete federation, fed-context-id lookup, zone list/subscribe/details/unsubscribe), forwarding the raw GSMA request/response byte-for-byte to FM including the query string, pinned to GSMA OPG.04 v6.0 / artifact v1.4.0; operator-triggered outbound only, separated from inbound EWBI at the ingress; also proxies partner registration (`POST`/`GET`/`PATCH` `/platform/v1/federation/partners[/{partnerOpId}]` onto FM's L.2, amended 2026-09-24) and our own federation identity (`GET /platform/v1/federation/identity`, amended 2026-10-13); keeps ADR-0046's sync-HTTP/stateless/no-DataBus transport and ADR-0050's error model (amends this ADR's own first version, which used the bare canonical OPG.04 paths and lost the partner addressing GSMA gets for free from the URL)
* [ADR-0055](0055-topology-discovery-is-a-separate-adapter-interface.md) - topology discovery is a separate TopologyDiscoveryAdapter interface, not optional methods on CapabilityExecutionAdapter (amends ADR-0027)
* [ADR-0056](0056-capability-kind-names-a-capability-not-an-operation.md) - capability_kind names what a capability is, never what is done to it; a separate kind requires its own realization identity
* [ADR-0057](0057-no-silent-substitution-of-a-resolved-target.md) - an adapter that cannot execute the resolved target raises InvalidTargetRef before calling the backend, and never substitutes another
Loading