Commit 65be4976 authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

Merge remote-tracking branch 'origin/oeg-fm' into fm-persistence-model

# Conflicts:
#	fm/implementation-and-delivery.md
#	fm/overview-and-architecture.md
#	fm/requirements-and-design.md
parents f46aef91 f17e83c0
Loading
Loading
Loading
Loading
+2 −2
Changes for decisions/0034-network-capability-delete-is-async.md: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ status: "accepted"

# ADR-0034: Network-capability delete is async, and its completion is consumed to a terminal record state

**Status:** accepted — the blanket "reads of a terminal record return `404`" rule and step 3's "returns `204`" are amended by [ADR-0047](/decisions/0047-terminal-record-read-follows-camara-schema.md) (each family follows its own vendored CAMARA schema: Traffic Influence reads a deleted policy as `200` and deletes with `202`); everything else stands
**Status:** accepted — the blanket "reads of a terminal record return `404`" rule and step 3's "returns `204`" are amended by [ADR-0049](/decisions/0049-terminal-record-read-follows-camara-schema.md) (each family follows its own vendored CAMARA schema: Traffic Influence reads a deleted policy as `200` and deletes with `202`); everything else stands
**Date:** 2026-08-05
**Affects:** [oeg/persistence-model.md](/oeg/persistence-model.md), [oeg/interface-contract.md](/oeg/interface-contract.md), [srm/interface-contract.md](/srm/interface-contract.md)

@@ -68,7 +68,7 @@ event is sent when the status was already `UNAVAILABLE`.
representation for a deleted resource — true of QoD, whose `QosStatus` enum is
`REQUESTED`/`AVAILABLE`/`UNAVAILABLE`. Traffic Influence's `state` enum does document
`deletion in progress` and `deleted`, so it reads them back as `200`
([ADR-0047](/decisions/0047-terminal-record-read-follows-camara-schema.md)). Either way the row is
([ADR-0049](/decisions/0049-terminal-record-read-follows-camara-schema.md)). Either way the row is
retained for audit and for the `operations` history; it is not removed.

## Consequences
+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, mints
`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.
+2 −2
Changes for decisions/0049-terminal-record-read-follows-camara-schema.md: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
---
type: Decision Record
title: "ADR-0047: Terminal-record read behaviour follows each family's own CAMARA schema — Traffic Influence returns 200 with `state: deleted`, QoD stays 404"
title: "ADR-0049: Terminal-record read behaviour follows each family's own CAMARA schema — Traffic Influence returns 200 with `state: deleted`, QoD stays 404"
description: "ADR-0034's rule that reads of a terminal record return 404 rests on CAMARA having no representation for a deleted resource. That holds for QoD's qosStatus enum and not for Traffic Influence, whose state enum documents 'deletion in progress' and 'deleted'. The rule is scoped to families whose vendored schema has no deleted representation."
tags: [adr]
timestamp: 2026-09-09T00:00:00Z
status: "proposed"
---

# ADR-0047: Terminal-record read behaviour follows each family's own CAMARA schema
# ADR-0049: Terminal-record read behaviour follows each family's own CAMARA schema

**Status:** proposed
**Date:** 2026-09-09
+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).
Loading