docs(fm,srm): align federated terminate with SRM's partner rows and add partner_not_found

§L.3a and SRM §F.4a still described terminate as firing when the instance isn't in SRM's inventory; since ADR-0053's 2026-09-25 amendment SRM keeps a hosted_by: partner row, and that row is the trigger. §L.3a also had FM matching on external_resource_id, which holds the partner-issued appInstIdentifier; FM resolves the instance through its outbound install-app transaction instead. ADR-0053, FM RD §M.1 and SRM §F.4a gain completion_basis partner_not_found for a RemoveApp answered 404.

Merge request reports

Loading
+2 −1
Changes for decisions/0053-srm-fm-federated-lifecycle-handoff-over-the-databus.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ status: "proposed"

# ADR-0053: SRM hands federated deploys and terminates to FM over the DataBus, and FM reports the outcome the same way

**Status:** proposed (amended 2026-09-25: SRM keeps instance rows for partner targets)
**Status:** proposed (amended 2026-09-25: SRM keeps instance rows for partner targets; 2026-09-29: `partner_not_found`)
**Date:** 2026-09-22
**Affects:** [fm/overview-and-architecture.md](/fm/overview-and-architecture.md) (open item G4, UC-FM-07), [fm/requirements-and-design.md](/fm/requirements-and-design.md) (the SRM→FM hand-off section §L.3a, FM's DataBus sections §K.2 and §M), [srm/interface-contract.md](/srm/interface-contract.md) (stream configuration §D, the federated hand-off flow §F.4a), [platform/architecture.md](/platform/architecture.md) (DataBus configuration)
**Amends:** ADR-0001 and ADR-0022, where they make SRM the only consumer of `OOP_TASKS`; ADR-0046, where it keeps the DataBus to `command.srm.*` / `event.srm.*`
@@ -165,6 +165,7 @@ things:
| `partner_confirmed` | deploy | the partner's `onInstanceStatusEvent` arrives | the instance is running |
| `deadline_expired` | deploy | no callback before the deadline | failed; the partner never answered |
| `partner_accepted` | terminate | the partner answers `RemoveApp` with `200` | the partner said yes; removal isn't confirmed |
| `partner_not_found` | terminate | the partner answers `RemoveApp` with `404` | the partner no longer has the instance; the terminate is complete |

Termination gets the weaker claim because OPG.04 offers nothing stronger to observe. We don't build
machinery to chase a partner who accepts a removal and then leaves the app running: that partner
+12 −6
Changes for fm/requirements-and-design.md: 12 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ status: "ETSI SDG OOP"
| REQ-FED-18 | *(later phase)* The system shall produce secure federation usage/charging records and integrate them with the operator-controlled charging system according to configured charging factors. FM does not perform inter-operator financial settlement. | OPG.02 v10.0 §4.3.3 and Annex G |
| REQ-FED-19 | The system shall provide an internal admin API (L.2) for the full partner onboarding, update, and decommission lifecycle, and for managing routing rules. For partner OP profiles and federation agreements specifically, the operator-triggered create/read/update/decommission lifecycle normally arrives via OEG's synchronous federation-lifecycle proxy (§N.7, §L.3b; ADR-0051) or, for establishment, FM's own bootstrap configuration; L.2's `POST /internal/partners` / `/internal/agreements` and the corresponding `GET`/`PATCH`/`DELETE` remain available for operational/break-glass use only. Routing rules are out of that scope (they serve subscriber-identifier routing, not federation establishment) — L.2's `POST /internal/routing-rules` remains their sole, primary creation path. | OA UC-FM-06, ADR-0051 |
| REQ-FED-20 | The system shall expose internal HTTP endpoints under `/internal/*` (§L.3b), mirroring OEG's `/platform/v1/federation/partners/{partnerOpId}/federations/...` paths 1:1, that accept the eight operator-triggered outbound federation-lifecycle operations proxied synchronously by OEG (`CreateFederation`, `GetFederationContextId`, `GetFederationDetails`, `DeleteFederationDetails`, `GetZoneData`, `ZoneSubscribe`, `GetZoneDetails`, `ZoneUnsubscribe`; `UpdateFederation` out of scope), receiving the raw GSMA request payload and query string OEG forwards byte-for-byte (no OEG↔FM envelope, no reshaping). This hop is protected only by cluster-internal network-policy reachability, not a dedicated credential — no `aud`/token check is added (ADR-0042 covers EWBI partner-facing authorization only, not this hop). FM shall resolve `partnerOpId` against the partner's registration record (REQ-FED-19) and reject a `federationContextId` presented against the wrong partner, validate and execute the outbound OPG.04 operation, and — for `CreateFederation` — store the partner-issued `federationContextId` and return the full `FederationResponseData` on the synchronous response. It shall support an equivalent self-initiated bootstrap path from startup configuration. OEG does not publish `command.fm.federation.*`, and inbound EWBI termination on the canonical prefix is unchanged (ADR-0051, keeping ADR-0046's synchronous-HTTP transport and superseding ADR-0043's DataBus transport). | ADR-0051 |
| REQ-FED-21 | The system shall expose an internal endpoint for SRM to query federated zone/capability data (`shared_resource_catalogues`) by `zone_id`, and shall accept a federated-deploy hand-off from SRM for zone-based deployment targets that resolve to a partner, executing the outbound OPG.04 `InstallApp` handshake through the same pipeline used for inbound federation-originated deploys. Exact hand-off transport is open — see [fm/overview-and-architecture.md §G4](/fm/overview-and-architecture.md). | ADR-0044, OA UC-FM-07 |
| REQ-FED-21 | The system shall expose an internal endpoint for SRM to query federated zone/capability data (`shared_resource_catalogues`) by `zone_id`, and shall consume SRM's federated hand-off (`command.fm.service.deploy` / `.terminate` on `OOP_TASKS`) for work that resolves to a partner, executing the outbound OPG.04 `InstallApp` or `RemoveApp` through the same pipeline used for inbound federation-originated requests and reporting the outcome on `event.fm.operation.completed` (§L.3a). | ADR-0044, ADR-0053, OA UC-FM-07 |

---

@@ -574,7 +574,7 @@ SRM treats a `404` as an ordinary placement failure, the same path as an unresol
| Subject | Stream | Payload | Published by SRM when |
|---|---|---|---|
| `command.fm.service.deploy` | `OOP_TASKS` | `SrmServiceDeployV1`, as SRM received it | the target zone came back federated |
| `command.fm.service.terminate` | `OOP_TASKS` | `SrmServiceTerminateV1`, as SRM received it | the `service_instance_id` isn't in SRM's inventory |
| `command.fm.service.terminate` | `OOP_TASKS` | `SrmServiceTerminateV1`, as SRM received it | the instance's row says `metadata.hosted_by: partner` |

The envelope keeps the original `operation_id` and `correlation_id`. SRM does no GSMA shaping; FM
maps deploy to OPG.04 `InstallApp` and terminate to `RemoveApp`.
@@ -584,10 +584,13 @@ acknowledges only after writing the `federation_transactions` row. It keys on `o
redelivered command whose `operation_id` already has a transaction row starts no second EWBI
exchange.

**Terminate resolves the instance in FM.** SRM doesn't look it up first. FM matches the
`service_instance_id` against its `federation_transactions` records, using the stored
`external_resource_id`, which holds the `appInstIdentifier` in its wire spelling (hex, because
OPG.04's `InstanceIdentifier` forbids hyphens).
**Terminate resolves the instance in FM.** SRM doesn't look it up first. FM finds the outbound
`install-app` transaction that recorded this `service_instance_id` when SRM handed the deploy over,
and takes from it what `RemoveApp` needs: the `federationContextId`, `appId` and partner `zoneId`,
and the `appInstIdentifier` in `external_resource_id`. That identifier is the one the partner
issued in its `InstallApp` answer, not ours, so FM can't derive it from the `service_instance_id`.
FM records the removal as a `remove-app` transaction keyed on the terminate's `operation_id`; a
redelivered command sends no second `RemoveApp` and publishes the stored outcome again.

**Refusals.** When FM can't act on a command, it publishes `event.fm.operation.completed` with
`status: failed` and one of these as `error.type` (RFC 7807), alongside the `partner-not-active`,
@@ -791,6 +794,7 @@ operations different guarantees:
|---|---|---|
| `partner_confirmed` | deploy | The partner's `onInstanceStatusEvent` callback reported the instance running |
| `partner_accepted` | terminate | The partner answered `RemoveApp` with `200`. **Not** confirmation that removal finished — OPG.04 defines no callback for removal and no `TERMINATED` state to observe |
| `partner_not_found` | terminate | The partner answered `RemoveApp` with `404`: it no longer has the instance, which is what the terminate asked for; `status` is `completed` |
| `deadline_expired` | deploy | No callback arrived within FM's configured wait (default 30 minutes); `status` is `failed` |

Consumers must not treat `partner_accepted` as proof of removal. Detecting a termination a partner
@@ -896,3 +900,5 @@ A locally connected App Provider queries device location for a subscriber on a p
| 2.0 | 2026-09-08 | ADR-0051 corrects ADR-0046's scope/payload/namespace for the OEG-proxied surface (transport unchanged). §L.3b retitled "Federation-Lifecycle Proxy": now seven canonical OPG.04 operations (create/read/update/delete federation, `GetFederationContextId`, zone subscribe/unsubscribe) served by OEG on `/operatorplatform/federation/v1/*` for the operator-triggered *outbound* direction, with the raw GSMA payload forwarded byte-for-byte (no reshaping, no §L.2 admin shape) and a no-log/no-persist rule for the credential-bearing body. Inbound partner traffic on the same paths still terminates at FM — the two directions split at the ingress. REQ-FED-19/20 and the L.2 note rewritten; priority federation-management table note updated. |
| 2.0 | 2026-09-16 | ADR-0051 amended after FM-side review, reversing this file's own previous entry above: OEG's federation-lifecycle proxy goes back onto its own `/platform/v1/federation/partners/{partnerOpId}/federations/...` namespace, not the canonical OPG.04 prefix — the canonical URL loses partner addressing once there's an OEG hop in front of FM. §L.3b, the priority federation-management table, and REQ-FED-19/20 rewritten: eight operations now (`GetZoneData` added, `UpdateFederation` out of scope), query string forwarded, spec pinned to GSMA OPG.04 v6.0 / artifact v1.4.0, `partnerCallbackCredentials` dropped (not a v1.4.0 field), partner resolution tied to FM's not-yet-built partner registration (federation-manager#20). The "FM authenticates OEG's platform service credential (ADR-0042)" auth claim is corrected everywhere it appeared: this hop has no dedicated credential, only cluster-internal network-policy reachability; ADR-0042 is EWBI partner-facing auth only. ADR-0047/0048 renumbered to ADR-0050/0051. |
| 2.0 | 2026-09-29 | §L.3b: FM's status set on the `/internal` federation-lifecycle routes pinned to OPG.04 v1.4.0's declared statuses, with the mapping for FM's EWBI-side `403`/`502` and for partner rejections (REQ-FED-14; OEG §K.6a's `502` gate relies on it). |
| 2.0 | 2026-09-29 | §L.3a: `command.fm.service.terminate` fires on SRM's `hosted_by: partner` instance row (ADR-0053's 2026-09-25 amendment), and FM resolves the instance from the outbound `install-app` transaction, since the partner issues the `appInstIdentifier`. §M.1: new `completion_basis` `partner_not_found` for a `RemoveApp` answered `404`. |
| 2.0 | 2026-09-29 | REQ-FED-21 restored to its ADR-0053 wording (DataBus hand-off, deploy and terminate, outcome on `event.fm.operation.completed`); a conflict resolution when §L.3b's status rule merged in had put back the pre-ADR-0053 text. |
+6 −2
Changes for srm/interface-contract.md: 6 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -876,7 +876,8 @@ on the DataBus in both directions, with only the zone lookup as a synchronous re

**Terminate:**

1. SRM looks up `service_instance_id` in its own inventory. Not found locally.
1. SRM looks up `service_instance_id` in its own inventory and finds the row it wrote at deploy
   time with `metadata.hosted_by: partner`.
2. SRM writes its `service_order` row and publishes `command.fm.service.terminate` with the
   `SrmServiceTerminateV1` payload it received. There's no lookup step: FM resolves the instance
   from its own federation transaction records, and reports `instance-not-federated` if it can't.
@@ -892,7 +893,9 @@ stuck in `INSTANTIATING` or `TERMINATING`.
`metadata.completion_basis` says what the outcome rests on and has to be carried through, not
flattened: `partner_confirmed` (the partner's deploy callback reported the instance running),
`partner_accepted` (the partner accepted a termination; OPG.04 defines no removal callback and no
`TERMINATED` state, so removal isn't confirmed), or `deadline_expired` (no deploy callback within
`TERMINATED` state, so removal isn't confirmed), `partner_not_found` (the partner answered
`RemoveApp` with `404`: the instance is already gone there, so the terminate completed), or
`deadline_expired` (no deploy callback within
FM's configured wait). A refusal (zone not federated, agreement forbids it, partner suspended,
instance unknown) arrives as the same event with `status: failed` and the reason in `error`.

@@ -925,3 +928,4 @@ interim layer until the runtime coordinator in platform/future-architecture.md
| 2.0 | 2026-09-04 | Added F.4a: SRM resolves zone locality and forwards non-local deploys to FM (ADR-0044). `GET /zones` (§E.3) may now include FM-sourced federated zones. F.4a step 1 corrected to cite F.1's direct zone lookup, not §B's device-targeted candidate-probing mechanism (different, unrelated resolution path). |
| 2.0 | 2026-09-08 | §B: a zone/domain pin on a device-targeted capability narrows the candidate set rather than bypassing target probing (ADR-0045). Deploy §B.2's "SRM selects a domain in it" no longer carries over; probing is triggered by how many candidates survive the pin, so a zone-pinned request in a two-NEF zone is probed and a zone+domain pin still resolves without one. |
| 2.0 | 2026-09-09 | §E.1: terminal-record read behaviour is per-family, following each vendored CAMARA schema rather than a blanket `404` (ADR-0049). Traffic Influence's `state` enum documents `deletion in progress`/`deleted`, so it reads them back as `200`; QoD's `QosStatus` has no such value and stays `404`. |
| 2.0 | 2026-09-29 | §F.4a: terminate step 1 finds the `hosted_by: partner` row (ADR-0053's 2026-09-25 amendment) instead of "not found locally"; `partner_not_found` added to the `completion_basis` values. |
Loading