Commit 781fd766 authored by George Papathanail's avatar George Papathanail
Browse files

fix: carve out GSMA problem+json error model for OEG federation-lifecycle proxy (ADR-0047)

parent 33ece3c3
Loading
Loading
Loading
Loading
+124 −0
Changes for decisions/0047-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-0047: 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-0047: OEG's federation-lifecycle proxy endpoints return the GSMA OPG.04 problem+json error model, not CAMARA ErrorInfo

**Status:** accepted
**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).
+1 −0
Changes for decisions/index.md: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -49,3 +49,4 @@ the reasoning lives here. Never renumber; supersede instead (see the record-deci
* [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-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
+5 −1
Changes for fm/requirements-and-design.md: 5 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -547,7 +547,10 @@ L.2 internal admin API directly (REQ-FED-19).
  Ingress; it is reachable only cluster-internally. Client mTLS on this hop is deferred
  defense-in-depth (ADR-0042).
- **Errors:** FM returns OPG.04 `application/problem+json` `ProblemDetails` (L.5); OEG relays the
  status and body unchanged. If FM is unreachable, OEG returns `503` to the operator.
  status and body unchanged. OEG's own faults on this path use the same `ProblemDetails` model,
  not CAMARA `ErrorInfo` (OEG interface-contract §K.6a, ADR-0047): `400` (operator body fails
  schema), `401`/`403` (operator JWT/role), `503` (FM unreachable or times out), `502` (FM
  answered with something OEG cannot use). `X-Correlator` is set on all of them.

### L.4 FM → SRM Internal HTTP

@@ -694,3 +697,4 @@ A locally connected App Provider queries device location for a subscriber on a p
| 2.0 | 2026-07-07 | OOP Release 2.0. |
| 2.0 | 2026-09-04 | REQ-FED-19 narrowed for partner profiles/agreements only (routing rules explicitly unaffected — out of ADR-0043's scope); added REQ-FED-20/21 for OEG-proxied federation establishment (ADR-0043) and SRM-forwarded federated deploys (ADR-0044). Added L.3a and `command.fm.federation.*` to K.2; L.3 scoped to subscriber-identifier routing only. |
| 2.0 | 2026-09-08 | OEG↔FM transport for the *operator-triggered* federation-establishment trigger moves from async over the DataBus (ADR-0043) to a synchronous internal HTTP call (ADR-0046); scope, the operator endpoint and its payload are unchanged from ADR-0043. REQ-FED-20 rewritten (consume `command.fm.federation.*` → expose §L.3b internal HTTP endpoints taking the §L.2 admin payload shape; FM authenticates OEG's platform service credential); REQ-FED-19 and the L.2 note repointed to §L.3b; the `command.fm.federation.*` table removed from K.2; added §L.3b; the priority federation-management table clarified — all EWBI paths terminate at FM, no Edge/OEG split; Federation Context entity notes synchronous `federationContextId` minting. |
| 2.0 | 2026-09-08 | §L.3b Errors bullet expanded: OEG's own faults on the proxy path (`400`/`401`/`403`/`502`/`503`, incl. the previously undeclared FM-unreachable `503`) use OPG.04 `application/problem+json` `ProblemDetails`, not CAMARA `ErrorInfo` — matches FM's relayed error shape (OEG interface-contract §K.6a, ADR-0047). FM's own EWBI error model (REQ-FED-14, §L.5) is unchanged. |
+4 −3
Changes for oeg/architecture.md: 4 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -87,7 +87,7 @@ Detailed delivery planning belongs outside this specification. This document def
| **Catalog read-through** | Proxy SRM catalog reads for a consistent NBI surface. Invalidate cache on `event.srm.catalog.updated` events. |
| **Federation-establishment trigger (ADR-0046)** | Expose the operator-facing establishment/lifecycle endpoints (§N.7, `POST /platform/v1/federation/partners` and siblings; `operator` role) and forward each to FM over a synchronous internal HTTP call, passing the operator-facing request and FM's response through unchanged — no DataBus, no OEG↔FM internal schema, no `operations` row, no GSMA/OPG.04 mapping in OEG. OEG authenticates to FM with a platform service credential (ADR-0042); FM maps the request to OPG.04, mints `federationContextId`, and returns it on the same call. Inbound EWBI is not on this path. See UC-OEG-06. |
| **Callback delivery** | POST CloudEvent callbacks (`application/cloudevents+json`) to registered sinks on terminal operation state; authenticate with `Authorization: Bearer` using the request's `sinkCredential` (ADR-0008). Retry through an implementation-specific background mechanism. |
| **CAMARA `ErrorInfo` error model** | All outward errors as CAMARA's `ErrorInfo` (`status`/`code`/`message`) with `X-Correlator` header per CAMARA Commonalities r4.3 — not RFC 7807 `application/problem+json`, which the vendored spec never defines. `X-Correlator` on error responses carries the same value as the inbound request's `x-correlator` and as `correlation_id` in internal bodies — one correlation value, generated at the edge when the client sends none, under the same header name inward and outward. |
| **CAMARA `ErrorInfo` error model** | On OEG's CAMARA northbound surface, all outward errors as CAMARA's `ErrorInfo` (`status`/`code`/`message`) with `X-Correlator` header per CAMARA Commonalities r4.3 — not RFC 7807 `application/problem+json`, which the vendored spec never defines. `X-Correlator` on error responses carries the same value as the inbound request's `x-correlator` and as `correlation_id` in internal bodies — one correlation value, generated at the edge when the client sends none, under the same header name inward and outward. **Exception:** the operator-facing federation-lifecycle proxy endpoints (§N.7, ADR-0046) are GSMA-aligned and return OPG.04 `application/problem+json` `ProblemDetails` for every error — relayed and OEG-originated (interface-contract §K.6a, ADR-0047). |
| **Idempotency** | `Idempotency-Key` header (e.g. client retries) prevents duplicate PENDING rows and duplicate `command.srm.*` publishes. |
| **Correlation ID propagation** | `correlation_id` attached to every `command.srm.*` envelope, and the `x-correlator` header to every internal HTTP call; same value as the external `X-Correlator`. |

@@ -269,7 +269,7 @@ This section is the boundary contract for OEG. If a responsibility seems ambiguo
- **Async operation orchestration**: PostgreSQL persistence, publish `command.srm.service.deploy`, `command.srm.service.scale`, `command.srm.service.terminate`, and `command.srm.network.capability.activate` / `.update` / `.deactivate` on the DataBus; consume `event.srm.operation.completed`; consume `event.srm.operation.status` (Phase 2 — not yet activated in Phase 1), `event.srm.catalog.updated`, and optionally `event.srm.inventory.drift_detected` / `.drift_resolved` for alerting (log + emit metrics now; Portal notification is a future addition).
- **Synchronous query/data proxy**: forward stateless CAMARA requests to SRM internal HTTP, log usage, return response.
- **Usage metering**: structured logging + `api_usage_records` (PostgreSQL) + runtime rate-limiting counters.
- **CAMARA `ErrorInfo`** error responses for all outward APIs (not RFC 7807).
- **CAMARA `ErrorInfo`** error responses for all outward APIs on the CAMARA northbound surface (not RFC 7807); the federation-lifecycle proxy endpoints (§N.7, ADR-0046/0047) are the one exception and use OPG.04 `application/problem+json` `ProblemDetails`.
- **Correlation ID** propagation (`correlation_id` in `command.srm.*` envelopes, `x-correlator` header on internal HTTP).
- **Callback delivery**: CloudEvent status-change notifications to registered sinks (`application/cloudevents+json`, `sinkCredential` bearer auth per ADR-0008), with retry.
- **API Product Catalog ownership**: what APIs are published to consumers, their versions, rate limit policies, subscription tiers.
@@ -341,7 +341,7 @@ These questions are not resolved by the current architecture text. Keep them vis
| REQ-OEG-01 | **Phase 1 (no Nginx):** The system shall terminate NBI HTTP requests directly on port 8080 at each API family's OEG mount path; client-facing URL equals OEG mount (ADR-0020). **Phase 2+ (with Nginx):** Nginx shall terminate HTTPS and route `/api/*` to OEG; OEG's own routes carry no `/api` segment (Nginx's exact strip/rewrite rules are TBD until deployed). The system shall require the `operator` role for all administrative routes; no administrative route shall be reachable without authentication. | OPG.02 v10.0 |
| REQ-OEG-02 | The system shall validate OAuth 2.0 Bearer JWTs issued by Keycloak (realm `oop`) using `oop-commons` authentication middleware and shall enforce `require_role()` on each route. | OPG.02 v10.0, CAMARA Commonalities r4.3 |
| REQ-OEG-03 | The system shall implement per-API-family versioning by serving each family at exactly the path its own vendored spec defines — no OOP-invented version substitution, including when the vendored spec is still `wip` (CAMARA EAM: vendored path segment `vwip`, e.g. `/edge-application-management/vwip/...`). No OEG-injected platform namespace segment; OEG platform endpoints live under `/platform/v1/`. Each API family versions independently; no platform-wide version prefix shall be used. | CAMARA Commonalities r4.3 |
| REQ-OEG-04 | The system shall return errors using CAMARA's `ErrorInfo` shape (`status`, `code`, `message`) with an `X-Correlator` response header for all client and server error responses; this is not RFC 7807 `application/problem+json`, which the vendored CAMARA spec does not define. | CAMARA Commonalities r4.3 |
| REQ-OEG-04 | On the CAMARA northbound surface, the system shall return errors using CAMARA's `ErrorInfo` shape (`status`, `code`, `message`) with an `X-Correlator` response header for all client and server error responses; this is not RFC 7807 `application/problem+json`, which the vendored CAMARA spec does not define. On the operator-facing federation-lifecycle proxy endpoints (§N.7, ADR-0046), the system shall instead return OPG.04 `application/problem+json` `ProblemDetails` for every error — relaying FM's error body unchanged, and using a fixed `ProblemDetails` template for its own `400`/`401`/`403`/`502`/`503` (ADR-0047). | CAMARA Commonalities r4.3, ADR-0047 |
| REQ-OEG-05 | For long-running operations, the system shall persist an operation record in `oeg_db` with status `PENDING` under a unique internal `operationId`, publish a `command.srm.*` message on the DataBus matching `CommandEnvelopeV1` + payload schemas shared with SRM, and return HTTP 202 with the CAMARA resource body (`AppInstanceInfo` or `{appDeploymentId}`); `operationId` is exposed only via `/platform/v1/operations/{operationId}` (ADR-0005). | OPG.02 v10.0, OPG.10 v2.0 |
| REQ-OEG-06 | The system shall consume `event.srm.operation.completed` events from the DataBus and shall update the matching `operations` row to `COMPLETED`, `PARTIALLY_COMPLETED`, or `FAILED`, including error details from the payload (ADR-0005). | OPG.02 v10.0 |
| REQ-OEG-07 | The system shall perform idempotent publishes: the same logical request (same `operationId` or client `Idempotency-Key`) shall not create duplicate `PENDING` rows or duplicate `command.srm.*` publishes. | CAMARA Commonalities r4.3 |
@@ -503,3 +503,4 @@ Structured logging and correlation IDs are the minimum observability baseline. P
| 2.0 | 2026-07-07 | OOP Release 2.0. |
| 2.0 | 2026-09-04 | UC-OEG-05 split by target type: zone-based deployment locality moves to SRM (ADR-0044), subscriber-identifier routing unchanged. Added UC-OEG-06 and REQ-OEG-17 for the OEG-proxied federation-establishment trigger (ADR-0043). Zone-based flow's cross-references corrected from UC-SRM-03/§G3 (now inbound/subscriber-identifier-only) to UC-SRM-03a/§G4. |
| 2.0 | 2026-09-08 | OEG↔FM transport for the *operator-triggered* federation-establishment trigger moves from async over the DataBus (ADR-0043) to a synchronous internal HTTP call (ADR-0046); scope, the operator endpoint (`POST /platform/v1/federation/partners`) and its payload are unchanged from ADR-0043. No `command.fm.federation.*`, no internal schema, no `operations` row; FM authenticates OEG's platform service credential and maps the payload to OPG.04. Inbound EWBI is unchanged — the whole `/operatorplatform/federation/v1/*` prefix stays with FM, no path split. UC-OEG-06 and REQ-OEG-17 rewritten, E6 replaced, Core Capabilities + boundary matrix + scope updated. |
| 2.0 | 2026-09-08 | Error-model rows (A.1 table, In-Scope bullet, REQ-OEG-04) scoped to OEG's CAMARA northbound surface and given an explicit exception: the federation-lifecycle proxy endpoints (§N.7) 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 (interface-contract §K.6a). ADR-0047. |
+48 −2

File changed.

Preview size limit exceeded, changes collapsed.

Loading